class Todo(db.Document): desc = db.StringField() create_at = db.DateTimeField(default=datetime.now()) finished_at = db.DateTimeField(default=None) # todo的完成时间 is_finished = db.BooleanField(default=False) # todo状态:1. finished, 2. open creator = db.ReferenceField('User') todolist = db.ReferenceField('TodoList') @property def id(self): return str(self._id) @property def status(self): return 'finished' if self.is_finished else 'open' def finished(self): self.finished_at = datetime.now() self.is_finished = True self.save() def reopen(self): self.finished_at = None self.is_finished = False self.save() def to_dict(self): return { 'description': self.desc, 'creator': self.creator.username, 'create_at': self.create_at, 'finished_at': self.finished_at, 'status': self.status }
class OrderEntry(BaseEntry): meta = { 'db_alias': 'order_db', } _item_snapshot = db.ReferenceField( 'ItemSnapshot') # store the info of the item when an order is produced _item_spec_snapshot = db.ReferenceField('ItemSpecSnapshot') remark = db.StringField() shipping_info = db.DictField() @property def item_snapshot(self): return self._item_snapshot or self.item @property def item_spec_snapshot(self): return self._item_spec_snapshot or self.spec @property def item_changed(self): if self.item_spec_snapshot and self.item_snapshot: return self.item_snapshot.is_changed or self.item_spec_snapshot.is_changed else: return False def create_snapshot(self, item=None, spec=None): if not spec: spec = self.spec if not item: item = self.spec.item from application import models as Models if not self._item_snapshot: item_snapshot = Models.ItemSnapshot.create(item) self._item_snapshot = item_snapshot self._item_snapshot.price = item.price self._item_snapshot.save() if not self._item_spec_snapshot: item_spec_snapshot = Models.ItemSpecSnapshot.create( spec, item_snapshot) self._item_spec_snapshot = item_spec_snapshot self._item_spec_snapshot.price = spec.price self._item_spec_snapshot.save() self.save() return self._item_snapshot, self._item_spec_snapshot def update_snapshot(self): if not (self._item_spec_snapshot and self._item_snapshot): return self.create_snapshot() self.item_snapshot.update_to_head() self.item_spec_snapshot.update_to_head() return self.save()
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
class TodoList(db.Document): title = db.StringField() created_at = db.DateTimeField(default=datetime.now()) creator = db.ReferenceField('User') todos = db.ListField(default=[]) @property def id(self): return str(self._id) @property def todo_count(self): return len(self.todos) @property def finished_count(self): return len(list(filter(lambda x: x.is_finished, self.todos))) @property def open_count(self): return len(list(filter(lambda x: not x.is_finished, self.todos))) def to_dict(self): return { 'title': self.title, 'create_at': self.created_at, 'creator': self.creator.username, 'todo_count': self.todo_count, 'finished_count': self.finished_count, 'open_count': self.open_count }
class BackendPermission(db.Document): meta = { 'indexes': ['name'], } name = db.StringField(required=True, unique=True) roles = db.ListField(db.ReferenceField('Role'))
class PostLike(db.Document): meta = { 'db_alias': 'content_db', 'indexes': ['user_id', 'post', 'created_at'], 'ordering': ['-created_at'] } user_id = db.ObjectIdField() post = db.ReferenceField('Post') created_at = db.DateTimeField(default=datetime.utcnow, required=True) @property def user(self): from application import models as Models return Models.User.objects(id=self.user_id).first() def to_json(self): user = self.user return dict( id=str(self.id), user=dict(id=str(user.id), name=user.name, avatar_url=user.avatar_url, avatar_thumb=user.avatar_thumb), )
class Item(db.Document): content = db.StringField(required=True) created_date = db.DateTimeField() completed = db.BooleanField(default=False) completed_date = db.DateTimeField() created_by = db.ReferenceField('User', required=True) notes = db.ListField(db.StringField()) priority = db.IntField() def __repr__(self): return "<Item: {} Content: {}>".format(str(self.id), self.content) def to_json(self): return { 'id': str(self.id), 'content': self.content, 'completed': self.completed, 'completed_at': self.completed_date.strftime("%Y-%m-%d %H:%M:%S") if self.completed else "", 'created_by': self.created_by.name, 'notes': self.notes, 'priority': self.priority }
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
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()
class CoinWallet(db.Document): meta = {'indexes': ['user']} user = db.ReferenceField('User', unique=True) balance = db.IntField(required=True, default=0) holdding = db.IntField(required=True, default=0) cash = db.IntField(required=True, default=0) holdding_cash = db.IntField(required=True, default=0) latest_expired_time = db.DateTimeField(default=datetime( 1989, 6, 4)) # latest expired_time of holdding trades @classmethod def create(cls, user, balance=0, holdding=0): wallet = cls.objects(user=user).modify(upsert=True, new=True, set__user=user, set__balance=balance, set__holdding=holdding) return wallet @classmethod def get_or_create(cls, user): wallet = CoinWallet.objects(user=user).first() if not wallet: wallet = CoinWallet.create(user=user) return wallet def pay(self, order, amount, coin_type=COIN_TYPE.COIN): if (coin_type == COIN_TYPE.COIN and amount > self.balance): current_app.logger.error( 'Order coin exceed wallet balance. order: {}, amount: {}, balance: {}.' .format(order.id, amount, self.balance)) amount = self.balance if (coin_type == COIN_TYPE.CASH and amount > self.cash): current_app.logger.error( 'Order cash exceed wallet balance. order: {}, amount: {}, balance: {}.' .format(order.id, amount, self.cash)) amount = self.cash from application import models as Models time = datetime.utcnow() reason = COIN_TRADE_REASON.PAY kind = COIN_TRADE_TYPE.OUTCOME user = Models.User.objects(id=order.customer_id).first() trade = CoinTrade.create(user=user, amount=amount, time=time, kind=kind, reason=reason, wallet=self, reason_id=str(order.id), coin_type=coin_type) return trade
class ItemSnapshot(db.DynamicDocument): meta = { 'db_alias': 'order_db', 'indexes': ['item_id', 'web_id', 'head'] } head = db.IntField(required=True, default=0) specs = db.ListField(db.ReferenceField('ItemSpecSnapshot')) created_at = db.DateTimeField(default=datetime.datetime.utcnow()) def __unicode__(self): return '%s' % self.head @classmethod def create(cls, item): data = item._data shot = cls(**data) shot.head = shot.item_id shot.save() return shot @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:] def update_to_head(self): from application.models import Item head = Item.objects(item_id=self.head).first() if head: data = head._data for k, v in data.items(): setattr(self, k, v) self.save() else: return self @property def is_changed(self): from application.models import Item head = Item.objects(item_id=self.head).first() if not head: return True if self.modified != head.modified:return True return False
class OrderExtra(db.Document): meta = { 'indexes': [ 'order', 'paid_date', 'device_id', 'client', 'version', 'client_channel' ] } order = db.ReferenceField('Order', unique=True) paid_date = db.DateTimeField() client = db.StringField() version = db.StringField() device_id = db.StringField() client_channel = db.StringField()
class ItemSpecSnapshot(db.DynamicDocument): meta = { 'db_alias': 'order_db', 'indexes': ['item_id', 'sku', 'head'] } head = db.IntField(required=True, default=0) item = db.ReferenceField('ItemSnapshot') created_at = db.DateTimeField(default=datetime.datetime.utcnow()) def __unicode__(self): return '%s:%s' % (self.item.head, self.head) @classmethod def create(cls, spec, itemsnapshot=None): if not itemsnapshot: itemsnapshot = ItemSnapshot.create(spec.item).save() data = spec._data shot = cls(**data) shot.head = shot.sku shot.item = itemsnapshot shot.save() itemsnapshot.specs.append(shot) itemsnapshot.save() return shot def update_to_head(self): from application.models import ItemSpec head = ItemSpec.objects(sku=self.head).first() if self.item and isinstance(self.item, ItemSnapshot): self.item.update_to_head() if head: data = head._data for k, v in data.items(): setattr(self, k, v) self.save() else: return self @property def is_changed(self): from application.models import ItemSpec head = ItemSpec.objects(sku=self.head).first() if not head: return True if self.modified != head.modified: return True return False
class Trade(db.Document): meta = { 'indexes': ['wallet', 'user', 'time', 'kind', 'reason_id', 'reason'], 'ordering': ['wallet'], 'allow_inheritance': True, } user = db.ReferenceField('User') wallet = db.ReferenceField(CoinWallet) amount = db.IntField(required=True) coin_type = db.StringField(choices=COIN_TYPE, default=COIN_TYPE.COIN) time = db.DateTimeField(required=True) kind = db.StringField(required=True, choices=tuple(COIN_TRADE_TYPE)) reason = db.StringField(required=True, choices=tuple(COIN_TRADE_REASON)) reason_id = db.StringField() description = db.StringField() is_hold = db.BooleanField() descs = { 'PAY': u'购买', 'CANCEL': u'取消订单', 'SHIPPING_FEE': u'返还运费', 'PROMOTE': u'参与活动', 'WECHAT_LOGIN': u'微信账号登陆', 'IOS_APP': u'下载手机客户端', 'VERIFIED_ID': u'上传身份证资料', 'SHARED': u'分享了一个链接', 'SECOND_SHARED': u'你的链接被分享了', 'SHARED_ORDER': u'通过你的链接购买', 'ORDER': u'订单完成', 'OTHER': u'其他', } def clean(self): if not self.description: self.description = self.descs.get(self.reason, '')
class PostFeedback(db.Document): meta = { 'db_alias': 'content_db', 'indexes': ['user_id', 'post', 'subject', 'created_at'], 'ordering': ['created_at'] } user_id = db.ObjectIdField() post = db.ReferenceField('Post') subject = db.StringField() status = db.StringField(default=ACTIVITY_STATUS.PENDING, choices=ACTIVITY_STATUS) created_at = db.DateTimeField(default=datetime.utcnow, required=True) @property def user(self): from application import models as Models return Models.User.objects(id=self.user_id).first()
class PostActivity(db.Document): meta = { 'db_alias': 'content_db', 'indexes': ['user_id', 'to_user_id', 'post', 'created_at', 'action'], 'ordering': ['-created_at'] } user_id = db.ObjectIdField() post = db.ReferenceField('Post') action = db.StringField(choices=NOTI_TYPE) created_at = db.DateTimeField(default=datetime.utcnow, required=True) to_user_id = db.ObjectIdField() info = db.StringField(default="") @property def user(self): from application import models as Models return Models.User.objects(id=self.user_id).first() @property def to_user(self): from application import models as Models return Models.User.objects(id=self.to_user_id).first() def to_json(self): user = self.user return dict( id=str(self.id), user=dict(id=str(user.id), name=user.name, avatar_url=user.avatar_url, avatar_thumb=user.avatar_thumb), ) @classmethod def create(cls, user, to_user, post, action, info=''): noti = cls(user_id=user, to_user_id=to_user, action=action, info=info, post=post).save() return noti
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()
class User(db.Document): name = db.StringField() password = db.StringField() email = db.StringField() role = db.ReferenceField('Role') @property def id(self): return str(self._id) def to_json(self): return {"name": self.name, "email": self.email, "role": self.role.name} def is_authenticated(self): return True def is_active(self): return True def is_anonymous(self): return False def get_id(self): return str(self.id)
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
class User(db.Document, UserMixin): ''' The User class contains only basic and frequently used information Superclass UserMixin can provide four authenticate methods required by Flask-Login. ''' meta = { 'db_alias': 'user_db', } name = db.StringField(required=True) account = db.EmbeddedDocumentField('UserAccount') information = db.EmbeddedDocumentField('UserInformation') selling_items = db.ListField(db.StringField()) sold_items = db.ListField(db.StringField()) owned_item = db.ListField(db.StringField()) bought_item = db.ListField(db.StringField()) roles = db.ListField(db.StringField()) # favor related (item_ids) num_favors = db.IntField(default=0, min_value=0) favor_items = db.ListField(db.StringField()) addresses = db.ListField(db.ReferenceField('Address')) default_address = db.ReferenceField('Address') verified = db.BooleanField(default=False) is_deleted = db.BooleanField(default=False) deleted_date = db.DateTimeField() def __unicode__(self): return '%s' % str(self.id) #return u'{}'.format(self.name) @property def avatar_thumb(self): return self.avatar_url[:23] + 'avatar_thumbs/80x80/' + self.avatar_url[23:] @db.queryset_manager def active(doc_cls, queryset): return queryset.filter(is_deleted=False) @property def is_admin(self): return USER_ROLE.ADMIN in self.roles @classmethod def is_verified(self): return self.verified def verify(self): self.verified = True self.save() return True def to_json(self): data = dict(name=self.name, created_at=str(self.account.created_at), id=str(self.id) ) return data @classmethod def authenticate(cls, mobile_number=None, password=None): if mobile_number: user = cls.active(account__mobile_number=mobile_number).first() else: user = None if user: authenticated = user.account.check_password(password) else: authenticated = False return user, authenticated def generate_auth_token(self, expires_in=604800): s = Serializer(current_app.config['SECRET_KEY'], expires_in=expires_in) return s.dumps({'id': str(self.id)}).decode('utf-8') @staticmethod def verify_auth_token(token): s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except: return None return User.objects(id=data['id']).first() @classmethod def create(cls, mobile_number, password, name, email=None): # account account = UserAccount(mobile_number=mobile_number, email=email, is_email_verified=True) account.password = password user = User(name=name, roles=[USER_ROLE.MEMBER], information=UserInformation(), account=account) user.save() signals.user_signup.send('system', user=user) return user
class User(db.Document, UserMixin): ''' The User class contains only basic and frequently used information Superclass UserMixin can provide four authenticate methods required by Flask-Login. ''' meta = { 'db_alias': 'user_db', } name = db.StringField(required=True) account = db.EmbeddedDocumentField('UserAccount') information = db.EmbeddedDocumentField('UserInformation') avatar_url = db.URLField(default='http://assets.maybi.cn/logo/panda.jpg') # level # 0: normal user # 1: normal member; 2: advance member # 3: premium member; 4: VIP member level = db.IntField(default=0) roles = db.ListField(db.StringField()) # whether subscribed our wechat account subscribed_mp = db.BooleanField(default=False) # favor related (item_ids) num_favors = db.IntField(default=0, min_value=0) favor_items = db.ListField(db.IntField()) addresses = db.ListField(db.ReferenceField('Address')) default_address = db.ReferenceField('Address') # favor related (post_ids) num_post_likes = db.IntField(default=0, min_value=0) like_posts = db.ListField(db.IntField()) is_deleted = db.BooleanField(default=False) deleted_date = db.DateTimeField() def __unicode__(self): return '%s' % str(self.id) #return u'{}'.format(self.name) @property def avatar_thumb(self): return self.avatar_url[:23] + 'avatar_thumbs/80x80/' + self.avatar_url[ 23:] @db.queryset_manager def active(doc_cls, queryset): return queryset.filter(is_deleted=False) @property def is_admin(self): return USER_ROLE.ADMIN in self.roles def to_json(self): data = dict(name=self.name, avatar_url=self.avatar_url, avatar_thumb=self.avatar_thumb, num_followers=self.num_followers, num_followings=self.num_followings, created_at=str(self.account.created_at), id=str(self.id)) return data @classmethod def authenticate(cls, email=None, password=None): if email: user = cls.active(account__email=email.lower()).first() else: user = None if user: authenticated = user.account.check_password(password) else: authenticated = False return user, authenticated def generate_auth_token(self, expires_in=604800): s = Serializer(current_app.config['SECRET_KEY'], expires_in=expires_in) return s.dumps({'id': str(self.id)}).decode('utf-8') @staticmethod def verify_auth_token(token): s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except: return None return User.objects(id=data['id']).first() @classmethod def create(cls, email, password, name, mobile_number=None): # account account = UserAccount(email=email.lower(), mobile_number=mobile_number, is_email_verified=True) account.password = password user = User(name=name, roles=[USER_ROLE.MEMBER], information=UserInformation(), account=account) user.save() signals.user_signup.send('system', user=user) return user
class SocialOAuth(db.Document): meta = {'indexes': ['site', 'user', ('site_uid', 'site'), 'unionid']} app = db.StringField(choices=['IOS', 'MOBILEWEB'], default='MOBILEWEB') site = db.StringField(max_length=255, required=True) site_uid = db.StringField(max_length=255, required=True, unique_with='site') unionid = db.StringField() user = db.ReferenceField('User') site_uname = db.StringField(max_length=255) access_token = db.StringField(required=True) expire_date = db.DateTimeField() refresh_token = db.StringField() # wether we can get information of this oauth can_refresh = db.BooleanField(default=True) last_active_date = db.DateTimeField() def to_json(self): return dict(site=self.site, site_uname=self.site_uname) @classmethod def create(cls, site, site_uid, site_uname, access_token, expires_in=0, refresh_token=None, email=None, mobile_number=None, gender=None, password=None, unionid=None, app='MOBILEWEB', is_email_verified=False): """ create an oauth record and an user""" oauth = cls(site=site, site_uid=site_uid, site_uname=site_uname, access_token=access_token, refresh_token=refresh_token, unionid=unionid, app=app) if not email: email = '{}-{}@maybi.cn'.format( site, hashlib.md5( (app + site + site_uid).encode('utf-8')).hexdigest()) # create an user user = User.create(email=email, mobile_number=mobile_number, password=password or site_uname, name=site_uname) user.account.is_email_verified = is_email_verified user.information.gender = gender if site == 'wechat': user.subscribed_mp = True user.save() oauth.user = user oauth.save() oauth.update_token(access_token, expires_in) return oauth def update_token(self, access_token, expires_in=0): expire_date = datetime.datetime.utcnow() + datetime.timedelta( seconds=int(expires_in)) self.update(set__access_token=access_token, set__expire_date=expire_date) def re_auth(self, access_token, expires_in, refresh_token=None, unionid=None): self.update_token(access_token, expires_in) self.update(set__refresh_token=refresh_token) if unionid: self.update(set__unionid=unionid) def update_avatar(self, url): self.user.update(set__avatar_url=url) @classmethod def get_user(cls, site, site_uid): so = cls.objects(site=site, site_uid=site_uid).first() return so.user if so else None @classmethod def refresh_active(cls, site, site_uid, dt): # ignore if document does not exist cls.objects(site=site, site_uid=site_uid).update_one(set__last_active_date=dt)
class User(db.Document, UserMixin, FavorAction): ''' The User class contains only basic and frequently used information Superclass UserMixin can provide four authenticate methods required by Flask-Login. ''' meta = { 'indexes': [ 'name', 'account.created_at', 'roles', 'level', 'account.email', 'account.is_email_verified', 'is_deleted' ], 'ordering': ['-account.created_at'] } name = db.StringField(required=True) account = db.EmbeddedDocumentField('UserAccount') information = db.EmbeddedDocumentField('UserInformation') avatar_url = db.URLField(default='http://assets.maybi.cn/logo/panda.jpg') # level # 0: normal user # 1: normal member; 2: advance member # 3: premium member; 4: VIP member level = db.IntField(default=0) roles = db.ListField(db.StringField()) addresses = db.ListField(db.ReferenceField('Address')) default_address = db.ReferenceField('Address') # followers num_followers = db.IntField(default=0, min_value=0) num_followings = db.IntField(default=0, min_value=0) followers = db.ListField(db.ReferenceField('User')) followings = db.ListField(db.ReferenceField('User')) # whether subscribed our wechat account subscribed_mp = db.BooleanField(default=False) # favor related (item_ids) num_favors = db.IntField(default=0, min_value=0) favor_items = db.ListField(db.IntField()) # favor related (post_ids) num_post_likes = db.IntField(default=0, min_value=0) like_posts = db.ListField(db.IntField()) # shopping cart cart = db.ReferenceField('Cart') # wallet wallet = db.ReferenceField('CouponWallet') is_deleted = db.BooleanField(default=False) deleted_date = db.DateTimeField() def __unicode__(self): return '%s' % str(self.id) #return u'{}'.format(self.name) @property def coin_wallet(self): import application.models as Models return Models.CoinWallet.get_or_create(user=self) @property def hongbao_wallet(self): import application.models as Models return Models.HongbaoWallet.by_user(user=self) @property def orders(self): import application.models as Models return Models.Order.objects(customer_id=self.id, is_paid=True) @property def avatar_thumb(self): return self.avatar_url[:23] + 'avatar_thumbs/80x80/' + self.avatar_url[ 23:] def used_coupon(self, code): import application.models as Models return bool( Models.Order.objects(customer_id=self.id, is_paid=True, coupon__codes__contains=code)) @db.queryset_manager def active(doc_cls, queryset): return queryset.filter(is_deleted=False) @property def is_admin(self): return USER_ROLE.ADMIN in self.roles # Follow / Following def follow(self, other): if self not in other.followers: other.followers.append(self) other.num_followers += 1 if other not in self.followings: self.followings.append(other) self.num_followings += 1 self.save() other.save() signals.site_message.send(self, dest=other.id, source=self, imgs=[self.avatar_url], noti_type=NOTI_TYPE.FOLLOW, title='') def unfollow(self, other): if self in other.followers: other.followers.remove(self) other.num_followers -= 1 if other in self.followings: self.followings.remove(other) self.num_followings -= 1 self.save() other.save() def is_following(self, other): return other in self.followings def to_json(self): data = dict(name=self.name, avatar_url=self.avatar_url, avatar_thumb=self.avatar_thumb, num_followers=self.num_followers, num_followings=self.num_followings, created_at=str(self.account.created_at), id=str(self.id)) return data @classmethod def authenticate(cls, email=None, password=None): if email: user = cls.active(account__email=email.lower()).first() else: user = None if user: authenticated = user.account.check_password(password) else: authenticated = False return user, authenticated def generate_auth_token(self, expires_in=604800): s = Serializer(current_app.config['SECRET_KEY'], expires_in=expires_in) return s.dumps({'id': str(self.id)}).decode('utf-8') @staticmethod def verify_auth_token(token): s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except: return None return User.objects(id=data['id']).first() @classmethod def create(cls, email, password, name, mobile_number=None): from application.models.coupon.wallet import CouponWallet from application.models.cart import Cart # init user account. cart = Cart() cart.save() wallet = CouponWallet() wallet.save() # account account = UserAccount(email=email.lower(), mobile_number=mobile_number, is_email_verified=True) account.password = password user = User(name=name, roles=[USER_ROLE.MEMBER], information=UserInformation(), cart=cart, wallet=wallet, account=account) user.save() signals.user_signup.send('system', user=user) return user def mark_deleted(self): if self.is_deleted: return # delete social oauth, otherwise user can still login via wechat SocialOAuth.objects(user=self).delete() self.is_deleted = True self.deleted_date = datetime.datetime.utcnow() self.save()
class Logistic(db.Document): meta = { 'db_alias': 'order_db', 'indexes': ['is_closed', '-created_at', 'order', 'entries', 'detail.status', 'detail.channel', 'detail.modified', 'detail.cn_tracking_no', 'detail.cn_logistic_name', 'detail.payment_received_date', 'detail.processing_date', 'detail.shipping_date', 'detail.port_arrived_date', 'detail.received_date', 'detail.partner', 'detail.partner_tracking_no', ('detail.status', '-created_at'), ] } order = db.ReferenceField('Order', required=True) entries = db.ListField(db.ReferenceField('OrderEntry')) returned_entries = db.ListField(db.ReferenceField('OrderEntry')) is_closed = db.BooleanField(default=False, required=True) close_reason = db.StringField() closed_at = db.DateTimeField() created_at = db.DateTimeField( required=True, default=datetime.datetime.utcnow ) detail = db.EmbeddedDocumentField('LogisticDetail') def __unicode__(self): return '%s' % str(self.id) @property def estimated_weight(self): return sum(float(entry.item_snapshot.weight) * entry.quantity for entry in self.entries) def to_json(self): detail = self.detail return dict(cn_tracking_no=detail.cn_tracking_no, cn_logistic_name=detail.cn_logistic_name, partner_tracking_no=detail.partner_tracking_no, partner_name=detail.partner.name if detail.partner else '', payment_received_date=get_date(detail, 'payment_received_date'), processing_date=get_date(detail, 'processing_date'), shipping_date=get_date(detail, 'shipping_date'), port_arrived_date=get_date(detail, 'port_arrived_date'), received_date=get_date(detail, 'received_date'), estimated_weight=self.estimated_weight, real_weight=detail.real_weight, status=detail.status) @property def amount(self): return sum(entry.amount for entry in self.entries) @property def amount_usd(self): return sum(entry.amount_usd for entry in self.entries) @classmethod def create(cls, order): from application.models.order.partner import Partner def update_order(o): o.logistics.append(log) o.save() o.update_logistic_status() def create_logistic(order): log = cls(detail=LogisticDetail()) log.order = order log.entries = order.entries log.detail.partner = Partner.objects().first() log.detail.partner_tracking_no = gen_uid() log.detail.status = order.status date_field = log.detail.attr_by_log_stat[order.status] setattr(log.detail, date_field, datetime.datetime.utcnow()) log.save() return log log = create_logistic(order) update_order(order) return log @classmethod def merge_with(cls, los): if not type(los) is list: return False start = 0 for index in range(len(los)-1): if los[index+1].detail.cn_tracking_no != \ los[start].detail.cn_tracking_no or \ los[index+1].order != los[0].order : return False for index in range(len(los)-1): map(lambda e: los[index+1].entries.append(e), los[index].entries) los[index].entries = [] los[index].save() los[index].close( 'merged with %s' % los[index+1].id, datetime.datetime.utcnow() ) los[index+1].save() if index+1 == len(los)-1: comment = LogisticRemark( content=u"合并单", creator=current_user.name ) los[index+1].detail.remarks.append(comment) los[index+1].save() return los[index+1] def close(self, reason, time=None): self.is_closed = True self.close_reason = reason self.closed_at = time or datetime.datetime.utcnow() self.save() order = self.order if self in order.logistics: order.logistics.remove(self) order.closed_logistics.append(self) order.save() def fork_by_entries(self, entry_ids): def get_entry(entries, eid): for e in entries: if str(eid) == str(e.id): return e return False if not type(entry_ids) is list: return False if len(self.entries) < 2: return False if self.detail.status != 'PAYMENT_RECEIVED': self.detail.status = 'PROCESSING' self.detail.processing_date = datetime.datetime.utcnow() self.save() forked_order = Logistic(detail=self.detail) entries = [get_entry(self.entries, e) for e in entry_ids] if not entries: False forked_order.entries = entries forked_order.order = self.order forked_order.save() self.order.logistics.append(forked_order) self.order.save() map(lambda e: self.entries.remove(e), entries) self.save() old_ptn = self.detail.partner_tracking_no new_ptn = gen_uid() forked_order.detail.partner_tracking_no = new_ptn remark = u"%s拆出了%s" % (old_ptn, new_ptn) forked_order.update_remark(remark, current_user.name) self.update_remark(remark, current_user.name) return self, forked_order def update_status(self, new_status): try: index = LOG_STATS.index(new_status) except ValueError: return log = self.detail old_status = log.status if index > LOG_STATS.index(old_status): log.status = new_status now = datetime.datetime.utcnow() date_field = log.attr_by_log_stat[new_status] if not getattr(log, date_field): setattr(log, date_field, now) self.save() self.order.update_logistic_status() def update_logistic(self, info): log = self.detail next_status = info.get('status', '') require = { 'SHIPPING': ['cn_tracking_no', 'cn_logistic_name'], } if next_status in require.keys(): values = require[next_status] for value in values: if value in info: continue value = getattr(self.detail, value) if value in [None, '', 'None', '?', u'?', u'None', 'Undefined', u'Undefined']: raise Exception("Fail to update logistic") if current_user and not current_user.is_anonymous: modified_by = current_user.name else: modified_by = 'system' self.update_status(next_status) remark = info.get('remark', None) if remark: self.update_remark(remark, modified_by) delay_content = info.get('delay', None) if delay_content: self.update_delay(delay_content, modified_by) irregularity = info.get('irregularity', None) if irregularity: self.update_irregularity(irregularity, modified_by) for field, value in info.items(): if field in ['status']: continue setattr(log, field, value) self.save() Signals.logistic_stat_changed.send( 'send to logistic partner', lo=self, status=next_status) Signals.logistic_info_updated.send('system', logistic=self) def update_remark(self, content, modified_by): remark = LogisticRemark(content=content, creator=modified_by) self.detail.remarks.append(remark) self.save() def update_delay(self, content, modified_by): delays = self.detail.delay_details.filter(status=self.detail.status) if delays: delays.update(reason=content, creator=modified_by) else: delay = LogisticDelay( reason=content, creator=modified_by, status=self.detail.status ) self.detail.delay_details.append(delay) remark = LogisticRemark(content=content, creator=modified_by) self.detail.remarks.append(remark) self.save() def update_irregularity(self, irregularity, modified_by): irregularity.creator = modified_by self.detail.irregular_details.append(irregularity) self.save() @property def shipping_history(self): res = [] status = self.detail.status for st in LOG_STATS: field = self.detail.attr_by_log_stat[st] val = getattr(self.detail, field) if not val: continue res.append({'time': format_date(val), 'desc': SHIPPING_HISTORY[st], 'status': st}) if st == status: break return res @property def express_tracking(self): import application.models as Models com = self.detail.cn_logistic_name nu = self.detail.cn_tracking_no if com and nu: tracking = Models.ExpressTracking.find( company=com, number=nu) return tracking return None @classmethod def pre_save_post_validation(cls, sender, document, **kwargs): if not kwargs['created']: old_log = cls.objects(id=document.id).first().detail new_log = document.detail for field in LogisticDetail._fields: if field == 'modified': continue if (getattr(old_log, field) != getattr(new_log, field)): new_log.modified = datetime.datetime.utcnow() if current_user and not current_user.is_anonymous: new_log.modified_by = current_user.name else: new_log.modified_by = 'system' return else: document.detail.modified = datetime.datetime.utcnow()
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()
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)