Beispiel #1
0
class Cart(db.Document):
    meta = {
        'db_alias': 'cart_db',
        'indexes': ['user_id']
    }
    entries = db.ListField(db.ReferenceField('CartEntry'))
    logistic_free = db.FloatField()
    total_price = db.FloatField()
    user_id = db.StringField()

    def __repr__(self):
        return '<Cart: {}>'.format(self.id)

    @classmethod
    def get_cart_or_create(cls, user_id):
        try:
            cart = cls.objects.get(user_id=user_id)

        except:
            cart = cls(user_id=user_id).save()

        return cart

    def total_price(self):
        total_price = 0
        for i in self.entries:
            total_price += i.price
        self.total_price = total_price
        return total_price
Beispiel #2
0
class Coupon(db.Document):
    meta = {
        'strict': False,
        'db_alias': 'order_db',
        'indexes': ['code', 'apply', 'expire_date', 'description']
    }

    scope = db.StringField(required=True, choices=COUPON_SCOPE,
                           default=COUPON_SCOPE.ORDER)
    coupon_type = db.StringField(default=COUPON_TYPES.NORMAL, required=True,
                                 choices=COUPON_TYPES)
    value = db.FloatField()
    description = db.StringField()
    effective_date = db.DateTimeField(default=datetime.utcnow)
    expire_date = db.DateTimeField(default=datetime(2019, 12, 31))
    code = db.StringField(unique=True)

    # by which means this coupon can be applied: by coupon code, by display_id
    apply = db.StringField(required=True, default=COUPON_APPLY.BY_DISPLAY_ID,
                           choices=COUPON_APPLY)

    required_amount = db.FloatField(default=0)
    required_final = db.FloatField(default=0)
    require_new_order = db.BooleanField(default=False)
    once_per_user = db.BooleanField(default=False)

    # note for internal usage
    note = db.StringField()
    coupon_category = db.StringField(choices=['PROMOTION', 'STAFF',
                                              'NEW_USER'], default='PROMOTION')

    @property
    def is_expired(self):
        if not self.expire_date:
            return False
        return (datetime.utcnow() >= self.expire_date)

    def to_json(self):
        return dict(
            coupon_type=self.coupon_type,
            value=self.value,
            code=self.code,
            effective_date=format_date(self.effective_date, '%Y-%m-%d'),
            expire_date=format_date(self.expire_date, '%Y-%m-%d'),
            is_expired=self.is_expired,
            description=self.description)

    def is_effective(self):
        return self.effective_date <= datetime.utcnow() < self.expire_date

    def can_apply(self, order):
        res = bool(
            (self.required_final <= order.final) and
            (self.required_amount <= order.amount) and
            not (self.require_new_order and order.customer.orders) and
            not (self.once_per_user and order.customer.used_coupon(self.code))
        )
        return res
Beispiel #3
0
class BaseEntry(db.Document):
    meta = {'allow_inheritance': True}

    spec = db.ReferenceField('ItemSpec')
    item = db.ReferenceField('Item')

    amount_usd = db.FloatField(default=0)
    amount = db.FloatField(default=0)  #CNY

    quantity = db.IntField(default=1, required=True)
    unit_price = db.FloatField(default=0)

    # after discount
    discount = db.ListField(db.DictField())

    modified = db.DateTimeField()
    created_at = db.DateTimeField(default=datetime.utcnow)

    @property
    def is_available(self):
        return (self.spec.availability and self.item.availability)

    def __unicode__(self):
        return '%s' % self.id

    def __repr__(self):
        # __repr__ method can be used by flask-cache
        return '{}({}:{})'.format(self.__class__.__name__,
                                  self.item_spec_snapshot.sku, self.quantity)

    def update_amount(self):
        from application.models import ForexRate
        self.unit_price = self.item_spec_snapshot.price
        unit_price_cny = self.unit_price * ForexRate.get()
        self.amount_usd = self.unit_price * self.quantity
        self.amount = unit_price_cny * self.quantity
        self.save()

    def to_json(self, snapshot=False):
        item = self.item_snapshot
        spec = self.item_spec_snapshot
        item_json = item.to_simple_json()
        return dict(id=str(self.id),
                    item=item_json,
                    spec=spec.to_json(),
                    unit_price=self.unit_price,
                    amount=self.amount,
                    quantity=self.quantity,
                    weight=item.weight)

    def clean(self):
        if self.spec and not self.item:
            self.item = self.spec.item
Beispiel #4
0
class LogisticProvider(db.Document, WeightPrice):
    meta = {
        'db_alias': 'order_db'
    }
    name = db.StringField()
    display_name = db.StringField()
    description = db.StringField()
    service_intro = db.DictField()
    logo = db.StringField()
    country = db.StringField()
    is_active = db.BooleanField(default=False)

    rule_desc = db.StringField()
    init_price = db.FloatField(required=True)
    init_weight = db.IntField(required=True)
    continued_price = db.FloatField(required=True)
    continued_weight = db.IntField(required=True)
    init_coin = db.IntField(default=0)

    features = db.ListField(db.StringField())
    promotion = db.StringField(default='')

    limited_weight = db.IntField(required=True)
    limited_category = db.ListField(db.StringField())
    is_recommended = db.BooleanField(default=False)

    rating = db.DecimalField(precision=1)
    rating_users = db.IntField()

    def __repr__(self):
        return '<LogisticProvider {}>'.format(self.name)

    # TODO: memoize here
    @classmethod
    def get_provider_shipping(cls, logistic_name, country, weight):
        if not logistic_name:
            logistic_name = 'default'
        provider = cls.objects(name=logistic_name, country=country).first()
        return provider.get_shipping(weight)

    @queryset_manager
    def active(doc_cls, queryset):
        return queryset.filter(is_active=True)

    def to_json(self):
        return dict(
            name = self.name,
            display_name = self.display_name,
            service_intro = self.service_intro,
            desc = self.description)
Beispiel #5
0
class ChannelProvider(db.Document):
    meta = {'db_alias': 'order_db'}
    name = db.StringField()
    display_name = db.StringField()
    description = db.StringField()
    service_intro = db.DictField()
    country = db.StringField()
    is_active = db.BooleanField(default=False)

    shipping = db.FloatField(required=True)
    is_recommended = db.BooleanField(default=False)

    def __repr__(self):
        return '<ChannelProvider {}>'.format(self.name)

    @classmethod
    def get_shipping(cls, channel_name, country):
        if not channel_name:
            channel_name = 'default'
        provider = cls.objects(name=channel_name, country=country).first()
        return provider.shipping

    @queryset_manager
    def active(doc_cls, queryset):
        return queryset.filter(is_active=True)

    def to_json(self):
        return dict(name=self.name,
                    display_name=self.display_name,
                    service_intro=self.service_intro,
                    desc=self.description)
Beispiel #6
0
class EntrySpec(db.Document):
    meta = {
        'db_alias': 'cart_db',
        'indexes': ['sku', 'item_id', ]
    }

    sku = db.IntField(required=True, unique=True)

    item_id = db.IntField()
    title = db.StringField()

    primary_image = db.StringField()
    item_available = db.BooleanField()

    price = db.FloatField()
    available = db.BooleanField()
    attributes = db.DictField()
    images = db.ListField(db.StringField())

    attribute_list = db.ListField(db.StringField())
    attribute_desc = db.DictField()

    brand = db.DictField()

    last_update_date = db.DateTimeField()

    carts = db.ListField(db.ReferenceField('Cart'))
    last_empty_date = db.DateTimeField()
Beispiel #7
0
class ItemSpec(db.Document):
    meta = {
        'db_alias':
        'inventory_db',
        'indexes': [
            'item_id', 'web_sku', 'sku', 'price', 'original_price',
            'availability', 'attributes', 'created_at', 'stock'
        ],
        'ordering': ['price']
    }

    item_id = db.IntField(required=True)
    sku = db.SequenceField(required=True, unique=True, primary_key=True)
    web_sku = db.StringField(required=True)

    images = db.ListField(db.StringField(required=True))

    original_price = db.FloatField(required=True, min_value=0)
    price = db.FloatField(required=True, min_value=0)
    china_price = db.FloatField(min_value=0, default=0)

    availability = db.BooleanField(default=True, required=True)
    stock = db.IntField(default=-1)

    # spec.attributes: {color: 'Blue', size: 'M'}
    attributes = db.DictField()
    shipping_info = db.DictField()

    created_at = db.DateTimeField(default=datetime.utcnow, required=True)
    modified = db.DateTimeField()

    url = db.StringField()

    extra = db.DictField(default={})

    @property
    def item(self):
        return Item.objects(item_id=self.item_id).first()

    def __unicode__(self):
        return '%s' % self.sku

    def update_spec(self, new_spec):
        for k, v in new_spec.items():
            setattr(self, k, v)
        self.save()
Beispiel #8
0
class OrderStat(db.Document):
    meta = {'db_alias': 'order_db', 'indexes': ['user_id']}
    user_id = db.ObjectIdField(required=True)
    orders = db.ListField(db.ReferenceField('Order'))
    items = db.ListField(db.IntField())

    total = db.FloatField(default=0)
    received = db.FloatField(default=0)

    num_orders = db.IntField(default=0)
    num_unpaid = db.IntField(default=0)
    num_waiting = db.IntField(default=0)

    def clean(self):
        for field in ('total', 'received', 'num_orders', 'num_unpaid',
                      'num_waiting'):
            if getattr(self, field, 0) < 0:
                setattr(self, field, 0)

    @classmethod
    def by_user(cls, user_id):
        cls.objects(user_id=user_id).update_one(set__user_id=user_id,
                                                upsert=True)
        return cls.objects(user_id=user_id).first()
Beispiel #9
0
class CartEntry(db.Document):
    meta = {
        'db_alias': 'cart_db'
    }
    item_id = db.StringField(required=True)
    title = db.StringField()
    primary_img = db.StringField()
    price = db.FloatField()
    created_at = db.DateTimeField(default=datetime.utcnow)

    def __unicode__(self):
        return '%s' % str(self.id)

    @property
    def fields(self):
        return ['item_id', 'title', 'primary_img', 'price', 'created_at']

    def to_json(self):
        result = {f: getattr(self, f) for f in self.fields}
        return result
Beispiel #10
0
class ForexRate(db.Document):
    currency = db.StringField(default=CURRENCY.USD, choices=CURRENCY)
    rate = db.FloatField()
    modified = db.DateTimeField()

    @classmethod
    def get(cls, currency=CURRENCY.USD):
        currency = currency.upper()
        if currency == 'US':
            currency = 'USD'
        if currency == 'CNY':
            return 1

        # rounding datetime, so we can use cache of mongoengine
        start = (datetime.utcnow() - timedelta(days=3)).replace(minute=0,
                                                                second=0,
                                                                microsecond=0)
        rate_objs = cls.objects(currency=currency, modified__gt=start)
        rates = [r.rate for r in rate_objs]
        try:
            rate = sum(rates) / float(len(rates))
        except ZeroDivisionError:
            rate = 0

        if not rate:
            rate = cls.get_latest_rate(currency)
        return _ceil(rate + RATE_INCR.get(currency, 0), 3)

    @classmethod
    def get_latest_rate(cls, currency=CURRENCY.USD):
        forex = cls.objects(currency=currency).order_by('-modified').first()
        rate = forex.rate if forex else DEFAULT_RX_RATE[currency]
        return rate

    @classmethod
    def put(cls, rate, d=None, currency=CURRENCY.USD):
        if d is None:
            d = datetime.utcnow()
        cls.objects(currency=currency, modified=d).update_one(set__rate=rate,
                                                              upsert=True)
Beispiel #11
0
class Price(db.EmbeddedDocument):
    price = db.FloatField()
    record_date = db.DateTimeField()
Beispiel #12
0
class CartEntry(db.EmbeddedDocument):
    sku = db.IntField(required=True)
    quantity = db.IntField(default=1, required=True)
    created_at = db.DateTimeField()

    first_price = db.FloatField(default=0)
Beispiel #13
0
class Item(db.Document):
    meta = {'db_alias': 'inventory_db'}

    owner = db.StringField()
    owner_id = db.StringField()
    item_id = db.StringField(primary_key=True)  # id
    eth_token = db.StringField()
    ## price
    price = db.FloatField()
    primary_img = db.StringField()
    images = db.ListField(db.StringField())
    # basic information
    title = db.StringField()
    brand = db.StringField()
    condition = db.StringField()
    description = db.StringField(default='')
    madein = db.StringField()
    #color = db.ListField(db.StringField())
    location = db.StringField()

    main_category = db.StringField()
    sub_category = db.StringField()
    sex_tag = db.StringField()
    tags = db.ListField(db.StringField())

    availability = db.BooleanField(default=True)
    state = db.StringField()

    # time
    created_at = db.DateTimeField(default=datetime.utcnow)
    modified = db.DateTimeField()
    creator = db.StringField()

    def __unicode__(self):
        return '%s' % self.item_id

    def __repr__(self):
        return '%s' % self.item_id

    @property
    def cart_fields(self):
        return ['item_id', 'title', 'primary_img', 'price']

    def to_cart(self):
        result = {f: getattr(self, f) for f in self.cart_fields}
        return result

    @db.queryset_manager
    def available_items(doc_cls, queryset):
        return queryset.filter(availability=True)

    @property
    def small_thumbnail(self):
        return self.primary_img[:23] + 'thumbnails/150x150/' + self.primary_img[
            23:]

    @property
    def large_thumbnail(self):
        return self.primary_img[:23] + 'thumbnails/400x400/' + self.primary_img[
            23:]

    @classmethod
    def create(cls, item):

        item = Item(**item).save()
        item_id = item.item_id

        return item_id
Beispiel #14
0
class Item(db.Document):
    meta = {'db_alias': 'inventory_db'}

    item_id = db.SequenceField(required=True, unique=True,
                               primary_key=True)  # id

    ## price
    original_price = db.FloatField(required=True, min_value=0)
    price = db.FloatField(required=True, min_value=0)
    discount = db.IntField()

    primary_img = db.StringField(required=True)

    # basic information
    vendor = db.StringField(required=True)
    vendor_id = db.StringField(required=True)

    brand = db.StringField(required=True)

    main_category = db.StringField(required=True)
    sub_category = db.StringField(required=True)

    sex_tag = db.StringField(required=True, choices=SEX_TAG)

    tags = db.ListField(db.StringField())

    # descritpion
    title = db.StringField(required=True)
    title_en = db.StringField()
    description = db.StringField(default='')

    attributes = db.ListField(db.StringField())

    # 如果有材质的话,以材质开头
    information = db.ListField(db.StringField())

    size_lookup = db.DictField(default={})
    extra = db.DictField(default={})

    status = db.StringField(default=ITEM_STATUS.NEW,
                            required=True,
                            choices=ITEM_STATUS)
    availability = db.BooleanField(default=True, required=True)

    # time
    created_at = db.DateTimeField(default=datetime.utcnow, required=True)
    modified = db.DateTimeField()
    creator = db.StringField()

    lookup_table = db.StringField()
    size_chart = db.ListField(db.StringField())

    fields_to_log = {
        'url',
        'original_price',
        'price',
        'discount',
        'primary_img',
        'vendor',
        'brand',
        'main_category',
        'sub_category',
        'sex_tag',
        'tags',
        'availability',
    }

    def __unicode__(self):
        return '%s' % self.item_id

    def __repr__(self):
        return '%s' % self.item_id

    @db.queryset_manager
    def available_items(doc_cls, queryset):
        return queryset.filter(availability=True)

    @property
    def specs(self):
        return ItemSpec.objects(item_id=self.item_id)

    @property
    def available_specs(self):
        return ItemSpec.objects(item_id=self.item_id, availability=True)

    @property
    def small_thumbnail(self):
        return self.primary_img[:23] + 'thumbnails/150x150/' + self.primary_img[
            23:]

    @property
    def large_thumbnail(self):
        return self.primary_img[:23] + 'thumbnails/400x400/' + self.primary_img[
            23:]

    @classmethod
    def create(cls, item):
        meta = item['meta']
        specs = item.get('specs', [])

        i = Item.objects(web_id=meta['web_id'])
        if i:
            item['meta']['status'] = ITEM_STATUS.MOD
            return cls.modify(item)

        # If category, brand and tags does not exisit then create.
        Category.get_category_or_create(meta['main_category'], 1)
        Category.get_category_or_create(meta['sub_category'], 2)
        Brand.get_brand_or_create(meta['brand'])
        Vendor.get_or_create(meta['vendor'])
        for tag in meta['tags']:
            Tag.get_tag_or_create(tag)

        Statistics.create(meta['main_category'], meta['sub_category'],
                          meta['brand'], meta['tags'], meta['sex_tag'])

        item = Item(**meta).save()
        item_id = item.item_id

        # Keep item specs clean. Remove accidentally left useless specs.
        old_specs = ItemSpec.objects(item_id=item_id)
        for spec in old_specs:
            spec.delete()

        for spec in specs:
            spec['item_id'] = item_id
            ItemSpec(**spec).save()

        PriceHistory.upsert_price_history(item_id, meta['price'])

        return item_id

    @classmethod
    def modify(cls, new_item, current_price=None):
        # If the item does not exist in our db, then do not trust the coming
        # mod data.
        try:
            old_item = Item.objects.get(web_id=new_item['meta']['web_id'])
        except DoesNotExist:
            current_app.logger.warning(
                'crawler send item not exist in db: {}'.format(new_item))
            return

        # Old item is an mongoengine object.
        # New item is a dictionary.
        cls.update_item(old_item, new_item)
        old_item.save()

        if current_price:
            PriceHistory.upsert_price_history(old_item.item_id, current_price)

        return old_item.item_id

    @classmethod
    def update_item(cls, old_item, new_item):
        meta = new_item['meta']

        # If category, brand and tags does not exisit then create.
        Category.get_category_or_create(meta['main_category'], 1)
        Category.get_category_or_create(meta['sub_category'], 2)
        Brand.get_brand_or_create(meta['brand'])
        Vendor.get_or_create(meta['vendor'])
        Statistics.create(meta['main_category'], meta['sub_category'],
                          meta['brand'], meta['tags'], meta['sex_tag'])

        for k, v in meta.items():
            setattr(old_item, k, v)
        old_item.save()

    @classmethod
    def delete_item(cls, web_id):
        try:
            item = cls.objects.get(web_id=web_id)
            item.status = 'DEL'
            item.availability = False
            item.save()
            return item.item_id
        except DoesNotExist:
            pass

    def to_simple_json(self):
        return dict(id=str(self.item_id),
                    title=self.title,
                    price=getattr(self, 'price_details', lambda: '')(),
                    primary_image=self.primary_img,
                    status=self.status)

    def price_details(self):
        return dict(
            price=self.price,
            orig_price=self.original_price,
            discount=ceil(
                ((self.original_price - self.price) / self.original_price) *
                100),
        )
Beispiel #15
0
class Price(db.EmbeddedDocument):
    price = db.FloatField(required=True, min_value=0)
    record_date = db.DateTimeField(default=datetime.utcnow(), required=True)
Beispiel #16
0
class Payment(db.Document):
    meta = {
        'db_alias': 'order_db',
        'indexes': ['order', 'ptype', '-created_at']
    }

    created_at = db.DateTimeField(default=datetime.datetime.utcnow,
                                  required=True)

    order = db.ReferenceField('Order')
    logistic = db.ReferenceField('Logistic')

    other_reason = db.StringField()
    ptype = db.StringField(required=True, choices=PAYMENT_TYPE)
    status = db.StringField(max_length=255,
                            required=True,
                            choices=PAYMENT_STATUS,
                            default=PAYMENT_STATUS.UNPAID)

    # transaction reference number from alipay/bank
    ref_number = db.StringField(max_length=100)
    paid_amount = db.FloatField()
    foreign_amount = db.FloatField()
    currency = db.StringField()
    buyer_id = db.StringField(max_length=50)
    trader = db.StringField(choices=PAYMENT_TRADERS)
    trade_type = db.StringField(choices=TRADE_TYPE)
    trade_status = db.StringField()
    trader_msg = db.StringField()
    extra = db.StringField()
    modified = db.DateTimeField()
    redirect_url = db.StringField()

    @property
    def is_paid(self):
        return self.status == PAYMENT_STATUS.PAID

    @property
    def amount(self):
        if self.ptype == PAYMENT_TYPE.WITHOUT_TAX:
            return self.order.final
        if self.ptype == PAYMENT_TYPE.WITH_TAX:
            return self.order.final + self.order.tax

    def mark_paid(self, data):
        if self.is_paid:
            return
        self.update(set__status=PAYMENT_STATUS.PAID)
        kwargs = {'set__' + key: value for key, value in data.items()}
        self.update(**kwargs)
        self.reload()

        paid_amount = float(data.get('paid_amount', 0))

        if self.ptype == PAYMENT_TYPE.WITHOUT_TAX:
            self.order.update_payment(self.ptype, paid_amount, self.trader)

    def to_json(self):
        return dict(id=self.id,
                    ref_num=self.ref_number,
                    status=self.status,
                    type=self.type,
                    amount=self.amount)
Beispiel #17
0
class Order(db.Document):
    meta = {
        'db_alias':
        'order_db',
        'ordering': ['-created_at'],
        'indexes': [
            'customer_id', 'short_id', 'created_at', 'status', 'address',
            'amount', 'final', 'order_type', 'is_paid', 'is_payment_abnormal',
            'refund_entries'
        ]
    }
    created_at = db.DateTimeField(default=datetime.datetime.utcnow,
                                  required=True)

    order_type = db.StringField(choices=ORDER_TYPE,
                                default=ORDER_TYPE.COMMODITY)
    expired_in = db.IntField(default=1440)  # in minutes
    payment_expired_in = db.IntField(
        default=1440)  # minutes before payment should expire
    short_id = db.SequenceField(required=True, unique=True)
    is_vip = db.BooleanField(default=False)
    status = db.StringField(max_length=255,
                            required=True,
                            choices=ORDER_STATUS,
                            default=ORDER_STATUS.PAYMENT_PENDING)
    status_modified = db.DateTimeField()
    source = db.StringField(choices=ORDER_SOURCES)
    is_rewards_given = db.BooleanField(default=False)

    # order detail
    amount_usd = db.FloatField(default=0)
    amount = db.FloatField(default=0)

    discount = db.ListField(db.DictField())  # only for coupon
    coupon_codes = db.ListField(db.StringField())
    coin = db.IntField()
    hongbao = db.IntField()
    cash = db.IntField()
    final = db.FloatField(required=True)

    logistic_provider = db.StringField()

    estimated_tax = db.FloatField(default=0)
    real_tax = db.FloatField(default=-1)
    paid_tax = db.FloatField(default=-1)

    # for internal usage
    forex = db.FloatField()
    real_shipping = db.FloatField()  # real shipping fee paid
    cn_shipping = db.FloatField(default=0)

    address = db.ReferenceField('Address')
    customer_id = db.ObjectIdField(required=True)
    is_new_customer = db.BooleanField(default=False)
    entries = db.ListField(
        db.ReferenceField('OrderEntry', reverse_delete_rule=mongoengine.PULL))
    extra = db.StringField()

    # keep old property it here for migration
    logistics = db.ListField(db.ReferenceField('Logistic'))
    closed_logistics = db.ListField(db.ReferenceField('Logistic'))

    # this doesn't indicate whether customer have paid tax or not
    is_paid = db.BooleanField(default=False)
    is_payment_abnormal = db.BooleanField(default=False)
    paid_date = db.DateTimeField()
    pay_tax_deadline = db.DateTimeField()

    # informations of refundation
    refund_entries = db.ListField(
        db.ReferenceField('OrderEntry', reverse_delete_rule=mongoengine.PULL))
    refund_amount = db.FloatField(default=0)
    is_test = db.BooleanField(default=False)

    fields_to_log = {
        'status',
        'amount',
        'coin',
        'final',
        'estimated_tax',
        'real_tax',
        'paid_tax',
        'real_shipping',
        'is_paid',
    }

    PROCESSING_STATUS = [ORDER_STATUS.PAYMENT_RECEIVED, ORDER_STATUS.SHIPPING]

    ABNORMAL_STATUS = [
        ORDER_STATUS.CANCELLED, ORDER_STATUS.ABNORMAL,
        ORDER_STATUS.ORDER_DELETED, ORDER_STATUS.EXPIRED, ORDER_STATUS.REFUNDED
    ]

    def __unicode__(self):
        return '%s' % self.sid

    @classmethod
    def get_order_or_404(cls, order_id, check_user=True):
        try:
            order = cls.objects(id=order_id).first_or_404()
        except mongoengine.ValidationError:
            try:
                short_id = int(order_id)
            except (ValueError, TypeError):
                abort(404)
            order = cls.objects(short_id=short_id).first_or_404()

        if check_user and str(order.customer_id) != str(current_user.id):
            abort(404)

        return order

    @queryset_manager
    def commodities(doc_cls, queryset):
        return queryset.filter(order_type=ORDER_TYPE.COMMODITY,
                               status__nin=doc_cls.ABNORMAL_STATUS)

    @queryset_manager
    def transfer(doc_cls, queryset):
        return queryset.filter(order_type=ORDER_TYPE.TRANSFER,
                               status__nin=doc_cls.ABNORMAL_STATUS)

    @queryset_manager
    def processing(doc_cls, queryset):
        return queryset.filter(status__in=doc_cls.PROCESSING_STATUS)

    @queryset_manager
    def payment_pending(doc_cls, queryset):
        return queryset.filter(status=ORDER_STATUS.PAYMENT_PENDING)

    @queryset_manager
    def abnormal(doc_cls, queryset):
        ''' Define abnormal: status is abnormal, or partial entries are refunded.'''
        return queryset.filter(
            Q(status__in=doc_cls.ABNORMAL_STATUS) | \
            (Q(refund_entries__0__exists=True) & Q(status__in=(doc_cls.PROCESSING_STATUS+[ORDER_STATUS.RECEIVED])))
        )

    @queryset_manager
    def received(doc_cls, queryset):
        return queryset.filter(status=ORDER_STATUS.RECEIVED)

    def is_processing(self):
        return self.status in self.PROCESSING_STATUS

    def is_payment_pending(self):
        return self.status == ORDER_STATUS.PAYMENT_PENDING

    def is_abnormal(self):
        if self.status in self.ABNORMAL_STATUS:
            return True
        if self.status in self.PROCESSING_STATUS or self.status == ORDER_STATUS.RECEIVED:
            return len(self.refund_entries) > 0
        return False

    def has_refund_entries(self):
        if self.status in (self.PROCESSING_STATUS +
                           [ORDER_STATUS.RECEIVED, ORDER_STATUS.REFUNDED]):
            return len(self.refund_entries) > 0
        return False

    @property
    def tax(self):
        if self.real_tax == -1:
            return self.estimated_tax
        else:
            return self.real_tax

    @property
    def shipping(self):
        return self.cn_shipping

    @property
    def estimated_weight(self):
        return sum(
            float(entry.item_snapshot.weight) * entry.quantity
            for entry in self.entries)

    @property
    def pay_tax_remain_days(self):
        if self.pay_tax_deadline:
            time_remain = self.pay_tax_deadline.date() - datetime.date.today()
            if time_remain.days > 0:
                return time_remain.days

    @property
    def latest_logistic(self):
        from application.models import LogisticDetail
        attr = LogisticDetail.attr_by_log_stat.get(self.status)
        if not attr:
            return None
        return max(self.logistics, key=lambda l: getattr(l.detail, attr))

    @classmethod
    def create_transfer(cls,
                        customer_id,
                        entries,
                        logistic_provider,
                        coupon_codes,
                        coin=0,
                        cash=0,
                        address=None,
                        **kwargs):
        from application.models import ForexRate

        order = cls(customer_id=customer_id,
                    entries=entries,
                    logistic_provider=logistic_provider,
                    coupon_codes=coupon_codes,
                    coin=coin,
                    cash=cash,
                    **kwargs)

        if not order.forex:
            order.forex = ForexRate.get()

        order.update_amount()

        order.reload()

        if address:
            order.set_address(address)

        import application.services.jobs as Jobs
        #Jobs.stat.update_user_stats(str(order.customer_id))
        Signals.order_created.send('system', order=order)

        return order

    @classmethod
    def create_from_skus(cls,
                         customer_id,
                         skus,
                         logistic_provider,
                         coupon_codes,
                         coin=0,
                         cash=0,
                         address=None,
                         **kwargs):
        from application.models import OrderEntry, ForexRate, Item, ItemSpec

        entries = []
        for e in skus:
            availability = check_availability_and_update_stock(
                e['item_id'], e['sku'], e['quantity'])
            if not availability:
                return e
            spec = ItemSpec.objects(sku=e['sku']).first()
            item = Item.objects(item_id=e['item_id']).first()

            entry = OrderEntry(spec=spec, item=item,
                               quantity=e['quantity']).save()
            entries.append(entry)

        order = cls(customer_id=customer_id,
                    entries=entries,
                    logistic_provider=logistic_provider,
                    coupon_codes=coupon_codes,
                    coin=coin,
                    cash=cash,
                    **kwargs)

        if not order.forex:
            order.forex = ForexRate.get()

        order.update_amount()

        order.reload()
        for e in order.entries:
            e.create_snapshot()

        if address:
            order.set_address(address)

        import application.services.jobs as Jobs
        #Jobs.stat.update_user_stats(str(order.customer_id))
        Signals.order_created.send('system', order=order)

        return order

    @classmethod
    def create(cls,
               customer_id,
               entries,
               logistic_provider,
               coupon_codes,
               coin=0,
               cash=0,
               address=None,
               **kwargs):
        import application.models as Models

        order_entries = []
        for entry in entries:
            availability = check_availability_and_update_stock(
                entry.item_snapshot.item_id, entry.item_spec_snapshot.sku,
                entry.quantity)
            if not availability:
                return entry
            if isinstance(entry, (Models.CartEntry, Models.OrderEntry)):
                e = deepcopy(entry)
                e.__class__ = Models.OrderEntry
                e.id = None
                order_entries.append(e.save())
                # entry.delete()

        order = cls(customer_id=customer_id,
                    entries=order_entries,
                    logistic_provider=logistic_provider,
                    coupon_codes=coupon_codes,
                    coin=coin,
                    cash=cash,
                    **kwargs)

        if not order.forex:
            order.forex = Models.ForexRate.get()

        order.update_amount()

        order.reload()
        for e in order.entries:
            e.create_snapshot()

        if address:
            order.set_address(address)

        import application.services.jobs as Jobs
        #Jobs.stat.update_user_stats(str(order.customer_id))
        Signals.order_created.send('system', order=order)

        return order

    @property
    def item_changed(self):
        res = False
        for e in entries:
            res = res and e.item_changed
            if res: return res
        return res

    def __get__(self, *args, **kwargs):
        order = super(Order, self).__get__(*args, **kwargs)
        if (not order.is_paid) and order.item_changed: order.update_entry()
        return order

    def update_entry(self):
        if self.is_paid: return
        map(lambda e: e.update_snapshot(), self.entries)
        self.update_amount()

    def set_address(self, addr):
        ''' Create a snapshot of `addr` and make it the address of `self`.
            It's important to creat a snapshot instead of referencing to the
            origin address object such that the detail of order's address can
            not be modified. '''
        from application.models import Address
        if not isinstance(addr, Address):
            addr = Address.objects(id=addr).first()
        if not addr:
            return False
        addr_snapshot = deepcopy(addr)
        addr_snapshot.id = None
        addr_snapshot.order_id = self.id
        addr_snapshot.save()

        if self.address:
            self.address.delete(w=1)
        self.address = addr_snapshot
        self.save()
        return True

    @property
    def customer(self):
        from application import models as Models
        return Models.User.objects(id=self.customer_id).first()

    def create_payment(self, ptype, trader):
        ptype = ptype.upper()
        self.update_entry()
        self.reload()
        if self.order_type != ORDER_TYPE.TRANSFER:
            is_available = self.check_entries_avaliable()
            if not is_available or self.status in [
                    'CANCELLED', 'ABNORMAL', 'ORDER_DELETED', 'EXPIRED'
            ]:
                return None

        new_payment = Payment(order=self, ptype=ptype, trader=trader).save()
        return new_payment

    def get_payment(self, ptype):
        ptype = ptype.upper()
        payments = Payment.objects(order=self,
                                   ptype=ptype).order_by('-created_at')
        paid_payments = payments.filter(status=PAYMENT_STATUS.PAID)
        if paid_payments:
            return paid_payments.first()
        else:
            return payments.first()

    @property
    def goods_payment(self):
        return self.get_payment(PAYMENT_TYPE.WITHOUT_TAX)

    @property
    def tax_payment(self):
        return self.get_payment(PAYMENT_TYPE.WITH_TAX)

    @property
    def refunds(self):
        from application.models import Refund
        return Refund.objects(order=self)

    def check_entries_avaliable(self):
        availability = all(
            map(lambda e: e.is_available or e.item_spec_snapshot.stock != -1,
                self.entries))
        if not availability:
            self.status = ORDER_STATUS.EXPIRED
            self.save()
        return availability

    def set_paid(self):

        if self.is_paid:
            return

        self.is_new_customer = not bool(
            Order.objects(customer_id=self.customer_id, is_paid=True))
        self.is_paid = True
        self.status = ORDER_STATUS.PAYMENT_RECEIVED
        self.paid_date = datetime.datetime.utcnow()
        self.save()

        from application.services.order import payment_received
        payment_received(self)

    def update_payment(self, paid_type, paid_amount, trader):
        if paid_type == PAYMENT_TYPE.WITHOUT_TAX and not self.is_paid and \
                self.status in [ORDER_STATUS.PAYMENT_PENDING, ORDER_STATUS.WAREHOUSE_IN]:
            if paid_amount == self.final and trader == PAYMENT_TRADERS.PAYPAL:
                self.set_paid()
            elif paid_amount == float("%.2f" % (self.final * self.forex)) and \
                    trader == PAYMENT_TRADERS.WEIXIN:
                self.set_paid()
            else:
                current_app.logger.error(
                    'error at updating payment. trader: {}; ptype: {}; amount:{} order id: {}'
                    .format(trader, paid_type, paid_amount, self.id))
                self.is_payment_abnormal = True
        else:
            current_app.logger.error(
                'error at updating payment. trader: {}; ptype: {}; amount:{} order id: {}'
                .format(trader, paid_type, paid_amount, self.id))
            self.is_payment_abnormal = True
        self.save()

    @property
    def coin_trades(self):
        import application.models as Models
        return Models.Trade.objects(reason_id=str(self.id))

    def update_logistic_status(self):
        if self.logistics:
            log_status = map(lambda m: m.detail.status, self.logistics)
            new_status = min(log_status, key=lambda l: LOG_STATS.index(l))
            self._change_status(new_status)

    def _change_status(self, new_status):
        from application.services.noti import noti_order
        if self.status == new_status:
            return

        self.status = new_status
        self.status_modified = datetime.datetime.utcnow()
        self.save()

        if new_status in LOG_STATS:
            noti_order(self, new_status)
            Signals.order_logistic_stat_changed.send(
                'Order.Logistic.Status.Changed',
                order=self,
                new_status=new_status)
        else:
            Signals.order_status_changed.send('order_status_changed',
                                              order=self,
                                              new_status=new_status)

    def delete_order(self):
        for mo in self.logistics:
            mo.delete(w=1)

        for entry in self.entries:
            entry.delete(w=1)

        import application.services.jobs as Jobs
        #Jobs.stat.update_user_stats(str(self.customer_id))

        if self.goods_payment:
            self.goods_payment.delete(w=1)
        self.delete(w=1)

    def cancel_order(self, reason, status=None):
        for mo in self.logistics:
            mo.close(reason)
        self.extra = reason
        self.save()

        if not status:
            status = ORDER_STATUS.ABNORMAL
        self._change_status(status)
        for entry in self.entries:
            try:
                if entry.spec.stock != -1:
                    entry.spec.update(inc__stock=entry.quantity,
                                      set__availability=True)
                    entry.item.update(set__availability=True,
                                      set__status='MOD')
            except AttributeError:
                pass

    def update_amount(self):
        from application.services.price import cal_order_price_and_apply, \
            cal_order_tax

        for e in self.entries:
            e.update_amount()

        cal_order_price_and_apply(self)

        self.estimated_tax = cal_order_tax(self)
        self.save()

    @property
    def sid(self):
        return self.short_id

    def to_json(self,
                include_logistic=False,
                replace_entries_to_refunded=False):
        if not self.is_paid:
            self.update_amount()
            self.reload()

        entries_json = []
        if replace_entries_to_refunded and self.has_refund_entries():
            for e in self.refund_entries:
                entries_json.append(e.to_json())
        else:
            for e in self.entries:
                entries_json.append(e.to_json())

        refund_entries_json = []
        for e in self.refund_entries:
            refund_entries_json.append(e.to_json())

        result = dict(
            id=str(self.id),
            short_id=str(self.sid),
            status=self.status,
            customer_id=str(self.customer_id),
            amount=self.amount,
            cn_shipping=self.cn_shipping,
            coin=self.coin,
            hongbao=self.hongbao,
            discount=self.discount,
            final=self.final,
            estimated_tax=self.estimated_tax,
            payment_status='PAID' if self.is_paid else 'UNPAID',
            payment_ref_number=[
                p.ref_number for p in Payment.objects(order=self)
            ],
            created_at=format_date(self.created_at),
            entries=entries_json,
            refund_entries=refund_entries_json,
            refund_amount=self.refund_amount,
            real_tax=self.real_tax,
        )

        if self.address:
            result.update({"address": self.address.to_json()})

        if include_logistic:
            result.update(dict(logistics=[l.to_json()
                                          for l in self.logistics]))

        return result

    def to_grouped_json(self):
        res = {
            'estimated_weight': self.estimated_weight,
            'amount': self.amount,
            'cn_shipping': self.cn_shipping,
            'coin': self.coin,
            'hongbao': self.hongbao,
            'discount': self.discount,
            'final': self.final,
            'estimated_tax': self.estimated_tax
        }

        res['sid'] = self.sid
        res['status'] = self.status
        if self.address:
            res.update(dict(address=self.address.to_json()))
        return res
Beispiel #18
0
class LogisticDetail(db.EmbeddedDocument):
    partner_tracking_no = db.StringField(default='', required=True)
    cn_tracking_no = db.StringField(default='')
    cn_logistic_name = db.StringField(default='')
    carrier_tracking_no = db.StringField(default='')
    partner = db.ReferenceField('Partner')
    channel = db.StringField()
    route = db.StringField(default="DEFAULT")

    pending_review_date = db.DateTimeField()
    transfer_approved_date = db.DateTimeField()
    warehouse_in_date = db.DateTimeField()
    payment_received_date = db.DateTimeField(default=datetime.datetime.utcnow)
    processing_date = db.DateTimeField()
    shipping_date = db.DateTimeField()
    port_arrived_date = db.DateTimeField()
    received_date = db.DateTimeField()
    pending_return_date = db.DateTimeField()
    returned_date = db.DateTimeField()

    remarks = db.EmbeddedDocumentListField('LogisticRemark')
    delay_details = db.EmbeddedDocumentListField('LogisticDelay')
    irregular_details = db.EmbeddedDocumentListField('LogisticIrregular')
    extra = db.StringField(default='')

    real_weight = db.FloatField(default=0)
    real_fee = db.FloatField()

    modified = db.DateTimeField()
    modified_by = db.StringField()
    status = db.StringField(max_length=255, required=True,
                            choices=LOG_STATS,
                            default=LOG_STATS.PAYMENT_RECEIVED)
    attr_by_log_stat = {
        'PENDING_REVIEW': 'pending_review_date',
        'TRANSFER_APPROVED': 'transfer_approved_date',
        'WAREHOUSE_IN': 'warehouse_in_date',
        'PAYMENT_RECEIVED': 'payment_received_date',
        'PROCESSING': 'processing_date',
        'SHIPPING': 'shipping_date',
        'PORT_ARRIVED': 'port_arrived_date',
        'RECEIVED': 'received_date',
        'PENDING_RETURN': 'pending_return_date',
        'RETURNED': 'returned_date'
    }

    fields_to_log = {
        'cn_tracking_no', 'partner_tracking_no', 'status',
        'cn_logistic_name', 'partner', 'payment_received_date',
        'shipping_date', 'processing_date',
        'received_date', 'port_arrived_date', 'modified_by',
    }

    def next(self):
        if self.status in LOG_STATS and self.status != 'RECEIVED':
            self.status = LOG_STATS[LOG_STATS.index(self.status) + 1]

    @property
    def cn_logistic(self):
        from application.models import Express
        return Express.objects(name=self.cn_logistic_name).first()