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

import copy
import datetime
import itertools
import json
import operator
import random
import base64
import math
from collections import defaultdict

from urllib import quote
from cached_property import cached_property
from django.conf import settings
from django.contrib.auth.models import User, AnonymousUser
from django.db import models
from django.db.models import Sum, Q, F, Case, When, Value, Count
from django.utils import timezone

from gm_types.gaia import DOCTOR_SECKILL_APPLY_STATUS, ACTIVITY_TYPE_ENUM, SERVICE_ITEM_PRICE_TYPE, SERVICE_SELL_TYPE, \
    VIDEO_CODE_STATUS, GROUPBUY_TYPE, ACTIVE_TYPE

from gm_types.gaia import SERVICE_FILTER_TYPE, SERVICE_REVIEW_STATUS, SERVICE_REVIEW_TYPE, YINUO_TYPE, ZHONGAN_TYPE, \
    COUPON_TYPES, TAG_TYPE, OPERTOR_REVIEW_TYPE, SERVICE_PLATFORM_TYPE
from gm_types.doctor import DOCTOR_TITLE_TYPE
from gm_types.plutus import INSURANCE_TYPE
from gm_types.error import ERROR
from gm_upload import IMG_TYPE, ImgUrlField
from hippo.models import Merchant, City
from rpc.tool.log_tool import info_logger, price_logger

import rpc.cache
import rpc.db_opt
from rpc.exceptions import GaiaRPCFaultException
from api.models.organization import OrganizationImage
from api.tool import const_strings
from api.tool.datetime_tool import get_timestamp, get_timestamp_or_none, get_timestamp_epoch
from api.tool.image_utils import get_full_path
from api.tool.log_tool import logging_exception, search_logger
from api.tool.price_specialized import CacheControl
from rpc.all import get_rpc_remote_invoker
from rpc.cache import model_cache, special_service_list_cache, ViewRecord
from rpc.tool.dict_mixin import to_dict
from search.utils.diary import filter_diary
from services.custom_phone_service import PhoneService
from .bodypartsubitem import BodyPartSubItem
from .doctor import Doctor
from .hospital import Hospital, PeriodHospital
from .itemwiki import ItemWiki
from .person import Person
from .tag import Tag
from .tickets import TicketsResult
from .types import (
    PAYMENT_TYPE,
    payment_type_to_payment_type_v2,
    SERVICE_CHANNEL,
    ORDER_STATUS,
    SERVICE_FLAG,
    DOCTOR_TYPE,
    SECKILL_STATUS,
    SERVICEREGISTER_STATUS,
    SERVICE_OPERATION_TYPE,
    SERVICE_MONITOR_STATUS
)
from .bodypartsubitem import BodyPartSubItem
from .doctor import Doctor
from .person import Person
from .hospital import Hospital, PeriodHospital
from .itemwiki import ItemWiki
from .tag import Tag
from .tickets import TicketsResult
from .area import City

import rpc.db_opt
from rpc.cache import model_cache
import rpc.cache
from api.tool.image_utils import get_w_path, get_half_path, get_thumb_path
from redlock import RedLock

price_cc = CacheControl(
    redis_connection=rpc.cache.price_cache,
    prefix='gaia:cache:price:eM4FCKGKLL8C5hpk',
    normal_expire=120,
    error_expire=10,
    random_renew_range=10,
    random_renew_prob=0.01,
)

# 这两个service_time，主要是用来生成一个时间范围，这个时间范围是可以覆盖datetime.datetime.now()
_min_service_time = datetime.datetime(2000, 1, 1)
_max_service_time = datetime.datetime(2038, 1, 18)

_orderding_limit = 10
_service_tag_limit = 2

lock_sku_connection_detail = [settings.REDIS['sku_stock_lock']]

try:
    _ol_setting = settings.SKU_ORDERING_LIMIT
    if _ol_setting >= 0:
        _orderding_limit = _ol_setting
except:
    logging_exception()


def _get_sku_buy_limit(service_single_user_buy_limit, sku_stock, price_sale_limit, price_single_user_buy_limit):
    """
    从几个限制里面取最小的值，如果这个值小于1就返回1
    """
    all_limit = [_orderding_limit,
                 sku_stock if sku_stock > 0 else 0,
                 price_sale_limit if price_sale_limit > 0 else 0
                 ]

    if service_single_user_buy_limit > 0:
        all_limit.append(service_single_user_buy_limit)
    if price_single_user_buy_limit > 0:
        all_limit.append(price_single_user_buy_limit)

    buy_limit = min(all_limit)

    return buy_limit if buy_limit >= 1 else 1


def _sort_price_with_all(item_price):
    '''

    :param item_price:
    :return:
    '''
    rule = item_price.selling_rule
    if rule and rule.activity_type == ACTIVITY_TYPE_ENUM.MOREBUY:
        return item_price.more_buy_price, item_price.pre_payment_price, -item_price.id
    else:
        return item_price.gengmei_price, item_price.pre_payment_price, -item_price.id


def _get_current_service_item_current_groupbuy_price_key(service_item_price):
    rule = service_item_price.selling_rule
    return service_item_price.gengmei_price, rule.groupbuy_nums, rule.groupbuy_type, -service_item_price.id


_special_and_seckill_activity_types = (
ACTIVITY_TYPE_ENUM.SPECIAL, ACTIVITY_TYPE_ENUM.SECKILL, ACTIVITY_TYPE_ENUM.MOREBUY)
flagship_support_types = (ACTIVITY_TYPE_ENUM.SPECIAL, ACTIVITY_TYPE_ENUM.SECKILL, ACTIVITY_TYPE_ENUM.MOREBUY)

service_type2special_type = {
    SERVICE_SELL_TYPE.FLAGSHIP_STORE_SERVICE: flagship_support_types
}


# _get_current_service_item_price_key和_add_current_service_item_price_order_by_to_query_set
# 这两个方法都有相同的逻辑，也就是 当前价格 的排序规则，其中一个用于内存中排序，另外一个用于给对应的QuerySet加上order_by
def _get_current_service_item_price_key(service_item_price):
    return service_item_price.gengmei_price, service_item_price.pre_payment_price, -service_item_price.id


def _add_current_service_item_price_order_by_to_query_set(queryset):
    return queryset.order_by("gengmei_price", "pre_payment_price", "-id")

def _sort_price_with_all(item_price):
    '''

    :param item_price:
    :return:
    '''
    rule = item_price.selling_rule
    if rule and rule.activity_type == ACTIVITY_TYPE_ENUM.MOREBUY:
        return item_price.more_buy_price, item_price.pre_payment_price, -item_price.id
    else:
        return item_price.gengmei_price, item_price.pre_payment_price, -item_price.id



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

    name = models.CharField(verbose_name=u'分类名称', max_length=128)
    doctor = models.ForeignKey(Doctor, verbose_name=u'关联商户')
    rank = models.IntegerField(verbose_name=u'排序')
    created_time = models.DateTimeField(default=datetime.datetime.now, verbose_name=u'创建时间')


class Service(models.Model):
    class Meta:
        verbose_name = u'福利'
        verbose_name_plural = u'福利'
        db_table = 'api_service'
        app_label = 'api'

    name = models.CharField(max_length=100, null=False, verbose_name=u'福利名称')
    short_description = models.CharField(max_length=100, null=False, verbose_name=u'一句话描述')
    detail_description = models.TextField(max_length=2000, verbose_name=u'详细描述')
    exchange_points_ceiling = models.IntegerField(verbose_name=u'最多可抵用积分', default=0)
    doctor = models.ForeignKey(Doctor, related_name='services')
    hospital = models.ForeignKey(Hospital, verbose_name=u'关联的医院', null=True, blank=True)
    special_remind = models.TextField(max_length=1000, null=True, verbose_name=u'特别提醒', default='')
    total_price = models.IntegerField(verbose_name=u'实付金额', default=999999, help_text=u'实际需要支付的金额')
    original_price = models.IntegerField(verbose_name=u'市场价', default=999999)  # 已废弃
    gengmei_price = models.IntegerField(verbose_name=u'更美价', default=999999)
    pre_payment_price = models.IntegerField(verbose_name=u'预付款', default=999999)
    ceiling_price = models.IntegerField(verbose_name=u'最高价', default=999999)
    payment_type = models.CharField(verbose_name=u'付款模式', max_length=1, choices=PAYMENT_TYPE,
                                    default=PAYMENT_TYPE.PREPAYMENT)
    channel = models.CharField(verbose_name=u'福利频道', max_length=1, choices=SERVICE_CHANNEL,
                               default=SERVICE_CHANNEL.SPECIAL_OFFER)
    service_flag = models.CharField(verbose_name=u'福利标记', max_length=1, choices=SERVICE_FLAG,
                                    default=SERVICE_FLAG.DOCTOR)
    phone = models.CharField(max_length=20, null=True, blank=True, verbose_name=u'服务电话')
    sms_phone = models.CharField(max_length=10, null=True, blank=True, verbose_name=u'短信通知号码')
    pm_content = models.TextField(max_length=1000, null=True, blank=True, verbose_name=u'医生私信内容')
    address = models.CharField(max_length=100, null=True, blank=True, verbose_name=u'服务地址')
    is_online = models.BooleanField(default=False, help_text=u"是否上线", verbose_name=u"上线")
    is_sale = models.BooleanField(default=True, help_text=u'是否在售', verbose_name=u'是否在售(当前仅用于排序规则)')
    is_voucher = models.BooleanField(default=False, help_text=u'是否是代金券', verbose_name=u'是否是代金券类型')
    ordering = models.IntegerField(default=10000, verbose_name=u"展示顺序", help_text=u"小的排在前，大的排在后")

    sell_num_limit = models.IntegerField(verbose_name=u'可售数量限制')
    total_num = models.IntegerField(verbose_name=u"总数量")

    start_time = models.DateTimeField(default=datetime.datetime.now, verbose_name=u'福利起始时间')
    end_time = models.DateTimeField(null=True, blank=True, verbose_name=u"福利结束时间")
    update_time = models.DateTimeField(auto_now=True, verbose_name=u"最近修改时间")

    only_use_points = models.BooleanField(verbose_name=u'只能用积分购买', default=False, null=False, blank=False)
    single_user_buy_limit = models.IntegerField(default=10, verbose_name=u'单个用户购买数量限制')
    need_address = models.BooleanField(verbose_name=u'需要地址', default=False)
    need_sms_alert = models.BooleanField(verbose_name=u'需要短信提醒', default=True)
    bodypart_subitem = models.ForeignKey(BodyPartSubItem, related_name="service_sub_item",
                                         default=None, null=True, blank=True, verbose_name=u"项目类别")
    image_header = ImgUrlField(img_type=IMG_TYPE.SERVICE, max_length=300, help_text=u"福利顶部图片", verbose_name=u'图片地址',
                               default='')
    image_detail = ImgUrlField(img_type=IMG_TYPE.SERVICE, max_length=300, help_text=u"福利详情图片", verbose_name=u'图片地址',
                               default='', blank=True)
    is_multiattribute = models.BooleanField(help_text=u"是否多属性福利", verbose_name=u"是否多属性福利", default=False)
    share_get_cashback = models.BooleanField(help_text=u"是否支持分享返现", verbose_name=u"是否支持分享返现", default=True)
    refund_anytime = models.BooleanField(help_text=u"是否支持随时退款", verbose_name=u"是否支持随时退款", default=True)
    compensation_in_advance = models.BooleanField(help_text=u"是否支持先行赔付", verbose_name=u"是否支持先行赔付", default=True)
    user_safe = models.BooleanField(verbose_name=u'用户保障', default=True)
    cash_back_rate = models.IntegerField(verbose_name=u'返现百分比', default=10)
    discount = models.IntegerField(verbose_name=u'抽成', default=999999)
    fake_sold_num = models.IntegerField(verbose_name=u'已抢购基数', default=0)
    notes = models.CharField(max_length=300, help_text=u"备注", default=u"", null=True, blank=True, verbose_name=u'备注')
    cash_back_fee = models.IntegerField(verbose_name=u'返现金额', default=0)
    tags = models.ManyToManyField(Tag, related_name="services", through="ServiceTag")
    show_location = models.BooleanField(help_text=u"是否显示地理位置", verbose_name=u"是否显示地理位置", default=True)

    # update definition at 6.0, tip contains multiple comma separated tips
    tip = models.CharField(max_length=128, null=False, verbose_name=u'提示标签', default='')
    tip_separator = ','

    rating = models.FloatField(verbose_name=u'评价', blank=True, default=0)
    operation_effect_rating = models.FloatField(verbose_name=u'术后效果评分',
                                                blank=True, default=0)
    doctor_attitude_rating = models.FloatField(verbose_name=u'医生态度评分',
                                               blank=True, default=0)
    hospital_env_rating = models.FloatField(verbose_name=u'医院环境评分',
                                            blank=True, default=0)
    is_operation = models.BooleanField(u'是否手术类', default=False)
    is_vouchsafe = models.BooleanField(u'是否特惠', default=False)
    is_sink = models.BooleanField(u'是否下沉', default=False)

    wiki = models.ForeignKey(
        ItemWiki, verbose_name=u'百科',
        help_text=u'一个service只有一个wiki',
        null=True, default=None,
        blank=True
    )
    is_stage = models.BooleanField(help_text=u'是否支持分期付款', verbose_name=u'是否支持分期付款', default=False)

    reservation = models.IntegerField(help_text=u"提前预约天数", verbose_name=u"提前预约天数", default=1)

    valid_duration = models.IntegerField(default=180, blank=False, null=True, verbose_name=u'订单有效期')
    points_deduction_percent = models.IntegerField(verbose_name=u'积分抵用百分比(*100)', default=10)

    # added at 5.7
    recommend_usecase = models.BooleanField(verbose_name=u'案列推荐', default=False)

    # 自营相关
    is_self_support = models.BooleanField(verbose_name=u'是否自营', default=False)
    self_support_discount = models.IntegerField(verbose_name=u'自营抽成', default=0)

    # ceo feature
    fake_price = models.IntegerField(verbose_name=u'指定更美价', default=999999)

    # 库存更改时间 add at hera 1.10
    sellcount_changetime = models.DateTimeField(null=True, blank=True, verbose_name=u"库存更改时间")

    SECKILL_ITEM_OBJS = '_seckill_item_objs'

    SECKILL_OBJ_FOR_SPECIAL_STR = '_seckill_obj_for_special'
    SECKILL_OBJ_FOR_SPECIAL_RECHECK_STR = '_seckill_obj_for_special_recheck'

    # todo
    # 2016 11-30 号上线后可以去掉(因为要跑数据) by lipeng
    is_insurance = models.BooleanField(verbose_name=u'是否支持保险', default=False)

    # 美购重构添加 2016-08-25
    photo_details_doctor = models.TextField(verbose_name=u'医生填写图文详情', max_length=20000, null=True, blank=True)
    photo_details_operate = models.TextField(verbose_name=u'运营图文详情', max_length=20000, null=True, blank=True)
    project_type = models.ForeignKey(Tag, verbose_name=u'项目类别', null=True)

    # 美购重构添加 2016-08-25
    tickets_result = models.ForeignKey(TicketsResult, verbose_name=u'关联处罚结果', null=True, blank=True,
                                       on_delete=models.DO_NOTHING)

    # 添加是否支持众安保险字段 by lipeng
    yinuo_type = models.CharField(verbose_name=u'一诺保险类型', max_length=20, choices=YINUO_TYPE,
                                  default=YINUO_TYPE.NONE)
    zhongan_type = models.CharField(verbose_name=u'众安保险类型', max_length=20, choices=ZHONGAN_TYPE,
                                    default=None, null=True, blank=True)
    insurance = models.IntegerField('保险类型', default=0)

    # 美购页改版添加 2017-03-27
    have_extra_pay = models.BooleanField(verbose_name=u'是否有额外消费提示', default=False)
    extra_pay_info = models.TextField(verbose_name=u'额外消费提示', null=True)
    disclaimer = models.TextField(verbose_name=u'免责声明', null=True)

    # 美购页改版添加 2017-03-29
    image_bigpic = ImgUrlField(img_type=IMG_TYPE.SERVICE, max_length=300, help_text=u"美购大图",
                               verbose_name=u'图片地址',
                               default='')

    # 美购 类型 2017-06-29添加
    service_type = models.IntegerField(verbose_name=u'美购类型', choices=SERVICE_SELL_TYPE,
                                       default=SERVICE_SELL_TYPE.NORMAL)

    # 美购待包装 描述 2017-12-06添加
    package_description = models.CharField(u'美购待包装描述', max_length=200, default='')
    # 关联商户
    merchant_id = models.BigIntegerField(u'商户ID')
    __saving = False

    @property
    def service_item(self):
        return ServiceItem.objects.filter(parent_id=0)

    @property
    def service_item_all(self):
        return ServiceItem.objects.all()

    def get_merchant(self):
        try:
            m = Merchant.objects.get(id=self.merchant_id)
            return {
                'id': m.id,
                'doctor_id': m.doctor_id,
                'name': m.name
            }
        except Exception as e:
            logging_exception()
            return None

    def doctor_badges(self):
        '''
        青年医生徽章
        :return:
        '''
        d_badges = []
        h_badges = []
        doctor = self.doctor
        hospital = self.hospital
        if doctor:
            d_badges = doctor.badges()
        if hospital and hospital.officer:
            h_badges = hospital.officer.badges()
        return {
            'doctor_badges': d_badges,
            'hospital_badges': h_badges
        }

    def save(self, *args, **kwargs):
        """
        remove possible `watermark suffix` during save.
        :param args:
        :param kwargs:
        :return:
        """
        self.__saving = True
        if self.image_header and self.image_header.find("watermark"):
            self.image_header = self.image_header.split("?")[0]
        super(Service, self).save(*args, **kwargs)
        self.__saving = False

    def watermark_suffix(self):
        """
        image
        :param size:
        :return:
        """
        from .watermark import WaterMark

        watermark_tpl = "watermark/1/image/{encodedURL}/dissolve/{dissolve}/gravity/{gravity}/dx/{dx}/dy/{dy}/ws/{ws}"
        watermark = WaterMark.objects.filter(services=self,
                                             start_time__lt=timezone.now(),
                                             end_time__gt=timezone.now()).order_by('-id').first()
        if watermark:
            url = base64.urlsafe_b64encode(watermark.image_url)
            return watermark_tpl.format(encodedURL=url, dissolve=100, gravity="NorthEast", dx=10, dy=10, ws=0.40625)
        return ""

    def __getattribute__(self, item):
        """
        change `image_header` when calling `service.image_header`.
        """
        value = super(Service, self).__getattribute__(item)
        if item != "image_header" or self.__saving:
            return value
        suffix = self.watermark_suffix()
        if suffix:
            return value + quote("|") + suffix if "?" in value else value + "?" + suffix
        return value

    @staticmethod
    def get_orderding_limit():
        return _orderding_limit

    def get_supplement_images(self):
        items = ImageRelatedService.objects.filter(service_id=self.id).order_by('id')
        images = [item.image_url for item in items]
        return images

    def update_supplement_images(self, images):
        for item in images:
            flag = ImageRelatedService.objects.filter(
                service_id=self.id, image_url=item
            ).exists()
            if not flag:
                ImageRelatedService.objects.get_or_create(
                    service_id=self.id, image_url=item
                )
        ImageRelatedService.objects.filter(service_id=self.id).filter(~Q(image_url__in=images)).delete()

    def update_supplement_images_order(self, images):
        objs = ImageRelatedService.objects.filter(service_id=self.id).order_by('-id')
        for obj in objs:
            if not images:
                break
            obj.image_url = images.pop()
            obj.save()

    def get_recommend_services(self):
        items = ServiceRelatedRecommendService.objects.filter(
            service_id=self.id
        ).values('recommend_service__id', 'recommend_service__name')
        services = [{'id': rs['recommend_service__id'], 'name': rs['recommend_service__name']} for rs in items]
        return services

    def get_recommend_services_object(self):
        return ServiceRelatedRecommendService.objects.filter(
            service_id=self.id
        )

    def get_video_info(self):
        video = getattr(self, 'video', None)
        item = {}
        if video:
            item = video.get_video_info()
        return item

    def update_recommend_services(self, service_ids):
        for service_id in service_ids:
            flag = ServiceRelatedRecommendService.objects.filter(
                service_id=self.id,
                recommend_service_id=service_id,
            ).exists()
            if not flag:
                ServiceRelatedRecommendService.objects.get_or_create(
                    service_id=self.id,
                    recommend_service_id=service_id,
                )
        old_services = ServiceRelatedRecommendService.objects.filter(service_id=self.id)
        old_services.filter(~Q(recommend_service_id__in=service_ids)).delete()

    @property
    def show_name(self):
        if self.project_type:
            return self.project_type.name + u'【' + self.city_name + u'@' + self.doctor.name + u'】'
        return self.name

    @property
    def show_tags(self):
        if self.project_type:
            return self.project_type.name
        return self.name

    @property
    def first_tip(self):
        return self.tips and self.tips[0] or ''

    @property
    def tips(self):
        """
        REMOTE REFERENCE: this property is referenced by project data-transfer
        """
        if not self.tip:
            return []

        return self.tip.split(self.tip_separator)

    @staticmethod
    def get_default_price_info_by_service_item_ids(service_item_ids):
        """

        :param service_item_ids: [1,2]
        :return: { 1 : price_info_dict, 2: None}
        """
        price_list = ServiceItemPrice.objects.select_related('selling_rule').filter(
            service_item_id__in=service_item_ids,
            is_enable=True,
            is_default_price=True) if len(service_item_ids) else []

        result = Service._to_service_item_id_to_price_info(price_list, service_item_ids)
        return result

    @staticmethod
    def get_price_list_by_item_ids_with_type(service_item_ids, special_type=None, now=None, non_special=True):
        '''
        获取指定sku价格
        :param service_item_ids: sku_id列表
        :param special_type: 专题类型列表, 如果为None, 则全部专题; 否则, 查找指定专题类型
        :param now:
        :param non_special: 是否要非专题价
        :return: []
        '''
        now = now or datetime.datetime.now()

        if not service_item_ids:
            return []

        common_filter = Q(service_item_id__in=service_item_ids, is_enable=True, sale_limit_lte_zero=False)
        non_special_filter = Q(selling_rule__isnull=True)
        special_filter = Q(selling_rule__is_enable=True,
                           selling_rule__start_time__lte=now,
                           selling_rule__end_time__gte=now)
        if special_type is not None:
            if special_type:
                special_filter &= Q(selling_rule__activity_type__in=special_type)

        if non_special:
            condition_special = non_special_filter | special_filter
        else:
            condition_special = special_filter

        all_prices = ServiceItemPrice.objects.select_related('selling_rule').filter(
            common_filter & (condition_special)
        )

        result = {}
        for p in all_prices:
            service_item_id = p.service_item_id
            if service_item_id not in result:
                result[service_item_id] = p
            else:
                old_p = result[service_item_id]
                new_p = min(old_p, p, key=_sort_price_with_all)
                result[service_item_id] = new_p

        result_list = [v for k, v in result.items()]
        return result_list

    @staticmethod
    def get_current_groupbuy_price_info_by_service_item_ids(service_item_ids):
        """

        :param service_item_ids: [1,2]
        :param special_id:
        :return: { 1 : price_info_dict, 2: None}
        """

        price_list = Service._get_current_groupbuy_price_by_service_item_ids(service_item_ids) if len(
            service_item_ids) else []

        result = Service._to_service_item_id_to_price_info(price_list, service_item_ids)

        return result

    @staticmethod
    def get_seckill_price_info_by_service_item_ids(service_item_ids, special_id):
        """

        :param service_item_ids: [1,2]
        :param special_id:
        :return: { 1 : price_info_dict, 2: None}
        """

        price_list = Service._get_seckill_price_by_service_item_ids(service_item_ids, special_id) if len(
            service_item_ids) else []

        result = Service._to_service_item_id_to_price_info(price_list, service_item_ids)
        return result

    # @staticmethod
    # def get_current_price_info_by_service_item_ids(service_item_ids, now=None):
    #     """
    #         这个方法不会检查 service_item的状态
    #     :param service_item_ids: [1,2]
    #     :param now:
    #     :return: { 1 : price_info_dict, 2: None}
    #     """
    #     price_list = Service._get_current_price_by_service_item_ids(service_item_ids, now) if len(
    #         service_item_ids) else []
    #     result = Service._to_service_item_id_to_price_info(price_list, service_item_ids)
    #     return result

    @staticmethod
    def get_price_list_by_item_ids_with_type(service_item_ids, special_type=None, now=None, non_special=True):
        '''
        获取指定sku价格
        :param service_item_ids: sku_id列表
        :param special_type: 专题类型列表, 如果为None, 则全部专题; 否则, 查找指定专题类型
        :param now:
        :param non_special: 是否要非专题价
        :return: []
        '''
        now = now or datetime.datetime.now()

        if not service_item_ids:
            return []

        common_filter = Q(service_item_id__in=service_item_ids, is_enable=True, sale_limit_lte_zero=False)
        non_special_filter = Q(selling_rule__isnull=True)
        special_filter = Q(selling_rule__is_enable=True,
                           selling_rule__start_time__lte=now,
                           selling_rule__end_time__gte=now)
        if special_type is not None:
            if special_type:
                special_filter &= Q(selling_rule__activity_type__in=special_type)

        if non_special:
            condition_special = non_special_filter | special_filter
        else:
            condition_special = special_filter

        all_prices = ServiceItemPrice.objects.select_related('selling_rule').filter(
            common_filter & (condition_special)
        )

        result = {}
        for p in all_prices:
            service_item_id = p.service_item_id
            if service_item_id not in result:
                result[service_item_id] = p
            else:
                old_p = result[service_item_id]
                new_p = min(old_p, p, key=_sort_price_with_all)
                result[service_item_id] = new_p

        result_list = [v for k, v in result.items()]
        return result_list


    @staticmethod
    def get_current_price_info_by_service_item_ids(service_item_ids, now=None,
                                                   special_type=_special_and_seckill_activity_types, non_special=True):
        """
            这个方法不会检查 service_item的状态
        :param service_item_ids: [1,2]
        :param now:
        :return: { 1 : price_info_dict, 2: None}
        """
        price_list = Service.get_price_list_by_item_ids_with_type(service_item_ids, special_type, now,
                                                                  non_special) if len(
            service_item_ids) else []
        result = Service._to_service_item_id_to_price_info(price_list, service_item_ids)
        return result


    @staticmethod
    def _to_service_item_id_to_price_info(price_list, service_item_ids):
        '''
            价格列表转成美购项列表
        '''
        result = {}
        for price in price_list:
            p = ServiceItem._to_price_info(price)
            result[price.service_item_id] = p
        for siid in service_item_ids:
            if siid not in result:
                result[siid] = None
        return result

    @staticmethod
    def _get_current_groupbuy_price_by_service_item_ids(service_item_ids, now=None):
        '''
            获得团购价格
        '''
        now = now or datetime.datetime.now()

        all_prices = ServiceItemPrice.objects.filter(
            service_item_id__in=service_item_ids,
            is_enable=True,
            sale_limit_lte_zero=False,
            selling_rule__is_enable=True,
            selling_rule__start_time__lte=now,
            selling_rule__end_time__gte=now,
            selling_rule__activity_type=ACTIVITY_TYPE_ENUM.GROUPBUY)

        result = {}
        for p in all_prices:
            service_item_id = p.service_item_id
            if service_item_id not in result:
                result[service_item_id] = p
            else:
                old_p = result[service_item_id]
                new_p = min(old_p, p, key=_get_current_service_item_current_groupbuy_price_key)
                result[service_item_id] = new_p

        result_list = [v for k, v in result.items()]
        return result_list

    @staticmethod
    def _get_seckill_price_by_service_item_ids(service_item_ids, special_id):

        all_prices = ServiceItemPrice.objects.filter(
            service_item_id__in=service_item_ids,
            is_enable=True,
            selling_rule__is_enable=True,
            selling_rule__activity_type=ACTIVITY_TYPE_ENUM.SECKILL,
            selling_rule__activity_id=special_id)

        if len(service_item_ids) == 1:
            p = _add_current_service_item_price_order_by_to_query_set(all_prices).first()
            return [p] if p else []
        else:
            result = {}
            for p in all_prices:
                service_item_id = p.service_item_id
                if service_item_id not in result:
                    result[service_item_id] = p
                else:
                    old_p = result[service_item_id]
                    new_p = min(old_p, p, key=_get_current_service_item_price_key)
                    result[service_item_id] = new_p

            result_list = [v for k, v in result.items()]
            return result_list

    @classmethod
    def get_price_range_from_item_ids(cls, item_ids, special_type=_special_and_seckill_activity_types):
        '''
        指定sku列表的gengmei_price区间
        :param item_ids: sku_ids
        :return:  {
            gengmei_price: (min_price, max_price),
            original_price: (min_price, max_price),
            pre_payment_price: (min_pre_price, max_pre_price)
        }
        '''
        price_list = filter(None, cls.get_current_price_info_by_service_item_ids(item_ids,
                                                                                 special_type=special_type).values())
        if not price_list:
            return {
                'gengmei_price': (0, 0),
                'original_price': (0, 0),
                'pre_payment_price': (0, 0),
                'gengmei_price_show': '',
                'original_price_show': '',
                # 'lowest_price': {}
            }
        original_price_list = [price['original_price'] for price in price_list]
        gengmei_price_list = [price['gengmei_price'] if (
                (price.get('selling_rule') or {}).get('activity_type') != ACTIVITY_TYPE_ENUM.MOREBUY)
                              else price['single_price'] for price in price_list]
        pre_pay_price_list = [price['pre_payment_price'] for price in price_list]
        min_gengmei_price, max_gengmei_price = min(gengmei_price_list), max(gengmei_price_list)
        min_ori_price, max_ori_price = min(original_price_list), max(original_price_list)
        min_pre_price, max_pre_price = min(pre_pay_price_list), max(pre_pay_price_list)
        gengmei_price_show = '%s - %s' % (
            min_gengmei_price, max_gengmei_price) if min_gengmei_price < max_gengmei_price else '%s' % min_gengmei_price
        if max_ori_price <= max_gengmei_price or min_ori_price <= min_gengmei_price:
            original_price_show = ''
        else:
            original_price_show = '%s - %s' % (
                min_ori_price, max_ori_price) if min_ori_price < max_ori_price else '%s' % min_ori_price

        # def sort_lowest_price(x):
        #     rule = x.get('selling_rule') or {}
        #     activity_type = rule.get('activity_type') or None
        #     gengmei_price = x.get('single_price') if activity_type == ACTIVITY_TYPE_ENUM.MOREBUY else x.get('gengmei_price')
        #     return gengmei_price, [None, ACTIVITY_TYPE_ENUM.SECKILL, ACTIVITY_TYPE_ENUM.SPECIAL, ACTIVITY_TYPE_ENUM.GROUPBUY, ACTIVITY_TYPE_ENUM.MOREBUY].index(activity_type)
        # def format_out_show_price(x):
        #     p = {}
        #     rule = x.get('selling_rule') or {}
        #     p['is_groupbuy'] = rule.get('activity_type') == ACTIVITY_TYPE_ENUM.GROUPBUY
        #     p['is_seckill'] = rule.get('activity_type') == ACTIVITY_TYPE_ENUM.SECKILL
        #     p['is_multibuy'] = rule.get('activity_type') == ACTIVITY_TYPE_ENUM.MOREBUY
        #     p['gengmei_price'] = x.get('single_price') if p['is_multibuy'] else x.get('gengmei_price')
        #     p['original_price'] = x.get('original_price')
        #     p['groupbuy_nums'] = x.get('selling_rule', {}).get('groupbuy_nums')
        #     p['countdown'] = rule and (rule.get('end_time') - datetime.datetime.now()).total_seconds() or 0
        #     return p
        #
        # lowestprice = min(price_list, key=sort_lowest_price)

        return {
            'gengmei_price': (min_gengmei_price, max_gengmei_price),
            'original_price': (min_ori_price, max_ori_price),
            'pre_payment_price': (min_pre_price, max_pre_price),
            'gengmei_price_show': gengmei_price_show,
            'original_price_show': original_price_show,
            # 'lowest_price': format_out_show_price(lowestprice)
        }

    @staticmethod
    def _get_current_price_by_service_item_ids(service_item_ids, now=None, is_child=False):
        """
        比如 service_item_ids = [ 1, 2, 3, -666]
        可能返回 [] 或者 [ ServiceItemPrice, ServiceItemPrice,] 这里假设只有1和2是有效的
        这里的current_price针对的是 默认价格、专场价、秒杀价 三种按照规则确定下来 下单价

        :return: [] or [ ServiceItemPrice, ] list中不会有None
        """
        now = now or datetime.datetime.now()

        if service_item_ids is None or len(service_item_ids) == 0:
            return {}
        if not is_child:
            all_prices = ServiceItemPrice.objects.select_related('selling_rule').filter(
                Q(service_item_id__in=service_item_ids, is_enable=True, sale_limit_lte_zero=False)
                & (Q(selling_rule__is_enable=True,
                    selling_rule__start_time__lte=now,
                    selling_rule__end_time__gte=now,
                    selling_rule__activity_type__in=_special_and_seckill_activity_types,
                    )
                |
                Q(selling_rule__isnull=True)
                ))
        else:
            all_prices = ServiceItemPrice.objects.select_related('selling_rule').filter(
                Q(service_item_id__in=service_item_ids, is_enable=True, sale_limit_lte_zero=False, more_buy_price__gt=0)
                & (Q(selling_rule__is_enable=True,
                    selling_rule__start_time__lte=now,
                    selling_rule__end_time__gte=now,
                    selling_rule__activity_type__in=_special_and_seckill_activity_types,
                    selling_rule__more_buy_count__gt=0,
                    )))

        if len(service_item_ids) == 1:
            p = _add_current_service_item_price_order_by_to_query_set(all_prices).first()
            return [p] if p else []

        else:
            result = {}
            for p in all_prices:
                service_item_id = p.service_item_id
                if service_item_id not in result:
                    result[service_item_id] = p
                else:
                    old_p = result[service_item_id]
                    new_p = min(old_p, p, key=_get_current_service_item_price_key)
                    result[service_item_id] = new_p

            result_list = [v for k, v in result.items()]
            return result_list

    @cached_property
    def _sku_stock_and_total_num(self):
        res = self._queryset_can_sell_items().aggregate(sku_stock=Sum('sku_stock'), total_num=Sum('total_num'))
        sku_stock = res['sku_stock'] if res['sku_stock'] > 0 else 0
        total_num = res['total_num'] if res['total_num'] > 0 else 0
        return sku_stock, total_num

    @cached_property
    def sell_num_limit(self):
        return self._sku_stock_and_total_num[0]

    @cached_property
    def total_num(self):
        return self._sku_stock_and_total_num[1]

    @staticmethod
    def any_can_sell_items_has_stock(service_id):
        sis = ServiceItem.objects.filter(parent_id=0, service_id=service_id, is_delete=False).values_list('id',
                                                                                                          'sku_stock')
        result = any([x for x in sis if x[1] > 0])
        return result

    @cached_property
    def _can_sell_items_info(self):
        service_item_ids = ServiceItem.objects.filter(parent_id=0, service_id=self.id, sku_stock_lte_zero=False,
                                                      is_delete=False).values_list('id', flat=True)

        prices = Service._get_current_price_by_service_item_ids(service_item_ids=service_item_ids)

        result = []

        class can_sell_items_info(object):
            pass

        service_item_id_2_sku_stock = {}
        if len(prices) > 0:
            can_sell_item_ids = [x.service_item_id for x in prices]
            stock_list = ServiceItem.objects.filter(parent_id=0, id__in=can_sell_item_ids).values_list('id',
                                                                                                       'sku_stock')
            service_item_id_2_sku_stock = {x[0]: x[1] for x in stock_list}

        for p in prices:
            d = can_sell_items_info()
            d.id = p.id
            d.service_item_id = p.service_item_id
            d.discount = p.discount
            d.original_price = p.original_price
            d.gengmei_price = p.gengmei_price
            d.pre_payment_price = p.pre_payment_price

            d.sale_limit = p.sale_limit
            d.single_user_buy_limit = p.single_user_buy_limit

            d.special_id = None
            d.activity_type = None

            d.start_time = None
            d.end_time = None

            if p.selling_rule and p.selling_rule.activity_type in (
                    ACTIVITY_TYPE_ENUM.SPECIAL, ACTIVITY_TYPE_ENUM.SECKILL):
                d.special_id = p.selling_rule.activity_id
                d.activity_type = p.selling_rule.activity_type
                d.start_time = p.selling_rule.start_time
                d.end_time = p.selling_rule.end_time

            # 这里开始的部分是serviceitem的属性
            d.sku_stock = service_item_id_2_sku_stock[
                p.service_item_id] if p.service_item_id in service_item_id_2_sku_stock else 0

            result.append(d)

        return result

    @staticmethod
    def get_groupbuy_item_ids_by_service_id(service_id):
        if not service_id:
            return []
        all_can_sell_item_ids = Service.get_service_ids_to_can_sell_item_ids([service_id], check_has_stock=True).get(
            service_id, [])
        groupbuy_prices = Service._get_current_groupbuy_price_by_service_item_ids(all_can_sell_item_ids)
        return [gbp.service_item_id for gbp in groupbuy_prices]

    def get_sku_detail_info(self, coupon_info):
        items = self._queryset_can_sell_items(check_has_stock=True)
        item_ids = [item.id for item in items]
        # ================== 非拼团sku列表处理 ================
        normal_sku_result = self.format_normal_sku_price_list(item_ids)
        self.fill_sku_info_with_coupon_info(normal_sku_result, coupon_info)
        # ========= 处理拼团sku 价格信息 ===========
        groupbuy_sku_result = self.format_groupbuy_sku_price_list(item_ids)
        self.filter_exclude_price_list(normal_sku_result, groupbuy_sku_result)
        return {'normal_sku_list': normal_sku_result, 'groupbuy_sku_list': groupbuy_sku_result}

    def filter_exclude_price_list(self, normal_sku_list, groupbuy_sku_list):
        '''
        如果同一sku同时存在互斥的多买价&拼团价, 保存价格较小的价格
        :param normal_sku_list:
        :param groupbuy_sku_list:
        :return:
        '''
        multibuy_price_info = {}
        groupbuy_price_info = {}
        exclude_m_ids = set()
        exclude_g_ids = set()
        for nsku in normal_sku_list:
            if nsku.get('multibuy'):
                sku_id = nsku.get('service_item_id')
                multibuy_price = nsku['multibuy']['single_price']
                multibuy_price_info[sku_id] = multibuy_price
        for gsku in groupbuy_sku_list:
            sku_id = gsku['service_item_id']
            g_price = gsku['groupbuy_price']
            groupbuy_price_info[sku_id] = g_price

        for nsku in normal_sku_list:
            sku_id = nsku['service_item_id']
            normal_price_value = nsku['gengmei_price']
            groupbuy_price_value = multi_price_value = None
            if multibuy_price_info.get(sku_id):
                multi_price_value = multibuy_price_info[sku_id]
                if multi_price_value >= normal_price_value:
                    exclude_m_ids.add(sku_id)
            if groupbuy_price_info.get(sku_id):
                groupbuy_price_value = groupbuy_price_info[sku_id]
                if groupbuy_price_value >= normal_price_value:
                    exclude_g_ids.add(sku_id)
            if groupbuy_price_value and multi_price_value:
                if groupbuy_price_value > multi_price_value:
                    exclude_g_ids.add(sku_id)
                else:
                    exclude_m_ids.add(sku_id)

        for mid in exclude_m_ids:
            multibuy_price_info.pop(mid, None)
        for gid in exclude_g_ids:
            groupbuy_price_info.pop(gid, None)

        for nsku in normal_sku_list:
            sku_id = nsku['service_item_id']
            if sku_id in exclude_m_ids:
                nsku.pop('multibuy', None)
            if multibuy_price_info.get(sku_id) or groupbuy_price_info.get(sku_id):
                nsku['is_seckill'] = False
                nsku['promotion_image'] = ''
        for gsku in groupbuy_sku_list[::-1]:
            if gsku['service_item_id'] in exclude_g_ids:
                groupbuy_sku_list.remove(gsku)

    @classmethod
    def get_service_coupon_valid_relation(cls, service_ids, coupon_info_obj):
        '''
        获取指定券对应美购的可用关系
        :param service_ids: list, [1,2,3]
        :param coupon_info_obj:
        :return:
        '''
        result = {}
        service_objs = Service.objects.filter(id__in=service_ids)
        for s in service_objs:
            result[s.id] = s.is_available_at_coupon(coupon_info_obj)
        return result

    @classmethod
    def fill_sku_info_with_coupon_info(cls, sku_info_list, coupon_info_obj):
        from pay.manager import settlement_manager
        if not sku_info_list:
            return

        if not coupon_info_obj:
            for sku_info in sku_info_list:
                sku_info['can_use_coupon'] = False
            return

        service_ids = [x['service_id'] for x in sku_info_list]
        item_ids = [x['service_item_id'] for x in sku_info_list]
        sku_id_to_online_special_id_set = settlement_manager.get_sku_id_to_online_special_id_set(item_ids)
        coupon_restrict_special_ids = coupon_info_obj.coupon_restrict_special_ids
        service_coupon_valid_map = cls.get_service_coupon_valid_relation(service_ids, coupon_info_obj)
        for sku_info in sku_info_list:
            can_use_coupon = False
            service_id = sku_info['service_id']
            if service_coupon_valid_map.get(service_id):
                can_use_coupon = True

            if not can_use_coupon:
                item_id = sku_info['service_item_id']
                if item_id in coupon_info_obj.coupon_restrict_sku_ids:
                    can_use_coupon = True
                elif item_id in sku_id_to_online_special_id_set:
                    sku_special_ids = sku_id_to_online_special_id_set[item_id]
                    can_use_coupon = len(coupon_restrict_special_ids & sku_special_ids) > 0
            sku_info['can_use_coupon'] = can_use_coupon

    @classmethod
    def format_normal_sku_price_list(cls, item_ids):
        '''
        格式化normal sku列表
        :return:
        '''
        result_list = []
        item_obj_list = ServiceItem.objects.filter(parent_id=0, id__in=item_ids).all()
        id2obj_map = {item.id: item for item in item_obj_list}
        item2price_map = cls.get_current_price_info_by_service_item_ids(item_ids)
        for _id in item_ids:
            price_info = item2price_map.get(_id)
            item_obj = id2obj_map.get(_id)
            sku_info = ServiceItem._format_normal_sku_price_info(price_info, item_obj)
            if sku_info:
                result_list.append(sku_info)

        multibuy_sku_price_dict = cls.format_multibuy_sku_price_dict(item_ids)
        for res in result_list:
            item_id = res['service_item_id']
            multibuy_info = multibuy_sku_price_dict.get(item_id)
            if multibuy_info:
                multibuy_info['save_money'] = max(0, multibuy_info['multibuy_count'] * res['gengmei_price'] -
                                                  multibuy_info['gengmei_price'])
                res['multibuy'] = multibuy_info
        return result_list

    @classmethod
    def format_multibuy_sku_price_dict(cls, item_ids):
        '''
        根式化多买价格信息
        :param p_item_ids: parent_sku
        :return:
        '''
        result_dict = {}
        item_obj_list = ServiceItem.objects.filter(parent_id=0, id__in=item_ids).all()
        id2obj_map = {item.id: item for item in item_obj_list}
        id2price_map = cls.get_current_multibuy_price_info_by_service_item_ids(item_ids)
        for _id in item_ids:
            price_info = id2price_map.get(_id)
            item_obj = id2obj_map.get(_id)
            multi_price_info = ServiceItem._format_multibuy_sku_price_info(price_info, item_obj)
            result_dict[_id] = multi_price_info
        return result_dict

    @classmethod
    def get_current_multibuy_price_info_by_service_item_ids(cls, p_item_ids):
        '''
        根据item_ids获取对应的多买价
        :param p_item_ids: 作为parent_id存在的serviceiitem
        :return:
        '''
        item_obj_list = []
        step = 8
        batch_id_list = [p_item_ids[i:i+step] for i in range(0, len(p_item_ids), step)]
        for batch_ids in batch_id_list:     ### in的列表过长不使用索引问题, hhh
            temp_item_obj_list = ServiceItem.objects.filter(parent_id__in=batch_ids, is_delete=False).all()
            item_obj_list.extend(temp_item_obj_list)

        id2obj_map = {item.id: item for item in item_obj_list}
        item_ids = id2obj_map.keys()
        item2price_map = cls.get_current_price_info_by_service_item_ids(item_ids,
                                                                        special_type=[ACTIVITY_TYPE_ENUM.MOREBUY],
                                                                        non_special=False)

        result_dict = {}
        for _id, price_info in item2price_map.items():
            if not price_info:
                continue

            p_sku_id = price_info['parent_id']
            if not result_dict.get(p_sku_id):
                result_dict[p_sku_id] = price_info
            else:
                old_p = result_dict[p_sku_id]
                target_p = min(old_p, price_info,
                               key=lambda x: (x['single_price'], x.get('selling_rule', {}).get('more_buy_count') or 1))
                result_dict[p_sku_id] = target_p
        return result_dict

    @classmethod
    def format_groupbuy_sku_price_list(cls, item_ids):
        '''
        格式化拼团 SKU 列表
        :param item_ids:
        :return:
        '''
        g_sku_info_list = []
        item_obj_list = ServiceItem.objects.filter(parent_id=0, id__in=item_ids).all()
        id2obj_map = {item.id: item for item in item_obj_list}
        item2price_map = cls.get_current_groupbuy_price_info_by_service_item_ids(item_ids)
        for _id in item_ids:
            price_info = item2price_map.get(_id)
            item_obj = id2obj_map.get(_id)
            sku_info = ServiceItem._format_groupbuy_sku_price_info(price_info, item_obj)
            if sku_info:
                g_sku_info_list.append(sku_info)
        return g_sku_info_list

    def get_city_sku_detail_info(self, coupon_info):
        '''
        获取根据根据层级返回的sku_list
        :param coupon_info:
        :return:
            {
                city_id:
                city_name:
                hospital_id:
                hospital_name:
                normal_sku_list: [{},{}...]
                groupbuy_sku_list: [{},{}...]
            }
        '''
        result = []
        if self.service_type != SERVICE_SELL_TYPE.FLAGSHIP_STORE_SERVICE:
            sku_info = self.get_sku_detail_info(coupon_info)
            if not sku_info.get('normal_sku_list'):
                return []
            item_list = self._queryset_can_sell_items(check_has_stock=True)
            item_ids = [_ite.id for _ite in item_list]
            price_range_info = self.get_price_range_from_item_ids(item_ids, special_type=None)
            min_pre_price, max_pre_price = price_range_info['pre_payment_price']
            data = {
                'city_id': self.city and self.city.id,
                'city_name': self.city_name,
                'hospital_name': self.hospital_name,
                'hospital_id': self.doctor and self.doctor.hospital and self.doctor.hospital.id,
                'gengmei_price': price_range_info.get('gengmei_price_show'),
                'original_price': price_range_info.get('original_price_show'),
                'pre_payment_price': min_pre_price,
            }

            sku_info.update(data)
            return [sku_info]
        else:
            item_list = self._queryset_can_sell_items(check_has_stock=True)
            city2items = defaultdict(list)
            for i in item_list:
                city2items[i.city_id].append(i)
            city_ids = city2items.keys()
            city_list = City.objects.filter(id__in=city_ids)
            id2obj_city_map = {city.id: city for city in city_list}
            for city_id, _items in city2items.items():
                city_hos_info = self.get_related_sub_hospital_by_city(city_id)
                if not city_hos_info:
                    continue
                city_hos_info.update({'city_id': city_id, 'city_name': id2obj_city_map[city_id].name})
                siids = [ite.id for ite in _items]
                normal_sku_list = self.format_normal_sku_price_list(siids)
                self.fill_sku_info_with_coupon_info(normal_sku_list, coupon_info)

                price_range_info = self.get_price_range_from_item_ids(siids, special_type=flagship_support_types)
                min_pre_price, max_pre_price = price_range_info['pre_payment_price']
                # city_hos_info.update(price_range_info['lowest_price'])
                city_hos_info['gengmei_price'] = price_range_info.get('gengmei_price_show')
                city_hos_info['original_price'] = price_range_info.get('original_price_show')
                city_hos_info['pre_payment_price'] = min_pre_price
                city_hos_info['normal_sku_list'] = normal_sku_list
                city_hos_info['groupbuy_sku_list'] = []
                result.append(city_hos_info)
            result.sort(key=lambda x: x['city_id'])
            return result

    @classmethod
    def get_price_range_from_item_ids(cls, item_ids, special_type=_special_and_seckill_activity_types):
        '''
        指定sku列表的gengmei_price区间
        :param item_ids: sku_ids
        :return:  {
            gengmei_price: (min_price, max_price),
            original_price: (min_price, max_price),
            pre_payment_price: (min_pre_price, max_pre_price)
        }
        '''
        price_list = filter(None, cls.get_current_price_info_by_service_item_ids(item_ids,
                                                                                 special_type=special_type).values())
        if not price_list:
            return {
                'gengmei_price': (0, 0),
                'original_price': (0, 0),
                'pre_payment_price': (0, 0),
                'gengmei_price_show': '',
                'original_price_show': '',
                # 'lowest_price': {}
            }
        original_price_list = [price['original_price'] for price in price_list]
        gengmei_price_list = [price['gengmei_price'] if (
                    (price.get('selling_rule') or {}).get('activity_type') != ACTIVITY_TYPE_ENUM.MOREBUY)
                              else price['single_price'] for price in price_list]
        pre_pay_price_list = [price['pre_payment_price'] for price in price_list]
        min_gengmei_price, max_gengmei_price = min(gengmei_price_list), max(gengmei_price_list)
        min_ori_price, max_ori_price = min(original_price_list), max(original_price_list)
        min_pre_price, max_pre_price = min(pre_pay_price_list), max(pre_pay_price_list)
        gengmei_price_show = '%s - %s' % (
            min_gengmei_price, max_gengmei_price) if min_gengmei_price < max_gengmei_price else '%s' % min_gengmei_price
        if max_ori_price <= max_gengmei_price or min_ori_price <= min_gengmei_price:
            original_price_show = ''
        else:
            original_price_show = '%s - %s' % (
                min_ori_price, max_ori_price) if min_ori_price < max_ori_price else '%s' % min_ori_price

        # def sort_lowest_price(x):
        #     rule = x.get('selling_rule') or {}
        #     activity_type = rule.get('activity_type') or None
        #     gengmei_price = x.get('single_price') if activity_type == ACTIVITY_TYPE_ENUM.MOREBUY else x.get('gengmei_price')
        #     return gengmei_price, [None, ACTIVITY_TYPE_ENUM.SECKILL, ACTIVITY_TYPE_ENUM.SPECIAL, ACTIVITY_TYPE_ENUM.GROUPBUY, ACTIVITY_TYPE_ENUM.MOREBUY].index(activity_type)
        # def format_out_show_price(x):
        #     p = {}
        #     rule = x.get('selling_rule') or {}
        #     p['is_groupbuy'] = rule.get('activity_type') == ACTIVITY_TYPE_ENUM.GROUPBUY
        #     p['is_seckill'] = rule.get('activity_type') == ACTIVITY_TYPE_ENUM.SECKILL
        #     p['is_multibuy'] = rule.get('activity_type') == ACTIVITY_TYPE_ENUM.MOREBUY
        #     p['gengmei_price'] = x.get('single_price') if p['is_multibuy'] else x.get('gengmei_price')
        #     p['original_price'] = x.get('original_price')
        #     p['groupbuy_nums'] = x.get('selling_rule', {}).get('groupbuy_nums')
        #     p['countdown'] = rule and (rule.get('end_time') - datetime.datetime.now()).total_seconds() or 0
        #     return p
        #
        # lowestprice = min(price_list, key=sort_lowest_price)

        return {
            'gengmei_price': (min_gengmei_price, max_gengmei_price),
            'original_price': (min_ori_price, max_ori_price),
            'pre_payment_price': (min_pre_price, max_pre_price),
            'gengmei_price_show': gengmei_price_show,
            'original_price_show': original_price_show,
            # 'lowest_price': format_out_show_price(lowestprice)
        }

    def get_related_sub_hospital_by_city(self, city_id):
        '''
        service_type=3 旗舰店美购下sku, 根据城市获取对应的子门店
        :return:
        '''
        from hippo.views.hospital import get_sub_hospital
        hos_list = get_sub_hospital(self.doctor_id, city_id)
        return hos_list and hos_list[0] or {}

    def is_available_at_coupon(self, coupon_info_obj):
        '''
        该美购是否可用该券, 对其下全部sku有效
        :param coupon_info_obj:
        :return:  True | False
        '''
        is_available = False
        if not coupon_info_obj:
            return is_available

        coupon_restrict_doctor_ids = coupon_info_obj.coupon_restrict_doctor_ids
        coupon_restrict_sku_ids = coupon_info_obj.coupon_restrict_sku_ids
        coupon_restrict_special_ids = coupon_info_obj.coupon_restrict_special_ids

        # 尾款券
        if self.doctor_id in coupon_restrict_doctor_ids and len(coupon_restrict_sku_ids) == 0:
            is_available = True
        # 预付款券
        elif coupon_info_obj.coupon.coupon_type == COUPON_TYPES.PLATFORM and \
                len(coupon_restrict_doctor_ids) == 0 and len(coupon_restrict_special_ids) == 0:
            is_available = True
        else:
            from pay.manager import settlement_manager
            service_id_in_online_special_by_tags = settlement_manager.get_service_id_to_online_special_ids_by_tags(
                [self.id])
            service_online_special_ids = set(service_id_in_online_special_by_tags.get(self.id, []))
            if service_online_special_ids:
                is_available = len(coupon_restrict_special_ids & service_online_special_ids) > 0

        return is_available

    def get_gift_rank_for_es(self):
        from api.tool.coupon_tool import get_coupon_gift_info_for_service_detail

        can_get_gift_ids, already_get_gift_ids = get_coupon_gift_info_for_service_detail(
            user=None,
            service_id=self.id)

        return 1 if len(can_get_gift_ids) > 0 else 0

    def get_special_rank_for_es(self):
        """
        获取专场/秒杀的rank, ES专用方法
        """
        check_pos = lambda pos: True if pos > 0 else False
        service_item = ServiceItem.objects.filter(parent_id=0, service=self, is_delete=False)
        rank_map = {}
        from api.models.special import SpecialItem
        specialitems = SpecialItem.objects.filter(
            service=self,
            special__is_online=True,
            serviceitem__in=service_item)
        for item in specialitems:
            pos = item.position
            special_id = item.special_id
            if item.special_id not in rank_map:
                rank_map[special_id] = {
                    'rank': item.rank,
                    'item_id': item.id,
                    'sku_id': item.serviceitem_id,
                    'position': item.position,
                    'has_pos': check_pos(pos),
                }
            else:
                rank_map[item.special_id]['rank'] = min(rank_map[special_id]['rank'], item.rank)
                if pos > 0 and (rank_map[special_id]['position'] <= 0 or pos < rank_map[special_id]['position']):
                    rank_map[special_id]['item_id'] = item.id
                    rank_map[special_id]['sku_id'] = item.serviceitem_id
                    rank_map[special_id]['position'] = pos
                    rank_map[special_id]['has_pos'] = check_pos(pos)

        for item in specialitems:
            special_id = item.special_id

            if special_id not in rank_map:
                rank_map[special_id] = {}

            if 'floor_id' not in rank_map[special_id]:
                rank_map[special_id]['floor_id'] = []

            if item.floor_id and item.floor_id not in rank_map[special_id]['floor_id']:
                rank_map[special_id]['floor_id'].append(item.floor_id)

        return rank_map

    def get_new_sku_special_rank_for_es(self):
        """
        获取专场/秒杀的rank, ES专用方法
        """
        check_pos = lambda pos: True if pos > 0 else False
        service_item = ServiceItem.objects.filter(service=self, is_delete=False)
        rank_map = {}
        from api.models.special import SpecialItem
        specialitems = SpecialItem.objects.filter(
            service=self,
            special__is_online=True,
            serviceitem__in=service_item)
        for item in specialitems:

            parent_id = ServiceItem.objects.filter(id=item.serviceitem_id,is_delete=False).values_list("parent_id", flat=True).first()

            is_delete_judg = ServiceItem.objects.filter(id=parent_id).values_list("is_delete", flat=True).first()
            if parent_id and is_delete_judg == False:
                if parent_id != 0:
                    sku_id = parent_id
                else:
                    sku_id = item.serviceitem_id
            else:
                sku_id = item.serviceitem_id
            pos = item.position
            special_id = item.special_id
            if item.special_id not in rank_map:
                rank_map[special_id] = {
                    'rank': item.rank,
                    'item_id': item.id,
                    'sku_id': sku_id,
                    'position': item.position,
                    'has_pos': check_pos(pos),
                }
            else:
                rank_map[item.special_id]['rank'] = min(rank_map[special_id]['rank'], item.rank)
                if pos > 0 and (rank_map[special_id]['position'] <= 0 or pos < rank_map[special_id]['position']):
                    rank_map[special_id]['item_id'] = item.id
                    rank_map[special_id]['sku_id'] = sku_id
                    rank_map[special_id]['position'] = pos
                    rank_map[special_id]['has_pos'] = check_pos(pos)

        for item in specialitems:
            special_id = item.special_id

            if special_id not in rank_map:
                rank_map[special_id] = {}

            if 'floor_id' not in rank_map[special_id]:
                rank_map[special_id]['floor_id'] = []

            if item.floor_id and item.floor_id not in rank_map[special_id]['floor_id']:
                rank_map[special_id]['floor_id'].append(item.floor_id)

        return rank_map

    def is_can_be_sold_for_es(self):
        """
        ES专用方法。如果SPU下任意一个有效SKU可售（在线并且有库存），那么SPU就可售（返回True）
        """
        can_sold = self._queryset_can_sell_items(check_has_stock=True).exists()
        return can_sold

    def can_be_sold_time_range_list_for_es(self):
        """
        ES专用方法，返回一个包含了(s,e)的list，list可能为[]，其中s和e都是datetime。
        对于任意时刻t,如果任意一个(s,e)满足s <= t <= e，那么在t时刻美购可售。
        这个方法的语义会尽可能和is_can_be_sold()一致。
        目前使用的is_can_be_sold_for_es()和is_can_be_sold()差别较大，之后将会淘汰
        :return: [(s1,e2), ... ] or []
        """
        result = self.is_can_be_sold(check_service_time_range=False)
        return [self._get_valid_service_time_ragne()] if result is True else []

    def get_dajiadouzaimai_rank_for_es(self):
        """
            http://wiki.gengmei.cc/pages/viewpage.action?pageId=4153726 为 大家都在买 提供数据

        :return: 大于等于 0 的int
        """
        service_item_id_to_order_count = self._get_dajiadouzaimai_rank_for_es()
        rank = sum([count for siid, count in service_item_id_to_order_count.items()])
        return rank

    def _get_dajiadouzaimai_rank_for_es(self):
        service_item_id_to_order_count = {}

        can_sold_item_ids = list(self._queryset_can_sell_items(check_has_stock=True).values_list('id', flat=True))
        if can_sold_item_ids:
            from api.models import ORDER_STATUS_USED
            from api.models import Order

            now = datetime.datetime.now()
            days = -90
            start_time = now + datetime.timedelta(days=days)
            sku_counts_data = Order.objects.filter(
                created_time__gte=start_time,
                service_item_id__in=can_sold_item_ids,
                status__in=ORDER_STATUS_USED).values('service_item_id').annotate(Count('service_item_id'))
            service_item_id_to_order_count = {d['service_item_id']: d['service_item_id__count'] for d in
                                              sku_counts_data}

        return service_item_id_to_order_count

    def seckill_time_range_for_es(self):
        """
        不关心SKU是否有库存，也不关心SKU价格是否有可销售数量
        :return:
        """
        now = datetime.datetime.now()
        can_sold_item_ids = self._queryset_can_sell_items().values_list('id', flat=True)

        if len(can_sold_item_ids) == 0:
            return []

        seckill_time_ranges_data = ServiceItemPrice.objects.filter(service_item_id__in=can_sold_item_ids,
                                                                   is_enable=True,
                                                                   selling_rule__is_enable=True,
                                                                   selling_rule__end_time__gt=now,
                                                                   selling_rule__activity_type=ACTIVITY_TYPE_ENUM.SECKILL, ) \
            .values_list('selling_rule__start_time', 'selling_rule__end_time') if can_sold_item_ids else []

        seckill_time_ranges = [{'seckill_start_time': x[0], 'seckill_end_time': x[1]} for x in seckill_time_ranges_data]

        return seckill_time_ranges

    def check_is_valid_for_es(self):
        """
            对于部分业务逻辑上无效的数据，会返回False。因为业务上无效的service可能无法获得正确的数据，因此在
            ES中对于无效的service将会通过在ES中生成{'id':x.'is_online':False}的记录用来覆盖掉原有的记录
        :return:
        """
        has_sku_online_and_can_sell = len(self._can_sell_items_info) > 0
        service_and_doctor_is_online = self.is_online
        if self.doctor:
            service_and_doctor_is_online = service_and_doctor_is_online and self.doctor.is_online

        end_time_is_valid = datetime.datetime.now() < self.end_time if self.end_time else True

        is_valid = has_sku_online_and_can_sell and service_and_doctor_is_online and end_time_is_valid
        return is_valid

    def get_price_range_for_es(self):
        """
        要求SKU有库存以及SKU价格可售数量大于0
        :return:
        """
        now = datetime.datetime.now()
        can_sold_item_ids = self._queryset_can_sell_items(check_has_stock=True).values_list('id', flat=True)

        if len(can_sold_item_ids) == 0:
            return ([], [])

        prices = ServiceItemPrice.objects.select_related('selling_rule').filter(
            Q(service_item_id__in=can_sold_item_ids, is_enable=True, sale_limit_lte_zero=False)
            & (Q(selling_rule__is_enable=True,
                 selling_rule__end_time__gte=now,
                 selling_rule__activity_type__in=_special_and_seckill_activity_types)
               |
               Q(selling_rule__isnull=True)
               )).values_list('id', 'is_default_price', 'selling_rule__start_time', 'selling_rule__end_time',
                              'gengmei_price', 'pre_payment_price', 'service_item_id')

        if len(prices) == 0:
            return ([], [])

        service_item_id_2_prices = {}

        for p in prices:
            sid = p[6]
            if sid not in service_item_id_2_prices:
                service_item_id_2_prices[sid] = []
            service_item_id_2_prices[sid].append(p)

        prices_info = []
        for k, v in service_item_id_2_prices.iteritems():
            pi = [(x[0], _min_service_time if x[1] else x[2], _max_service_time if x[1] else x[3], x[4], x[5]) for x in
                  v]
            prices_info.append(pi)

        prices_range = Service._get_price_range_for_es(prices_info)

        return prices_range

    def get_sku_price_range_for_es(self, sku):
        """获取sku按时间分段的价格, 每个时间点都能唯一确定一个价格

        :param sku: sku object
        :return:
        """
        now_t = datetime.datetime.now()
        sku_can_sold = self._queryset_can_sell_items(check_has_stock=True).filter(id=sku.id)
        if not sku_can_sold.exists():
            return []

        prices = ServiceItemPrice.objects.select_related('selling_rule').filter(
            Q(service_item_id=sku.id, is_enable=True, sale_limit_lte_zero=False)
            & (Q(selling_rule__is_enable=True,
                 selling_rule__end_time__gte=now_t,
                 selling_rule__activity_type__in=_special_and_seckill_activity_types)
               |
               Q(selling_rule__isnull=True))
        ).values_list(
            'id', 'is_default_price', 'selling_rule__start_time', 'selling_rule__end_time',
            'gengmei_price', 'pre_payment_price', 'service_item_id')
        prices_info = []
        for p in prices:
            # pi struct: [price_id, selling_rule__start_time, selling_rule__end_time, gengmei_price, pre_payment_price]
            pi = [p[0], _min_service_time if p[1] else p[2], _max_service_time if p[1] else p[3], p[4], p[5]]
            prices_info.append(pi)

        sku_prices_range = Service._get_range_for_prices(prices_info, Service._select_for_sku)

        sku_prices_range_for_read = [{'start_time': x[1], 'end_time': x[2], 'price': x[3]} for x in sku_prices_range]
        return sku_prices_range_for_read

    @staticmethod
    def _get_range_for_prices(prices_info, selector):

        time_dict = {}
        # time_dict: {'开始时间/结束时间': (price_id, 开始True/结束False)}
        for pinfo in prices_info:
            if pinfo[1] < pinfo[2]:
                data = [(pinfo[1], pinfo[0], True), (pinfo[2], pinfo[0], False)]
                for d in data:
                    if d[0] not in time_dict:
                        time_dict[d[0]] = []
                    time_dict[d[0]].append((d[1], d[2]))

        # 按时间排序
        time_series = [(k, v) for (k, v) in sorted(time_dict.items(), key=lambda (k, v): k)]

        ct = None  # (pid,st,gp)
        all_ct = {}  # { pid: (st) }
        result = []  # [ (pid,st,ed,gp) ]

        for time, id_is_st_list in time_series:
            st_list = []
            et_list = []

            for item in id_is_st_list:
                id = item[0]
                is_st = item[1]
                if is_st is False:
                    et_list.append(id)
                else:
                    st_list.append(id)

            for id in et_list:
                if ct and id == ct[0]:
                    result.append((ct[0], ct[1], time, ct[2]))
                    ct = None
                all_ct.pop(id, None)

            for id in st_list:
                if id not in all_ct:
                    all_ct[id] = time

            if all_ct:
                select_info = [x for x in prices_info if x[0] in all_ct]

                if len(all_ct) == 0:
                    pass

                se = selector(select_info)
                new_ct = (se[0], time, se[3])
                if ct:
                    if new_ct[0] != ct[0]:
                        result.append((ct[0], ct[1], time, ct[2]))
                        ct = new_ct
                else:
                    ct = new_ct

        return result

    @staticmethod
    def _merge_range(data_list):
        '''

        :param data_list: sorted [ (start_time,end_time,price)]
        :return: merged
        '''
        merged_list = []
        i = 0

        while i < len(data_list):
            cur = data_list[i]

            n = i
            while n < len(data_list) and data_list[n][2] == cur[2]:
                n += 1

            merged_list.append((cur[0], data_list[n - 1][1], cur[2]))
            i = n

        return merged_list

        # m = _merge_range([])
        # m = _merge_range([
        #     (datetime.datetime(2010, 1, 1, 0, 0), datetime.datetime(2017, 1, 1, 0, 0), 100),
        # ])
        # m = _merge_range([
        #     (datetime.datetime(2010, 1, 1, 0, 0), datetime.datetime(2017, 1, 1, 0, 0), 100),
        #     (datetime.datetime(2017, 1, 1, 0, 0), datetime.datetime(2017, 1, 4, 0, 0), 100),
        #     (datetime.datetime(2017, 1, 4, 0, 0), datetime.datetime(2017, 1, 7, 0, 0), 100),
        # ])
        # m = _merge_range([
        #     (datetime.datetime(2010, 1, 1, 0, 0), datetime.datetime(2017, 1, 1, 0, 0), 100),
        #     (datetime.datetime(2017, 1, 1, 0, 0), datetime.datetime(2017, 1, 3, 0, 0), 100),
        #     (datetime.datetime(2017, 1, 3, 0, 0), datetime.datetime(2017, 1, 9, 0, 0), 998),
        #     (datetime.datetime(2017, 1, 9, 0, 0), datetime.datetime(2017, 1, 11, 0, 0), 100),
        #     (datetime.datetime(2017, 1, 11, 0, 0), datetime.datetime(2017, 2, 3, 0, 0), 100),
        # ])

    @staticmethod
    def _select_for_sku(prices_info):
        selected = min(prices_info, key=lambda t: (t[3], t[4], -t[0]))
        return selected

    @staticmethod
    def _select_for_spu(prices_info):
        selected = min(prices_info, key=lambda t: (t[3], -t[0]))
        return selected

    @staticmethod
    def _get_price_range_for_es(prices_info):

        # prices_info = { (id,s_time,e_time, gengmei_price, pre_payment_price)}
        import itertools
        # 针对每一个SKU，计算出这个SKU的价格区间
        sku_prices_range = [Service._get_range_for_prices(d, Service._select_for_sku) for d in prices_info]

        sku_prices_range_for_read = [{'start_time': x[1], 'end_time': x[2], 'price': x[3]} for x in
                                     itertools.chain.from_iterable(sku_prices_range)]

        sku_prices_range_info = []
        i = 0
        for y in sku_prices_range:
            for x in y:
                i += 1
                sku_prices_range_info.append((i, x[1], x[2], x[3], 1))  # 第一个是新生成的唯一Id, 最后一个是固定的值（因为用不上了）

        spu_prices_range = Service._get_range_for_prices(sku_prices_range_info, Service._select_for_spu)

        final_answer = Service._merge_range([(x[1], x[2], x[3]) for x in spu_prices_range])

        final_answer_for_read = [{'start_time': x[0], 'end_time': x[1], 'price': x[2]} for x in final_answer]

        return (sku_prices_range_for_read, final_answer_for_read)

    @staticmethod
    def _get_price_range_for_es_test():

        # 在输入人工加入一个 超长有效时间的默认价格（并且排序优先级相对更低），就可以用默认价格覆盖所有没有价格的范围
        # 然后SPU级别就可以

        from datetime import timedelta

        ds_time = _min_service_time
        de_time = _max_service_time

        data_1 = [[],

                  [
                      # default_price的id一定是同一个SKU中最小，然后给default_price赋值一个较大的时间范围，用来覆盖没有活动价的部分
                      (1, ds_time, de_time, 100, 10),
                      (2, datetime.datetime(2017, 2, 11), datetime.datetime(2017, 2, 13), 80, 10),
                      (3, datetime.datetime(2017, 2, 13), datetime.datetime(2017, 2, 15), 20, 10),

                      (8, datetime.datetime(2017, 2, 13), datetime.datetime(2017, 2, 15), 8888, 10),
                      (9, datetime.datetime(2017, 2, 13), datetime.datetime(2017, 2, 15), 9999, 10),
                  ],

                  [
                      # default_price的id一定是同一个SKU中最小，然后给default_price赋值一个较大的时间范围，用来覆盖没有活动价的部分
                      (11, ds_time, de_time, 100, 10),
                      (12, datetime.datetime(2017, 2, 11), datetime.datetime(2017, 2, 13), 80, 10),
                      (13, datetime.datetime(2017, 2, 11), datetime.datetime(2017, 2, 15), 90, 10),

                      (18, datetime.datetime(2017, 2, 13), datetime.datetime(2017, 2, 15), 8888, 10),
                      (19, datetime.datetime(2017, 2, 13), datetime.datetime(2017, 2, 15), 9999, 10),
                  ],

                  [
                      # default_price的id一定是同一个SKU中最小，然后给default_price赋值一个较大的时间范围，用来覆盖没有活动价的部分
                      (21, ds_time, de_time, 100, 10),
                      (22, datetime.datetime(2017, 2, 11), datetime.datetime(2017, 2, 15), 90, 10),
                      (23, datetime.datetime(2017, 2, 13), datetime.datetime(2017, 2, 15), 80, 10),

                      (28, datetime.datetime(2017, 2, 13), datetime.datetime(2017, 2, 15), 8888, 10),
                      (29, datetime.datetime(2017, 2, 13), datetime.datetime(2017, 2, 15), 9999, 10),
                  ],

                  [
                      # default_price的id一定是同一个SKU中最小，然后给default_price赋值一个较大的时间范围，用来覆盖没有活动价的部分
                      (31, ds_time, de_time, 100, 10),
                      (32, datetime.datetime(2017, 2, 11), datetime.datetime(2017, 2, 13), 90, 10),
                      (33, datetime.datetime(2017, 2, 15), datetime.datetime(2017, 2, 17), 80, 10),

                      (38, datetime.datetime(2017, 2, 13), datetime.datetime(2017, 2, 15), 8888, 10),
                      (39, datetime.datetime(2017, 2, 13), datetime.datetime(2017, 2, 15), 9999, 10),
                  ],

                  [
                      # default_price的id一定是同一个SKU中最小，然后给default_price赋值一个较大的时间范围，用来覆盖没有活动价的部分
                      (41, ds_time, de_time, 100, 10),
                      (42, datetime.datetime(2017, 2, 11), datetime.datetime(2017, 2, 13), 80, 10),
                      (43, datetime.datetime(2017, 2, 13), datetime.datetime(2017, 2, 15), 90, 10),
                      (44, datetime.datetime(2017, 2, 15), datetime.datetime(2017, 2, 17), 80, 10),

                      (48, datetime.datetime(2017, 2, 13), datetime.datetime(2017, 2, 15), 8888, 10),
                      (49, datetime.datetime(2017, 2, 13), datetime.datetime(2017, 2, 15), 9999, 10),
                  ],

                  [
                      # default_price的id一定是同一个SKU中最小，然后给default_price赋值一个较大的时间范围，用来覆盖没有活动价的部分
                      (51, ds_time, de_time, 100, 10),
                      (52, datetime.datetime(2017, 2, 13), datetime.datetime(2017, 2, 15), 80, 10),
                      (53, datetime.datetime(2017, 2, 11), datetime.datetime(2017, 2, 17), 90, 10),

                      (58, datetime.datetime(2017, 2, 13), datetime.datetime(2017, 2, 15), 8888, 10),
                      (59, datetime.datetime(2017, 2, 13), datetime.datetime(2017, 2, 15), 9999, 10),
                  ],

                  [
                      # default_price的id一定是同一个SKU中最小，然后给default_price赋值一个较大的时间范围，用来覆盖没有活动价的部分
                      (61, ds_time, de_time, 150, 10),
                      (62, datetime.datetime(2017, 1, 1), datetime.datetime(2017, 2, 10), 100, 10),

                      (68, datetime.datetime(2017, 2, 13), datetime.datetime(2017, 2, 15), 8888, 10),
                      (69, datetime.datetime(2017, 2, 13), datetime.datetime(2017, 2, 15), 9999, 10),
                  ],

                  [
                      # default_price的id一定是同一个SKU中最小，然后给default_price赋值一个较大的时间范围，用来覆盖没有活动价的部分
                      (71, ds_time, de_time, 5, 5),
                      (72, datetime.datetime(2017, 2, 11), datetime.datetime(2017, 2, 13), 80, 10),
                      (73, datetime.datetime(2017, 2, 13), datetime.datetime(2017, 2, 15), 90, 10),
                      (74, datetime.datetime(2017, 2, 15), datetime.datetime(2017, 2, 17), 80, 10),
                  ],
                  ]

        price_id = 1
        cut = datetime.datetime(2016, 1, 1)
        sku_count = 20
        sku_price_count = 40

        ps = []

        import random

        for x in range(0, sku_count):
            sk = []
            dfprice = random.randint(998, 1999)
            price_id += 1
            sk.append((price_id, ds_time, de_time, dfprice, 1))
            for y in range(0, sku_price_count):
                price_id += 1
                np = random.randint(dfprice / 2, dfprice - 1)
                cut = cut + timedelta(days=1)
                edt = cut + timedelta(days=1)
                sk.append((price_id, cut, edt, np, 1))
                cut = edt
            ps.append(sk)

        res = Service._get_price_range_for_es(data_1)

        t1 = datetime.datetime.now()

        res2 = Service._get_price_range_for_es(ps)

        t2 = datetime.datetime.now()

        el = (t2 - t1).microseconds

        reee = []
        for d in data_1:
            eeee = Service._get_sku_price_range_list_for_es(d) if len(d) else []
            reee.append(eeee)

        pass

    def get_sku_price_range_list_for_es(self):
        """

        :return: [ {'sku_id': 1, } ]
        """
        spu_can_be_sold = self.is_can_be_sold(check_any_sku_has_stock=False)

        if not spu_can_be_sold:
            return []

        now = datetime.datetime.now()
        can_sold_item_ids = self._queryset_can_sell_items(check_has_stock=True).values_list('id', flat=True)

        if len(can_sold_item_ids) == 0:
            return []

        prices = ServiceItemPrice.objects.select_related('selling_rule').filter(
            Q(service_item_id__in=can_sold_item_ids, is_enable=True, sale_limit_lte_zero=False)
            & (Q(selling_rule__is_enable=True,
                 selling_rule__end_time__gte=now,
                 selling_rule__activity_type__in=_special_and_seckill_activity_types)
               |
               Q(selling_rule__isnull=True)
               ))

        service_item_id_2_prices = {}

        for p in prices:
            siid = p.service_item_id
            if siid not in service_item_id_2_prices:
                service_item_id_2_prices[siid] = []
            service_item_id_2_prices[siid].append(p)

        service_item_id_2_prices_info_list = {}
        price_id_2_prices_info = {}

        for siid, prices in service_item_id_2_prices.items():
            service_item_id_2_prices_info_list[siid] = []
            for p in prices:
                price_info = ServiceItem._to_price_info(p)
                price_id = price_info['id']

                start_time, end_time = self._get_valid_service_time_ragne()

                if 'selling_rule' in price_info and 'start_time' in price_info['selling_rule']:
                    start_time = price_info['selling_rule']['start_time']

                if 'selling_rule' in price_info and 'end_time' in price_info['selling_rule']:
                    end_time = price_info['selling_rule']['end_time']

                pi = {
                    'sku_id': siid,
                    'price_id': price_id,
                    'start_time': start_time,
                    'end_time': end_time,
                    'price_type': price_info['price_type'],
                    'pre_payment_price': price_info['pre_payment_price'],
                    'gengmei_price': price_info['gengmei_price'],
                }
                service_item_id_2_prices_info_list[siid].append(pi)
                price_id_2_prices_info[price_id] = pi

        all_sku_price_range_list = []
        for siid, price_info_list in service_item_id_2_prices_info_list.items():
            sku_price_range_list = Service._get_sku_price_range_list_for_es(
                [(
                    p['price_id'], p['start_time'], p['end_time'], p['gengmei_price'], p['pre_payment_price']
                ) for p in price_info_list])
            for p in sku_price_range_list:
                all_sku_price_range_list.append(p)

        service_item_id_to_option_name_list = ServiceItem.get_items_name(can_sold_item_ids)

        days = -30
        start_time = now + datetime.timedelta(days=days)
        all_sku_ids = [siid for siid, _ in service_item_id_2_prices_info_list.items()]
        order_sold_status = (ORDER_STATUS.PAID, ORDER_STATUS.USED, ORDER_STATUS.SETTLED)

        from api.models import Order
        sku_counts_data = Order.objects.filter(
            created_time__gte=start_time,
            status__in=order_sold_status,
            service_item_id__in=all_sku_ids).values('service_item_id').annotate(Count('service_item_id'))

        service_item_id_to_order_count = {d['service_item_id']: d['service_item_id__count'] for d in sku_counts_data}

        result = []
        for p in all_sku_price_range_list:
            price_id = p[0]
            start_time = p[1]
            end_time = p[2]
            price_info = price_id_2_prices_info[price_id]
            sku_id = price_info['sku_id']
            name = ' '.join(service_item_id_to_option_name_list.get(sku_id, ''))
            data = {
                'sku_id': sku_id,
                'start_time': start_time,
                'end_time': end_time,
                "price": price_info['gengmei_price'],
                "name": name,
                "sku_rank": service_item_id_to_order_count[sku_id] if sku_id in service_item_id_to_order_count else 0,
                "price_type": price_info['price_type']
            }
            result.append(data)

        return result

    @staticmethod
    def _get_sku_price_range_list_for_es(prices_info):
        """
            prices_info = { (price_id, start_time, end_time, gengmei_price, pre_payment_price)}
        :return: [ ( price_id, start_time, end_time) ]
        """

        def _select_for_sku(prices_info):
            selected = min(prices_info, key=lambda t: (t[3], t[4], -t[0]))
            return selected

        def _get_range_for_prices(prices_info, selector):

            time_dict = {}
            for pinfo in prices_info:
                if pinfo[1] < pinfo[2]:
                    data = [(pinfo[1], pinfo[0], True), (pinfo[2], pinfo[0], False)]
                    for d in data:
                        if d[0] not in time_dict:
                            time_dict[d[0]] = []
                        time_dict[d[0]].append((d[1], d[2]))

            time_series = [(k, v) for (k, v) in sorted(time_dict.items(), key=lambda (k, v): k)]

            ct = None  # (pid,st,gp)
            all_ct = {}  # { pid: (st) }
            result = []  # [ (pid,st,ed,gp) ]

            for time, id_is_st_list in time_series:
                st_list = []
                et_list = []

                for item in id_is_st_list:
                    id = item[0]
                    is_st = item[1]
                    if is_st is False:
                        et_list.append(id)
                    else:
                        st_list.append(id)

                for id in et_list:
                    if ct and id == ct[0]:
                        result.append((ct[0], ct[1], time, ct[2]))
                        ct = None
                    all_ct.pop(id, None)

                for id in st_list:
                    if id not in all_ct:
                        all_ct[id] = time

                if all_ct:
                    select_info = [x for x in prices_info if x[0] in all_ct]

                    if len(all_ct) == 0:
                        pass

                    se = selector(select_info)
                    new_ct = (se[0], time, se[3])
                    if ct:
                        if new_ct[0] != ct[0]:
                            result.append((ct[0], ct[1], time, ct[2]))
                            ct = new_ct
                    else:
                        ct = new_ct

            return result

        sku_prices_range = _get_range_for_prices(prices_info, _select_for_sku)
        result = [(r[0], r[1], r[2]) for r in sku_prices_range]

        return result

    def _get_current_seckill_item_price(self, special_id=None):
        can_sold_item_ids = self._queryset_can_sell_items().values_list('id', flat=True)

        if len(can_sold_item_ids) == 0:
            return None

        now = datetime.datetime.now()

        qs = ServiceItemPrice.objects.filter(
            service_item_id__in=can_sold_item_ids,
            is_enable=True,
            selling_rule__is_enable=True,
            selling_rule__activity_type=ACTIVITY_TYPE_ENUM.SECKILL, )
        qs = qs.filter(
            selling_rule__start_time__lte=now,
            selling_rule__end_time__gte=now, ) if special_id is None else qs.filter(
            selling_rule__activity_id=special_id, )

        all_prices = list(qs)
        all_can_sold = [p for p in all_prices if p.sale_limit_lte_zero is False]
        price_list = all_can_sold if len(all_can_sold) > 0 else all_prices

        price = min(price_list, key=_get_current_service_item_price_key) if len(price_list) > 0 else None
        return price

    def get_lowest_price_seckill_info(self):
        key = '_get_lowest_price_seckill_info_dict'
        if hasattr(self, key):
            info = getattr(self, key)
            return copy.deepcopy(info)

        info = None
        now = datetime.datetime.now()

        can_sold_item_ids = self._queryset_can_sell_items().values_list('id', flat=True)

        if len(can_sold_item_ids) == 0:
            return None

        qs = ServiceItemPrice.objects.filter(
            service_item_id__in=can_sold_item_ids,
            is_enable=True,
            sale_limit_lte_zero=False,
            selling_rule__is_enable=True,
            selling_rule__start_time__lte=now,
            selling_rule__end_time__gte=now,
            selling_rule__activity_type=ACTIVITY_TYPE_ENUM.SECKILL, )
        qs = _add_current_service_item_price_order_by_to_query_set(qs)
        lowest_price_in_seckill_now = qs.first()

        if lowest_price_in_seckill_now:

            sk_seckill_status = SECKILL_STATUS.UNDERWAY
            sk_seckill_price = lowest_price_in_seckill_now.gengmei_price
            sk_seckill_sell_num_limit = lowest_price_in_seckill_now.sale_limit

            service_item_id = lowest_price_in_seckill_now.service_item_id
            sk_seckill_tip = ''

            from api.models import SpecialItem
            sp_list = SpecialItem.objects.filter(serviceitem_id=service_item_id).values_list('tip', flat=True)
            if len(sp_list):
                sk_seckill_tip = sp_list[0]

            sk_pre_payment_price = lowest_price_in_seckill_now.pre_payment_price
            sk_discount = lowest_price_in_seckill_now.discount
            sk_seckill_single_user_buy_limit = lowest_price_in_seckill_now.single_user_buy_limit
            sk_countdown = (lowest_price_in_seckill_now.selling_rule.end_time - datetime.datetime.now()).total_seconds()

            info = {'seckill_status': sk_seckill_status,
                    'seckill_price': sk_seckill_price,
                    'seckill_sell_num_limit': sk_seckill_sell_num_limit,
                    'seckill_tip': sk_seckill_tip,
                    'pre_payment_price': sk_pre_payment_price,
                    'discount': sk_discount,
                    'seckill_single_user_buy_limit': sk_seckill_single_user_buy_limit,
                    'countdown': sk_countdown,
                    }

            # 兼容
            self.seckill_price = sk_seckill_price
            self.seckill_sell_num_limit = sk_seckill_sell_num_limit
            self.seckill_tip = sk_seckill_tip
            self.pre_payment_price = sk_pre_payment_price
            self.discount = sk_discount
            self.seckill_single_user_buy_limit = sk_seckill_single_user_buy_limit
            self.countdown = sk_countdown

        setattr(self, key, info)

        return copy.deepcopy(info)

    def is_item_seckill(self, itemkey):
        """check if service in seckill mode.

        .. versionadded:: 5.5
           if service in seckill mode, set following properties:
           - seckill_status
           - seckill_price
           - seckill_sell_num_limit
           - seckill_tip
           - pre_payment_price
        """
        # seckill_item_obj = self.SECKILL_ITEM_OBJS.get(itemkey, None)
        seckill_item_obj = getattr(self, self.SECKILL_ITEM_OBJS, None)
        if not seckill_item_obj:
            from api.models.special import SpecialSeckillService
            seckill_item_obj = SpecialSeckillService.fetch(self, itemkey)
            # self.SECKILL_ITEM_OBJS = {}
            # self.SECKILL_ITEM_OBJS[itemkey] = seckill_item_obj
            setattr(self, self.SECKILL_ITEM_OBJS, seckill_item_obj)

            if not seckill_item_obj:
                return False
        # from api.models.special import SpecialSeckillService
        # self._seckill_obj = SpecialSeckillService.fetch(self, itemkey)
        # if not self._seckill_obj:
        #     self._seckill_status = SECKILL_STATUS.AFTER
        #     return False

        # set seckill related properties
        self._seckill_status = SECKILL_STATUS.UNDERWAY
        self.seckill_price = seckill_item_obj.seckill_price
        self.seckill_sell_num_limit = seckill_item_obj.stock
        self.seckill_tip = seckill_item_obj.tip
        self.pre_payment_price = seckill_item_obj.pre_payment_price
        self.discount = seckill_item_obj.commission
        self.seckill_single_user_buy_limit = seckill_item_obj.buy_up_to
        return True

    def is_unusual_special(self):
        # 判断是否是特殊专场美购
        now = datetime.datetime.now()
        qs = self.specialitem_set.filter(
            special__id__in=settings.DOUBLE_EVELEN_SPECIAL,
            special__start_time__lt=now,
            special__end_time__gt=now,
            special__is_online=True,
        )
        if qs.exists():
            return True, qs.last().special
        return False, None

    @property
    def payment_type_v2(self):
        return payment_type_to_payment_type_v2(self.payment_type)

    def is_can_be_sold(self, check_any_sku_has_stock=True, check_service_time_range=True):
        """updated at 2016-08-02, what the f comment.

        如刚才沟通，因为在将秒杀和常规美购库存分开时，
        没有去掉“常规美购为0时，秒杀美购也不可售”的逻辑，
        导致目前无法将项目所有的库存配置到秒杀上。
        此逻辑为当初修改时遗留，辛苦此次将此逻辑去掉。

        pm: ylx
        """
        can_sell = False

        if self.is_online is True and self.doctor.is_online is True:

            time_valid = True
            has_can_sell_sku_and_has_stock = False

            if check_service_time_range:
                now = datetime.datetime.now()
                start_time, end_time = self._get_valid_service_time_ragne()
                time_valid = start_time <= now <= end_time

            if time_valid:
                if check_any_sku_has_stock:
                    can_sell_items_info = self._can_sell_items_info
                    if len(can_sell_items_info):
                        has_can_sell_sku_and_has_stock = (any([x.sku_stock > 0 for x in can_sell_items_info]))
                else:
                    has_can_sell_sku_and_has_stock = True

            can_sell = time_valid and has_can_sell_sku_and_has_stock

        return can_sell

    def _get_valid_service_time_ragne(self):
        """
        :return: 该美购的有效时间范围 (start_time,end_time)
                 这个时间范围主要用于根据datetime.datetime.now()判断now是否在范围内
                 如果start_time为None就返回 _min_service_time
                 如果end_time为None就返回 _max_service_time
        """
        return (_min_service_time if self.start_time is None else self.start_time,
                _max_service_time if self.end_time is None else self.end_time)

    def _queryset_can_sell_items(self, check_has_stock=False):
        if check_has_stock:
            return self.items.filter(sku_stock_lte_zero=False, is_delete=False)
        else:
            return self.items.filter(is_delete=False)

    @staticmethod
    def get_service_ids_to_can_sell_item_ids(service_ids, check_has_stock=False):
        if not service_ids:
            return {}

        if check_has_stock:
            qs = ServiceItem.objects.filter(parent_id=0, service_id__in=service_ids, sku_stock_lte_zero=False,
                                            is_delete=False)
        else:
            qs = ServiceItem.objects.filter(parent_id=0, service_id__in=service_ids, is_delete=False)

        grouped = itertools.groupby(
            qs.values_list('service_id', 'id'),
            operator.itemgetter(0),
        )

        result = {
            k: [e[1] for e in v]
            for k, v in grouped
        }
        return result

    def get_can_sell_item_ids(self, check_has_stock=False):
        result = Service.get_service_ids_to_can_sell_item_ids([self.id], check_has_stock)

        return result.get(self.id, [])

    def get_can_sell_item_ids_and_current_special_id(self):
        """

        :return: {
            1: 123,
            2: None
        }
        """
        result = {x.service_item_id: x.special_id for x in self._can_sell_items_info}

        return result

    def get_can_sell_item_ids_and_current_special_info(self):
        """

        :return: {
            1: { 'id':123, 'activity_type': ACTIVITY_TYPE_ENUM.SECKILL,},
            2: {}
        }
        """
        result = {}

        for info in self._can_sell_items_info:
            result[info.service_item_id] = {
                'id': info.special_id,
                'activity_type': info.activity_type,
            } if info.special_id else {}

        return result

    @staticmethod
    def get_support_renmai_no_pay_price_range(service_ids):
        """

        :param service_ids: [1, 2]
        :return: { 1: (1000,2000), 2: [] }
        """
        price_range = (settings.RENMAI_INSTALLMENT_MIN_PAY, settings.RENMAI_INSTALLMENT_MAX_PAY)
        return Service._get_support_installment_no_pay_price_range(service_ids, price_range)

    @staticmethod
    def get_support_yirendai_no_pay_price_range(service_ids):
        """

        :param service_ids: [1, 2]
        :return: { 1: (1000,2000), 2: [] }
        """
        price_range = (settings.YIRENDAI_INSTALLMENT_MIN_PAY, settings.YIRENDAI_INSTALLMENT_MAX_PAY)
        return Service._get_support_installment_no_pay_price_range(service_ids, price_range)

    @staticmethod
    def _get_support_installment_no_pay_price_range(service_ids, installment_price_range):
        sid_to_hid = {s[0]: s[1] for s in
                      Service.objects.filter(
                          id__in=service_ids,
                          payment_type=PAYMENT_TYPE.PREPAYMENT,
                          is_stage=True).select_related('doctor__hospital_id').values_list('id', 'doctor__hospital_id')}

        hids = [v for k, v in sid_to_hid.items()]

        hospital_ids_in_list = set(PeriodHospital.get_hospital_ids_in_list(hospital_ids=hids))

        service_id_to_price_range = {}
        for sid in service_ids:
            price_range = []
            if sid in sid_to_hid and sid_to_hid[sid] in hospital_ids_in_list:
                price_range = installment_price_range
            service_id_to_price_range[sid] = price_range

        return service_id_to_price_range

    @property
    def is_support_renmai_no_pay(self):
        seckill_info = self.get_lowest_price_seckill_info()
        if seckill_info:
            return False

        prices = self.gengmei_final_price

        if not prices:
            return False

        min_price = min(prices)
        max_price = max(prices)

        if self.payment_type == PAYMENT_TYPE.PREPAYMENT and self.is_stage \
                and (settings.RENMAI_INSTALLMENT_MIN_PAY <= min_price <= settings.RENMAI_INSTALLMENT_MAX_PAY) \
                and (settings.RENMAI_INSTALLMENT_MIN_PAY <= max_price <= settings.RENMAI_INSTALLMENT_MAX_PAY) \
                and PeriodHospital.check_hospital_in_list(self.doctor.hospital_id):
            return True
        else:
            return False

    @property
    def is_support_yirendai_no_pay(self):
        seckill_info = self.get_lowest_price_seckill_info()
        if seckill_info:
            return False

        prices = self.gengmei_final_price

        if not prices:
            return False

        min_price = min(prices)
        max_price = max(prices)

        if self.payment_type == PAYMENT_TYPE.PREPAYMENT and self.is_stage \
                and (settings.YIRENDAI_INSTALLMENT_MIN_PAY <= min_price <= settings.YIRENDAI_INSTALLMENT_MAX_PAY) \
                and (settings.YIRENDAI_INSTALLMENT_MIN_PAY <= max_price <= settings.YIRENDAI_INSTALLMENT_MAX_PAY) \
                and PeriodHospital.check_hospital_in_list(self.doctor.hospital_id):
            return True
        else:
            return False

    @property
    def sell_amount_display(self):
        # 用于展示的销售量(报名或购买数)
        sell_amount_display = int(ViewRecord(const_strings.SERVICE)[self.id] or '0')
        sell_amount_display = self.fake_sold_num if sell_amount_display < 0 else sell_amount_display + self.fake_sold_num
        return sell_amount_display

    @property
    def time_limit(self):
        # 用于处理福利截至时间，返回时间戳
        if self.end_time:
            end_time = get_timestamp(self.end_time)
        else:
            end_time = None
        return end_time

    @property
    def sell_num_limit_display(self):
        # 用于显示的总量
        if self.get_service_type == const_strings.APPLY_FREE_EVENT:
            multiply = 3
        elif self.get_service_type == const_strings.EXCHANGE_GIFT:
            multiply = 5
        elif self.get_service_type == const_strings.GROUPON:
            multiply = 2
        else:
            multiply = 1
        sell_num_limit_display = self.total_num * multiply if self.total_num else 0
        return sell_num_limit_display

    @property
    def sell_amount(self):
        paid_status = (ORDER_STATUS.PAID, ORDER_STATUS.USED, ORDER_STATUS.SETTLED)
        order_amount = self.order_set.filter(status__in=paid_status).count()
        return order_amount

    def get_tags(self):
        tags = []
        for tag in self.tags.all():
            tags.append({'name': tag.name, 'tag_id': tag.id, "type": tag.tag_type})
        return tags

    def get_tags_for_search(self):
        tags = []
        for tag in self.tags.exclude(tag_type=TAG_TYPE.YUNYING):
            tags.append(tag.name)
        return tags

    def operate_tag(self):
        tinfos = Service.get_operate_tag_info_by_service_id([self.id])
        ti = tinfos.get(self.id, [])

        return ti

    @staticmethod
    def get_operate_tag_info_by_service_id(service_ids):
        result = {}
        if not service_ids:
            return result

        sts = list(ServiceTag.objects.filter(
            service_id__in=service_ids, tag__tag_type=TAG_TYPE.YUNYING, tag__is_online=True
        ).select_related('tag').values_list('service_id', 'tag_id', 'tag__name'))

        sid_and_tid_set = set()

        for sid, tid, tname in sts:
            if sid not in result:
                result[sid] = []

            if (sid, tid) not in sid_and_tid_set and len(result[sid]) < _service_tag_limit:
                result[sid].append({'id': tid, 'name': tname})
                sid_and_tid_set.add((sid, tid))

        return result

    @property
    def itemwiki_tags(self):
        """service related level 3 tags."""
        tags = []
        if self.serviceitemwiki_set.exists():
            tags = [
                {'name': sw.itemwiki.tag.name, 'tag_id': sw.itemwiki.tag_id}
                for sw in self.serviceitemwiki_set.all()
            ]
        return tags

    @property
    def stock(self):
        # 用于处理库存量显示
        if self.total_num and self.total_num > 0:
            surplus = self.sell_num_limit_display - self.sell_amount_display
            if self.sell_num_limit > 0:
                if surplus > 0:
                    stock = surplus
                else:
                    stock = self.sell_num_limit
            else:
                stock = 0
        else:
            stock = None
        return stock

    @property
    def real_sell_amount(self):
        status_set = (ORDER_STATUS.NOT_PAID, ORDER_STATUS.PAID,
                      ORDER_STATUS.USED, ORDER_STATUS.SETTLED,
                      ORDER_STATUS.AUDITING, ORDER_STATUS.SETTLING)
        amount = self.order_set.filter(status__in=status_set).count()
        return amount

    @property
    def real_order_amount(self):
        status_set = (ORDER_STATUS.NOT_PAID, ORDER_STATUS.PAID,
                      ORDER_STATUS.USED, ORDER_STATUS.SETTLED,
                      ORDER_STATUS.AUDITING, ORDER_STATUS.SETTLING,
                      ORDER_STATUS.REFUNDED, ORDER_STATUS.WAIT_REFUNDED)
        amount = self.order_set.filter(status__in=status_set).count()
        return amount

    @property
    def real_sell_amount_v2(self):
        """
            医生版定义，已售订单：已付款、已使用、已结算、审核中、结算中 的所有订单
        """
        #  TODO Deprecated since ascle重构, app过几个版本可以去掉了
        status_set = (
            ORDER_STATUS.PAID,
            ORDER_STATUS.USED, ORDER_STATUS.SETTLED,
            ORDER_STATUS.AUDITING, ORDER_STATUS.SETTLING)
        amount = self.order_set.filter(status__in=status_set).count()
        return amount

    @classmethod
    def order_amount_status_set(cls):
        """
            医生版定义，成单量：已付款、已使用、已结算、审核中、结算中、已退款、等待退款的所有订单

            成单量的这些状态要一起维护
            doctor.view.service.get_doctor_services使用annotate也需要这个
        """
        status_set = (
            ORDER_STATUS.PAID,
            ORDER_STATUS.USED, ORDER_STATUS.SETTLED,
            ORDER_STATUS.AUDITING, ORDER_STATUS.SETTLING,
            ORDER_STATUS.REFUNDED, ORDER_STATUS.WAIT_REFUNDED)
        return status_set

    @property
    def real_order_amount_v2(self):
        """
            医生版定义，成单量
        """
        amount = self.order_set.filter(status__in=self.order_amount_status_set).count()
        return amount

    @property
    def used_amount(self):
        amount = self.order_set.filter(status=ORDER_STATUS.USED).count()
        return amount

    @property
    def refunded_amount(self):
        amount = self.order_set.filter(status=ORDER_STATUS.REFUNDED).count()
        return amount

    @property
    def real_stock(self):
        total_seckill_stock = self.specialseckillservice_set \
                                  .aggregate(t=Sum('stock')).get('t') or 0
        return self.total_num - self.real_sell_amount - total_seckill_stock \
            if isinstance(self.total_num, (int, long)) else None

    @property
    def get_service_type(self):

        if self.need_address:
            # 需要地址的为礼品兑换
            return const_strings.EXCHANGE_GIFT
        else:
            if self.payment_type == PAYMENT_TYPE.FREE_PAYMENT:
                # 不需要地址&不花钱的为活动报名
                return const_strings.APPLY_FREE_EVENT
            else:
                # 不需要地址&花钱的为团购项目
                return const_strings.GROUPON

    def get_original_price_display(self):
        items = self._can_sell_items_info
        prices = [item.original_price for item in items if item.original_price]
        if len(prices):
            if self.is_multiattribute:
                min_price = min(prices)
                max_price = max(prices)
                return '%s - %s' % (min_price, max_price)
            else:
                return str(prices[0])

    original_price_display = property(get_original_price_display)

    @property
    def show_original_price(self):
        items = self._can_sell_items_info
        prices = [item.original_price for item in items if item.original_price]
        if len(prices) == 1:
            return str(prices[0])
        elif len(prices) > 1:
            min_price = min(prices)
            max_price = max(prices)
            return '%s - %s' % (min_price, max_price)
        return ''

    @property
    def pre_payment_price_list(self):
        items = self._can_sell_items_info
        prices = [item.pre_payment_price for item in items]
        return prices

    @property
    def gengmei_price_list(self):
        items = self._can_sell_items_info
        prices = [item.gengmei_price for item in items]
        return prices

    @property
    def original_price_list(self):
        items = self._can_sell_items_info
        prices = [item.original_price for item in items]
        return prices

    @property
    def discount_list(self):
        items = self._can_sell_items_info
        discounts = [item.discount for item in items]
        return discounts

    def get_special_show_info(self, city_id=None):
        from api.manager import service_info_manager
        service_id_list = service_info_manager.add_double_eleven_image()
        double_eleven_image = ''
        if self.id in service_id_list:
            double_eleven_image = 'https://heras.igengmei.com/serviceactivity/2019/10/21/d9c715646c'
        result = special_service_list_cache.get(str(self.id))
        if result:
            result = json.loads(result)
        else:
            price = self.gengmei_price_display
            min_price = price.split('-')[0]
            min_price = min_price.strip()
            # 7.3.5 修改，city_doctor，若为医生非机构管理者，显示city+doctor；否则只显示机构医院名称
            if self.doctor.name:
                if self.doctor.doctor_type == DOCTOR_TYPE.DOCTOR:
                    show_name = self.city_name + " " + self.doctor.name
                else:
                    show_name = self.doctor.name
            else:
                show_name = self.hospital.name
            result = {
                "gengmei_price": min_price,
                "service_name": self.name,
                "service_image": get_full_path(self.image_header, '-half'),
                "is_price_range": True if self.is_multiattribute else False,
                "service_id": self.id,
                "service_url": '',
                "service_tag": self.show_tags,
                "city_doctor": show_name,
                "origin_price": self.lowest_original_price,
                'double_eleven_image': double_eleven_image,
            }
            if city_id == settings.PROHIBITED_CITY:
                for p_word, r_word in settings.PROHIBITED_WORDS.items():
                    service_tag = result['service_tag']
                    if service_tag.find(p_word) == -1:
                        continue
                    result['service_tag'] = service_tag.replace(p_word, r_word)
            special_service_list_cache.setex(str(self.id), 60 * 2, json.dumps(result))
        return result

    @property
    def gengmei_price_display(self):
        '''
        美购的更美价(未选中sku状态下的默认展示)
        --> 获取美购下所有sku的更美价列表(一对一)
          :1. 多个可售sku: 显示多个更美价的价格区间
          :2. 单个可售sku: 显示对应的更美价
          :default: 0
        '''
        gengmei_price_list = self.gengmei_price_list
        if not gengmei_price_list:
            return '0'
        if len(gengmei_price_list) == 1:
            return str(gengmei_price_list[0])

        min_price = min(gengmei_price_list)
        max_price = max(gengmei_price_list)
        if int(max_price) != int(min_price):
            show_msg = "%d - %d" % (int(min_price), int(max_price))
        else:
            show_msg = str(max_price)
        return show_msg

    @property
    def gengmei_final_price(self):
        if self.is_multiattribute:
            items = self._can_sell_items_info
            prices = ([int(item.gengmei_price) -
                       int(item.pre_payment_price) for item in items])
            return prices
        else:
            item = self._can_sell_items_info[0]
            if item:
                return [int(item.gengmei_price) -
                        int(item.pre_payment_price)]

    @staticmethod
    def get_huabei_period_info(payment):
        """
            计算花呗分期每一期的 手续费 和 总费用（本金+手续费）。注意结果
        :param payment: 单位，元
        :return: {'12': {'fee': 6.94, 'pay': 99.53, 'period': 12},
 '3': {'fee': 8.52, 'pay': 378.89, 'period': 3},
 '6': {'fee': 8.33, 'pay': 193.51, 'period': 6}}
        其中 fee 是每一期手续费， pay是每一期总费用，period是期数
        """
        # 花呗费率 https://docs.open.alipay.com/277/105952#s7
        rate_dict = {3: 0.023, 6: 0.045, 12: 0.075}

        result = {}
        for period in [3, 6, 12]:
            rate = rate_dict[period]

            fee_cent = int(round(float(payment) * rate / period * 100))
            pay_cent = int(round(float(payment) / period * 100)) + fee_cent

            fee = float(fee_cent) / 100
            pay = float(pay_cent) / 100

            result[str(period)] = {
                "period": period,
                "pay": pay,
                "fee": fee,
            }

        return result

    @staticmethod
    def get_renmai_period_price(post_price):
        price = (float(post_price) / settings.RENMAI_INSTALLMENT_PERIOD) + float(
            post_price) * settings.RENMAI_INSTALLMENT_RATE
        return round(price, 2)

    @staticmethod
    def get_yirendai_period_price(post_price):
        price = (float(post_price) / settings.YIRENDAI_INSTALLMENT_PERIOD) + float(
            post_price) * settings.YIRENDAI_INSTALLMENT_RATE
        return round(price, 2)

    @cached_property
    def renmai_period_price(self):
        price = self.min_post_price
        price = Service.get_renmai_period_price(price)
        return round(price, 2)

    @cached_property
    def yirendai_period_price(self):
        price = self.min_post_price
        price = Service.get_yirendai_period_price(price)
        return price

    @cached_property
    def min_post_price(self):
        items = self._can_sell_items_info
        prices = [item.gengmei_price - item.pre_payment_price for item in items]
        if len(prices) > 0:
            price = min(prices)
            return price
        return 0

    @property
    def gengmei_price_no_multiattribute(self):
        prices = self.gengmei_price_list
        if prices:
            min_price = min(prices)
            return min_price
        else:
            return str(self.gengmei_price)

    @property
    def lowest_original_price(self):
        prices = self.original_price_list
        if len(prices) > 0:
            return min(prices)

    @property
    def lowest_gengmei_price(self):
        prices = self.gengmei_price_list
        if len(prices) > 0:
            return min(prices)

    @property
    def get_lowest_price_serviceitem_id(self):
        def get_key(info):
            return info.gengmei_price

        items = self._can_sell_items_info
        item = min(items, key=get_key)
        return item.service_item_id

    @property
    def lowest_pre_payment_price(self):
        prices = self.pre_payment_price_list
        if len(prices) > 0:
            return min(prices)

    @property
    def lowest_discount(self):
        discounts = self.discount_list
        if len(discounts) > 0:
            return min(discounts)

    @property
    def payment_type_text(self):
        if self.payment_type == PAYMENT_TYPE.FULL_PAYMENT:
            return u'全款'
        elif self.payment_type == PAYMENT_TYPE.PREPAYMENT:
            return u'预付款'
        elif self.payment_type == PAYMENT_TYPE.FREE_PAYMENT:
            return u'免费'
        elif self.payment_type == PAYMENT_TYPE.EXCHANGE_GIFT:
            return u'礼品换购'

    @property
    def ad_str(self):
        today = datetime.datetime.today()
        from api.models import AdvertiseManagement
        advertise = AdvertiseManagement.objects.filter(service=self, is_online=True,
                                                       start_time__lt=today, end_time__gt=today).first()
        if advertise:
            if hasattr(advertise, 'extend_tip'):
                return advertise.extend_tip.tip_name
        return ''

    @property
    def discount_price(self):
        items = self._can_sell_items_info
        if len(items) == 0:
            return (1, 1)
        lowest_original_price = min([item.original_price for item in items])
        if lowest_original_price == 0:
            return (1, 1)
        lowest_gengmei_price = min([item.gengmei_price for item in items])
        return (lowest_original_price, lowest_gengmei_price)

    @property
    def is_floor_price(self):
        if self.is_multiattribute:
            return all(x.discount == 0 for x in self._can_sell_items_info)

        elif self.get_lowest_price_seckill_info() and all(x.discount == 0 for x in self._can_sell_items_info):
            return True

        elif self.discount == 0:
            return True

        return False

    def _get_seckill_info_for_special(self, special_id, user):
        data = {}
        price = self._get_current_seckill_item_price(special_id) if special_id else None

        if price is None:
            return data

        now = timezone.now()

        from api.models import Special
        special_is_underway = Special.objects.filter(id=special_id, start_time__lte=now, end_time__gte=now).exists()

        if special_is_underway:
            # 这段从_get_current_seckill_item_price复制过来的，这里假设了会先调用_get_current_seckill_item_price
            # 并且返回值不为None，在这个前提下，len(all_seckill_prices)一定大于0
            can_sold_item_ids = self._queryset_can_sell_items().values_list('id', flat=True)

            if len(can_sold_item_ids) == 0:
                return None

            qs = ServiceItemPrice.objects.filter(
                service_item_id__in=can_sold_item_ids,
                is_enable=True,
                selling_rule__is_enable=True,
                selling_rule__activity_id=special_id,
                selling_rule__activity_type=ACTIVITY_TYPE_ENUM.SECKILL, )

            all_seckill_prices = list(qs)
            all_seckill_prices_service_item_ids = [p.service_item_id for p in all_seckill_prices]

            all_current_prices = Service._get_current_price_by_service_item_ids(all_seckill_prices_service_item_ids)

            service_item_id_to_current_price = {p.service_item_id: p for p in all_current_prices}

            all_seckill_price_match_current_price_list = []

            for sp in all_seckill_prices:
                cp = service_item_id_to_current_price.get(sp.service_item_id, None)
                if cp and cp.id == sp.id:
                    all_seckill_price_match_current_price_list.append(sp)

            if len(all_seckill_price_match_current_price_list) > 0:
                price = min(all_seckill_price_match_current_price_list, key=_get_current_service_item_price_key)
                sale_limit = price.sale_limit
            else:
                price = min(all_seckill_prices, key=_get_current_service_item_price_key)
                # 所有秒杀价都没有一个可以下单，选择最低那个秒杀价，然后认为卖完了
                sale_limit = 0

            sk_seckill_sell_num_limit = sale_limit
            sk_seckill_price = price.gengmei_price
            sk_pre_payment_price = price.pre_payment_price
            sk_total_num_fake = price.total_num_fake

            service_item_id = price.service_item_id

            sk_seckill_tip = ''

            from api.models import SpecialItem
            sp_list = SpecialItem.objects.filter(serviceitem_id=service_item_id).values_list('id', 'tip')
            if len(sp_list):
                sk_seckill_tip = sp_list[0][1]

            data['seckill_sell_num_limit'] = sk_seckill_sell_num_limit
            data['seckill_price'] = sk_seckill_price
            data['tip'] = sk_seckill_tip
            data['pre_payment_price'] = sk_pre_payment_price

            data['seckill_status'] = SECKILL_STATUS.UNDERWAY

            data['is_seckill'] = True

            data['mark_notice'] = False
            data['total_num_fake'] = sk_total_num_fake

        else:
            if price.selling_rule.end_time < now:
                # if seckill has ended
                data['seckill_status'] = SECKILL_STATUS.AFTER
                return data

            sk_seckill_sell_num_limit = price.sale_limit
            sk_seckill_price = price.gengmei_price
            sk_pre_payment_price = price.pre_payment_price
            sk_total_num_fake = price.total_num_fake

            service_item_id = price.service_item_id

            sk_seckill_tip = ''

            from api.models import SpecialItem
            sp_list = SpecialItem.objects.filter(serviceitem_id=service_item_id).values_list('id', 'tip')
            if len(sp_list):
                sk_seckill_tip = sp_list[0][1]

            data['seckill_sell_num_limit'] = sk_seckill_sell_num_limit
            data['seckill_price'] = sk_seckill_price
            data['tip'] = sk_seckill_tip
            data['pre_payment_price'] = sk_pre_payment_price
            data['total_num_fake'] = sk_total_num_fake

            if price.selling_rule.start_time > now:
                data['seckill_status'] = SECKILL_STATUS.BEFORE
            else:
                # seckill is ongoning
                data['seckill_status'] = SECKILL_STATUS.UNDERWAY

                data['is_seckill'] = True

            data['mark_notice'] = False

            from api.models import SeckillNotify
            # 是否设置秒杀提醒状态
            if user and hasattr(user, 'person') and special_id \
                    and SeckillNotify.objects.filter(person=user.person, special_id=special_id,
                                                     service_id=self.id).exists():
                data['mark_notice'] = True

        return data

    @property
    def is_support_insurance(self):
        return True if self.insurance_info else False

    @property
    def city(self):
        # return city object
        try:
            return self.doctor and self.doctor.hospital and self.doctor.hospital.city or None
        except:
            return None

    @property
    def city_name(self):
        if self.city:
            return self.city.name

        return ''

    @rpc.db_opt.select_related(
        'doctor',
        'doctor__hospital',
        'doctor__hospital__city',
    )
    @rpc.db_opt.prefetch_related(
        'tags',
        'doctor__hospital__city',
    )
    @rpc.db_opt.select_nested(
        lambda: Service.get_original_price_display,
    )
    # 尽量不要调用此接口了, 最好自己另写一个
    # 逻辑已经混乱了
    def get_service_info(self, user=None, service_item_key=None, amt=None, tnr=None, special_id=None,
                         get_diary_num=False, advertise_id=None, service_item_id=None):
        """get service info.

        args:
            special_id: if this given, try to get seckill status from SpecialSeckillService

        .. changelog:: 5.5, at special_id param
        """
        from api.manager import service_info_manager
        service_id_list = service_info_manager.add_double_eleven_image()
        double_eleven_image = ''
        if self.id in service_id_list:
            double_eleven_image = 'https://heras.igengmei.com/serviceactivity/2019/10/21/d9c715646c'
        can_be_sold = self.is_can_be_sold()

        is_favored = self.is_favored(user)

        data = {
            'service_id': self.id,
            'service_name': self.name,
            'province_id': (
                    self.doctor.hospital and
                    self.doctor.hospital.city and
                    self.doctor.hospital.city.province_id or ''
            ),
            'city': self.city_name,
            'hospital': (
                    self.doctor.doctor_type == DOCTOR_TYPE.DOCTOR and
                    self.doctor.hospital and
                    self.doctor.hospital.name or ''
            ),
            'need_address': self.need_address,
            'doctor_id': self.doctor.id if self.doctor else '',
            'doctor_name': self.doctor.name if self.doctor else '',
            'doctor_portrait': get_full_path(self.doctor.portrait) if self.doctor else '',
            'total_price': self.total_price,
            'original_price': self.lowest_original_price,
            'is_can_be_sold': can_be_sold,
            'payment_type': self.payment_type,
            'payment_type_v2': self.payment_type_v2,
            'sell_amount': self.sell_amount_display,
            'short_description': self.short_description,
            'start_time': get_timestamp_or_none(self.start_time),
            'end_time': self.time_limit,
            'total_num': self.sell_num_limit_display,
            'stock': self.stock,
            'gengmei_price': self.gengmei_price_display,
            'service_image': self.image_header,
            'image_detail': self.image_detail,
            'is_multiattribute': self.is_multiattribute,
            'limit_hint': self.limit_hint,
            'exchange_points_ceiling': self.exchange_points_ceiling,
            'sell_num_limit': self.sell_num_limit,
            'pre_payment_price': self.lowest_pre_payment_price,
            'channel': self.channel,
            'service_type': self.get_service_type,
            'is_online': self.is_online,
            'ordering': self.ordering,
            'update_time': get_timestamp_or_none(self.update_time),
            'show_location': self.doctor.show_location,
            'original_price_display': self.get_original_price_display(),
            'is_stage': self.is_stage,
            'is_insurance': bool(int(self.yinuo_type)),
            'points_deduction': int(self.discount * self.points_deduction_percent * 0.01),
            'points_deduction_percent': self.points_deduction_percent,
            'is_floor_price': self.is_floor_price,
            'is_favored': is_favored,
            'is_seckill': False,
            'tags': self.get_tags(),
            'is_available_soon': self.is_available_soon,
            'tip': self.first_tip,
            'tips': self.tips,
            'label': self.label,  # http://wiki.gengmei.cc/pages/viewpage.action?pageId=1048948
            'is_sink': self.is_sink,
            'renmai_period_price': self.renmai_period_price,  # gengmei_price_display多属性下显示的是区间
            'yirendai_period_price': self.yirendai_period_price,
            'insurance': self.insurance,
            'insurance_info': self.insurance_info,
            'hospital_lng': self.doctor.hospital.baidu_loc_lng,
            'hospital_lat': self.doctor.hospital.baidu_loc_lat,
            'operate_tag': self.operate_tag(),
            'double_eleven_image': double_eleven_image,
        }

        data['is_support_renmai_payment'] = self.is_support_renmai_no_pay
        data['is_support_yirendai_payment'] = self.is_support_yirendai_no_pay

        seckill_info = {}
        if special_id:
            seckill_info = self._get_seckill_info_for_special(special_id, None)
        else:
            sk_info = self.get_lowest_price_seckill_info()
            if sk_info:
                seckill_info['seckill_sell_num_limit'] = sk_info['seckill_sell_num_limit']
                seckill_info['seckill_status'] = sk_info['seckill_status']
                seckill_info['seckill_price'] = sk_info['seckill_price']
                seckill_info['tip'] = sk_info['seckill_tip']
                seckill_info['pre_payment_price'] = sk_info['pre_payment_price']
                seckill_info['is_seckill'] = True

        data.update(seckill_info)

        if service_item_key:
            data['service_item'] = {}
            try:
                obj = ServiceItem.objects.get(service=self, key=service_item_key, is_delete=False)
                data['service_item'].update(obj.service_item_data())
            except ServiceItem.DoesNotExist:
                pass

        if service_item_id:
            data['service_item'] = {}
            try:
                obj = ServiceItem.objects.get(service=self, id=service_item_id, is_delete=False)
                data['service_item'].update(obj.service_item_data())
            except ServiceItem.DoesNotExist:
                pass

        if get_diary_num:
            try:
                filter_result = filter_diary(filters={'service_id': self.id}, expose_total=True)
                data['diary_count'] = filter_result['total']
            except:
                data['diary_count'] = 0

        if advertise_id is not None:
            from api.models import AdvertiseManagement
            try:
                ad = AdvertiseManagement.objects.select_related('extend_tip').get(pk=advertise_id)
                data['extend_tips'] = ad.extend_tip.tip_name
            except AdvertiseManagement.DoesNotExist:
                data['extend_tips'] = None

        return data

    def get_service_info_for_zone(self):
        return {
            'service_id': self.id,
            'service_name': self.name,
            'service_image': self.image_header,
            'original_price': self.lowest_original_price,
            'gengmei_price': self.gengmei_price_no_multiattribute,
            'is_multiattribute': self.is_multiattribute,
        }

    @property
    def is_available_soon(self):
        """
        即将开售
        """
        if self.start_time:
            now = datetime.datetime.now()
            if self.is_online and now < self.start_time:
                return True

        return False

    @property
    def limit_hint(self):
        if self.is_available_soon:
            return u'即将开售'

        # Nijiahua Note：注释掉这个消耗很大但是意义不大的代码，如果要修改请联系我
        # if not self.is_can_be_sold():
        #     return u"抢光啦"

        return u''

    @property
    def label(self):
        if self.is_available_soon:
            return u'即将开售'

        # Nijiahua Note：注释掉这个消耗很大但是意义不大的代码，如果要修改请联系我
        # if not self.is_can_be_sold():
        #     return u'抢完啦'

        return self.first_tip or None

    @property
    def snapshot(self):
        return dict(
            id=self.id,
            name=self.name,
            short_description=self.short_description,
            detail_description=self.detail_description,
            exchange_points_ceiling=self.exchange_points_ceiling,
            doctor=self.doctor.id if self.doctor else None,
            hospital=self.hospital.id if self.hospital else None,
            special_remind=self.special_remind,
            total_price=self.total_price,
            original_price=self.original_price,
            gengmei_price=self.gengmei_price,
            pre_payment_price=self.pre_payment_price,
            ceiling_price=self.ceiling_price,
            payment_type=self.payment_type,
            channel=self.channel,
            phone=self.phone,
            sms_phone=self.sms_phone,
            pm_content=self.pm_content,
            address=self.address,
            is_online=self.is_online,
            is_sale=self.is_sale,
            is_voucher=self.is_voucher,
            ordering=self.ordering,
            total_num=self.total_num,
            start_time=get_timestamp_or_none(self.start_time),
            end_time=get_timestamp_or_none(self.end_time),
            update_time=get_timestamp_or_none(self.update_time),
            only_use_points=self.only_use_points,
            single_user_buy_limit=self.single_user_buy_limit,
            need_address=self.need_address,
            need_sms_alert=self.need_sms_alert,
            bodypart_subitem=self.bodypart_subitem.id if self.bodypart_subitem else None,
            image_header=self.image_header,
            image_detail=self.image_detail,
            is_multiattribute=self.is_multiattribute,
            share_get_cashback=self.share_get_cashback,
            refund_anytime=self.refund_anytime,
            is_stage=self.is_stage,
            is_insurance=bool(int(self.yinuo_type)),
            compensation_in_advance=self.compensation_in_advance,
            cash_back_rate=self.cash_back_rate,
            discount=self.discount,
            points_deduction_percent=self.points_deduction_percent,
            is_floor_price=self.is_floor_price,
            is_self_support=self.is_self_support,
            self_support_discount=self.self_support_discount,
            photo_details_operate=self.photo_details_operate,
            photo_details_doctor=self.photo_details_doctor,
            richtext=self.rich_text,
        )

    @property
    def diaries_count(self):
        from api.manager import service_info_manager

        service_id_to_diary_count_dict = service_info_manager.get_service_id_to_diary_count_dict([self.id])
        dc = service_id_to_diary_count_dict.get(self.id, 0)
        return dc

    @property
    def rich_text(self):
        if self.photo_details_doctor:
            return self.photo_details_doctor
        if self.photo_details_operate:
            return self.photo_details_operate
        return ""

    @property
    def get_buy_notice(self):
        from api.tool.service_tool import ServiceConfig
        return ServiceConfig.get()['config_content']

    @property
    def get_all_services_count(self):
        if self.doctor:
            return Service.objects.filter(doctor=self.doctor).count()
        if self.hospital:
            return Service.objects.filter(hospital=self.hospital).count()
        return 1

    def get_multiattributes(self, coupon_info):
        if self.is_multiattribute:
            all_sku_can_use_coupon = False
            coupon_restrict_sku_ids = set()
            coupon_restrict_special_ids = set()
            sku_id_to_online_special_id_set = {}

            multiattribute = {}
            items = self._queryset_can_sell_items()
            items = sorted(items, key=lambda ServiceItem: ServiceItem.pre_payment_price)
            service_item_ids = [i.id for i in items]

            if coupon_info:

                coupon_restrict_doctor_ids = coupon_info.coupon_restrict_doctor_ids
                coupon_restrict_sku_ids = coupon_info.coupon_restrict_sku_ids
                coupon_restrict_special_ids = coupon_info.coupon_restrict_special_ids

                if self.doctor_id in coupon_restrict_doctor_ids and len(coupon_restrict_sku_ids) == 0:
                    all_sku_can_use_coupon = True
                elif coupon_info.coupon.coupon_type == COUPON_TYPES.PLATFORM and len(
                        coupon_restrict_doctor_ids) == 0 and len(coupon_restrict_special_ids) == 0:
                    all_sku_can_use_coupon = True
                else:
                    from pay.manager import settlement_manager
                    service_id_in_online_special_by_tags = settlement_manager.get_service_id_to_online_special_ids_by_tags(
                        [self.id])

                    service_online_special_ids = set(service_id_in_online_special_by_tags.get(self.id, []))

                    if service_online_special_ids:
                        all_sku_can_use_coupon = len(coupon_restrict_special_ids & service_online_special_ids) > 0

                    if not all_sku_can_use_coupon:
                        sku_id_to_online_special_id_set = settlement_manager.get_sku_id_to_online_special_id_set(
                            service_item_ids)

            serviceitems = []
            for item in items:
                # TODO: 把万恶之源 service_item_data 给砍掉
                item_data = item.service_item_data()
                item_data['service_item_id'] = item.id
                if 'countdown' not in item_data:
                    item_data['countdown'] = 0

                price_info = item.get_current_price_info()
                price_sale_limit = price_info['sale_limit'] if price_info else _orderding_limit
                price_single_user_buy_limit = price_info['single_user_buy_limit'] if price_info else _orderding_limit

                buy_limit = _get_sku_buy_limit(self.single_user_buy_limit,
                                               item.sku_stock,
                                               price_sale_limit,
                                               price_single_user_buy_limit,
                                               )

                item_data['buy_limit'] = buy_limit

                can_use_coupon = False
                if all_sku_can_use_coupon:
                    can_use_coupon = True
                else:
                    sku_id = item.id
                    if sku_id in coupon_restrict_sku_ids:
                        can_use_coupon = True
                    elif sku_id in sku_id_to_online_special_id_set:
                        sku_special_ids = sku_id_to_online_special_id_set[sku_id]
                        can_use_coupon = len(coupon_restrict_special_ids & sku_special_ids) > 0

                item_data['can_use_coupon'] = can_use_coupon

                serviceitems.append(item_data)

            service_item_dict = {}
            service_item_keys = ServiceItemKey.objects.filter(serviceitem_id__in=service_item_ids)
            for service_item_key in service_item_keys:
                serviceitem_id = service_item_key.serviceitem.id
                if service_item_dict.get(serviceitem_id):
                    service_item_dict[serviceitem_id].append(service_item_key)
                else:
                    service_item_dict[serviceitem_id] = [service_item_key]

            default_attrs = []
            if service_item_ids:
                first_item_id = service_item_ids[0]
                service_item_keys = ServiceItemKey.objects.filter(serviceitem_id=first_item_id)
                default_attrs = [int(service_item_key.serviceattroption.id) for service_item_key in
                                 service_item_keys]

            for service_item_id in service_item_ids:
                service_item_keys = service_item_dict.get(service_item_id)
                attr_data = {}
                options = []
                attr = None
                if service_item_keys:
                    for service_item_key in service_item_keys:
                        option_data = {}
                        option = service_item_key.serviceattroption
                        if option:
                            attr = option
                            option_data['option_id'] = option.id
                            option_data['name'] = option.name
                            option_data['selected'] = True if option.id in default_attrs else False
                            options.append(option_data)

                        if attr:
                            tag_attr = attr.tag_attr
                            if str(tag_attr.id) in multiattribute:
                                from api.tool.service_tool import check_option_id_is_exist
                                if not check_option_id_is_exist(option_data,
                                                                multiattribute[str(tag_attr.id)]["options"]):
                                    multiattribute[str(tag_attr.id)]["options"].append(copy.deepcopy(option_data))
                            else:
                                attr_data['attr_id'] = tag_attr.id
                                attr_data['name'] = tag_attr.name
                                attr_data["options"] = [copy.deepcopy(option_data)]
                                multiattribute[str(tag_attr.id)] = copy.deepcopy(attr_data)

            multiattribute_list = []
            for i in multiattribute:
                multiattribute_list.append(multiattribute[i])
            return {
                'multiattribute_list': multiattribute_list,
                'serviceitems': serviceitems
            }
        return {
            'multiattribute_list': [],
            'serviceitems': []
        }

    def get_service_detail(self, user=None, is_new=False):
        doctor_type = self.doctor.doctor_type

        sk_info = self.get_lowest_price_seckill_info()

        data = {
            'service_id': self.id,
            'service_name': self.name,
            'service_show_name': self.show_name,
            'diaries_count': self.diaries_count,
            'share_image': get_full_path(u'img%2Ficon114.png'),
            'need_address': self.need_address,
            'short_description': self.short_description,
            'detail_description': self.detail_description,
            'special_remind': self.special_remind,
            'sell_amount': self.sell_amount_display,
            'start_time': get_timestamp_or_none(self.start_time),
            'end_time': self.time_limit,
            'total_num': self.sell_num_limit_display,
            'stock': self.stock,
            'original_price': self.lowest_original_price if self.lowest_original_price else 0,
            'total_price': self.lowest_gengmei_price,
            'pre_payment_price': self.lowest_pre_payment_price,
            'exchange_points_ceiling': self.exchange_points_ceiling if self.exchange_points_ceiling else 0,
            'is_can_be_sold': self.is_can_be_sold(),
            'payment_type': self.payment_type,  # 0 全款 1 预付款 2 免费 3 礼品换购
            'payment_type_v2': self.payment_type_v2,
            'gengmei_price': self.gengmei_price_display,
            'is_favored': self.is_favored(user),
            'is_multiattribute': self.is_multiattribute if self.is_multiattribute else False,
            'share_get_cashback': self.share_get_cashback,
            'refund_anytime': self.refund_anytime,
            'is_stage': self.is_stage,
            'compensation_in_advance': self.compensation_in_advance,
            'city': self.city_name or u'全国',
            'service_type': self.get_service_type,
            'tags': self.get_tags(),
            'show_location': self.doctor.show_location,
            'original_price_display': self.original_price_display,
            'doctor': {
                'id': self.doctor.id,
                'user_id': self.doctor.user.id,
                'introduction': self.doctor.introduction,
                "is_recommend_doctor": self.doctor.is_recommend,
                # NOTE: google or baidu, fuck me.
                "google_loc_lng": self.doctor.hospital.baidu_loc_lng,
                "google_loc_lat": self.doctor.hospital.baidu_loc_lat,
                "department": self.doctor.department,
                'name': self.doctor.name,
                'doctor_type': doctor_type,
                'portrait': get_full_path(self.doctor.portrait),
                'city': self.city_name,
                'rate': self.doctor.rate,
                'rate_count': self.doctor.rate_count,
                'hospital': self.doctor.hospital.name,
                'hospital_id': self.doctor.hospital.id,
                'hospital_address': self.doctor.hospital.location,
                'is_hospital': True if self.doctor.hospital else False,
                'title': self.doctor.title,
                'accept_private_msg': self.doctor.accept_private_msg,
                "accept_call": self.doctor.accept_call,
                'phone': PhoneService.get_phone_prefix(self.doctor.phone_ext),
                'phone_ext': self.doctor.phone_ext,
                'diaries_count': self.doctor.share_diary_num,
                'is_online': self.doctor.is_online,
                'good_at': self.doctor.good_at,
            },
            'image_header': self.image_header,
            'image_detail': self.image_detail,
            'limit_hint': self.limit_hint,
            'sell_num_limit': self.sell_num_limit,
            'wiki': self.wiki and self.wiki.get_wiki_detail(),
            'is_seckill': True if sk_info else False,
            'is_floor_price': self.is_floor_price,
            'is_available_soon': self.is_available_soon,
            'tip': self.first_tip,
            'tips': self.tips,
            'label': self.label,
            'is_online': self.is_online and self.doctor.is_online,
            'service_features': self.service_features,
            'richtext': self.rich_text,
            'renmai_period_price': self.renmai_period_price,
            'yirendai_period_price': self.yirendai_period_price,
            'extra_prompt': {
                'has_extra_expense': self.have_extra_pay,
                'introduction': self.extra_pay_info,
            },
            'disclaimer': self.disclaimer,
            'buy_notice': self.get_buy_notice,
            'services_count': self.get_all_services_count,
            'service_sell_type': self.service_type,
            'rating': self.rating,
            'operation_effect_rating': self.operation_effect_rating,
            'doctor_attitude_rating': self.doctor_attitude_rating,
            'hospital_env_rating': self.hospital_env_rating
        }
        # 资质显示
        show_lincence = self.doctor.get_doctor_lincence(data['doctor']['title'], 1)

        data['doctor']['show_v'] = show_lincence['show_v']
        data['doctor']['title'] = show_lincence['title']
        data['doctor']['show_rating'] = show_lincence['show_rating']

        if sk_info:
            data['countdown'] = sk_info.get('countdown')

        if is_new:
            items = self._queryset_can_sell_items()
            items = sorted(items, key=lambda ServiceItem: ServiceItem.pre_payment_price)
            serviceitems = []
            for item in items:
                serviceitems.append(item.service_item_data())
            data["serviceitems"] = serviceitems
            if self.is_multiattribute:
                multiattribute = {}
                service_item_dict = {}
                service_item_keys = ServiceItemKey.objects.filter(serviceitem__in=items)
                for service_item_key in service_item_keys:
                    serviceitem_id = service_item_key.serviceitem.id
                    if service_item_dict.get(serviceitem_id):
                        service_item_dict[serviceitem_id].append(service_item_key)
                    else:
                        service_item_dict[serviceitem_id] = [service_item_key]

                default_attrs = []
                if items:
                    first_item = items[0]
                    service_item_keys = ServiceItemKey.objects.filter(serviceitem=first_item)
                    default_attrs = [int(service_item_key.serviceattroption.id) for service_item_key in
                                     service_item_keys]
                    data['_pre_payment_price'] = first_item.pre_payment_price
                    data['pre_payment_price'] = first_item.pre_payment_price
                    data['_original_price'] = first_item.original_price
                    data['_gengmei_price'] = first_item.gengmei_price

                for item in items:
                    service_item_keys = service_item_dict.get(item.id)
                    attr_data = {}
                    options = []
                    attr = None
                    if service_item_keys:
                        for service_item_key in service_item_keys:
                            option_data = {}
                            option = service_item_key.serviceattroption
                            if option:
                                attr = option
                                option_data['option_id'] = option.id
                                option_data['name'] = option.name
                                option_data['selected'] = True if option.id in default_attrs else False
                                options.append(option_data)

                            if attr:
                                tag_attr = attr.tag_attr
                                if str(tag_attr.id) in multiattribute:
                                    from api.tool.service_tool import check_option_id_is_exist
                                    if not check_option_id_is_exist(option_data,
                                                                    multiattribute[str(tag_attr.id)]["options"]):
                                        multiattribute[str(tag_attr.id)]["options"].append(copy.deepcopy(option_data))
                                else:
                                    attr_data['attr_id'] = tag_attr.id
                                    attr_data['name'] = tag_attr.name
                                    attr_data["options"] = [copy.deepcopy(option_data)]
                                    multiattribute[str(tag_attr.id)] = copy.deepcopy(attr_data)

                multiattribute_list = []
                for i in multiattribute:
                    multiattribute_list.append(multiattribute[i])

                data["multiattribute"] = multiattribute_list
                data['pre_payment_price'] = self.lowest_pre_payment_price

            if sk_info:
                data['seckill_sell_num_limit'] = sk_info['seckill_sell_num_limit']
                data['seckill_status'] = sk_info['seckill_status']
                data['seckill_price'] = sk_info['seckill_price']
                data['tip'] = sk_info['seckill_tip']
                data['pre_payment_price'] = sk_info['pre_payment_price']

        # TODO 去掉下面这段重复逻辑
        else:
            if self.is_multiattribute:
                multiattribute = []
                attrs = self.attrs.all()
                items = self.items.all()
                items = sorted(items, key=lambda ServiceItem: ServiceItem.pre_payment_price)
                serviceitems = []
                for item in items:
                    serviceitems.append(item.service_item_data())
                data["serviceitems"] = serviceitems

                default_attrs = []
                if items:
                    first_item = items[0]
                    default_attrs = first_item.key.split('-')
                    default_attrs = [int(attr) for attr in default_attrs]
                    data['_pre_payment_price'] = first_item.pre_payment_price
                    data['pre_payment_price'] = first_item.pre_payment_price
                    data['_original_price'] = first_item.original_price
                    data['_gengmei_price'] = first_item.gengmei_price

                for attr in attrs:
                    attr_data = {}
                    attr_data["attr_id"] = attr.id
                    attr_data["name"] = attr.name
                    options = []
                    option_objs = attr.options.all()
                    for option in option_objs:
                        option_data = {}
                        option_data["option_id"] = option.id
                        option_data["name"] = option.name
                        option_data['selected'] = True if option.id in default_attrs else False
                        options.append(option_data)
                    attr_data["options"] = options
                    multiattribute.append(attr_data)
                data["multiattribute"] = multiattribute

            if sk_info:
                data['seckill_sell_num_limit'] = sk_info['seckill_sell_num_limit']
                data['seckill_status'] = sk_info['seckill_status']
                data['seckill_price'] = sk_info['seckill_price']
                data['tip'] = sk_info['seckill_tip']
                data['pre_payment_price'] = sk_info['pre_payment_price']

        # TODO: remove this after 9.9
        if user:
            from api.manager.game_manager import get_discount
            data['game_discount'] = get_discount(user.id, self.id)

        data['is_support_renmai_payment'] = self.is_support_renmai_no_pay
        data['is_support_yirendai_payment'] = self.is_support_yirendai_no_pay
        data['insurance_info'] = self.insurance_info

        return data

    def __unicode__(self):
        return self.name

    @property
    def cashback_limit(self):
        try:
            cash_back_fee = self.cash_back_fee
        except:
            cash_back_fee = 0

        return cash_back_fee / 50 + 1

    def smart_self_support(self):
        '''
        设置医生是否自营
        '''
        doctor = self.doctor
        if self.doctor.services.filter(is_online=True, is_self_support=True).count():
            doctor.self_run = True
        else:
            doctor.self_run = False
        doctor.save()

    @property
    def service_features(self):
        """
        返回美购的特别标签: 随时退款 先行赔付 分期付款 分享返现 底价
        """

        def make_doc_url(postfix):
            return settings.FEATURE_BASE_URL + postfix

        def make_new_url(feature_name):
            return settings.FEATRUE_AGGREGATE_URL.replace('{service_id}', str(self.id)).replace('{type}', feature_name)

        features = []
        for feature, name, url_postfix, content in settings.TOTAL_FEATURES:
            if getattr(self, feature):
                features.append({
                    'name': name,
                    'feature': feature,
                    'url': make_doc_url(url_postfix),
                    'content': content,
                    'aggregate_url': make_new_url(feature)
                })
        if self.insurance_info:
            d = {
                'name': self.insurance_info['title'],
                'feature': 'insurance',
                'url': make_doc_url('beauty_insurance'),
                'content': u'让变美多一重保障',
                'aggregate_url': make_new_url('insurance')
            }
            # if self.insurance_info['insurance_type'] == INSURANCE_TYPE.YINUO:
            #     d['content'] = u'由中国人民财产保险股份有限公司承保'
            # else:
            #     d['content'] = u'购买专业客服服务包，即免费送一份保险'

            features.append(d)

        result = []
        for tag in settings.FEATURES_SORT:
            for feature in features:
                if feature['feature'] == tag:
                    result.append(feature)

        return result

    @property
    def insurance_info(self):
        # 返回缓存结果， 展示逻辑调用， 下单等逻辑不要调用
        if not self.insurance:
            return None

        from api.manager import service_info_manager
        info = service_info_manager.get_insurance_info_by_insurance_service_id(self.insurance)
        return info

    def _get_fields_value_from_cache(self):
        """try to get fields data from cache first."""
        k = 'service:%s' % self.id

        try:
            d = model_cache.get(k)
            if d:
                return json.loads(d)

        except:
            logging_exception()

        doctor_name = self.doctor and self.doctor.name or ''
        doctor_portrait = self.doctor and get_full_path(self.doctor.portrait) or ''

        try:
            hospital_name = (
                    self.doctor and
                    self.doctor.doctor_type == DOCTOR_TYPE.DOCTOR and
                    self.doctor.hospital.name or ''
            )
        except Hospital.DoesNotExist:
            hospital_name = ''

        data = {
            'gengmei_price': self.gengmei_price_display,
            'doctor_name': doctor_name,
            'doctor_portrait': doctor_portrait,
            'hospital': hospital_name,
            'tip': self.first_tip,
            'city': self.city_name or u"全国",
            'original_price_display': self.get_original_price_display(),
        }

        try:
            model_cache.setex(k, 60 * 5, json.dumps(data))
        except:
            logging_exception()

        return data

    def get_data_for_list_with_sell_info(self, user):
        data = self.get_data_for_list(user, None)
        if data['sell_amount'] and int(data['sell_amount']) > 0:
            data['sell_amount'] = u'已购买%s' % data['sell_amount']
        if data['is_multiattribute']:
            data['gengmei_price'] = data['gengmei_price'].split('-')[0].strip()
            data['is_price_range'] = True
        else:
            data['is_price_range'] = False
        return data

    def get_data_for_shopcart(self, service_item_key=None, service_item_id=None):
        data = {
            'service_id': self.id,
            'service_image': self.image_header,
            'service_name': self.name,
            'pre_payment_price': self.pre_payment_price,
            'is_can_be_sold': self.is_can_be_sold(),
            'is_stage': self.is_stage,
            'is_multiattribute': self.is_multiattribute,
        }

        if service_item_key:
            data['service_item'] = {}
            try:
                obj = ServiceItem.objects.get(service=self, key=service_item_key, is_delete=False)
                serivce_itme_data = obj.service_item_data()
                data['service_item'] = {
                    'pre_payment_price': serivce_itme_data['pre_payment_price'],
                    'items_name': serivce_itme_data['items_name']
                }
            except ServiceItem.DoesNotExist:
                pass

        return data

    def is_favored(self, user):
        is_favored = False
        if user and not isinstance(user, AnonymousUser):
            is_favored = ServiceFavor.objects.filter(
                user=user, service=self, is_deleted=False
            ).exists()

        return is_favored

    def get_data_for_list(self, user, special_id):
        """return scoped fields for list view."""
        is_favored = self.is_favored(user)

        data = {
            'limit_hint': self.limit_hint,
            'is_favored': is_favored,
            'is_can_be_sold': self.is_can_be_sold(),
            'service_name': self.name,
            'sell_num_limit': self.sell_num_limit,
            'service_id': self.id,
            'original_price': self.lowest_original_price,
            'service_image': self.image_header,
            'image_header': get_full_path(self.image_header, '-half'),
            'short_description': self.short_description,
            'is_multiattribute': self.is_multiattribute,
            'is_seckill': False,
            'end_time': self.time_limit,
            'sell_amount': self.sell_amount_display,
            'label': self.label,
            'payment_type': self.payment_type,
            'exchange_points_ceiling': self.exchange_points_ceiling,
            'pre_payment_price': self.pre_payment_price,
            'image_detail': self.image_detail,
            'is_support_renmai_payment': self.is_support_renmai_no_pay,
            'is_support_yirendai_payment': self.is_support_yirendai_no_pay,
            'renmai_period_price': self.renmai_period_price,
            'yirendai_period_price': self.yirendai_period_price,
            'is_stage': self.is_stage,
            'insurance_info': self.insurance_info,
            'hospital_lng': self.doctor.hospital.baidu_loc_lng,
            'hospital_lat': self.doctor.hospital.baidu_loc_lat,
        }

        seckill_info = {}
        if special_id:
            # seckill_info = self._get_seckill_info_for_special(special_id, user)
            pass  # 这段逻辑无用了
        else:
            sk_info = self.get_lowest_price_seckill_info()
            if sk_info:
                seckill_info['seckill_sell_num_limit'] = sk_info['seckill_sell_num_limit']
                seckill_info['seckill_status'] = sk_info['seckill_status']
                seckill_info['seckill_price'] = sk_info['seckill_price']
                seckill_info['tip'] = sk_info['seckill_tip']
                seckill_info['pre_payment_price'] = sk_info['pre_payment_price']
                seckill_info['is_seckill'] = True

        data.update(seckill_info)

        # get fields from cache
        data.update(self._get_fields_value_from_cache())

        return data

    @property
    def is_lock(self):
        # TODO Deprecated 废弃了，维度锁定到sku上
        now = datetime.datetime.now()
        return self.locklist_set.filter(start_time__lt=now, end_time__gt=now).exists()

    @property
    def has_sku_lock(self):
        items = self.items.filter(is_delete=False).values_list('id', flat=True)
        now = datetime.datetime.now()
        re = SKULock.objects.filter(
            serviceitem_id__in=items, locklist__start_time__lt=now, locklist__end_time__gt=now
        ).exists()
        return re

    def get_max_gengmei_price(self):
        gengmei_price_list = self.gengmei_price_list
        return max(gengmei_price_list)

    @property
    def tag_name(self):
        return ','.join(tag.name for tag in self.tags.all())

    @property
    def album_pic_num(self):
        doctor = self.doctor
        pic_num = ImageRelatedService.objects.filter(service=self).count()
        if doctor.doctor_type == DOCTOR_TYPE.OFFICER:
            doctor_pics = Doctor.objects.filter(hospital_id=doctor.hospital_id,
                                                is_online=True).filter(~Q(portrait=''))
        else:
            doctor_pics = Doctor.objects.filter(id=doctor.id,
                                                is_online=True).filter(~Q(portrait=''))
        organization_pics = OrganizationImage.objects.filter(doctor=doctor)
        count = doctor_pics.count() + organization_pics.count() + pic_num
        return count

    def get_service_features(self):
        post_price = self.min_post_price
        hospital_id = self.doctor.hospital_id

        phid = set(PeriodHospital.get_hospital_ids_in_list([hospital_id]))
        is_stage = self.is_stage and hospital_id in phid

        support_installment_info = {}
        if is_stage and post_price >= 1:
            huabei_info = Service.get_huabei_period_info(post_price)
            period = "12"
            if period in huabei_info:
                huabei = {
                    "fee": huabei_info[period]["fee"],
                    "pay": huabei_info[period]["pay"],
                    "period": huabei_info[period]["period"],
                }
                support_installment_info["huabei"] = huabei

        return {
            'features': self.service_features,
            'installment': {
                'yirendai_period_price': self.yirendai_period_price,
                'renmai_period_price': self.renmai_period_price,
                'is_support_renmai_payment': self.is_support_renmai_no_pay,
                'is_support_yirendai_payment': self.is_support_yirendai_no_pay,
                'url': settings.FEATURE_BASE_URL + 'installment',
                'aggregate_url': settings.FEATRUE_AGGREGATE_URL.replace('{service_id}', str(self.id)).replace('{type}',
                                                                                                              'is_stage'),
                'support_installment_info': support_installment_info,
                'name': u'分期服务',
                'content': u'支持花呗分期，先变美，后还款',
                'feature': 'is_stage',
            }
        }

    @property
    def hospital_name(self):
        hospital_name = ''
        try:
            hospital_name = (
                    self.doctor and
                    self.doctor.hospital.name or ''
            )
        except Hospital.DoesNotExist:
            pass
        except:
            logging_exception()
        return hospital_name

    @property
    def share_content(self):
        if self.project_type:
            share_tag_name = self.project_type.name
        else:
            return self.short_description

        if self.is_multiattribute:
            price = self.gengmei_price_display.split('-')[0] + u'起'
        else:
            price = self.gengmei_price_display

        if self.doctor.doctor_type == DOCTOR_TYPE.OFFICER:
            return share_tag_name + '|' + self.hospital_name + ',更美价￥' + price
        else:
            return share_tag_name + '|' + self.doctor.name + '|' + self.hospital_name + ',更美价￥' + price

    def get_doctor_info(self):
        from hippo.tool.merchant_tool import doctor_merchant_map

        service_doctor_id = self.doctor.id if self.doctor else ''
        merchant_doctor_id = ''

        if service_doctor_id:
            merchant_doctor_id = doctor_merchant_map([service_doctor_id]).get(service_doctor_id, '')

        result = {}

        result['user_id'] = self.doctor.user_id
        result['diaries_count'] = self.doctor.share_diary_num

        show_lincence = self.doctor.get_doctor_lincence(self.doctor.title, self.doctor.services.count())

        result['show_v'] = show_lincence['show_v']
        result['show_rating'] = show_lincence['show_rating']
        result['doctor_title'] = show_lincence['title']
        result['show_location'] = self.doctor.show_location

        result['doctor_id'] = service_doctor_id
        result['merchant_doctor_id'] = merchant_doctor_id
        result['hospital_id'] = self.doctor.hospital.id
        result['portrait'] = get_full_path(self.doctor.portrait, '-thumb') if self.doctor else ''
        doctor = self.doctor
        result['doctor_name'] = self.doctor.name if self.doctor else ''
        result['hospital_name'] = self.hospital_name
        result['longitude'] = self.doctor.hospital.baidu_loc_lng
        result['latitude'] = self.doctor.hospital.baidu_loc_lat
        result['membership_level'] = '0'
        result['hospital_address'] = self.doctor.hospital.location
        result['service_count'] = self.doctor.get_doctor_service_num()
        if doctor.doctor_type == DOCTOR_TYPE.OFFICER:
            result['is_hospital_officer'] = True
        else:
            result['is_hospital_officer'] = False
        result['score'] = self.doctor.rate

        result['hospital_title'] = ''  # todo 医院标签暂未实现 180104

        return result

    def get_hospital_and_doctor_info(self):
        from hippo.tool.merchant_tool import doctor_merchant_map

        result = {}

        service_doctor_id = self.doctor.id if self.doctor else ''
        merchant_doctor_id = ''

        if service_doctor_id:
            merchant_doctor_id = doctor_merchant_map([service_doctor_id]).get(service_doctor_id, '')

        doctor_lincence = self.doctor.get_doctor_lincence(self.doctor.title, self.doctor.services.count())
        # ------------- build doctor_info -------------
        doctor_info = {}
        doctor_info['show_v'] = doctor_lincence['show_v']
        doctor_info['show_rating'] = doctor_lincence['show_rating']
        doctor_info['doctor_title'] = doctor_lincence['title']
        doctor_info['doctor_service_count'] = doctor_info['service_count'] = self.doctor.get_doctor_service_num()
        doctor_info['good_at'] = self.doctor.good_at
        doctor_info['doctor_score'] = doctor_info['score'] = self.doctor.rate
        doctor_info['is_hospital_officer'] = self.doctor.doctor_type == DOCTOR_TYPE.OFFICER
        doctor_info['user_id'] = self.doctor.user_id
        doctor_info['diaries_count'] = self.doctor.share_diary_num
        doctor_info['show_location'] = self.doctor.show_location
        doctor_info['doctor_name'] = self.doctor.name if self.doctor else ''
        doctor_info['doctor_id'] = service_doctor_id
        doctor_info['merchant_doctor_id'] = merchant_doctor_id
        doctor_info['doctor_portrait'] = get_full_path(self.doctor.portrait, '-thumb') if self.doctor else ''
        doctor_info['portrait'] = doctor_info['doctor_portrait']
        if doctor_info['is_hospital_officer']:
            doctor_info['doctor_name'] = ''
            doctor_info['doctor_title'] = ''
            doctor_info['good_at'] = ''
        #  -------- build hospital_info -----------
        hospital_info = {}
        hospital_info['show_v'] = '0'
        hospital_info['show_rating'] = '0'
        hospital_info['hospital_portrait'] = self.doctor.hospital.get_hospital_portrait()
        hospital_info['portrait'] = hospital_info['hospital_portrait']
        hospital_info['hospital_score'] = self.doctor.hospital.rate
        if doctor_info['is_hospital_officer']:  # 医院的头像取值自机构管理者
            hospital_info['show_v'] = doctor_lincence['show_v']
            hospital_info['show_rating'] = doctor_lincence['show_rating']
        else:
            officer = self.doctor.hospital.officer
            if officer:
                officer_license = officer.get_doctor_lincence('', officer.get_doctor_service_num())
                hospital_info['show_v'] = officer_license['show_v']
                hospital_info['show_rating'] = officer_license['show_rating']
        hospital_info['hospital_id'] = self.doctor.hospital.id
        hospital_info['hospital_name'] = self.hospital_name
        hospital_info['hospital_address'] = self.doctor.hospital.location
        hospital_info['hospital_service_count'] = self.doctor.get_hospital_service_num()
        hospital_info['has_v_certification'] = hospital_info['hospital_service_count'] > 0
        hospital_info['diaries_count'] = self.doctor.hospital.share_diary_num
        hospital_info['diary_topic_count'] = self.doctor.hospital.share_diary_topic_num
        hospital_info['hospital_id'] = self.doctor.hospital.id
        hospital_info['hospital_address'] = self.doctor.hospital.location
        hospital_info['hospital_point'] = {
            'longitude': self.doctor.hospital.baidu_loc_lng,
            'latitude': self.doctor.hospital.baidu_loc_lat
        }
        hospital_info['longitude'] = self.doctor.hospital.baidu_loc_lng
        hospital_info['latitude'] = self.doctor.hospital.baidu_loc_lat

        hospital_info['hospital_title'] = self.doctor.hospital.hospital_medical_type
        # 这里将机构信息与医生信息封装在result中, 在backend层将对应的数据提到上层
        # 在7710前, 显示的为doctor信息; 7710之后显示hospital机构信息(对diaries_count等信息)
        # 对于公共字段可公共复写, 对于doctor_name或hospital_name等字段, 都进行展示
        result['hospital_info'] = hospital_info
        result['doctor_info'] = doctor_info

        return result

    def _get_lowest_current_seckill_price(self):
        result = {}
        all_current_seckill = [info for info in self._can_sell_items_info if
                               info.activity_type == ACTIVITY_TYPE_ENUM.SECKILL]
        if all_current_seckill:
            p = min(all_current_seckill, key=_get_current_service_item_price_key)

            now = datetime.datetime.now()
            countdown = (p.end_time - now).total_seconds() if p.end_time else 0
            result = {
                'seckill_price': p.gengmei_price,
                'countdown': countdown if countdown > 0 else 0,
                'sale_limit': p.sale_limit,
                'single_user_buy_limit': p.single_user_buy_limit,
                'sku_stock': p.sku_stock,
                'service_item_id': p.service_item_id,
            }

        return result

    def _get_lowest_current_groupbuy_price(self):
        result = {}
        all_can_sell_item_ids = Service.get_service_ids_to_can_sell_item_ids([self.id], check_has_stock=True).get(
            self.id, [])
        groupbuy_price_list = Service._get_current_groupbuy_price_by_service_item_ids(all_can_sell_item_ids)
        if groupbuy_price_list:
            p = min(groupbuy_price_list, key=_get_current_service_item_current_groupbuy_price_key)
            result = ServiceItem._to_price_info(p)
        return result

    def _get_lowest_current_multibuy_price(self):
        result = {}
        all_can_sell_item_ids = Service.get_service_ids_to_can_sell_item_ids([self.id], check_has_stock=True).get(
            self.id, [])
        multibuy_price_dict = Service.get_current_multibuy_price_info_by_service_item_ids(all_can_sell_item_ids)
        multibuy_price_list = multibuy_price_dict.values()
        if multibuy_price_list:
            result = min(multibuy_price_list,
                         key=lambda x: (x['single_price'], x.get('selling_rule', {}).get('more_buy_count') or 1))
        return result

    def get_top_price_show_with_type(self, special_type):
        '''
        不区分专题类型, 返回全局展示的价格区间
        :return:
        '''
        item_list = self._queryset_can_sell_items(check_has_stock=True)
        item_ids = [_i.id for _i in item_list]

        price_range_info = self.get_price_range_from_item_ids(item_ids, special_type=special_type)
        return {
            'gengmei_price': price_range_info.get('gengmei_price_show'),
            'original_price': price_range_info.get('original_price_show')
        }

    def get_top_price_show(self, support_groupbuy, support_seckill=True):
        '''
        按照以下规则返回价格信息
        规则：
        1. 如果有拼团. 展示最低价格的拼团价(更美价/划线价 -- gengmei_price/original_price)
        2. 没有拼团：
            original_price:  显示所有skus中的最低市场价
            gengmei_price:
                有秒杀: 最低的秒杀价
                没有秒杀: 所有gengmei_price的价格区间
        '''
        top_price_show = {
            'gengmei_price': 0,         # 展示价
            'original_price': 0,        # 划线价
            'is_groupbuy': False,       # 是否拼团
            'is_seckill': False,        # 是否秒杀
            'countdown': 0,             # 剩余时间, 针对秒杀/拼团, 单位:秒
            'remain_seckill_count': 0,  # 秒杀剩余数量,秒杀用
            'groupbuy_nums': 0,         # 拼团人数
            'service_item_id': 0,       # 价格策略对应的service_item_id, 兼容, # 7710之后删除
            'buy_limit': 1,             # 购买限制
            'pointed_service_item_id': 0,  # 后台规则默认选中的sku, 和service_item_id概念重合
            'promotion_image': '',      # 普通专场大促图片，显示在美购详情页中
        }

        if support_groupbuy:  # 参数默认为False, 为兼容ship端接口调用
            g_price = self._get_lowest_current_groupbuy_price()
            if g_price:
                top_price_show['gengmei_price'] = g_price['gengmei_price']
                top_price_show['original_price'] = g_price['original_price']
                top_price_show['is_groupbuy'] = True
                top_price_show['groupbuy_nums'] = g_price['selling_rule']['groupbuy_nums']
                top_price_show['countdown'] = (
                            g_price['selling_rule']['end_time'] - datetime.datetime.now()).total_seconds()
                top_price_show['service_item_id'] = g_price['service_item_id']
                top_price_show['buy_limit'] = 1  # 拼团目前只支持1件/人团

        if not top_price_show['is_groupbuy']:
            top_price_show['original_price'] = self.lowest_original_price

            seckill_info = self._get_lowest_current_seckill_price()
            if support_seckill and seckill_info:
                top_price_show['is_seckill'] = True
                top_price_show['gengmei_price'] = seckill_info['seckill_price']
                top_price_show['service_item_id'] = seckill_info['service_item_id']
                top_price_show['countdown'] = seckill_info['countdown']

                buy_limit = _get_sku_buy_limit(self.single_user_buy_limit,
                                               seckill_info['sku_stock'],
                                               seckill_info['sale_limit'],
                                               seckill_info['single_user_buy_limit'],
                                               )
                remain_seckill_count = min(seckill_info['sku_stock'], seckill_info['sale_limit'])
                top_price_show['buy_limit'] = buy_limit
                top_price_show['remain_seckill_count'] = remain_seckill_count
            else:
                items = self._can_sell_items_info
                # 价格竞争力二期 SPU维度时 存在划线价格小于等于更美价的SKU时 不展示划线价
                original_price_list = [item.original_price for item in items]
                gengmei_price_list = [item.gengmei_price for item in items]
                if original_price_list and gengmei_price_list:
                    if max(original_price_list) <= max(gengmei_price_list) or min(original_price_list) <= min(
                            gengmei_price_list):
                        top_price_show['original_price'] = ''
                    else:
                        top_price_show['original_price'] = self.show_original_price
                top_price_show['gengmei_price'] = self.gengmei_price_display
                # 价格竞争力2期 获取显示的专场
                special_ids = [i.special_id for i in items if
                               i.special_id and i.activity_type == ACTIVITY_TYPE_ENUM.SPECIAL]
                from api.models import Special
                display_specials = Special.objects.filter(id__in=special_ids).exclude(promotion_image='').order_by(
                    '-id')
                if display_specials:
                    display_special = display_specials.first()
                    now = datetime.datetime.now()
                    countdown = (display_special.end_time - now).total_seconds() if display_special.end_time else 0
                    top_price_show['countdown'] = countdown
                    top_price_show['promotion_image'] = display_special.promotion_image
                top_price_show['buy_limit'] = _orderding_limit

        top_price_show['pointed_service_item_id'] = top_price_show['service_item_id']
        return top_price_show

    def get_base_service_detail(self, user, support_groupbuy=False, support_seckill=True, default_select=True):

        from api.manager import service_info_manager
        service_id_list = service_info_manager.add_double_eleven_image()
        double_eleven_image = ''
        if self.id in service_id_list:
            double_eleven_image = 'https://heras.igengmei.com/serviceactivity/2019/10/21/d9c715646c'

        if default_select:
            top_price_show_info = self.get_top_price_show(support_groupbuy, support_seckill)
        else:
            special_type = service_type2special_type.get(self.service_type)
            top_price_show_info = self.get_top_price_show_with_type(special_type)

        tags_id_and_name_list = (self.tags.filter(is_online=True).values_list('id', 'name'))

        detail = {
            "service_id": self.id,
            "service_type": self.service_type,
            "diaries_count": self.diaries_count,
            "project_type_name": self.project_type.name if self.project_type and self.project_type.name else '',
            'doctor_recommend': True if self.image_bigpic else False,
            'image_detail': self.image_detail,
            'pic': get_full_path(self.image_bigpic or self.image_header, '-w'),
            'pic_half': get_full_path(self.image_header, '-half'),  # 这个字段主要是用于微信分享时候的图片URL地址，有大小限制
            'pic_num': self.album_pic_num,
            'sell_amount': self.sell_amount_display,
            'city': self.city_name,
            'service_name': self.show_name,
            'url': '',
            'share_content': self.share_content,
            'special_info': '',
            'is_multiattribute': self.is_multiattribute,
            'short_description': self.short_description,
            'pic_small': get_full_path(self.image_header, '-thumb'),
            'extra_info': {
                'is_online': self.is_online,
                'can_sell': self.is_can_be_sold(),
                'payment_type': self.payment_type,
                'accept_private_msg': self.doctor.accept_private_msg,
                'accept_call': self.doctor.accept_call,
                'phone': PhoneService.get_phone_prefix(self.doctor.phone_ext),
                'phone_ext': self.doctor.phone_ext,
                'is_multiattribute': self.is_multiattribute,
                'prepay_price': self.lowest_pre_payment_price,
                'doctor_user_id': self.doctor.user_id,
                'is_favored': self.is_favored(user)
            },
            'tag_ids': [tid for tid, tname in tags_id_and_name_list],
            'tag_names': [tname for tid, tname in tags_id_and_name_list],
            'comment_rate': self.rating,
            'wiki_id': self.wiki.id if self.wiki else None,
            'disclaimer': self.disclaimer,
            'buy_notice': self.get_buy_notice,
            'have_extra_pay': self.have_extra_pay,
            'richtext': self.rich_text,
            'image_header': self.image_header,
            'double_eleven_image': double_eleven_image,
        }

        detail.update(top_price_show_info)
        return detail

    @classmethod
    def get_service_infos_by_hospital_id(cls, hospital_id, q=None):
        services = Service.objects.filter(doctor__hospital=hospital_id, is_online=True)
        result = []
        if q:
            services = services.filter(name__icontains=q)
        for service in services:
            result.append({
                'service_name': service.name,
                'service_id': service.id,
            })
        return result

    # ----------------------------------------- since 780 -----------------------------
    def get_base_share_data(self):
        '''
        返回美购分享的基本数据, 不包含sku的价格信息
        '''
        result = {}
        result['image_header'] = self.image_header
        result['doctor_name'] = self.doctor.name if self.doctor else ''
        result['hospital_name'] = self.hospital_name
        result['spu_name'] = self.short_description
        result['project_type'] = self.project_type.name or '' if self.project_type else ''
        return result


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

    service = models.ForeignKey(Service, verbose_name=u'美购关联')
    storeclassification = models.ForeignKey(StoreClassification, verbose_name=u'关联店铺分类')
    doctor = models.ForeignKey(Doctor, verbose_name=u'关联商户')
    rank = models.IntegerField(verbose_name=u'排序')


# 美购改版 2017-03-29
class ImageRelatedService(models.Model):
    class Meta:
        app_label = 'api'
        unique_together = ('service', 'image_url')

    service = models.ForeignKey(Service, verbose_name=u'美购列表', related_name="supplement_images")
    image_url = ImgUrlField(img_type=IMG_TYPE.SERVICE, max_length=255, verbose_name=u"图片URL")


class ServiceRelatedRecommendService(models.Model):
    class Meta:
        app_label = 'api'
        unique_together = ('service', 'recommend_service')

    service = models.ForeignKey(Service, verbose_name=u'美购', related_name="original_service")
    recommend_service = models.ForeignKey(Service, verbose_name=u'推荐美购', related_name="service_recommend_service")


class ServiceFavor(models.Model):
    class Meta:
        unique_together = ("user", "service")
        verbose_name = u'服务收藏'
        verbose_name_plural = u'服务收藏'
        db_table = 'api_servicefavor'
        app_label = 'api'

    user = models.ForeignKey(User, related_name="service_favor_user", help_text=u"用户")
    service = models.ForeignKey(Service, related_name="service_favor_doctor", help_text=u"关注的服务")
    created_time = models.DateTimeField(auto_now_add=True, verbose_name=u"收藏时间")
    is_deleted = models.BooleanField(default=False, help_text=u"是否已经删除")

    def data(self):
        return {
            "user_id": self.user.id,
            "service_id": self.service.id,
            "service_detail": self.service.get_service_detail(),
        }

    @classmethod
    def query(cls, q, offset=None, limit=None):
        favors = cls.objects.filter(q)

        if offset is None:
            offset = 0
        if limit is None:
            favors = favors[offset:]
        else:
            favors = favors[offset:(offset + limit)]
        data = {}
        items = []
        for favor in favors:
            items.append(favor.data())
        data["service_favors"] = items
        return data


# added at 4.5 service related to multiple wikis
class ServiceItemWiki(models.Model):
    service = models.ForeignKey(Service)
    itemwiki = models.ForeignKey(ItemWiki, verbose_name=u'三级分类')

    class Meta:
        verbose_name = u'美购三级分类'
        verbose_name_plural = u'美购三级分类'
        app_label = 'api'

    def __unicode__(self):
        return self.service.name + self.itemwiki.item_name


class UploadButton(models.Model):
    class Meta:
        verbose_name = u'秒杀美购上传按钮管理'
        db_table = 'api_uploadbutton'
        app_label = 'api'

    button_document = models.CharField(verbose_name=u'文案', max_length=100)
    start_show_time = models.DateTimeField(verbose_name=u'按钮开始出现时间')
    end_show_time = models.DateTimeField(verbose_name=u'按妞结束出现时间')
    start_sell_time = models.DateTimeField(verbose_name=u'默认开始售卖时间')
    end_sell_time = models.DateTimeField(verbose_name=u'默认结束售卖时间')
    button_prefix = models.CharField(verbose_name=u'前缀', max_length=100)
    discount = models.IntegerField(verbose_name=u'抽成', default=0)


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

    name = models.CharField(max_length=100, null=False, verbose_name=u'福利名称')
    user = models.ForeignKey(User, verbose_name='申请人')
    comment = models.TextField(null=True, verbose_name=u'特殊说明', default='')
    reservation = models.IntegerField(help_text=u"提前预约天数", verbose_name=u"提前预约天数", default=1)
    refund_anytime = models.BooleanField(help_text=u"是否支持随时退款", verbose_name=u"是否支持随时退款", default=True)
    is_stage = models.BooleanField(help_text=u'是否支持分期付款', verbose_name=u'是否支持分期付款', default=False)
    compensation_in_advance = models.BooleanField(help_text=u"是否支持先行赔付", verbose_name=u"是否支持先行赔付", default=True)
    show_location = models.BooleanField(help_text=u"是否显示地理位置", verbose_name=u"是否显示地理位置", default=True)
    share_get_cashback = models.BooleanField(help_text=u"是否支持分享返现", verbose_name=u"是否支持分享返现", default=True)
    user_safe = models.BooleanField(verbose_name=u'用户保障', default=True)
    created_time = models.DateTimeField(null=True, blank=True, verbose_name=u'创建时间', default=datetime.datetime.now)
    start_time = models.DateTimeField(null=True, blank=True, verbose_name=u'福利起始时间')
    end_time = models.DateTimeField(null=True, blank=True, verbose_name=u"福利结束时间")
    delay = models.CharField(max_length=20, null=True, blank=True, verbose_name=u'美购上线时长', default=90)
    total_num = models.IntegerField(default=None, null=True, blank=True,
                                    verbose_name=u"总数量,废弃,通过registerItem.stock_add_num控制库存")
    single_user_buy_limit = models.IntegerField(default=10, verbose_name=u'单个用户购买数量限制')
    valid_duration = models.IntegerField(verbose_name=u'订单有效期(天)', null=True, blank=True, default=180)
    fake_sold_num = models.IntegerField(verbose_name=u'已抢购基数', default=0)
    extra_charge = models.CharField(max_length=200, null=True, verbose_name=u'额外费用', default='')
    soyoung_url = models.CharField(verbose_name=u'新氧链接', max_length=256, default='')
    yuemei_url = models.CharField(verbose_name=u'悦美链接', max_length=256, default='')
    doctor = models.ForeignKey(Doctor, related_name='serviceregister', null=True, blank=True)
    # 现在已经启用(美购重构)
    status = models.CharField(
        max_length=20, choices=SERVICEREGISTER_STATUS,
        default=SERVICEREGISTER_STATUS.DRAFT, null=False,
        verbose_name=u'申请状态',
    )
    reason = models.CharField(max_length=200, null=True, verbose_name=u'原因', default='')
    reason1 = models.CharField(max_length=200, null=True, verbose_name=u'原因', default='')
    reason2 = models.CharField(max_length=200, null=True, verbose_name=u'原因', default='')
    approver = models.ForeignKey(User, verbose_name=u'审核通过的用户', related_name='approver',
                                 blank=True, null=True, default=None)
    service = models.ForeignKey(Service, verbose_name=u"福利", related_name="servicereg", null=True)
    # type = models.CharField(verbose_name=u'美购类型', max_length=1, choices=SERVICE_TYPE,
    #                         default=SERVICE_TYPE.OPERATION)
    audit_time = models.DateTimeField(verbose_name=u'审核时间', null=True, blank=True)
    edit_time = models.DateTimeField(verbose_name=u'编辑时间', null=True, blank=True)
    online_time = models.DateTimeField(verbose_name=u'上线时间', null=True, blank=True)
    submit_time = models.DateTimeField(verbose_name=u'上线时间', null=True, blank=True)
    is_operation = models.BooleanField(u'是否手术类', default=False)
    service_name = models.CharField(max_length=100, verbose_name=u'美购名称', default='')
    ordering = models.IntegerField(default=10000, verbose_name=u"展示顺序", help_text=u"小的排在前，大的排在后")
    # add review_status (添加审核状态字段) last_review_time(最近审核时间),
    review_status = models.CharField(max_length=8, choices=SERVICE_REVIEW_STATUS, verbose_name=u'审核状态',
                                     default=SERVICE_REVIEW_STATUS.UNDER_REVIEW)

    last_review_time = models.DateTimeField(verbose_name=u'最近审核时间', null=True, blank=True)

    last_submit_time = models.DateTimeField(verbose_name=u'最近提交时间', null=True, blank=True)

    photo_details = models.TextField(verbose_name=u'图文详情', max_length=20000, null=True, blank=True)
    upload_button = models.ForeignKey(UploadButton, verbose_name=u'关联的秒杀button', null=True)
    project_type = models.ForeignKey(Tag, verbose_name=u'项目类别', null=True)
    is_register = models.BooleanField(verbose_name=u'是否是注册申请', default=True)
    short_description = models.CharField(max_length=100, null=False, verbose_name=u'一句话描述(美购描述)')
    image_header = ImgUrlField(img_type=IMG_TYPE.SERVICE, max_length=300, help_text=u"福利顶部图片",
                               verbose_name=u'图片地址',
                               default='')
    is_specialreview = models.BooleanField(verbose_name=u'是否是特批审核', default=False)
    add_num = models.IntegerField(verbose_name=u'库存增加数(废弃,通过registeritem.stock_add_num控制库存)', default=0)

    #  判断医生是否能看的到,(美购重构后遗留老数据没有对应的美购,这些状态是被驳回,草稿等之类的,医生是不能看到的)
    doctor_can_see = models.BooleanField(verbose_name=u'医生是否能看得到', default=True)
    is_draft = models.BooleanField(verbose_name=u'是否是保存的草稿', default=False)

    #  美购重构结束
    is_lock = models.BooleanField(verbose_name=u'是否处于审核中', default=False)

    # 美购改版 2017-03-29
    image_bigpic = ImgUrlField(img_type=IMG_TYPE.SERVICE, max_length=300, help_text=u"美购大图",
                               verbose_name=u'图片地址',
                               null=True)
    extra_pay_info = models.TextField(verbose_name=u'额外消费提示', null=True)
    have_extra_pay = models.BooleanField(verbose_name=u'是否有额外消费提示', default=False)

    # 美购 类型 2017-06-29添加
    service_type = models.IntegerField(verbose_name=u'美购类型', default=0, choices=SERVICE_SELL_TYPE)

    operator_review_type = models.CharField(verbose_name=u'运营审核类型', max_length=4, choices=OPERTOR_REVIEW_TYPE.choices,
                                            default=OPERTOR_REVIEW_TYPE.NO_REVIEW)
    # 关联商户
    merchant_id = models.BigIntegerField(u'商户ID')

    # 美购改版 2017-03-29
    def get_merchant(self):
        try:
            m = Merchant.objects.get(id=self.merchant_id)
            return {
                'id': m.id,
                'doctor_id': m.doctor_id,
                'name': m.name
            }
        except Exception as e:
            logging_exception()
            return None

    def get_supplement_images(self):
        items = ImageRelatedServiceRegister.objects.filter(serviceregister_id=self.id).order_by('id')
        images = [item.image_url for item in items]
        return images

    def update_supplement_images(self, images):
        for item in images:
            ImageRelatedServiceRegister.objects.get_or_create(
                serviceregister_id=self.id, image_url=item
            )
        ImageRelatedServiceRegister.objects.filter(serviceregister_id=self.id).filter(~Q(image_url__in=images)).delete()

    def update_supplement_images_order(self, images):
        objs = ImageRelatedServiceRegister.objects.filter(serviceregister_id=self.id).order_by('-id')
        for obj in objs:
            if not images:
                break
            obj.image_url = images.pop()
            obj.save()

    def get_video_info(self):
        video = getattr(self, 'video', None)
        item = {}
        if video:
            item = video.get_video_info()
        return item

    def get_recommend_services(self):
        items = ServiceRegisterRelatedRecommendService.objects.filter(
            serviceregister_id=self.id
        ).values('recommend_service__id', 'recommend_service__name')
        services = [{'id': rs['recommend_service__id'], 'name': rs['recommend_service__name']} for rs in items]
        return services

    def update_recommend_services(self, service_ids):
        for service_id in service_ids:
            ServiceRegisterRelatedRecommendService.objects.get_or_create(
                serviceregister_id=self.id,
                recommend_service_id=service_id,
            )
        old_services = ServiceRegisterRelatedRecommendService.objects.filter(serviceregister_id=self.id)
        old_services.filter(~Q(recommend_service_id__in=service_ids)).delete()

    def record_info(self, with_operator_name=True):
        ret = []
        records = self.servicereviewrecord_set.order_by('-created_time')
        for record in records:
            if record.reason:
                ret.append({
                    'name': record.operator.user.last_name or record.operator.user.username
                    if with_operator_name and record.operator else u'审核人',
                    'time': get_timestamp_epoch(record.audit_time) if with_operator_name else get_timestamp(
                        record.audit_time),
                    'text': record.reason,
                })
            if record.explanation:
                ret.append({
                    'name': record.person.user.last_name or record.person.user.username,
                    'time': get_timestamp_epoch(record.created_time) if with_operator_name else get_timestamp(
                        record.created_time),
                    'text': record.explanation,
                })
        return ret

    def _get_activity_status(self):
        """
        报名状态，0-可报名 1-活动中 2-无活动
        :return:
        """
        # 不可报名
        if not self.service:
            return 2
        if not self.service.is_online:
            return 2
        # 已报名
        now = datetime.datetime.now()
        city = Doctor.objects.get(id=self.doctor.id).hospital.city
        from api.models import SpecialSeckillButton
        ss = SpecialSeckillButton.objects.filter(
            start_show_time__lte=now, end_show_time__gte=now
        ).order_by("-start_show_time")
        items = self.items.all()
        if items:
            for item in items:
                if not item.serviceitem:
                    continue
                info = item.serviceitem.get_current_price_info()
                if info['gengmei_price'] != item.gengmei_price:
                    return 1
        # 可报名
        ssb_ids = []
        for s in ss:
            if s.cities.all():
                if city in s.cities.all():
                    if city in s.cities.all():
                        ssb_ids.append(s.id)
            else:
                ssb_ids.append(s.id)
        ss = ss.filter(id__in=ssb_ids)
        for ssb in ss:
            ssb_tags = SpecialSeckillButton.objects.get(id=ssb.id).tags.all()
            tags = []
            if ssb_tags:
                for ssb_tag in ssb_tags:
                    tags.append(ssb_tag.id)
                    for s2 in ssb_tag.child_tags():
                        tags.append(s2.id)
                        for s3 in s2.child_tags():
                            tags.append(s3.id)
                if not self.service.tags.all():
                    return 0
                s_tags = self.service.tags.all().values_list('id', flat=True)
                same_tags = set(s_tags) & set(tags)
                if list(same_tags):
                    return 0
                else:
                    continue
            else:
                return 0
        return 2

    def get_activity_status(self):
        """
        报名状态，0-可报名 1-活动中 2-无活动
        :return:
        """
        from rpc.cache import serviceregister_activity_status_cache
        activity_status = serviceregister_activity_status_cache.get(str(self.id))
        if not activity_status:
            activity_status = self._get_activity_status()
            serviceregister_activity_status_cache.setex(str(self.id), 60 * 10, activity_status)
        return activity_status


# 美购改版 2017-03-29
class ImageRelatedServiceRegister(models.Model):
    class Meta:
        app_label = 'api'
        unique_together = ('serviceregister', 'image_url')

    serviceregister = models.ForeignKey(ServiceRegister, verbose_name=u'美购注册列表', related_name="supplement_images")
    image_url = ImgUrlField(img_type=IMG_TYPE.SERVICE, max_length=255, verbose_name=u"图片URL")


class ServiceRegisterRelatedRecommendService(models.Model):
    class Meta:
        app_label = 'api'
        unique_together = ('serviceregister', 'recommend_service')

    serviceregister = models.ForeignKey(ServiceRegister, verbose_name=u'美购注册', related_name="original_serviceregister")
    recommend_service = models.ForeignKey(Service, verbose_name=u'推荐美购',
                                          related_name="serviceregister_recommend_service")


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

    service = models.ForeignKey(Service, verbose_name=u"福利", related_name="items")
    key = models.CharField(max_length=40, verbose_name=u"标识符")

    total_num = models.IntegerField(verbose_name=u"总数量", default=0)
    sku_stock = models.IntegerField(verbose_name=u'库存，不要直接修改', default=0)
    sku_stock_lte_zero = models.BooleanField(verbose_name=u'库存小于等于零，不要直接修改', default=False)

    # 积分抵用 统一使用servie里面的
    # points_deduction_percent = models.IntegerField(verbose_name=u'积分抵用百分比(*100)', default=10)

    topic_cash_back_limit = models.IntegerField(verbose_name=u'返现贴数阀值', default=0)

    sort = models.IntegerField(verbose_name=u'排序', default=99999)

    is_delete = models.BooleanField(verbose_name=u'是否删除', default=False)
    cost_price = models.IntegerField(verbose_name=u'成本', default=0)
    # 为旗舰店美购添加sku城市属性
    city = models.ForeignKey(City, verbose_name=u'所属城市', null=True, blank=True)

    parent_id = models.IntegerField(verbose_name=u"父级SKU", default=0, db_index=True)

    def get_special_valid_status(self):
        from api.models.special import SpecialItem, Special
        specialitems = SpecialItem.objects.filter(
            special__is_online=True,
            serviceitem=self,
        )
        if not specialitems:
            return False
        special_ids = [item.special_id for item in specialitems]
        special = Special.objects.filter(is_more_buy=True, id__in=special_ids).first()
        return True if special else False


    def gets_children(self):
        '''
        获取当前SKU的所有子SKU
        '''
        if self.parent_id:
            return None
        return ServiceItem.objects.filter(parent_id=self.id)

    def update_children(self):
        ServiceItem.objects.filter(parent_id=self.id).update(
            service_id=self.service_id, key=self.key, total_num=self.total_num, sku_stock=self.sku_stock,
            sku_stock_lte_zero=self.sku_stock_lte_zero, topic_cash_back_limit=self.topic_cash_back_limit,
            sort=self.sort, is_delete=self.is_delete, cost_price=self.cost_price, city_id=self.city_id,
        )

    def get_parent(self):
        '''
        获取当前SKU的父SKU
        '''
        if not self.parent_id:
            return None
        return ServiceItem.objects.filter(id=self.parent_id).first()

    @classmethod
    def get_sub_hos_info(cls, item_id):
        '''
        根据sku_id获取同城下关联的子门店-旗舰店用
        :param item_id: sku_id
        :return:
        '''
        try:
            item = cls.objects.get(id=item_id)
            city_id = item.city_id
            return item.service.get_related_sub_hospital_by_city(city_id)
        except:
            return {}

    def order_show_name(self):
        '''
        购物车显示的名称信息
        :return:
        '''
        if self.service.service_type != SERVICE_SELL_TYPE.FLAGSHIP_STORE_SERVICE:
            return self.service.name
        else:
            return '@'.join([self.city_name, self.service.doctor.name, self.sku_name])

    @classmethod
    def get_order_show_name(cls, sku_id):
        '''
        根据item_id获取订单中展示名称
        :param sku_id:
        :return:
        '''
        try:
            item = ServiceItem.objects.get(id=sku_id)
            return item.order_show_name()
        except:
            return ''

    def get_show_price(self, support_groupbuy=True):
        '''获取展示价格, 优先级: 拼团>秒杀>专场&默认价, 同属性显示价格最低'''
        show_price = groupbuy_price = None
        if support_groupbuy:
            show_price = groupbuy_price = self.get_current_groupbuy_price_info()
        if not groupbuy_price:
            # 除拼团价的最低价
            show_price = self.get_current_price_info()
        return show_price

    @cached_property
    def city_name(self):
        return self.city and self.city.name or ''

    def get_current_groupbuy_price_info(self):
        ''' 返回最低拼团价'''
        price_obj_list = Service._get_current_groupbuy_price_by_service_item_ids([self.id])
        price_obj = price_obj_list[0] if price_obj_list else None
        if price_obj:
            return self._to_price_info(price_obj)
        return {}

    @classmethod
    def _format_groupbuy_sku_price_info(cls, price_info, item_obj):
        '''
        格式化service_item 拼团价格信息
        :param price_info:
        :param item_obj:
        :return:
        '''
        if not price_info or not item_obj:
            return

        copy_name_list = ['id', 'original_price', 'service_item_id', 'pre_payment_price', 'gengmei_price']
        g_sku_info = {}
        for cn in copy_name_list:
            g_sku_info[cn] = price_info[cn]
        g_sku_info['price_id'] = price_info.pop('id', 0)
        item_id = price_info['service_item_id']
        g_sku_info['service_id'] = price_info['service_id']
        g_sku_info['name'] = item_obj.sku_name
        g_sku_info['buy_limit'] = 1  # 7710当前拼团每个用户只支持一件
        g_sku_info['groupbuy_price'] = price_info['gengmei_price']
        g_sku_info['is_groupbuy'] = True
        # 拼团不支持用券
        g_sku_info['can_use_coupon'] = False
        countdown = (price_info['selling_rule']['end_time'] - datetime.datetime.now()).total_seconds()
        g_sku_info['countdown'] = countdown
        g_sku_info['groupbuy_nums'] = price_info['selling_rule']['groupbuy_nums']
        g_sku_info['active_type']=price_info['selling_rule']['active_type']
        g_sku_info['sale_limit']=price_info['sale_limit']
        return g_sku_info

    @classmethod
    def _format_multibuy_sku_price_info(cls, price_info, item_obj):
        '''
        多买价格式化
        :param price_info: 价格信息
        :param item_obj: 多买item_obj
        :return:
        '''
        if not price_info or not item_obj:
            return

        copy_name_list = ['id', 'original_price', 'service_item_id', 'pre_payment_price', 'gengmei_price', 'parent_id',
                          'single_price']
        m_sku_info = {}
        for cn in copy_name_list:
            m_sku_info[cn] = price_info[cn]
        m_sku_info['price_id'] = price_info.pop('id', 0)
        m_sku_info['service_id'] = item_obj.service_id
        m_sku_info['name'] = item_obj.sku_name
        m_sku_info['buy_limit'] = _get_sku_buy_limit(item_obj.service.single_user_buy_limit, item_obj.sku_stock,
                                                     price_info['sale_limit'], price_info['single_user_buy_limit'])
        m_sku_info['multibuy_price'] = price_info['gengmei_price']
        m_sku_info['is_multibuy'] = True
        countdown = (price_info['selling_rule']['end_time'] - datetime.datetime.now()).total_seconds()
        m_sku_info['countdown'] = countdown
        m_sku_info['multibuy_count'] = price_info['selling_rule']['more_buy_count']
        return m_sku_info

    @classmethod
    def _format_normal_sku_price_info(cls, price_info, item_obj):
        '''
        格式化service_item normal 价格信息
        :return:
        '''
        if not price_info or not item_obj:
            return

        sku_info = {}
        copy_name_list = ['id', 'original_price', 'service_item_id', 'pre_payment_price', 'gengmei_price']
        price_type = price_info['price_type']
        for cn in copy_name_list:
            sku_info[cn] = price_info.get(cn)
        sku_info['service_id'] = item_obj.service_id
        sku_info['price_id'] = sku_info.pop('id', 0)
        sku_info['is_seckill'] = False
        sku_info['seckill_price'] = 0
        sku_info['countdown'] = 0
        sku_info['name'] = item_obj.sku_name
        sku_info['remain_seckill_count'] = 0
        sku_info['buy_limit'] = _get_sku_buy_limit(item_obj.service.single_user_buy_limit, item_obj.sku_stock,
                                                   price_info['sale_limit'], price_info['single_user_buy_limit'])
        sku_info['promotion_image'] = ''
        # 处理秒杀类型, 修改is_seckill
        if price_type == SERVICE_ITEM_PRICE_TYPE.SECKILL:
            countdown = (price_info['selling_rule']['end_time'] - datetime.datetime.now()).total_seconds()
            sku_info['seckill_price'] = sku_info['gengmei_price']
            sku_info['is_seckill'] = True
            sku_info['countdown'] = countdown
            sku_info['remain_seckill_count'] = min(item_obj.sku_stock, price_info['sale_limit'])
        elif price_type == SERVICE_ITEM_PRICE_TYPE.SPECIAL:
            # 价格竞争力二期 对于划线价格小于等于更美价的SKU，不展示划线价
            if price_info['gengmei_price'] >= price_info['original_price']:
                sku_info['original_price'] = ''
            countdown = (price_info['selling_rule']['end_time'] - datetime.datetime.now()).total_seconds()
            sku_info['countdown'] = countdown
            special_id = price_info['selling_rule']['activity_id']
            from api.models import Special
            special = Special.objects.filter(id=special_id)
            if special:
                promotion_image = special.first().promotion_image
                sku_info['promotion_image'] = promotion_image
        elif price_type == SERVICE_ITEM_PRICE_TYPE.DEFAULT:
            if price_info['gengmei_price'] >= price_info['original_price']:
                sku_info['original_price'] = ''
        return sku_info

    @property
    def discount(self):
        return self._get_value_from_current_price_info('discount')

    @property
    def pre_payment_price(self):
        return self._get_value_from_current_price_info('pre_payment_price')

    @property
    def original_price(self):
        return self._get_value_from_current_price_info('original_price')

    @property
    def gengmei_price(self):
        return self._get_value_from_current_price_info('gengmei_price')

    @property
    def cash_back_rate(self):
        return self._get_value_from_current_price_info('cash_back_rate')

    @property
    def cash_back_fee(self):
        return self._get_value_from_current_price_info('cash_back_fee')

    @property
    def is_seckill_price(self):
        return self._get_value_from_current_price_info('price_type') == SERVICE_ITEM_PRICE_TYPE.SECKILL

    @property
    def self_support_discount(self):
        return self._get_value_from_current_price_info('self_support_discount')

    def _get_value_from_current_price_info(self, key):
        info = self.get_current_price_info()
        v = info.get(key) if info else None
        if v is None:
            raise ValueError("no data for " + key)
        return v

    def get_current_price_info(self, now=None, is_child=False):
        """
        当前价格获取逻辑：
            1. 获得SKU所有符合条件的Price （Price启用，Price的sale_limit大于0，Price对应的Rule(如果有的话)启用，
                当前时间now在Rule的有效时间范围内）的Price
            2. 如果存在多个符合条件的美购属性价格，那么有效价格就是更美价最低的那个美购属性。如果有多个最低更美价，就选择预付款最低那个。
               如果有多个最低预付款，就选择价格Id最大的。如果不存在就返回None
            2. 对于一个ServiceItem，第一次获取过current_price_info的结果会被保存在这个ServiceItem内部，之后针对同一个ServiceItem
                实例都会直接从实例获取。
        :param now: 可选的datetime参数
        :return: price_info or None
        """
        key = '_current_price_info'
        if hasattr(self, key):
            info = getattr(self, key)
            return copy.deepcopy(info)

        now = now or datetime.datetime.now()

        price_list = Service._get_current_price_by_service_item_ids([self.id], now=now, is_child=is_child)
        price = price_list[0] if len(price_list) > 0 else None

        current_price_info = ServiceItem._to_price_info(price)

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

    def _get_service_item_price(self, is_default_price=False, price_id=None, selling_rule_id=None):
        result = None

        query = ServiceItemPrice.objects.select_related('selling_rule')
        q = Q(service_item=self)
        if is_default_price:
            try:
                q &= Q(is_default_price=True)
                return query.get(q)
            except ServiceItemPrice.DoesNotExist:
                return None

        if price_id:
            q &= Q(id=price_id)

        if selling_rule_id:
            q &= Q(selling_rule_id=selling_rule_id)

        try:
            result = query.get(q)
        except ServiceItemPrice.DoesNotExist:
            pass

        return result

    @staticmethod
    def _to_price_info(service_item_price):
        """
        修改price_info dict的定义时，必须保证所有不同类型数据都输出的情况下，能正确的被
        order_manager._json_dump_current_price_info dump成JSON
        """

        if not service_item_price:
            return {}

        rule = None

        price_type = SERVICE_ITEM_PRICE_TYPE.DEFAULT

        if service_item_price.selling_rule:
            rule = {
                "id": service_item_price.selling_rule.id,
                "name": service_item_price.selling_rule.name,
                "is_enable": service_item_price.selling_rule.is_enable,
                "start_time": service_item_price.selling_rule.start_time,
                "end_time": service_item_price.selling_rule.end_time,
                "activity_type": service_item_price.selling_rule.activity_type,
                "activity_id": service_item_price.selling_rule.activity_id,
                "more_buy_count": service_item_price.selling_rule.more_buy_count,
            }

            if service_item_price.selling_rule.activity_type == ACTIVITY_TYPE_ENUM.SPECIAL:
                price_type = SERVICE_ITEM_PRICE_TYPE.SPECIAL
            if service_item_price.selling_rule.activity_type == ACTIVITY_TYPE_ENUM.SECKILL:
                price_type = SERVICE_ITEM_PRICE_TYPE.SECKILL
            if service_item_price.selling_rule.activity_type == ACTIVITY_TYPE_ENUM.GROUPBUY:
                price_type = SERVICE_ITEM_PRICE_TYPE.GROUPBUY
            if service_item_price.selling_rule.activity_type == ACTIVITY_TYPE_ENUM.MOREBUY:
                price_type = SERVICE_ITEM_PRICE_TYPE.MOREBUY

            if service_item_price.selling_rule.refund_anytime is not None:
                rule["refund_anytime"] = service_item_price.selling_rule.refund_anytime

            if service_item_price.selling_rule.can_use_points is not None:
                rule["can_use_points"] = service_item_price.selling_rule.can_use_points

            if service_item_price.selling_rule.share_get_cashback is not None:
                rule["share_get_cashback"] = service_item_price.selling_rule.share_get_cashback

            if service_item_price.selling_rule.groupbuy_type is not None:
                rule["groupbuy_type"] = service_item_price.selling_rule.groupbuy_type

            if service_item_price.selling_rule.active_type is not None:
                rule["active_type"] = service_item_price.selling_rule.active_type

            if service_item_price.selling_rule.groupbuy_nums is not None:
                rule["groupbuy_nums"] = service_item_price.selling_rule.groupbuy_nums

            if service_item_price.selling_rule.groupbuy_countdown is not None:
                rule["groupbuy_countdown"] = service_item_price.selling_rule.groupbuy_countdown

        total_num = service_item_price.total_num_fake
        sold_percent = math.ceil(
            100.0 * (total_num - service_item_price.sale_limit) / total_num
        )

        price_info = {
            "id": service_item_price.id,
            "service_id": service_item_price.service_item.service_id,
            "service_item_id": service_item_price.service_item_id,
            "is_enable": service_item_price.is_enable,
            "sale_limit": service_item_price.sale_limit,
            "sold_percent": sold_percent,
            "is_default_price": service_item_price.is_default_price,
            "price_type": price_type,
            "discount": service_item_price.discount,
            "pre_payment_price": service_item_price.pre_payment_price,
            "original_price": service_item_price.original_price,
            "gengmei_price": service_item_price.gengmei_price,
            "cash_back_rate": service_item_price.cash_back_rate,
            "cash_back_fee": service_item_price.cash_back_fee,
            "self_support_discount": service_item_price.self_support_discount,
            "single_user_buy_limit": service_item_price.single_user_buy_limit,
            "single_price": service_item_price.more_buy_price,
            "parent_id": service_item_price.service_item.parent_id,
            "total_num": service_item_price.total_num,
        }

        if rule:
            price_info["selling_rule"] = rule

        return price_info

    def get_default_price_info(self):
        """
        获取默认Price_Info

        :return: price_info or None
        """
        service_item_price = self._get_service_item_price(is_default_price=True)
        return ServiceItem._to_price_info(service_item_price)

    def get_price_info(self, price_id=None, selling_rule_id=None):
        """
        根据price_id或者selling_rule_id获取对应的Price_Info，至少需要输入一个参数

        :return: price_info or None
        """
        if price_id is None and selling_rule_id is None:
            raise ValueError("all parameter is None")

        service_item_price = self._get_service_item_price(price_id=price_id, selling_rule_id=selling_rule_id)

        return ServiceItem._to_price_info(service_item_price)

    def _update_sku_stock_lte_zero(self):
        ServiceItem.objects \
            .filter(id=self.id) \
            .update(sku_stock_lte_zero=Case(When(sku_stock__lte=0, then=Value(True)),
                                            When(sku_stock__gt=0, then=Value(False))
                                            ))

    def increase_stock(self, count=1):
        """
        对于需要同时变更库存以及可销售数量的情况，应该先对可销售数量进行处理，然后对库存进行处理。
        切记确保操作全部成功，否则应该考虑回滚事务。

        :param count: 必须大于0
        :return: 成功增加库存就返回True
        :rtype: bool
        """
        if not count > 0:
            raise ValueError("increase_stock count must > 0")

        row_count = ServiceItem.objects \
            .filter(id=self.id) \
            .update(sku_stock=F('sku_stock') + count)

        if row_count > 0:
            self._update_sku_stock_lte_zero()

        return row_count > 0

    def decrease_stock(self, count=1, sku_stock_must_gte_count=False):
        """
        对于需要同时变更库存以及可销售数量的情况，应该先对可销售数量进行处理，然后对库存进行处理。
        切记确保操作全部成功，否则应该考虑回滚事务。

        :param count: 必须大于0
        :return: 成功扣取库存就返回True
        :rtype: bool
        """
        if not count > 0:
            raise ValueError("decrease_stock count must > 0")

        q = ServiceItem.objects

        q = q.filter(sku_stock__gte=count, id=self.id) if sku_stock_must_gte_count else q.filter(id=self.id)

        row_count = q.update(sku_stock=F('sku_stock') - count)

        if row_count > 0:
            self._update_sku_stock_lte_zero()

        return row_count > 0

    def _update_sale_limit_lte_zero(self, price_id):
        ServiceItemPrice.objects \
            .filter(id=price_id, service_item=self) \
            .update(sale_limit_lte_zero=Case(When(sale_limit__lte=0, then=Value(True)),
                                             When(sale_limit__gt=0, then=Value(False))
                                             ))

    def increase_whole_sale_limit(self, price_id, count=1):
        """
        :param price_id:
        :param count:
        :return:
        """
        from api.models import SpecialItem, SpecialStock, DoctorSeckillApply
        try:
            serviceitem_price_obj = ServiceItemPrice.objects.get(id=price_id)
        except Exception:
            serviceitem_price_obj = None
            logging_exception()

        if serviceitem_price_obj and serviceitem_price_obj.selling_rule:
            activity_id = serviceitem_price_obj.selling_rule.activity_id
            specialitem = SpecialItem.objects.filter(special_id=activity_id, serviceitem=self).first()
            doctorseckillapply_id = specialitem and specialitem.doctorseckillapply_id
            if doctorseckillapply_id:
                special_stock_obj = SpecialStock.objects.filter(doctorseckillapply_id=doctorseckillapply_id).first()
                if not special_stock_obj:
                    return

                try:
                    service_item_price_ids = json.loads(special_stock_obj.price_id)
                except Exception:
                    logging_exception()
                    return

                if not isinstance(service_item_price_ids, list):
                    return

                DoctorSeckillApply.objects.filter(id=doctorseckillapply_id).update(stock=F('stock') + count)
                ServiceItemPrice.objects.filter(id__in=service_item_price_ids).exclude(id=price_id).update(sale_limit=F('sale_limit') + count)
                SpecialStock.objects.filter(id=special_stock_obj.id).update(stock=F('stock') + count)

    def increase_price_sale_limit(self, price_id, count=1):
        """
        对于需要同时变更库存以及可销售数量的情况，应该先对可销售数量进行处理，然后对库存进行处理。
        切记确保操作全部成功，否则应该考虑回滚事务。

        :param count: 必须大于0
        :return: 成功增加可销售数量就返回True
        :rtype: bool
        """
        if not count > 0:
            raise ValueError("increase_price_sale_limit count must > 0")

        row_count = ServiceItemPrice.objects \
            .filter(id=price_id, service_item=self) \
            .update(sale_limit=F('sale_limit') + count)

        # 增加库存
        while True:
            try:
                with RedLock('increase_sku_lock_{price_id}'.format(price_id=price_id),
                             connection_details=lock_sku_connection_detail,
                             retry_delay=500, retry_times=10) as L:
                    self.increase_whole_sale_limit(price_id, count)
                break
            except Exception:
                logging_exception()

        if row_count > 0:
            self._update_sale_limit_lte_zero(price_id=price_id)

        return row_count > 0

    def decrease_whole_sale_limit(self, price_id, count=1):
        """
        :param price_id:
        :param count:
        :return:
        """
        from api.models import SpecialItem, SpecialStock, DoctorSeckillApply
        try:
            serviceitem_price_obj = ServiceItemPrice.objects.get(id=price_id)
        except Exception:
            serviceitem_price_obj = None
            logging_exception()

        if serviceitem_price_obj and serviceitem_price_obj.selling_rule:
            activity_id = serviceitem_price_obj.selling_rule.activity_id
            specialitem = SpecialItem.objects.filter(special_id=activity_id, serviceitem=self).first()

            # 目前只有新专题共享库存
            if specialitem and specialitem.special.is_new_special is False:
                return

            doctorseckillapply_id = specialitem and specialitem.doctorseckillapply_id
            if doctorseckillapply_id:
                special_stock_obj = SpecialStock.objects.filter(doctorseckillapply_id=doctorseckillapply_id).first()
                if not special_stock_obj:
                    return

                try:
                    service_item_price_ids = json.loads(special_stock_obj.price_id)
                except Exception:
                    logging_exception()
                    return

                if not isinstance(service_item_price_ids, list):
                    return

                DoctorSeckillApply.objects.filter(id=doctorseckillapply_id).update(stock=F('stock') - count)
                ServiceItemPrice.objects.filter(id__in=service_item_price_ids).exclude(id=price_id).update(sale_limit=F('sale_limit') - count)
                SpecialStock.objects.filter(id=special_stock_obj.id).update(stock=F('stock') - count)

    def decrease_price_sale_limit(self, price_id, count=1, sale_limit_must_gte_count=False):
        """
        对于需要同时变更库存以及可销售数量的情况，应该先对可销售数量进行处理，然后对库存进行处理。
        切记确保操作全部成功，否则应该考虑回滚事务。

        :param count: 必须大于0
        :return: 成功扣取可销售数量就返回True
        :rtype: bool
        """
        if not count > 0:
            raise ValueError("decrease_price_sale_limit count must > 0")

        q = ServiceItemPrice.objects

        q = q.filter(sale_limit__gte=count, id=price_id,
                     service_item=self) if sale_limit_must_gte_count else q.filter(id=price_id, service_item=self)

        row_count = q.update(sale_limit=F('sale_limit') - count)

        while True:
            try:
                with RedLock('decrease_sku_lock_{price_id}'.format(price_id=price_id),
                             connection_details=lock_sku_connection_detail,
                             retry_delay=500, retry_times=10) as L:
                    self.decrease_whole_sale_limit(price_id, count)
                break
            except Exception:
                logging_exception()


        if row_count > 0:
            self._update_sale_limit_lte_zero(price_id=price_id)

        return row_count > 0

    def create_price(self,
                     sale_limit,
                     is_default_price,
                     selling_rule_id,
                     discount,
                     pre_payment_price,
                     original_price,
                     gengmei_price,
                     cash_back_rate,
                     cash_back_fee,
                     self_support_discount,
                     single_user_buy_limit,
                     more_buy_price=0,
                     ):
        """
        为了避免出现潜在价格数据不一致冲突，调用这个方法的ServiceItem应该通过objects.select_for_update()获得。

        :param sale_limit: 可销售数量，必须大于等于0。如果是默认价格的话会在内部自动设置为999999
        :param is_default_price:是否默认价格。同一个SKU只允许有一个默认价格。
        :param selling_rule_id: 促销规则Id，如果是默认价格的话会被自动忽略。同一个SKU只允许有一个促销规则Id。
        :param discount:
        :param pre_payment_price:
        :param original_price:
        :param gengmei_price:
        :param cash_back_rate:
        :param cash_back_fee:
        :param self_support_discount:
        :param single_user_buy_limit: 单个用户购买数量限制，必须大于等于0，0代表没有数量限制。
        :return: 新创建的price_id
        """
        if single_user_buy_limit < 0:
            raise ValueError("single_user_buy_limit must >= 0")

        if not is_default_price:
            if sale_limit < 0:
                raise ValueError("sale_limit must >= 0")
            if not selling_rule_id:
                raise ValueError("selling_rule_id is None")

            rule_exists = SKUPriceRule.objects.filter(id=selling_rule_id).exists()
            if not rule_exists:
                raise ValueError("selling_rule not exists.")

        selling_rule_id = None if is_default_price else selling_rule_id

        prices = list(ServiceItemPrice.objects.filter(service_item=self))

        for price in prices:
            if is_default_price and price.is_default_price:
                raise ValueError("default price exists.")

            if not is_default_price and price.selling_rule_id == selling_rule_id:
                raise ValueError("selling_rule_id exists.")

        if gengmei_price:
            if not self.validate_price_in(gengmei_price, selling_rule_id=selling_rule_id):
                raise GaiaRPCFaultException(ERROR.UNIVERSAL, u'拼团价必须小于非拼团价', data=None)
                # raise ValueError('price update failed for groupbuy_strategy')

        _sale_limit = 999999 if is_default_price else sale_limit
        sale_limit_lte_zero = _sale_limit <= 0
        total_num_fake = round(_sale_limit * random.randint(110, 150) * 0.01)
        service_item_price = ServiceItemPrice(service_item=self,
                                              is_enable=True,
                                              sale_limit=_sale_limit,
                                              sale_limit_lte_zero=sale_limit_lte_zero,
                                              total_num=_sale_limit,
                                              total_num_fake=total_num_fake,
                                              is_default_price=is_default_price,
                                              selling_rule_id=selling_rule_id,
                                              discount=discount,
                                              pre_payment_price=pre_payment_price,
                                              original_price=original_price,
                                              gengmei_price=gengmei_price,
                                              cash_back_rate=cash_back_rate,
                                              cash_back_fee=cash_back_fee,
                                              self_support_discount=self_support_discount,
                                              single_user_buy_limit=0 if is_default_price else single_user_buy_limit,
                                              more_buy_price=more_buy_price,
                                              )
        service_item_price.save()

        self.create_price_log(service_item_price)

        return service_item_price

    def update_price(self,
                     price_id,
                     **kwargs
                     ):
        """
        为了避免出现潜在价格数据不一致冲突，调用这个方法的ServiceItem应该通过objects.select_for_update()获得。

        更新价格信息，支持以下字段更新'is_enable', 'selling_rule_id', 'discount', 'pre_payment_price', 'original_price',
        'gengmei_price', 'cash_back_rate', 'cash_back_fee', 'self_support_discount', 'single_user_buy_limit'。

        如果有更新selling_rule_id的话，会要求selling_rule_id不能为None并且其他Price没有使用这个selling_rule_id。
        其他字段不会进行检查，请调用方自己进行必要的格式和业务逻辑检查。

        :param price_id: 需要修改的价格Id，必须属于当前ServiceItem
        :param kwargs: 更新字段字典
        """
        if not price_id:
            raise ValueError("price_id is None")

        price = ServiceItemPrice.objects.get(id=price_id)

        if price.service_item != self:
            raise ValueError("price_id invalid")

        filed_names = {'is_enable', 'selling_rule_id', 'discount', 'pre_payment_price', 'original_price',
                       'gengmei_price',
                       'cash_back_rate', 'cash_back_fee', 'self_support_discount', 'single_user_buy_limit'}

        update_fields = []
        if 'gengmei_price' in kwargs:
            if not self.validate_price_in(kwargs['gengmei_price'], price_obj=price,
                                          selling_rule_id=kwargs.get('selling_rule_id')):
                # raise ValueError('price update failed for groupbuy_strategy')
                raise GaiaRPCFaultException(ERROR.UNIVERSAL, u'拼团价必须小于非拼团价', data=None)

        for k, v in kwargs.iteritems():
            if k in filed_names:
                if k == 'selling_rule_id':
                    if v is None:
                        raise ValueError("price_id invalid")

                    price_list = ServiceItemPrice.objects.filter(service_item=self, selling_rule_id=v)
                    for p in price_list:
                        if p.selling_rule_id and p != price and p.selling_rule_id == v:
                            raise ValueError("selling_rule_id exists.")
                    rule_count = len(SKUPriceRule.objects.filter(id=v))
                    if rule_count == 0:
                        raise ValueError("price_id invalid")

                setattr(price, k, v)
                update_fields.append(k)

        price.save(update_fields=update_fields)

        self.create_price_log(price)

        return price

    @classmethod
    def validate_sku_price_for_special(cls, special_id):
        '''
        专题上线或修改时间时, 批量验证美购下的sku价格
        '''
        try:
            price_rule_obj = SKUPriceRule.objects.get(activity_id=special_id, is_enable=True)
        except:
            return

        sku_prices = ServiceItemPrice.objects.filter(is_enable=True, selling_rule_id=price_rule_obj.id).all()
        for sp in sku_prices:
            service_item_obj = sp.service_item
            if not service_item_obj.validate_price_in(sp.gengmei_price, selling_rule_id=price_rule_obj.id):
                sp.is_enable = False
                sp.save()
                price_logger.info('disable price(%s)-(%s) for groupbuy price strategy' % (sp.id, sp.gengmei_price))

    def validate_price_in(self, target_price, price_obj=None, is_default_price=False, selling_rule_id=None,
                          updatable=True):
        '''
        在价格创建或更新时, 添加逻辑判断
        非拼团价: 存在拼团价比该价格要高, 默认下线对应拼团价, 验证通过;
        拼团价: 如果存在非拼团低于该价格, 取返回false, 验证不通过;
        params target_price: 目标价格
        params price_obj: 更新时的原serviceitemprice对象
        params is_default_price: 是否是默认价
        params selling_rule_id: 指定价格对应的规则  ## 更新时没有传, 需要从price_obj的属性中取
        updatable: 在非拼团价低于拼团价时, 是否将拼团价下线, 默认为是
        return:
            eg: True | False
        '''
        selling_rule = None
        price_type = None
        _act = 'create'
        if price_obj:
            _act = 'update_' + str(price_obj.id)
            selling_rule_id = price_obj.selling_rule_id
        if selling_rule_id:
            try:
                selling_rule = SKUPriceRule.objects.get(id=selling_rule_id)
                price_type = selling_rule.activity_type
            except:
                return False

        _price_is_groupbuy = price_type == ACTIVITY_TYPE_ENUM.GROUPBUY

        s_time = selling_rule and selling_rule.start_time
        e_time = selling_rule and selling_rule.end_time

        if not _price_is_groupbuy:
            if updatable:
                self.disable_groupbuy_price_bigger(target_price, s_time, e_time)
                return True
            else:
                pri_ids = self._exist_bigger_groupbuy_price(target_price, s_time, e_time, result_list=True)
                if pri_ids:
                    price_logger.info(
                        'existing equal or bigger groupbuyprice(%s) for target_price(%s, rule_id(%s)-(%s)-(%s)) for sku(%s) at action(%s)' % (
                        pri_ids, target_price, selling_rule_id, s_time, e_time, self.id, _act))
                return not bool(pri_ids)

        else:
            pri_ids = self._exist_lower_not_groupbuy_price(target_price, selling_rule.start_time, selling_rule.end_time,
                                                           result_list=True)
            if pri_ids:
                price_logger.info(
                    'exist equal or lower price(%s) than groupbuy target_price(%s, rule_id(%s)-(%s)-(%s)) for sku(%s) at action(%s)' % (
                    pri_ids, target_price, selling_rule_id, s_time, e_time, self.id, _act))
            return not bool(pri_ids)

    def _exist_lower_not_groupbuy_price(self, target_price, start_time, end_time, result_list=False):
        '''
        该sku下是否存在比指定价格低或相等的非拼团价
        target_price: 目标价的value
        start_time: 目标价拟开始时间
        end_time: 目标价拟结束时间
        '''
        default_query = Q(is_default_price=True, selling_rule__isnull=True)
        rule_query = Q(selling_rule__is_enable=True,
                       selling_rule__activity_type__in=_special_and_seckill_activity_types,
                       selling_rule__start_time__lt=end_time,
                       selling_rule__end_time__gt=start_time)

        common_query = Q(gengmei_price__lte=target_price, service_item=self, is_enable=True)
        s_query = ServiceItemPrice.objects.filter(common_query).filter(default_query | rule_query)

        if not result_list:
            return s_query.exists()
        else:
            return list(s_query.values_list('id', flat=True))

    def _exist_bigger_groupbuy_price(self, target_price, start_time=None, end_time=None, result_list=False):
        '''
        判断是否存在比指定价更高的拼团价
        result_list: 返回符合条件的serviceitemprice.id, 列表形式, 如非必要, 默认即可
        '''
        time_query = Q()
        if start_time and end_time:
            time_query = Q(selling_rule__end_time__gt=start_time, selling_rule__start_time__lt=end_time)
        else:
            now_time = datetime.datetime.now()
            time_query = Q(selling_rule__end_time__gt=now_time)
        common_query = Q(service_item=self,
                         is_enable=True,
                         gengmei_price__gte=target_price,
                         sale_limit_lte_zero=False,
                         selling_rule__is_enable=True,
                         selling_rule__activity_type=ACTIVITY_TYPE_ENUM.GROUPBUY)
        s_query = ServiceItemPrice.objects.filter(common_query & time_query)
        if not result_list:
            return s_query.exists()
        else:
            return list(s_query.values_list('id', flat=True))

    def disable_groupbuy_price_bigger(self, target_price, start_time=None, end_time=None):
        '''
            下线sku下比指定价格高的拼团价, 生效或者待生效的
        params target_price: 指定价格
        params start_time: 指定价格的开始时间
        params end_time: 指定价格的结束时间
        '''

        price_ids = self._exist_bigger_groupbuy_price(target_price, start_time, end_time, result_list=True)
        if price_ids:
            ServiceItemPrice.objects.filter(id__in=price_ids).update(is_enable=False)
            price_logger.info('disable groupbuy price(%s) bigger than (%s)-(%s)-(%s) for sku(%s)' % (
            price_ids, target_price, start_time, end_time, self.id))

    def get_doctor_seckill_apply(self):
        from api.models.special import SpecialItem
        now = datetime.datetime.now()
        qs = self.doctorseckillapply_set.filter(
            id__in=SpecialItem.objects.filter(
                serviceitem_id=self.id,
                special__start_time__lt=now,
                special__end_time__gt=now,
                special__is_online=True,
            ).values_list('doctorseckillapply_id', flat=True),
            status=DOCTOR_SECKILL_APPLY_STATUS.PASS,
            available_num__gt=0,
        ).order_by('seckill_price')
        return qs.first()
        # qs = self.doctorseckillapply_set.filter(
        #     specialitem__special__start_time__lt=now,
        #     specialitem__special__end_time__gt=now,
        #     specialitem__special__is_online=True,
        #     status=DOCTOR_SECKILL_APPLY_STATUS.PASS,
        #     available_num__gt=0,
        # )
        # if qs.exists():
        #     return qs.order_by('seckill_price').first()
        # return None

    @property
    def yuan(self):
        return int(self.gengmei_price)

    @staticmethod
    def get_items_name(service_item_ids):
        service_item_id_to_option_name_list = {}
        data = ServiceItemKey.objects.select_related("serviceattroption").filter(
            serviceitem_id__in=service_item_ids
        ).values_list('id', 'serviceitem_id', 'serviceattroption__name')
        for r in data:
            siid = r[1]
            name = r[2]
            if siid not in service_item_id_to_option_name_list:
                service_item_id_to_option_name_list[siid] = []
            service_item_id_to_option_name_list[siid].append(name)

        return service_item_id_to_option_name_list

    @staticmethod
    def filter_valid_item_ids(service_item_ids):
        if not service_item_ids:
            return []
        else:
            valid_item_ids = ServiceItem.objects.filter(parent_id=0, id__in=service_item_ids). \
                filter(is_delete=False). \
                filter(sku_stock_lte_zero=False). \
                values_list('id', flat=True)
            valid_item_ids.sort(key=service_item_ids.index)
            return valid_item_ids

    @classmethod
    def update_sku_name(cls, sku_id, sku_name):
        sik_obj = ServiceItemKey.objects.filter(serviceitem_id=sku_id).select_related('serviceattroption').first()
        if not sik_obj:
            return
        attr_obj = sik_obj.serviceattroption
        attr_obj.name = sku_name.strip()
        attr_obj.save()

    @cached_property
    def items_name(self):
        if self.parent_id:
            id_to_result = ServiceItem.get_items_name([self.parent_id])
            result = id_to_result.get(self.parent_id, [])
            if result:
                result.append('  (多买)')
            return result
        id_to_result = ServiceItem.get_items_name([self.id])
        result = id_to_result.get(self.id, [])
        return result

    @cached_property
    def sku_name(self):
        if self.parent_id:
            id_to_result = ServiceItem.get_items_name([self.parent_id])
            result = id_to_result.get(self.parent_id, [])
            return result and result[0] + '  (多买)' or ''
        id_to_result = ServiceItem.get_items_name([self.id])
        result = id_to_result.get(self.id, [])
        return result and result[0] or ''

    # @property
    # def seckill(self):
    #     from api.models.special import SpecialSeckillService
    #     seckill = SpecialSeckillService.fetch_item_seckill(self.service, self)
    #     return seckill

    def get_more_buy_count(self):
        from api.models.special import SpecialItem, Special
        specialitems = SpecialItem.objects.filter(
            # special__is_online=True,
            serviceitem=self,
        )
        if not specialitems:
            return 0
        special_ids = [item.special_id for item in specialitems]
        special = Special.objects.filter(is_more_buy=True, id__in=special_ids).first()
        if not special:
            return 0
        return special.more_buy_count if special.more_buy_count else 0

    def create_price_log(self, price):
        start_time = None
        end_time = None
        activity_id = None
        if price.selling_rule_id:
            start_time = price.selling_rule.start_time
            end_time = price.selling_rule.end_time
            activity_id = price.selling_rule.activity_id
        SKUPriceLog.objects.create(sku_id=self.id,
                                   sku_name=''.join(self.items_name),
                                   gengmei_price=price.gengmei_price,
                                   pre_payment_price=price.pre_payment_price,
                                   cash_back_rate=price.cash_back_rate,
                                   cash_back_fee=price.cash_back_fee,
                                   discount=price.discount,
                                   self_support_discount=price.self_support_discount,
                                   start_time=start_time or datetime.datetime.now(),
                                   end_time=end_time,
                                   activity_id=activity_id,
                                   )

    def get_buy_limit(self):
        single_user_buy_limit = self.service.single_user_buy_limit
        price_info = self.get_current_price_info()

        price_sale_limit = price_info['sale_limit'] if price_info else _orderding_limit
        price_single_user_buy_limit = price_info['single_user_buy_limit'] if price_info else _orderding_limit

        buy_limit = _get_sku_buy_limit(single_user_buy_limit,
                                       self.sku_stock,
                                       price_sale_limit,
                                       price_single_user_buy_limit,
                                       )
        return buy_limit

    def service_item_data(self):
        price_info = self.get_current_price_info()

        if price_info['price_type'] == SERVICE_ITEM_PRICE_TYPE.SECKILL:
            countdown = (price_info['selling_rule']['end_time'] - datetime.datetime.now()).total_seconds()
            seckill_info = {
                'is_seckill': True,
                'seckill_price': self.gengmei_price,
                'countdown': countdown,
                'pre_payment_price': self.pre_payment_price,
            }
        else:
            seckill_info = {
                'is_seckill': False,
                'pre_payment_price': self.pre_payment_price,
            }
        service_item_keys = ServiceItemKey.objects.filter(serviceitem=self).first()
        result = {
            "key": self.key,
            "price": int(self.gengmei_price),
            "discount": self.discount,
            "gengmei_price": self.gengmei_price,
            "original_price": self.original_price,
            "cash_back_fee": self.cash_back_fee,
            "cash_back_rate": self.cash_back_rate,
            "items_name": self.items_name,
            'self_support_discount': self.self_support_discount,
            'points_deduction': int(self.service.discount * self.service.points_deduction_percent * 0.01),
            'points_deduction_percent': self.service.points_deduction_percent,
            'is_available': self.service.is_can_be_sold(),
            'service_item_id': self.id,
        }
        try:
            result['id'] = service_item_keys.serviceattroption.id
        except AttributeError:
            result['id'] = None
            logging_exception()
        result.update(seckill_info)
        return result

    @property
    def is_lock(self):
        now = datetime.datetime.now()
        return self.locklist_set.filter(start_time__lt=now, end_time__gt=now).exists()

    @property
    def can_modify_price(self):
        now = datetime.datetime.now()
        return self.skuexaminelist_set.filter(start_time__lt=now, end_time__gt=now).exists()

    def get_special_info_for_es(self):
        """
        在sku上获取专场/秒杀的信息, ES专用
        :return:
        """
        check_pos = lambda pos: True if pos > 0 else False
        from api.models.special import SpecialItem
        specialitems = SpecialItem.objects.filter(
            # special__is_online=True,
            serviceitem=self,
        )
        special_map = {}
        for item in specialitems:
            pos = item.position
            special_id = item.special_id
            if item.special_id not in special_map:
                special_map[special_id] = {
                    'item_id': item.id,
                    'sku_id': item.serviceitem_id,
                    'position': pos,
                    'has_pos': check_pos(pos),
                    'floor_id': item.floor_id,
                }
            else:
                if pos > 0 and (special_map[special_id]['position'] <= 0 or pos < special_map[special_id]['position']):
                    special_map[special_id]['item_id'] = item.id
                    special_map[special_id]['sku_id'] = item.serviceitem_id
                    special_map[special_id]['position'] = pos
                    special_map[special_id]['floor_id'] = item.floor_id
                    special_map[special_id]['has_pos'] = check_pos(pos)
        return special_map


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

    name = models.CharField(max_length=100, null=False, verbose_name=u'福利属性价格规则')

    is_enable = models.BooleanField(null=False, verbose_name=u"是否启用", default=True)
    start_time = models.DateTimeField(null=False, blank=True, verbose_name=u"开始时间")
    end_time = models.DateTimeField(null=False, blank=True, verbose_name=u"结束时间")

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

    # 下列所有的规则，如果是NULL就没有任何作用，否则就覆盖现有的美购规则
    refund_anytime = models.NullBooleanField(null=True, verbose_name=u"是否支持随时退款", default=None)
    can_use_points = models.NullBooleanField(null=True, verbose_name=u"是否支持使用美分", default=None)
    share_get_cashback = models.NullBooleanField(null=True, verbose_name=u"是否支持分享返现", default=None)

    # 拼团规则
    groupbuy_type = models.IntegerField(null=True, verbose_name=u'拼团类型', choices=GROUPBUY_TYPE)
    active_type = models.IntegerField(null=True, verbose_name=u'活动类型', choices=ACTIVE_TYPE)
    groupbuy_nums = models.IntegerField(null=True, verbose_name=u'成团人数')
    groupbuy_countdown = models.IntegerField(null=True, verbose_name=u'拼团限时(秒)')

    # 多买属性
    more_buy_count = models.IntegerField(null=True, verbose_name=u'多买单品个数')


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

    service_item = models.ForeignKey(ServiceItem, verbose_name=u"福利属性价格")

    is_enable = models.BooleanField(null=False, help_text=u"是否启用", verbose_name=u"是否启用", default=True)

    sale_limit = models.IntegerField(verbose_name=u'可销售数量，不要直接修改', default=999999)
    sale_limit_lte_zero = models.BooleanField(verbose_name=u'可销售数量小于等于零，不要直接修改', default=False)
    total_num = models.IntegerField(verbose_name=u'总数量', default=999999)
    total_num_fake = models.IntegerField(verbose_name=u'总数量', default=999999)

    # 只有新建ServiceItem的时候才会插入一条is_default_price为True的记录
    is_default_price = models.BooleanField(verbose_name=u'是否默认价格', default=False)
    # 只有 is_default_price == False 才需要考虑检查这些规则
    selling_rule = models.ForeignKey(SKUPriceRule, verbose_name=u"福利属性价格规则", null=True)

    # from Item
    discount = models.IntegerField(verbose_name=u'抽成', default=0)
    pre_payment_price = models.IntegerField(verbose_name=u'预付款', default=999999)
    original_price = models.IntegerField(verbose_name=u'市场价', default=999999)
    gengmei_price = models.IntegerField(verbose_name=u'更美价', default=999999)
    cash_back_rate = models.IntegerField(verbose_name=u'返现百分比', default=10)
    cash_back_fee = models.IntegerField(verbose_name=u'返现金额', default=0)
    self_support_discount = models.IntegerField(verbose_name=u'自营抽成', default=0)
    # from Item

    single_user_buy_limit = models.IntegerField(default=0, verbose_name=u'单个用户购买数量限制')

    # 积分抵用 统一使用servie里面的
    # points_deduction_percent = models.IntegerField(verbose_name=u'积分抵用百分比(*100)', default=10)

    # opic_cash_back_limit = models.IntegerField(verbose_name=u'返现贴数阀值', default=0)
    more_buy_price = models.IntegerField(null=True, verbose_name=u'多买单品价')


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

    service_register = models.ForeignKey(ServiceRegister, verbose_name=u"福利申请", related_name="items")
    key = models.CharField(max_length=40, verbose_name=u"标识符")
    discount = models.IntegerField(verbose_name=u'抽成', default=0)
    pre_payment_price = models.IntegerField(verbose_name=u'预付款', default=999999)
    pre_payment_price_int = models.IntegerField(verbose_name=u'预付款', default=999999)
    original_price = models.IntegerField(verbose_name=u'市场价', default=999999)
    gengmei_price = models.IntegerField(verbose_name=u'更美价', default=999999)
    cash_back_rate = models.IntegerField(verbose_name=u'返现百分比', default=10)
    cash_back_fee = models.IntegerField(verbose_name=u'返现金额', default=0)
    seckill_price = models.IntegerField(verbose_name=u'最低价', default=999999)
    is_online = models.BooleanField(verbose_name=u"是否上线", default=False)
    # points_deduction_percent = models.IntegerField(verbose_name=u'积分抵用百分比(*100)', default=10)
    sort = models.IntegerField(verbose_name=u'排序', default=99999)
    is_delete = models.BooleanField(verbose_name=u'是否删除', default=False)
    serviceitem = models.ForeignKey(ServiceItem, null=True)
    stock_add_num = models.IntegerField(verbose_name=u'sku库存增加值', default=0)
    # 为旗舰店美购添加sku城市属性
    city = models.ForeignKey(City, verbose_name=u'所属城市', null=True, blank=True)


class ServiceImage(models.Model):
    """
    3.9.4之后将逐渐废弃
    """

    class Meta:
        verbose_name = u'服务图片'
        verbose_name_plural = u'服务图片'
        db_table = 'api_serviceimage'
        app_label = 'api'

    service = models.ForeignKey(Service, related_name='images')
    image_url = ImgUrlField(img_type=IMG_TYPE.SERVICE, max_length=300, help_text=u"服务图片", verbose_name=u'图片地址')

    def __unicode__(self):
        return self.service.name + self.image_url


class ServiceReserve(models.Model):
    class Meta:
        verbose_name = u'用户预约服务'
        verbose_name_plural = u'用户预约服务'
        unique_together = ('service', 'user',)
        db_table = 'api_servicereserve'
        app_label = 'api'

    service = models.ForeignKey(Service)
    user = models.ForeignKey(User)
    stat = models.CharField(max_length=1, default='1', null=False, blank=False, verbose_name=u'状态')
    created_time = models.DateTimeField(auto_now_add=True, verbose_name=u'添加时间')


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

    service = models.ForeignKey(Service)
    tag = models.ForeignKey(Tag)

    def __unicode__(self):
        return "%s:%d" % (self.tag.name, self.service.id)


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

    service = models.ForeignKey(Service, verbose_name=u"福利", related_name="attrs")
    name = models.CharField(max_length=40, verbose_name=u"名称")


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

    service_attr = models.ForeignKey(ServiceAttr, verbose_name="福利属性", related_name="options")
    name = models.CharField(max_length=40, verbose_name=u"名称")


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

    service_register = models.ForeignKey(ServiceRegister, verbose_name=u"福利注册申请", related_name="attrs", null=True,
                                         blank=True)
    name = models.CharField(max_length=40, verbose_name=u"名称")
    checked = models.BooleanField(verbose_name=u"是否选中", default=False)


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

    service_register_attr = models.ForeignKey(ServiceRegisterAttr, verbose_name="福利属性", related_name="options")
    name = models.CharField(max_length=40, verbose_name=u"名称")
    checked = models.BooleanField(verbose_name=u"是否选中", default=False)


def set_default_operator(fn):
    def _wrap(self, operator_id=None, reason=u'系统处理'):
        if operator_id is None:
            operator_id = User.objects.get(id=settings.BOSS).person.id
        return fn(self=self, operator_id=operator_id, reason=reason)

    return _wrap


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

    service = models.OneToOneField(Service, related_name='monitor')
    handle_at = models.DateTimeField(
        blank=True,
        verbose_name=u'最后一次标记正常时间',
        default=datetime.datetime.now() - datetime.timedelta(days=180)
    )
    status = models.IntegerField(verbose_name=u'状态', choices=SERVICE_MONITOR_STATUS,
                                 default=SERVICE_MONITOR_STATUS.NORMAL)
    refund_num = models.IntegerField(verbose_name=u'返现成功订单数', default=0)
    settled_num = models.IntegerField(verbose_name=u'结算订单数', default=0)

    @set_default_operator
    def set_alert(self, operator_id, reason=u'系统处理'):
        if self.status == SERVICE_MONITOR_STATUS.NORMAL:
            ServiceMonitorOperation.objects.create(monitor=self, operator_id=operator_id,
                                                   optype=SERVICE_OPERATION_TYPE.ALERT, reason=reason)
            self.status = SERVICE_MONITOR_STATUS.ALERT
            self.save()

    @set_default_operator
    def cancel_alert(self, operator_id, reason=u'系统处理'):
        if self.status == SERVICE_MONITOR_STATUS.ALERT:
            ServiceMonitorOperation.objects.create(monitor=self, operator_id=operator_id,
                                                   optype=SERVICE_OPERATION_TYPE.CANCEL_ALERT, reason=reason)
            self.status = SERVICE_MONITOR_STATUS.NORMAL
            self.save()

    @set_default_operator
    def set_black(self, operator_id, reason=u'系统处理'):
        ServiceMonitorOperation.objects.create(monitor=self, operator_id=operator_id,
                                               optype=SERVICE_OPERATION_TYPE.SET_BLACK, reason=reason)
        self.status = SERVICE_MONITOR_STATUS.BLACK
        self.save()

    @set_default_operator
    def cancel_black(self, operator_id, reason=''):
        if self.status == SERVICE_MONITOR_STATUS.BLACK:
            ServiceMonitorOperation.objects.create(monitor=self, operator_id=operator_id,
                                                   optype=SERVICE_OPERATION_TYPE.CANCEL, reason=reason)
            self.status = SERVICE_MONITOR_STATUS.NORMAL
            self.handle_at = datetime.datetime.now()
            self.save()


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

    monitor = models.ForeignKey(ServiceMonitor, related_name='operations')
    handle_at = models.DateTimeField(blank=True, verbose_name=u'处理时间', auto_now_add=True)
    operator = models.ForeignKey(Person)
    optype = models.IntegerField(verbose_name=u'操作类型', choices=SERVICE_OPERATION_TYPE,
                                 default=SERVICE_OPERATION_TYPE.ALERT)
    reason = models.TextField()


class ServiceFilter(models.Model):
    class Meta:
        verbose_name = u'美购筛选管理'
        verbose_name_plural = u'美购筛选管理'
        db_table = 'api_servicefilter'
        app_label = 'api'

    name = models.CharField(verbose_name=u'筛选项名称', max_length=128)
    value = models.CharField(verbose_name=u'筛选值', max_length=128)
    type = models.IntegerField(verbose_name=u'筛选类型', choices=SERVICE_FILTER_TYPE, default=SERVICE_FILTER_TYPE.SPECIAL)
    ordering = models.IntegerField(verbose_name=u'展示顺序', help_text=u'小的排在前，大的排在后')
    is_online = models.BooleanField(verbose_name=u'是否上线', default=False)


class PackageServiceRemark(models.Model):
    class Meta:
        verbose_name = u'美购包装备注记录'
        app_label = 'api'

    person = models.ForeignKey(Person, verbose_name=u'操作人')
    remark = models.CharField(max_length=500, verbose_name=u'备注记录')
    service = models.ForeignKey(Service, verbose_name=u'关联美购')
    created_time = models.DateTimeField(null=True, blank=True, verbose_name=u'创建时间', default=datetime.datetime.now)


class ServiceReviewRecord(models.Model):
    class Meta:
        verbose_name = u'美购申请审核记录表'
        app_label = 'api'

    person = models.ForeignKey(Person, verbose_name=u'申请人')
    update_data = models.TextField(max_length=2000, verbose_name=u'修改数据')
    serviceregister = models.ForeignKey(ServiceRegister, verbose_name=u'修改的美购申请')
    created_time = models.DateTimeField(null=True, blank=True, verbose_name=u'创建时间', default=datetime.datetime.now)
    now_review_status = models.CharField(max_length=8, choices=SERVICE_REVIEW_STATUS, verbose_name=u'当前审核状态',
                                         default=SERVICE_REVIEW_STATUS.UNDER_REVIEW)
    review_type = models.CharField(max_length=8, choices=SERVICE_REVIEW_TYPE, verbose_name=u'审核类型',
                                   default=SERVICE_REVIEW_TYPE.ORDINARY)
    reason = models.CharField(max_length=1000, verbose_name=u'驳回理由', null=True, blank=True)
    explanation = models.CharField(max_length=1000, verbose_name=u'审核说明', null=True, blank=True)
    audit_time = models.DateTimeField(null=True, blank=True, verbose_name=u'审核时间')
    operator = models.ForeignKey(Person, null=True, blank=True, verbose_name=u'审核人',
                                 related_name='service_record_operator')


class TagAttr(models.Model):
    class Meta:
        verbose_name = u'项目多属性模板'
        app_label = 'api'

    tag = models.ForeignKey(Tag, verbose_name=u'项目', null=True, blank=True)
    name = models.CharField(verbose_name=u'名字', max_length=200, null=False, default='')
    is_online = models.BooleanField(verbose_name=u'是否上线', default=False)
    created_time = models.DateTimeField(null=True, blank=True, verbose_name=u'创建时间', default=datetime.datetime.now)


class AttrOptions(models.Model):
    class Meta:
        verbose_name = u'项目多属性模板具体属性项'
        app_label = 'api'

    name = models.CharField(verbose_name=u'名字', max_length=200, null=False, default='')
    is_online = models.BooleanField(verbose_name=u'是否删除', default=False)
    created_time = models.DateTimeField(verbose_name=u'创建时间', auto_now_add=True)
    is_doctor_create = models.BooleanField(verbose_name=u'是否是转过来的', default=False)
    tag_attr = models.ForeignKey(TagAttr, verbose_name=u'属性名', null=True)

class AuditAttrOptions(models.Model):
    class Meta:
        verbose_name = u'审核项目多属性模板具体属性项名字'
        app_label = 'api'

    name = models.CharField(verbose_name=u'名字', max_length=200, null=False, default='')
    serviceregisteritem = models.ForeignKey(ServiceRegisterItem, verbose_name=u'美购申请多属性')
    is_delete = models.BooleanField(verbose_name=u'是否删除', default=False)
    created_time = models.DateTimeField(verbose_name=u'创建时间', auto_now_add=True)
    update_time = models.DateTimeField(verbose_name=u'更新时间', auto_now=True)

# # 这个表可以遗弃掉
# class AttrReLatedOptions(models.Model):
#     class Meta:
#         verbose_name = u'多属性具体项中间表'
#         app_label = 'api'
#
#     options = models.ForeignKey(AttrOptions, verbose_name=u'属性')
#     attr = models.ForeignKey(TagAttr, verbose_name=u'属性具体项')


class ServiceItemKey(models.Model):
    class Meta:
        verbose_name = u'美购多属性Key表'
        app_label = 'api'

    serviceitem = models.ForeignKey(ServiceItem, verbose_name=u'美购多属性')
    serviceattroption = models.ForeignKey(AttrOptions, verbose_name=u'美购多属性的key')


class ServiceRegisterItemKey(models.Model):
    class Meta:
        app_label = 'api'
        verbose_name = u'美购申请多属性Key表'

    serviceregisteritem = models.ForeignKey(ServiceRegisterItem, verbose_name=u'美购申请多属性')
    serviceregisterattroption = models.ForeignKey(AttrOptions, verbose_name=u'美购注册申请多属性的key')


# 创建美购申请,更新美购申请记录表
def add_reviewrecord(serviceregister, person_id, review_type, explanation=None):
    update_list = []
    # 更改register 数值
    register_item_names = {
        'short_description': u'美购描述',
        'start_time': u'开始时间',
        'end_time': u'结束时间',
        'reservation': u'预约天数',
        'image_header': u'美购图片改动',
        'photo_details': u'图文介绍改动',
        # 'total_num': u'总数量',
    }
    if not serviceregister.service:
        update_list.append(u'新增美购申请')
    for key, value in register_item_names.items():
        if serviceregister.service:
            if key == 'photo_details':
                old_data = None
            else:
                old_data = getattr(serviceregister.service, key)
        else:
            old_data = None
        # if key == 'total_num':  # 添加库存(add_num)
        #     new_data = serviceregister.total_num + serviceregister.add_num
        # else:
        #     new_data = getattr(serviceregister, key)
        new_data = getattr(serviceregister, key)
        if isinstance(old_data, datetime.datetime):
            old_data = datetime.datetime.strftime(old_data, '%Y-%m-%d %H:%M:%S')
        if isinstance(new_data, datetime.datetime):
            new_data = datetime.datetime.strftime(new_data, '%Y-%m-%d %H:%M:%S')

        # if key == 'total_num' and serviceregister.total_num != 0:
        #     update_list.append(u'总数量增加了{}'.format(serviceregister.add_num))
        if old_data != new_data:
            if key in ['image_header', 'photo_details']:
                change = value
            else:
                change = u'{}从{}被修改为{},'.format(value, old_data, new_data)
            update_list.append(change)
    update_list.append(u'审核状态变为了{}'.format(SERVICE_REVIEW_STATUS.getDesc(
        serviceregister.review_status)))
    # 处理更美价
    serviceregister_items = ServiceRegisterItem.objects.filter(service_register_id=serviceregister.id, is_delete=False)
    serviceregiser_items_list = []
    for serviceregister_item in serviceregister_items:
        update_list.append(u'{}的sku库存增加了: {} '.format(serviceregister_item.id, serviceregister_item.stock_add_num))
        serviceregiser_items_list.append(json.dumps(to_dict(serviceregister_item)))
    update_list.append(u'美购新价格为:{}'.format(serviceregiser_items_list))
    if serviceregister.service:
        service_items_list = []
        service_items = ServiceItem.objects.filter(parent_id=0, service_id=serviceregister.service.id, is_delete=False)

        for service_item in service_items:
            service_items_list.append(json.dumps(to_dict(service_item)))
        update_list.append(u'美购的老价格为:{}'.format(service_items_list))

    data = dict(
        person_id=person_id,
        update_data='\n'.join(update_list),
        serviceregister_id=serviceregister.id,
        created_time=datetime.datetime.now(),
        now_review_status=serviceregister.review_status,
        review_type=review_type,
        explanation=explanation,
    )
    if serviceregister.review_status in [
        SERVICE_REVIEW_STATUS.PASS,
        SERVICE_REVIEW_STATUS.SPECIAL_APPROVAL_TURN_DOWN,
        SERVICE_REVIEW_STATUS.TURN_DOWN
    ]:
        data['audit_time'] = datetime.datetime.now()

    ServiceReviewRecord.objects.create(**data)


class LockList(models.Model):
    class Meta:
        verbose_name = u'锁定列表'
        app_label = 'api'

    title = models.CharField(verbose_name=u'列表名称', max_length=128)
    start_time = models.DateTimeField(verbose_name=u'锁定开始时间')
    end_time = models.DateTimeField(verbose_name=u'锁定结束时间')
    created_time = models.DateTimeField(verbose_name=u'创建时间', auto_now_add=True)
    services = models.ManyToManyField(Service, verbose_name=u'锁定美购', through='ServiceLock')
    # 2017.6.12 锁定美购改为锁定sku
    skus = models.ManyToManyField(ServiceItem, verbose_name=u'锁定sku', through='SKULock')


class ServiceLock(models.Model):
    class Meta:
        verbose_name = u'锁定美购'
        app_label = 'api'
        unique_together = (('locklist', 'service'),)

    locklist = models.ForeignKey(LockList, verbose_name=u'关联列表')
    service = models.ForeignKey(Service, verbose_name=u'关联美购')
    created_time = models.DateTimeField(verbose_name=u'创建时间', auto_now_add=True)


class SKULock(models.Model):
    class Meta:
        verbose_name = u'锁定SKU'
        app_label = 'api'
        unique_together = (('locklist', 'serviceitem'),)

    locklist = models.ForeignKey(LockList, verbose_name=u'关联列表')
    serviceitem = models.ForeignKey(ServiceItem, verbose_name=u'关联SKU')
    created_time = models.DateTimeField(verbose_name=u'创建时间', auto_now_add=True)


class SKUPriceLog(models.Model):
    class Meta:
        verbose_name = u'sku历史价格'
        app_label = 'api'

    sku = models.ForeignKey(ServiceItem, verbose_name=u'关联SKU')
    sku_name = models.CharField(verbose_name=u'sku名称', max_length=50)
    gengmei_price = models.IntegerField(verbose_name=u'更美价')
    pre_payment_price = models.IntegerField(verbose_name=u'预付款')
    cash_back_rate = models.IntegerField(verbose_name=u'返现比例')
    cash_back_fee = models.IntegerField(verbose_name=u'返现金额')
    discount = models.IntegerField(verbose_name=u'抽成金额')
    self_support_discount = models.IntegerField(verbose_name=u'自营抽成', default=0)
    start_time = models.DateTimeField(verbose_name=u'开始时间')
    end_time = models.DateTimeField(verbose_name=u'结束时间', null=True, default=None)
    activity_id = models.CharField(max_length=50, verbose_name=u'活动Id', null=True, default=None)
    create_time = models.DateTimeField(verbose_name=u'创建时间', auto_now_add=True)


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

    service = models.OneToOneField(Service, default=None, null=True, related_name="video")
    video_url = models.CharField(max_length=128, verbose_name=u'视屏地址')
    video_pic = models.CharField(max_length=128, verbose_name=u'视屏封面')
    persistentId = models.CharField(max_length=128, verbose_name=u'七牛视频处理id')
    persistent_status = models.IntegerField(default=VIDEO_CODE_STATUS.NOSTART, verbose_name=u"七牛状态", db_index=True)
    water_url = models.CharField(max_length=128, verbose_name=u'水印视屏地址')

    def get_video_info(self):
        if self.video_pic:
            video_pic = get_full_path(self.video_pic, '-w')
        else:
            video_pic = settings.VIDEO_HOST + self.video_url + settings.VIDEO_PIC_URL

        if self.water_url:
            video_url = self.water_url
        else:
            video_url = self.video_url

        return {
            'video_url': settings.VIDEO_HOST + video_url,
            'video_pic': video_pic
        }

    @classmethod
    def cleaned_video_url(cls, video_url):
        if not video_url:
            return ''

        return video_url.replace(settings.VIDEO_HOST, '')


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

    serviceregister = models.OneToOneField(ServiceRegister, default=None, null=True, related_name="video")
    video_url = models.CharField(max_length=128, verbose_name=u'视屏地址')
    video_pic = models.CharField(max_length=128, verbose_name=u'视屏封面')
    persistentId = models.CharField(max_length=128, verbose_name=u'七牛视频处理id')
    persistent_status = models.IntegerField(default=VIDEO_CODE_STATUS.NOSTART, verbose_name=u"七牛状态", db_index=True)
    water_url = models.CharField(max_length=128, verbose_name=u'水印视屏地址')

    def get_video_info(self):
        if self.water_url:
            video_url = self.water_url
        else:
            video_url = self.video_url

        return {
            'video_url': settings.VIDEO_HOST + video_url if video_url != '' else '',
            'video_pic': get_full_path(self.video_pic, '-w') if self.video_pic != '' else ''
        }

    def get_url(self):
        if self.water_url:
            video_url = self.water_url
        else:
            video_url = self.video_url
        return video_url

    @classmethod
    def cleaned_video_url(cls, video_url):
        return video_url.replace(settings.VIDEO_HOST, '')


class SkuExamineList(models.Model):
    class Meta:
        verbose_name = u'sku改价审核列表'
        app_label = 'api'

    title = models.CharField(verbose_name=u'列表名称', max_length=128)
    start_time = models.DateTimeField(verbose_name=u'开始时间')
    end_time = models.DateTimeField(verbose_name=u'结束时间')
    created_time = models.DateTimeField(verbose_name=u'创建时间', auto_now_add=True)
    skus = models.ManyToManyField(ServiceItem, verbose_name=u'待审核sku', through='SKUExamine')


class SKUExamine(models.Model):
    class Meta:
        verbose_name = u'待审核SKU'
        app_label = 'api'
        unique_together = (('skuexaminelist', 'serviceitem'),)

    skuexaminelist = models.ForeignKey(SkuExamineList, verbose_name=u'关联列表')
    serviceitem = models.ForeignKey(ServiceItem, verbose_name=u'关联SKU')
    created_time = models.DateTimeField(verbose_name=u'创建时间', auto_now_add=True)


class Soyoung_ServiceItem(models.Model):
    class Meta:
        verbose_name = u'新氧sku信息表'
        app_label = 'api'

    soyoung_serviceitem_id = models.IntegerField(verbose_name=u'新氧skuID', unique=True)
    sku_name = models.CharField(verbose_name=u'新氧sku名称', max_length=128)
    doctor_name = models.CharField(verbose_name=u'医生名称', max_length=128)
    hospital_name = models.CharField(verbose_name=u'医院名称', max_length=128)
    sku_price = models.IntegerField(verbose_name=u'sku价格')
    reservation_nums = models.CharField(verbose_name=u'预约数', max_length=128)
    created_time = models.DateTimeField(verbose_name=u'创建时间', auto_now_add=True)
    update_time = models.DateTimeField(verbose_name=u'更新时间', auto_now=True)
    is_online = models.BooleanField(verbose_name=u'是否上下线', default=True)
    serviceitem_id = models.BigIntegerField(verbose_name=u'更美美购ID')


class Third_Platform_ServiceItem(models.Model):
    class Meta:
        verbose_name = u'第三方平台sku信息表'
        app_label = 'api'

    third_platform_serviceitem_id = models.IntegerField(verbose_name=u'第三方skuID', unique=True)
    sku_name = models.CharField(verbose_name=u'第三方sku名称', max_length=128)
    doctor_name = models.CharField(verbose_name=u'医生名称', max_length=128)
    hospital_name = models.CharField(verbose_name=u'医院名称', max_length=128)
    sku_price = models.IntegerField(verbose_name=u'sku价格')
    reservation_nums = models.CharField(verbose_name=u'预约数', max_length=128)
    created_time = models.DateTimeField(verbose_name=u'创建时间', auto_now_add=True)
    update_time = models.DateTimeField(verbose_name=u'更新时间', auto_now=True)
    is_online = models.BooleanField(verbose_name=u'是否上下线', default=True)
    serviceitem_id = models.BigIntegerField(verbose_name=u'更美美购ID')
    platform_type = models.IntegerField(choices=SERVICE_PLATFORM_TYPE, verbose_name=u'美购平台')
