12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838 |
- # -*- coding: utf-8 -*-
- # !/usr/bin/env python
- import datetime
- import logging
- import warnings
- from bson import ObjectId
- from django.conf import settings
- from django.contrib.auth.hashers import make_password, check_password
- from mongoengine import StringField, DateTimeField, DynamicDocument, ListField, IntField, BooleanField, DictField, \
- ObjectIdField, EmbeddedDocument, EmbeddedDocumentListField, DoesNotExist
- from typing import Optional, List, Dict, TYPE_CHECKING
- from apilib.monetary import RMB, sum_rmb, AccuracyRMB
- from apilib.systypes import IterConstant
- from apilib.utils_datetime import to_datetime, get_zero_time
- from apilib.utils_json import json_dumps, json_loads
- from apilib.utils_sys import memcache_lock, ThreadLock
- from apps.web import district
- from apps.web.agent.define import AGENT_INCOME_TYPE
- from apps.web.common.transaction import WithdrawStatus, WITHDRAW_PAY_TYPE, WithdrawHandler, OrderNoMaker, OrderMainType
- from apps.web.constant import Const
- from apps.web.core import APP_KEY_DELIMITER, PayAppType, ROLE
- from apps.web.core.db import Searchable, StrictDictField, MonetaryField, RoleBaseDocument, PermillageField, \
- AccuracyMoneyField, BooleanIntField
- from apps.web.core.exceptions import ImproperlyConfigured
- from apps.web.core.models import WechatPayApp
- from apps.web.core.payment import WithdrawGateway
- from apps.web.dealer.define import DEALER_INCOME_TYPE
- from apps.web.utils import LimitAttemptsManager
- from library.misc import BankAPI
- if TYPE_CHECKING:
- from apps.web.core.messages.sms import WithdrawSMSProvider
- from apps.web.core.payment.wechat import WechatPaymentGateway
- from apps.web.core.models import BankCard
- from pymongo.results import UpdateResult
- from apps.web.device.models import DeviceDict, GroupDict
- from apps.web.core import PayAppBase
- logger = logging.getLogger(__name__)
- class District(object):
- memDict = {}
- @staticmethod
- def load_in_mem(dict_, parentId = None):
- for value in dict_:
- if value.has_key('children'):
- District.memDict[value['value']] = {'text': value['text'], 'parentId': parentId}
- District.load_in_mem(value['children'], value['value'])
- else:
- District.memDict[value['value']] = {'text': value['text'], 'parentId': parentId}
- @staticmethod
- def get_district(districtId):
- if not District.memDict:
- District.load_in_mem(district.DISTRICT)
- textList = []
- area = District.memDict.get(districtId, {})
- while area.get('parentId'):
- textList.append(area.get('text', ''))
- area = District.memDict.get(area.get('parentId', {}))
- textList.append(area.get('text', ''))
- return ' '.join(textList[::-1])
- @staticmethod
- def get_area(districtId):
- if not District.memDict:
- District.load_in_mem(district.DISTRICT)
- area = District.memDict.get(districtId, {})
- return area.get('text')
- @staticmethod
- def Is_include(bigId, smallId):
- if not District.memDict:
- District.load_in_mem(district.DISTRICT)
- if bigId == smallId:
- return True
- parentId = District.memDict.get(smallId).get('parentId')
- while parentId is not None:
- if parentId == bigId:
- return True
- parentId = District.memDict.get(parentId).get('parentId')
- return False
- @staticmethod
- def get_subIds(districtId):
- if not District.memDict:
- District.load_in_mem(district.DISTRICT)
- result = []
- for k, v in District.memDict.items():
- if v['parentId'] == districtId:
- result.append(k)
- return result
- # DeviceWarningField = namedtuple("DeviceWarningField", "warningPart warningStatus warningTime warningDesc warningUart")
- #
- #
- # class DeviceWarning(DeviceWarningField):
- #
- # def to_cache_dict(self):
- # _key = self.warningPart
- # _value = {
- # "warningStatus": self.warningStatus,
- # "warningTime": self.warningTime,
- # "warningDesc": self.warningDesc,
- # "warningUart": self.warningUart
- # }
- #
- # return {_key: _value}
- #
- # @classmethod
- # def create(cls, **kwargs):
- #
- # return cls(
- # warningPart=kwargs["warningPart"],
- # warningStatus=kwargs["warningStatus"],
- # warningTime=kwargs["warningTime"],
- # warningDesc=kwargs.get("warningDesc", ""),
- # warningUart=kwargs.get("warningUart", "")
- # )
- #
- # def to_dict(self):
- # return {
- # "index": self.warningPart if self.warningPart.isdigit() else 0,
- # "warningStatus": self.warningStatus,
- # "warningTime": self.warningTime,
- # "warningDesc": self.warningDesc
- # }
- #
- # @property
- # def isPart(self):
- # """
- # 是否是部件告警信息
- # :return:
- # """
- # return self.warningPart.isdigit()
- #
- class FrontendLog(Searchable):
- """
- 从前端收集到的日志
- """
- context = StringField(verbose_name = "上下文", default = "")
- cookies = StringField(verbose_name = "cookie", default = "")
- level = StringField(verbose_name = "日志级别")
- content = StringField(verbose_name = "日志内容", min_length = 1)
- path = StringField(verbose_name = "哪个页面")
- dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = '生成时间')
- search_fields = ('content', 'level', 'context', 'path')
- meta = {'collection': 'frontend_logs', 'db_alias': 'logdata'}
- class CardBank(DynamicDocument):
- warnings.warn("CardBank is deprecated", DeprecationWarning)
- CardType = StringField(verbose_name = '银行卡类名', unique = True, null = False)
- CardName = StringField(verbose_name = '银行卡名称', null = False)
- meta = {'collection': 'card_bank', 'db_alias': 'default'}
- buffer = {}
- buffer_lock = ThreadLock()
- @classmethod
- def get_collection(cls):
- return cls._get_collection()
- @staticmethod
- def acquire_lock():
- CardBank.buffer_lock.acquire_lock()
- @staticmethod
- def release_lock():
- CardBank.buffer_lock.release_lock()
- @staticmethod
- def try_load_and_acquire_lock():
- CardBank.buffer_lock.acquire_lock()
- CardBank.load_in_mem()
- @staticmethod
- def reset_buffer():
- try:
- CardBank.acquire_lock()
- CardBank.buffer = {}
- finally:
- CardBank.release_lock()
- @staticmethod
- def load_in_mem():
- try:
- CardBank.acquire_lock()
- if CardBank.buffer:
- return
- banks = CardBank.objects.all()
- for bank in banks:
- CardBank.buffer[bank.CardType] = bank.CardName
- finally:
- CardBank.release_lock()
- @staticmethod
- def get(cardType):
- try:
- CardBank.try_load_and_acquire_lock()
- if cardType in CardBank.buffer:
- return CardBank.buffer[cardType]
- else:
- return None
- finally:
- CardBank.release_lock()
- @staticmethod
- def has(cardType):
- try:
- CardBank.try_load_and_acquire_lock()
- if cardType in CardBank.buffer:
- return True
- else:
- return False
- finally:
- CardBank.release_lock()
- # : 微信支持的银行列表
- #: REF https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=24_4
- class Banks(DynamicDocument):
- CARD_TYPE_MAP = {
- 'DC': "储蓄卡",
- 'CC': "信用卡",
- 'SCC': "准贷记卡",
- 'PC': "预付费卡"
- }
- bankName = StringField(verbose_name = u'银行名称', unique = True, null = False)
- bankName2 = StringField(verbose_name = u'银行名称2')
- bankCode = StringField(verbose_name = u'银行code', unique = True, null = False)
- sn = IntField(verbose_name = u'排序序号', default = 0)
- wechatBankCode = StringField(verbose_name = u'微信银行ID', default = '')
- patterns = ListField(verbose_name = u'匹配正则', default = [])
- meta = {'collection': 'banks', 'db_alias': 'default'}
- buffer = {}
- buffer_lock = ThreadLock()
- def to_dict(self):
- return {
- 'bankCode': self.bankCode,
- 'bankName': self.bankName,
- 'bankName2': self.bankName2,
- 'wechatBankCode': self.wechatBankCode,
- 'patterns': self.patterns
- }
- @classmethod
- def get_bank_info(cls, cardNo):
- cls.__load_in_mem()
- bank_info = BankAPI().get_bank_card_info(card_no = cardNo)
- if bank_info:
- bank_code = bank_info['bankCode']
- card_type = bank_info['cardType']
- if bank_info['bankCode'] in cls.buffer['code']:
- bank_name = cls.buffer['code'][bank_code]['bankName']
- bank_name2 = cls.buffer['code'][bank_code]['bankName2']
- card_type = cls.CARD_TYPE_MAP.get(card_type, card_type)
- return {
- 'cardNo': cardNo,
- 'bankCode': bank_code,
- 'cardType': card_type,
- 'bankName': bank_name,
- 'bankName2': bank_name2
- }
- else:
- return {
- 'cardNo': cardNo,
- 'bankCode': bank_code,
- 'cardType': card_type,
- 'bankName': '',
- 'bankName2': ''
- }
- return None
- @classmethod
- def get_public_banks(cls):
- cls.__load_in_mem()
- return cls.buffer['public']
- @classmethod
- def get_personal_banks(cls):
- cls.__load_in_mem()
- return cls.buffer['personal']
- @classmethod
- def support_personal(cls, bankName):
- cls.__load_in_mem()
- return bankName in cls.buffer['personal']
- @classmethod
- def support_public(cls, bankName):
- cls.__load_in_mem()
- return bankName in cls.buffer['public']
- @classmethod
- def get_wechat_bank_code(cls, bankName):
- cls.__load_in_mem()
- if bankName in cls.buffer['personal']:
- return cls.buffer['name'][bankName]['wechatBankCode']
- else:
- return ''
- @classmethod
- def get_collection(cls):
- return cls._get_collection()
- @classmethod
- def __acquire_lock(cls):
- cls.buffer_lock.acquire_lock()
- @classmethod
- def __release_lock(cls):
- cls.buffer_lock.release_lock()
- def reset_buffer(self):
- try:
- self.__acquire_lock()
- self.buffer = {}
- finally:
- self.__release_lock()
- @classmethod
- def __load_in_mem(cls):
- if cls.buffer:
- return
- try:
- cls.__acquire_lock()
- if cls.buffer:
- return
- cls.buffer = {
- 'public': [], # type: list
- 'personal': [], # type: list
- 'name': {},
- 'code': {}
- }
- banks = cls.objects().order_by('sn')
- for bank in banks:
- cls.buffer['name'][bank.bankName] = bank.to_dict()
- cls.buffer['name'][bank.bankName2] = bank.to_dict()
- cls.buffer['code'][bank.bankCode] = bank.to_dict()
- cls.buffer['public'].append(bank.bankName)
- if bank.bankName2:
- cls.buffer['public'].append(bank.bankName2)
- if bank.wechatBankCode:
- cls.buffer['personal'].append(bank.bankName)
- if bank.bankName2:
- cls.buffer['personal'].append(bank.bankName2)
- finally:
- cls.__release_lock()
- class FAQ(Searchable):
- """
- 配置FAQ,首先让经销商|终端用户看到常用问题,找不到问题再联系客服
- """
- question = StringField(verbose_name = '问题')
- answer = StringField(verbose_name = '答案')
- target = StringField(verbose_name = '目标 := (Dealer|Agent|EndUser)', default = '*')
- devTypeId = StringField(verbose_name = '设备类型ID,默认为普适性问题,为空', default = '')
- images = ListField(verbose_name = '辅助说明图像')
- videos = ListField(verbose_name = '辅助说明视频')
- upvotes = IntField(verbose_name = '该FAQ有帮助')
- downvotes = IntField(verbose_name = '该FAQ没有帮助')
- managerId = StringField(verbose_name = '管理员ID', null = False)
- meta = {'collection': 'faqs', 'db_alias': 'default'}
- def to_dict(self):
- return {
- 'id': str(self.id),
- 'question': self.question,
- 'answer': self.answer,
- 'target': self.target,
- 'images': self.images,
- 'videos': self.videos,
- 'devTypeId': self.devTypeId,
- 'upvotes': self.upvotes,
- 'downvotes': self.downvotes
- }
- @classmethod
- def get_by_role(cls, role):
- #: 目前无用户体系,暂时将匿名用户作为终端用户
- role = ROLE.myuser if role == ROLE.anonymoususer else role
- return cls.objects(target__in = ['*', role])
- class Feature(Searchable):
- """
- 特性列表的所有特性,由超级管理员配置
- (角色,特性名称) 唯一
- """
- name = StringField(verbose_name = u'特性名称', min_length = 1)
- key = StringField(verbose_name = u'具体的特性代号,为英文')
- role = StringField(verbose_name = u'特性分配的角色', choices = ROLE.choices())
- # managerId = StringField(verbose_name = u'厂商ID')
- default = BooleanField(verbose_name = u'默认值')
- # by = StringField(verbose_name = u'创建的超级管理员的ID')
- desc = StringField(verbose_name = u'描述')
- dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = u'生成时间')
- search_fields = ('key', 'name')
- meta = {
- 'indexes': [
- {'fields': ['name', 'role'], 'unique': True},
- ],
- "collection": "features",
- "db_alias": "default"
- }
- @classmethod
- def for_dealer(cls, **kwargs): return cls.objects(role = ROLE.dealer, **kwargs)
- @classmethod
- def for_agent(cls, **kwargs): return cls.objects(role = ROLE.agent, **kwargs)
- @classmethod
- def for_manager(cls, **kwargs): return cls.objects(role = ROLE.manager, **kwargs)
- def to_dict(self):
- return {
- 'id': str(self.id),
- 'role': self.role,
- 'name': self.name,
- # 'managerId': self.managerId,
- # 'by': self.by,
- 'key': self.key,
- 'default': self.default,
- 'desc': self.desc,
- 'dateTimeAdded': self.dateTimeAdded
- }
- def __repr__(self):
- return '<Feature key=%s, role=%s>' % (self.key, self.role)
- DEFAULT_DEALER_FEATURES = ['show_offline_coins']
- class AddressType(Searchable):
- value = StringField(verbose_name = "地址值", unique = True) #: e.g. school
- label = StringField(verbose_name = "地址名称", default = "") #: e.g. 学校
- showWeight = IntField(verbose_name = '显示权重', default = 0)
- createdAt = DateTimeField(default = datetime.datetime.now)
- meta = {"collection": "AddressType", "db_alias": "default"}
- class UserSearchable(RoleBaseDocument):
- class Status(IterConstant):
- NORMAL = 'normal'
- FORBIDDEN = 'forbidden'
- ABNORMAL = 'abnormal'
- NOWITHDRAW = 'noWithdraw'
- username = StringField(verbose_name = "用户名称:=手机号", max_length = 100)
- password = StringField(verbose_name = "密码", min_length = 1)
- nickname = StringField(verbose_name = '名字,用来标识', default = '')
- remarks = StringField(verbose_name = "备注", default = "")
- avatar = StringField(verbose_name = u"用户头像", default = "")
- activited = BooleanField(verbose_name = "是否激活", default = True)
- status = StringField(verbose_name = "账号状态", default = Status.NORMAL)
- dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = '添加进来的时间')
- last_login = DateTimeField(default = datetime.datetime.now, verbose_name = '最近登录时间')
- phoneOS = StringField(verbose_name = u"终端操作系统", default = "")
- lastLoginUserAgent = StringField(verbose_name = u"最后一次登录的userAgent", default = "")
- smsVendor = StringField(verbose_name = u'sms提供商', default = '')
- meta = {
- 'abstract': True,
- 'indexes': [
- {
- 'fields': ['$username', '$nickname'],
- 'weights': {'username': 5, 'nickname': 5}
- }
- ]
- }
- def __str__(self):
- return '{}<id={} username={} nickname={}>'.format(
- self.__class__.__name__, str(self.id), self.username, self.nickname)
- @property
- def human_id(self):
- return '{}({})'.format(self.username, self.role)
- @property
- def identity_id(self):
- return '{}_{}'.format(self.role, str(self.id))
- def to_dict(self, shadow = False):
- return {
- 'username': self.username if not shadow else '******',
- 'nickname': self.nickname,
- 'remarks': self.remarks,
- 'activited': self.activited,
- 'status': self.status,
- 'dateTimeAdded': self.dateTimeAdded,
- 'last_login': self.last_login,
- 'role': self.role,
- 'avatar': self.my_avatar
- }
- @classmethod
- def create_user(cls, username, password, **attrs):
- user = cls(username = username, **attrs)
- user.password = make_password(password)
- user.save()
- return user
- def is_authenticated(self):
- return True
- def set_password(self, raw_password):
- self.password = make_password(raw_password)
- self.save()
- return self
- def check_password(self, raw_password):
- return check_password(raw_password, self.password)
- def unlock_login(self):
- lm = LimitAttemptsManager(identifier = self.username, category = "%sLogin" % self.role)
- lm.clear()
- @classmethod
- def get_last_login1(cls, **kwargs):
- return cls.objects(**kwargs).order_by('-last_login').first()
- def __check_feature_setup(self):
- # type:()->(str, set)
- self_features = getattr(self, 'features', None)
- if self_features is None:
- raise ImproperlyConfigured('no features attr provided')
- return self.role, set(self_features)
- ## Feature Module
- @property
- def feature_list(self):
- # type:()->List[dict]
- role, self_features = self.__check_feature_setup()
- features = [_.to_dict() for _ in Feature.objects(role = role)] # type: List[dict]
- rv = [
- {
- 'name': feature['name'],
- 'key': feature['key'],
- 'value': feature['key'] in self_features
- } for feature in features
- ]
- #: unique to user, special features
- # set difference
- unique_features = self_features - {_['key'] for _ in features}
- rv += [{'name': _, 'key': _, 'value': True} for _ in unique_features]
- return rv
- def edit_feature_list(self, feature_list, special = None):
- # type:(list, Optional[str])->int
- self.__check_feature_setup()
- features = [_['key'] for _ in feature_list if _['value']]
- if special:
- features.append(special)
- return self.update(set__features = features)
- @property
- def feature_boolean_map(self):
- # type: ()->Dict[str, bool]
- features = {}
- for _ in self.feature_list:
- if _['key']:
- features.update({_['key']: _['value']})
- return features
- def supports(self, feature):
- # type: (str)->bool
- _, self_features = self.__check_feature_setup()
- return feature in self_features
- @property
- def normal(self):
- return self.status in [self.Status.NORMAL, self.Status.NOWITHDRAW]
- @property
- def forbidden(self):
- return self.status == self.Status.FORBIDDEN
- @property
- def abnormal(self):
- return self.status == self.Status.ABNORMAL
- @property
- def no_withdraw(self):
- return self.status == self.Status.NOWITHDRAW
- @property
- def my_avatar(self):
- if self.avatar:
- return self.avatar
- else:
- return ''
- @property
- def universal_password_login(self):
- return getattr(self, '__universal_password_login__', False)
- @universal_password_login.setter
- def universal_password_login(self, upl):
- setattr(self, '__universal_password_login__', upl)
- class CapitalUser(UserSearchable):
- """
- 带资金以及提现用户
- """
- meta = {
- 'abstract': True,
- }
- INCOME_SOURCE_LIST = list()
- INCOME_SOURCE_TO_TYPE = dict()
- INCOME_TYPE_LIST = list()
- INCOME_TYPE_TO_FIELD = dict()
- DEFAULT_WITHDRAW_OPTIONS = {
- 'autoWithdrawSwitch': False,
- 'autoWithdrawType': "wechat",
- 'autoWithdrawMin': RMB(settings.WITHDRAW_MINIMUM).mongo_amount,
- 'autoWithdrawStrategy': {'type': 'asWeek', 'value': 3},
- 'autoWithdrawBankFee': True
- }
- ongoingWithdrawList = ListField(field = StringField())
- inhandWithdrawList = ListField(field = DictField())
- withdrawOptions = DictField(verbose_name = u"提现相关选项", default = DEFAULT_WITHDRAW_OPTIONS)
- @classmethod
- def income_field_name(cls, income_type):
- # type: (str)->str
- assert income_type in cls.INCOME_TYPE_LIST, 'not support this income type({})'.format(income_type)
- return cls.INCOME_TYPE_TO_FIELD[income_type]
- @classmethod
- def fund_key(cls, income_type, source_key):
- return '{field}.{key}'.format(field = cls.income_field_name(income_type), key = source_key)
- def balance_dict(self, income_type):
- return dict(getattr(self, self.income_field_name(income_type = income_type)))
- def set_balance(self, income_type, source_key, money):
- # type: (str, str, RMB)->bool
- """
- 单元测试使用.初始化值
- :param income_type:
- :param source_key:
- :param money:
- :return:
- """
- assert isinstance(money, RMB), 'money has to be a RMB instance'
- assert income_type in self.INCOME_TYPE_LIST, 'not support this source{}'.format(income_type)
- assert source_key, 'source key must not be none'
- query = {'_id': ObjectId(self.id)}
- update = {
- '$set': {
- '{fund_key}.balance'.format(fund_key = self.fund_key(income_type, source_key)): money.mongo_amount,
- '{fund_key}.frozenBalance'.format(fund_key = self.fund_key(income_type, source_key)): RMB(
- 0).mongo_amount
- },
- }
- result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult
- return bool(result.modified_count == 1)
- def incr_fund(self, income_type, source_key, money):
- # type: (str, str, RMB)->bool
- assert isinstance(money, RMB), 'money has to be a RMB instance'
- assert income_type in self.INCOME_TYPE_LIST, 'not support this source'
- assert source_key, 'gateway key must not be none'
- query = {'_id': ObjectId(self.id)}
- update = {
- '$inc': {
- '{fund_key}.balance'.format(fund_key = self.fund_key(income_type, source_key)): money.mongo_amount
- }
- }
- result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult
- return bool(result.modified_count == 1)
- def decr_fund(self, income_type, source_key, money):
- # type: (str, str, RMB)->int
- """
- 正常流程不使用该接口,会让账对不上
- :param income_type:
- :param source_key:
- :param money:
- :return:
- """
- assert isinstance(money, RMB), 'money has to be a RMB instance'
- assert income_type in self.INCOME_TYPE_LIST, 'not support this source'
- assert source_key, 'gateway key must not be none'
- query = {'_id': ObjectId(self.id)}
- update = {
- '$inc':
- {
- '{fund_key}.balance'.format(fund_key = self.fund_key(income_type, source_key)): (
- -money).mongo_amount
- }
- }
- result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult
- return bool(result.modified_count == 1)
- @classmethod
- def prepare_sellet_income(cls, object_id, transaction_id, source, source_key, money, selfAgg=False):
- assert isinstance(money, RMB), 'money has to be a RMB instance'
- assert source in cls.INCOME_SOURCE_LIST, 'not support this source'
- assert source_key, 'source key must not be none'
- if money == RMB(0):
- logger.debug('{}<id={}> sellet zero.'.format(cls.__name__, object_id))
- return
- income_type = cls.INCOME_SOURCE_TO_TYPE[source]
- query = {
- '_id': ObjectId(object_id),
- 'ongoingWithdrawList.transaction_id': {
- '$ne': transaction_id
- }
- }
- update = {
- '$inc': {
- '{filed}.{key}.balance'.format(filed=cls.INCOME_TYPE_TO_FIELD[income_type],
- key=source_key): money.mongo_amount
- },
- '$addToSet': {
- 'ongoingWithdrawList': {'transaction_id': transaction_id, 'money': money.mongo_amount}
- }
- }
- if selfAgg:
- update['$inc'].update({
- 'aggregatedIncome.{source}'.format(source=source): money.mongo_amount,
- 'incomeMap.{source}'.format(source=source): money.mongo_amount
- })
- result = cls.get_collection().update_one(query, update, upsert=False) # type: UpdateResult
- return bool(result.modified_count == 1)
- @classmethod
- def commit_sellet_income(cls, object_id, transaction_id):
- query = {
- '_id': ObjectId(object_id),
- 'ongoingWithdrawList': {
- '$elemMatch': {
- 'transaction_id': transaction_id
- }
- }
- }
- update = {
- '$pull': {
- 'ongoingWithdrawList': {'transaction_id': transaction_id}
- }
- }
- result = cls.get_collection().update_one(query, update, upsert=False) # type: UpdateResult
- return bool(result.modified_count == 1)
- def sub_balance(self, income_type, source_key=None, only_ledger=True):
- # type: (str, str, bool)->RMB
- balance_dict = self.balance_dict(income_type=income_type) # type: dict
- if not source_key:
- balance_list = []
- for key, value in balance_dict.iteritems():
- if only_ledger:
- if WithdrawGateway.is_ledger(key):
- balance_list.append(value.balance)
- else:
- balance_list.append(value.balance)
- return sum_rmb(balance_list)
- else:
- return balance_dict.get(source_key, Balance()).balance
- @property
- def total_balance(self):
- # type: ()->RMB
- total = RMB(0)
- for income_type in self.INCOME_TYPE_LIST:
- total += self.sub_balance(income_type)
- return total
- def sub_frozen_balance(self, income_type, source_key = None):
- # type: (str, str)->RMB
- field = self.income_field_name(income_type = income_type)
- balance_list = []
- for item in self.inhandWithdrawList:
- if item['field'] != field:
- continue
- if source_key and item['key'] != source_key:
- continue
- balance_list.append(item['value'])
- balance_dict = self.balance_dict(income_type = income_type) # type: dict
- if not source_key:
- for key, value in balance_dict.iteritems():
- if WithdrawGateway.is_ledger(key):
- balance_list.append(value.frozenBalance)
- return sum_rmb(balance_list)
- else:
- balance_list.append(balance_dict.get(source_key, Balance()).frozenBalance)
- return sum_rmb(balance_list)
- @property
- def total_frozen_balance(self):
- # type: ()->RMB
- total = RMB(0)
- for income_type in self.INCOME_TYPE_LIST:
- total += self.sub_frozen_balance(income_type)
- return total
- def _freeze_balance(self, income_type, money, source_key, transaction_id, freeze_type):
- assert source_key, 'gateway is null'
- assert transaction_id, 'transaction id is null'
- field = self.income_field_name(income_type = income_type)
- fund_key = self.fund_key(income_type = income_type, source_key = source_key)
- query = {'_id': self.id, '{}.transaction_id'.format(freeze_type): {'$ne': transaction_id}}
- update = {
- '$inc': {
- '{fund_key}.balance'.format(fund_key=fund_key): (-money).mongo_amount
- },
- '$addToSet': {
- freeze_type: {
- 'transaction_id': transaction_id,
- 'field': field,
- 'key': source_key,
- 'value': money.mongo_amount
- }
- }
- }
- result = self.get_collection().update_one(query, update) # type: UpdateResult
- return bool(result.modified_count == 1)
- def _clear_frozen_balance(self, transaction_id, freeze_type):
- query = {'_id': self.id, '{}.transaction_id'.format(freeze_type): transaction_id}
- update = {
- '$pull': {
- freeze_type:
- {
- 'transaction_id': transaction_id
- }
- }
- }
- result = self.get_collection().update_one(query, update) # type: UpdateResult
- return bool(result.modified_count == 1)
- def _recover_frozen_balance(self, income_type, money, source_key, transaction_id, freeze_type):
- assert source_key, 'gateway is null'
- assert transaction_id, 'transaction id is null'
- fund_key = self.fund_key(income_type=income_type, source_key=source_key)
- query = {'_id': self.id, '{}.transaction_id'.format(freeze_type): transaction_id}
- update = {
- '$inc': {
- '{fund_key}.balance'.format(fund_key=fund_key): money.mongo_amount
- },
- '$pull': {
- freeze_type:
- {
- 'transaction_id': transaction_id
- }
- }
- }
- result = self.get_collection().update_one(query, update) # type: UpdateResult
- return bool(result.modified_count == 1)
- def freeze_balance(self, income_type, money, source_key, transaction_id): # type:(str, RMB, str, str)->bool
- """
- 提现的时候冻结经销商的金额
- :param source_key:
- :param income_type:
- :param money:
- :param transaction_id:
- """
- return self._freeze_balance(income_type, money, source_key, transaction_id, "inhandWithdrawList")
- def clear_frozen_balance(self, transaction_id): # type:(str)->bool
- """
- 提现完成 清理冻结
- :param transaction_id:
- :return:
- """
- assert transaction_id, 'transaction id is null'
- return self._clear_frozen_balance(transaction_id, "inhandWithdrawList")
- def recover_frozen_balance(self, income_type, money, source_key, transaction_id): # type:(str, RMB, str, str)->bool
- """
- 提现遇到问题 直接回滚
- :param source_key:
- :param income_type:
- :param money:
- :param transaction_id:
- :return:
- """
- assert source_key, 'gateway is null'
- assert transaction_id, 'transaction id is null'
- return self._recover_frozen_balance(income_type, money, source_key, transaction_id, "inhandWithdrawList")
- def withdraw_source_key(self, pay_app = None):
- # type:(PayAppBase)->str
- """
- 供PaymentGateway调用获取提现网关
- :param pay_app:
- :return:
- """
- raise NotImplementedError()
- @property
- def withdraw_sms_provider(self):
- # type: () -> WithdrawSMSProvider
- raise NotImplementedError()
- @property
- def withdraw_sms_phone_number(self):
- # type: () -> basestring
- raise NotImplementedError()
- def new_withdraw_record(self, withdraw_gateway, pay_entity, source_key, income_type, amount, pay_type, manual, recurrent):
- # type: (WithdrawGateway, BankCard, str, str, RMB, str, bool, bool) -> WithdrawRecord
- raise NotImplementedError()
- def new_withdraw_handler(self, record):
- # type: (WithdrawRecord) -> WithdrawHandler
- raise NotImplementedError()
- def get_bound_pay_openid(self, key):
- raise NotImplementedError()
- def withdraw_bank_card(self, bank_card_no = None):
- # type: (str)->BankCard
- raise NotImplementedError()
- @property
- def auto_withdraw_min(self):
- return self.withdrawOptions.get('autoWithdrawMin')
- @property
- def auto_withdraw_type(self):
- return self.withdrawOptions.get('autoWithdrawType')
- @property
- def auto_withdraw_switch(self):
- return self.withdrawOptions.get('autoWithdrawSwitch')
- @property
- def auto_withdraw_strategy(self):
- return self.withdrawOptions.get('autoWithdrawStrategy')
- @property
- def auto_withdraw_bank_fee(self):
- return self.withdrawOptions.get('autoWithdrawBankFee', True)
- @property
- def withdraw_open_id(self):
- return getattr(self, '__withdraw_openid__')
- @withdraw_open_id.setter
- def withdraw_open_id(self, open_id):
- setattr(self, '__withdraw_openid__', open_id)
- @property
- def auto_withdraw_bound_open_id(self):
- raise NotImplementedError()
- def can_withdraw_today(self):
- return True
- @property
- def current_wallet_withdraw_source_key(self):
- logger.debug('get withdraw source key. source = {}'.format(repr(self)))
- from apps.web.helpers import AgentCustomizedBrandVisitor
- return AgentCustomizedBrandVisitor(factory_type = 'withdraw_source_key', pay_app = None).visit(self)
- class WithdrawRefundRecord(Searchable):
- withdraw_record_id = ObjectIdField(unique = True)
- createdTime = DateTimeField(default = datetime.datetime.now)
- role = StringField(choices = ROLE.choices())
- meta = {
- "collection": "withdraw_refund_record",
- "db_alias": "logdata"
- }
- class WithdrawRecord(Searchable):
- class WithdrawPayType(IterConstant):
- BANK = 'bank'
- WECHAT = 'wechat'
- order = StringField(verbose_name = u"订单号", unique = True)
- ownerId = StringField(verbose_name = u"提现者对应对象ID")
- role = StringField(verbose_name = u'提现者账号类型', choices = ROLE.choices())
- name = StringField(verbose_name = u"提现人姓名", default = "") # type: str
- phone = StringField(verbose_name = u"持卡人电话", default = "") # type: str
- balance = MonetaryField(verbose_name = u"提现人提现时刻余额", default = RMB('0.00')) # type: RMB
- incomeType = StringField(verbose_name = u'提现收入类型',
- choices = AGENT_INCOME_TYPE.choices() + DEALER_INCOME_TYPE.choices())
- withdrawSourceKey = StringField(verbose_name = u'提现来源KEY', default = None)
- payType = StringField(verbose_name = u"支付方式(微信,银行卡)", default = "") # type: str
- manual = BooleanField(verbose_name = u'是否手动处理', default = False) # type: bool
- recurrent = BooleanField(verbose_name = u"是否为自动循环提现", default = False)
- status = IntField(verbose_name = u"状态", default = WithdrawStatus.SUBMITTED)
- remarks = StringField(verbose_name = u"备注系统内部状态", default = "")
- description = StringField(verbose_name = u'给用户看的描述', default = '')
- postTime = DateTimeField(verbose_name = u"请求时间", default = datetime.datetime.now)
- finishedTime = DateTimeField(verbose_name = u"提现订单完成时间")
- amount = MonetaryField(verbose_name = u"提现金额", default = RMB('0.00')) # type: RMB
- serviceFee = MonetaryField(verbose_name = u"手续费", default = RMB('0.00')) # type: RMB
- actualPay = MonetaryField(verbose_name = u"实际金额", default = RMB('0.00')) # type: RMB
- bankTransFee = MonetaryField(verbose_name = u"转账银行费用", default = RMB('0.00')) # type: RMB
- refunded = BooleanField(verbose_name = u'是否已经退款了', default = False)
- cardUserName = StringField(verbose_name = u"持卡人姓名", default = "")
- accountCode = StringField(verbose_name = u"提现银行卡号", default = "")
- parentBankName = StringField(verbose_name = u"银行名称", default = "")
- subBankName = StringField(verbose_name = u"银行支行名称", default = "")
- payAgentId = StringField(verbose_name = u'提现处理平台代理商ID')
- withdrawFeeRatio = PermillageField(verbose_name = u'提现费率', default = Const.PLATFORM_DEFAULT_WITHDRAW_FEE_RATIO)
- partition = ListField(verbose_name = u'提现费率收入划分', default = [])
- withdrawGatewayKey = StringField(verbose_name = u"提现资金池gateway key", required = True)
- extras = DictField(verbose_name = u"记录提现额外信息", default = {})
- search_fields = ('order', 'phone', 'name')
- meta = {
- 'indexes': [
- {
- 'fields': ['ownerId', 'role'],
- },
- 'status',
- '-postTime',
- 'payAgentId'
- ],
- 'collection': 'WithdrawRecord',
- 'db_alias': 'default'
- }
- @classmethod
- def get_record(cls, order_no):
- return cls.objects(order = order_no).first()
- @classmethod
- def get_collection(cls):
- return cls._get_collection()
- @classmethod
- def get_succeeded_records(cls, **kwargs):
- return cls.objects(status = WithdrawStatus.SUCCEEDED, **kwargs)
- @classmethod
- def get_processing_via_bank(cls):
- return cls.objects(status__in = [WithdrawStatus.PROCESSING, WithdrawStatus.BANK_PROCESSION],
- payType = WITHDRAW_PAY_TYPE.BANK, manual = False)
- @classmethod
- def get_failed_records(cls):
- return cls.objects(status = WithdrawStatus.FAILED, refunded = False, manual = False)
- @classmethod
- def create(cls, withdraw_user, withdraw_gateway, pay_entity, source_key, income_type, pay_type, fund_map,
- manual, recurrent, **kwargs):
- # type: (CapitalUser, WithdrawGateway, BankCard, str, str, str, dict, bool, bool, dict) -> WithdrawRecord
- assert pay_type in (WITHDRAW_PAY_TYPE.WECHAT, WITHDRAW_PAY_TYPE.BANK), 'not support this pay type'
- if manual:
- assert pay_type == WITHDRAW_PAY_TYPE.BANK, 'pay type must be bank in manual'
- withdraw_record_payload = {
- 'order': WithdrawRecord.make_no(withdraw_user.role, str(withdraw_user.id)),
- 'ownerId': str(withdraw_user.id),
- 'role': withdraw_user.role,
- 'name': withdraw_user.nickname,
- 'phone': withdraw_user.username,
- 'balance': withdraw_user.sub_balance(income_type = income_type, source_key = source_key),
- 'incomeType': income_type,
- 'withdrawSourceKey': source_key,
- 'payType': pay_type,
- 'manual': manual,
- 'amount': fund_map.get('amount'),
- 'serviceFee': fund_map.get('serviceFee'),
- 'actualPay': fund_map.get('actualPay'),
- 'bankTransFee': fund_map.get('bankTransFee'),
- 'accountCode': pay_entity.cardNo,
- 'cardUserName': pay_entity.holderName,
- 'parentBankName': pay_entity.bankName,
- 'subBankName': pay_entity.branchName,
- 'partition': fund_map.get('partition'),
- 'payAgentId': withdraw_gateway.occupantId,
- 'withdrawGatewayKey': withdraw_gateway.gateway_key,
- 'withdrawFeeRatio': fund_map.get('withdrawFeeRatio'),
- 'extras': {'v': 2}
- }
- if pay_type == WITHDRAW_PAY_TYPE.BANK:
- withdraw_record_payload['extras'].update({
- 'withdrawBankCard': pay_entity.id
- })
- if kwargs:
- withdraw_record_payload.update(**kwargs)
- if hasattr(withdraw_gateway, 'version'):
- withdraw_record_payload['extras'].update({
- 'gateway_version': getattr(withdraw_gateway, 'version')
- })
- record = cls(**withdraw_record_payload)
- record.save()
- return record
- @property
- def finished(self):
- return self.status in WithdrawStatus.finished_status()
- def succeed(self, **kwargs):
- payload = {
- 'status': WithdrawStatus.SUCCEEDED
- }
- if kwargs:
- payload.update(kwargs)
- result = self.get_collection().update_one(
- filter = {'_id': ObjectId(self.id),
- 'status': {'$nin': WithdrawStatus.finished_status()}},
- update = {'$set': payload},
- upsert = False)
- return result.matched_count == 1
- def fail(self, remarks = '', description = u'支付网关返回失败'):
- result = self.get_collection().update_one(
- filter = {'_id': ObjectId(self.id),
- 'status': {'$nin': WithdrawStatus.finished_status()}},
- update = {'$set': {
- 'status': WithdrawStatus.FAILED,
- 'remarks': remarks,
- 'description': description,
- 'finishedTime': datetime.datetime.now()
- }},
- upsert = False)
- return result.matched_count == 1
- def revoke(self, remarks = '', description = u'提现失败'):
- result = self.get_collection().update_one(
- filter = {'_id': ObjectId(self.id),
- 'status': {'$nin': WithdrawStatus.finished_status()}},
- update = {'$set': {
- 'status': WithdrawStatus.CLOSED,
- 'refunded': True,
- 'remarks': remarks,
- 'description': description,
- 'finishedTime': datetime.datetime.now()
- }},
- upsert = False)
- return result.matched_count == 1
- def set_processing(self, remarks = u'提现申请已经受理', description = u'提现申请已经受理', **kwargs):
- extras = self.extras
- extras.update(**kwargs)
- set_payload = {
- 'status': WithdrawStatus.PROCESSING,
- 'remarks': remarks,
- 'description': description,
- 'extras': extras
- }
- result = self.get_collection().update_one(
- filter = {'_id': ObjectId(self.id),
- 'status': {'$nin': WithdrawStatus.finished_status()}},
- update = {'$set': set_payload},
- upsert = False)
- return result.matched_count == 1
- def set_bank_processing(self, **kwargs):
- extras = self.extras
- extras.update(**kwargs)
- set_payload = {
- 'status': WithdrawStatus.BANK_PROCESSION,
- 'description': u'银行正在处理中',
- 'remarks': '',
- 'extras': extras
- }
- result = self.get_collection().update_one(
- filter = {
- '_id': ObjectId(self.id),
- 'status': {
- '$nin': WithdrawStatus.finished_status()
- }
- },
- update = {'$set': set_payload},
- upsert = False)
- return result.matched_count == 1
- @staticmethod
- def make_no(role, user_id):
- # type: (str, str)->str
- assert role in ROLE.choices(), 'no this role'
- return OrderNoMaker.make_order_no_32(identifier = str(user_id),
- main_type = OrderMainType.WITHDRAW,
- sub_type = ROLE.sub_type(role))
- @staticmethod
- def is_my(order_no):
- try:
- return order_no[0] == OrderMainType.WITHDRAW and order_no[1] in ROLE.sub_type_list()
- except Exception as e:
- return False
- @staticmethod
- def process_memcache_lock(order):
- # type: (str)->None
- return memcache_lock('WithdrawRecord_%s' % order, 'processing')
- @property
- def source_key(self):
- if not self.withdrawSourceKey:
- pay_app_type, occupant_id, tokens = WithdrawGateway.parse_gateway_key(self.withdrawGatewayKey)
- appid, mchid = tokens[0], tokens[1]
- app = WechatPayApp(appid = appid, mchid = mchid) # type: WechatPayApp
- app.occupantId = occupant_id
- return APP_KEY_DELIMITER.join(
- [WithdrawGateway.LEDGER_PREFIX, PayAppType.WECHAT, getattr(app, '__source_key__')])
- else:
- return self.withdrawSourceKey
- @property
- def is_new_version(self):
- return self.extras.get('v', 1) == 2
- @classmethod
- def count_today(cls, ownerId, role = ROLE.dealer):
- return cls.objects(
- ownerId = ownerId, role = role,
- status__nin = [WithdrawStatus.CLOSED, WithdrawStatus.SUCCEEDED, WithdrawStatus.REFUND],
- postTime__gte = get_zero_time(datetime.datetime.now())).count()
- class PushRecord(Searchable):
- createdTime = DateTimeField(default = datetime.datetime.now)
- func_name = StringField()
- result = DictField()
- meta = {
- 'collection': 'push_records',
- 'db_alias': 'logdata',
- 'max_documents': 100,
- 'max_size': 2000000
- }
- def __repr__(self): return '<PushRecord func_name=%s>' % (self.func_name,)
- class DealerDoneReportRecord(DynamicDocument):
- reportName = StringField()
- reportDay = StringField()
- dealerIds = ListField()
- expireAt = DateTimeField(default = None)
- meta = {
- 'collection': 'dealer_done_report_record',
- 'db_alias': 'logdata'
- }
- @classmethod
- def update_done_list(cls, report_name, report_day, done_list):
- return cls.objects(reportName = report_name, reportDay = report_day).upsert_one(
- push_all__dealerIds = done_list,
- expireAt = to_datetime(report_day, time_format = "%Y-%m-%d") + datetime.timedelta(days = 7))
- class RefundWithdrawRecord(Searchable):
- """
- 退款提现单,需要记录给经销商/代理商退款的情况
- """
- withdraw_record_id = ObjectIdField(unique = True)
- createdTime = DateTimeField(default = datetime.datetime.now)
- meta = {
- 'abstract': True,
- }
- class ChangeLog(Searchable):
- """
- 在一些敏感的,涉及到需要确保后续可回溯的操作的时候,需要进行记录.
- TODO 这里更加合理的实现,是运用MongoDB的changeStream特性
- 脚本监听,然后过滤处理存储。
- """
- content = StringField()
- event = StringField()
- sender = StringField()
- detail = StrictDictField()
- pre = StrictDictField()
- post = StrictDictField()
- createdTime = DateTimeField(default = datetime.datetime.now)
- meta = {"collection": "change_logs", "db_alias": "logdata"}
- search_fields = ('content', 'event', 'sender')
- def to_dict(self):
- return {
- 'content': self.content,
- 'event': self.event,
- 'sender': self.sender,
- 'detail': self.detail
- }
- class Balance(EmbeddedDocument):
- balance = MonetaryField(verbose_name = '提现收入', default = RMB('0.00'))
- frozenBalance = MonetaryField(verbose_name = '冻结提现收入', default = RMB('0.00'))
- def __repr__(self):
- return "balance = {}, frozenBalance = {}".format(self.balance, self.frozenBalance)
- class AccuracyBalance(EmbeddedDocument):
- balance = AccuracyMoneyField(verbose_name = '提现收入', default = AccuracyRMB('0.00'))
- frozenBalance = AccuracyMoneyField(verbose_name = '冻结提现收入', default = AccuracyRMB('0.00'))
- class TempValues(Searchable):
- """
- 用于存放一些重要信息的临时数据库表,放memcached中不安全,就用这个
- """
- key = StringField(verbose_name = 'key', default = '', unique = True)
- value = StringField(verbose_name = 'value', default = '')
- expireAt = DateTimeField(default = None, verbose_name = u'过期时间')
- meta = {"collection": "temp_values", "db_alias": "logdata"}
- @staticmethod
- def get(key):
- obj = TempValues.objects.filter(__raw__ = {'key': key, '$or': [{'expireAt': {'$exists': False}}, {
- 'expireAt': {'$gte': datetime.datetime.now()}}]}).first()
- if obj:
- return json_loads(obj.value)
- return None
- @staticmethod
- def set(key, value, expire = 60 * 60):
- if isinstance(value, dict):
- _value = json_dumps(value, serialize_type = True)
- else:
- _value = value
- TempValues.objects(key = key).update_one(upsert = True,
- key = key, value = _value, expireAt = datetime.datetime.now() + datetime.timedelta(seconds = expire))
- @staticmethod
- def remove(key):
- try:
- TempValues.objects(key = key).update(
- expireAt = datetime.datetime.now() - datetime.timedelta(seconds = 8 * 60 * 60))
- except Exception, e:
- pass
- class OperatorLog(Searchable):
- """
- 关键用户行为变更日志
- 着重记录用户的并不高频但重要的操作,更改配置项
- """
- class LogLevel(IterConstant):
- INFO = 'INFO'
- NOTICE = 'NOTICE'
- WARNING = 'WARNING'
- CRITICAL = 'CRITICAL'
- EMERGENCY = 'EMERGENCY'
- username = StringField(verbose_name = u'操作员账号')
- operatorId = ObjectIdField(verbose_name=u'操作员关联ID')
- role = StringField(verbose_name = u'操作员角色')
- level = StringField(verbose_name = u'日志级别')
- operatorName = StringField(verbose_name = u'操作名称')
- content = DictField(verbose_name = u'日志内容')
- dateTimeAdded = DateTimeField(verbose_name = u'添加时间', default = datetime.datetime.now)
- meta = {'collection': 'operator_logs', 'db_alias': 'logdata'}
- @classmethod
- def log(cls, user, level, operator_name, content):
- # type: (UserSearchable, basestring, basestring, dict)->None
- try:
- cls(username = user.username,
- role = user.role,
- operatorId = user.id,
- level = level,
- content = content,
- operatorName = operator_name).save()
- except Exception, e:
- logger.exception(e)
- @classmethod
- def log_dev_operation(cls, operator, device, operator_name, content, level=LogLevel.WARNING):
- try:
- content.update({
- 'logicalCode': device.logicalCode,
- 'devNo': device.devNo,
- 'devTypeCode': device.devTypeCode
- })
- return cls(username=operator.username,
- role=operator.role,
- operatorId=operator.id,
- level=level,
- content=content,
- operatorName=operator_name).save()
- except Exception, e:
- logger.exception(e)
- @classmethod
- def record_dev_setting_changes_log(cls, user, operator_name, logicalCode, devTypeCode, content):
- # type: (UserSearchable, basestring, str, str, dict)->Searchable
- try:
- content.update({
- 'logicalCode': logicalCode,
- 'devTypeCode': devTypeCode
- })
- return cls(username = user.username,
- role = user.role,
- operatorId = user.id,
- level = 'INFO',
- content = content,
- operatorName = operator_name).save()
- except Exception, e:
- logger.exception(e)
- class ExceptionLog(Searchable):
- """
- 异常记录
- """
- user = StringField()
- dateTimeAdded = DateTimeField(verbose_name = u'添加时间', default = datetime.datetime.now)
- exception = StringField()
- extra = DictField()
- meta = {'collection': 'exception_log', 'db_alias': 'logdata'}
- @classmethod
- def log(cls, user, exception, extra):
- # type: (str, basestring, dict)->None
- try:
- cls(
- user = user,
- exception = exception,
- extra = extra
- ).save()
- except Exception, e:
- logger.exception(e)
- class MonthlySubTemp(EmbeddedDocument):
- displayName = StringField(verbose_name = u"展示名称", default = "包月套餐")
- price = MonetaryField(verbose_name = u"价格", default = RMB(30))
- numOfMonth = IntField(verbose_name = u"包月的时间 几个月", default = 1)
- def to_dict(self):
- return {
- "displayName": self.displayName,
- "price": str(self.price),
- "numOfMonth": self.numOfMonth
- }
- @classmethod
- def default(cls):
- return cls()
- @classmethod
- def create(cls, **kwargs):
- displayName = kwargs.get("displayName")
- price = kwargs.get("price")
- numOfMonth = kwargs.get("numOfMonth")
- obj = cls.default()
- if displayName is not None:
- obj.displayName = displayName
- if price is not None:
- obj.price = RMB(price)
- if numOfMonth is not None:
- obj.numOfMonth = numOfMonth
- return obj
- # 包月计费模板
- class MonthlyPackageTemp(Searchable):
- """
- 设备类型 和包月计费 拆开 不管什么样的设备类型 次是使用的单位 而每次使用多少 则可以再具体有电量和时间两个基本的计量
- """
- ownerId = StringField(verbose_name = u"包月规则制定的经销商", default = "")
- name = StringField(verbose_name = u"模板名称", default = u"默认模板")
- isDefault = BooleanIntField(verbose_name = u"是否是默认规则", default = 0)
- saleable = BooleanIntField(verbose_name = u"是否开启此模板", default = 0)
- isDelete = BooleanIntField(verbose_name = u"是否被经销商删除", default = 0)
- bothCardAndMobile = BooleanIntField(verbose_name = u"是否通用", default = 0)
- maxCountOfDay = IntField(verbose_name = u"每日的最大的次数", default = 1, min_value = 0)
- maxCountOfMonth = IntField(verbose_name = u"每月的最大次数", default = 30, min_value = 0)
- maxTimeOfCount = IntField(verbose_name = u"每次最大充电量 单位度", default = 240, min_value = 0)
- maxElecOfCount = IntField(verbose_name = u"每次最大的充电时间 单位分钟", default = 1, min_value = 0)
- dateTimeAdded = DateTimeField(verbose_name = u"设置模板的时间", default = datetime.datetime.now)
- dateTimeUpdated = DateTimeField(verbose_name = u"计费模板更新的时间", default = datetime.datetime.now)
- subTemplate = EmbeddedDocumentListField(verbose_name = u"子模版", document_type = MonthlySubTemp,
- default = [MonthlySubTemp.default()])
- meta = {
- "collection": "MonthlyPackageTemp",
- "db_alias": "default"
- }
- __unchargeable_fields = ("id", "ownerId", "dateTimeAdded")
- @classmethod
- def get_package_by_id(cls, _id):
- return cls.objects.filter(id = _id).first()
- @classmethod
- def default_one(cls, ownerId):
- """
- :param ownerId:
- :return:
- """
- obj = cls.objects.filter(ownerId = ownerId, isDefault = 1).first()
- if not obj:
- obj = cls(ownerId = ownerId, isDefault = 1).save()
- return obj
- @classmethod
- def get_template_by_dealer(cls, ownerId):
- """
- 获取经销商的 所有包月模板
- :param ownerId:
- :return:
- """
- objs = cls.objects.filter(ownerId = ownerId, isDelete = 0)
- if not objs:
- return [cls.default_one(ownerId)]
- return objs
- @classmethod
- def get_dealer_package(cls, ownerId, **kwargs):
- return cls.objects.filter(ownerId = ownerId, **kwargs).first()
- def set_default(self):
- """
- 设置为默认
- :return:
- """
- self.__class__.objects.filter(ownerId = self.ownerId, isDefault = 1).update(isDefault = 0)
- self.update(isDefault = 1)
- def to_dict(self):
- result = super(MonthlyPackageTemp, self).to_dict()
- subList = list()
- for _index, _sub in enumerate(self.subTemplate):
- _subResult = _sub.to_dict()
- _subResult["index"] = _index
- subList.append(_subResult)
- result["subTemplate"] = subList
- return result
- def update_by_fields(self, **kwargs):
- """
- 更新 包月套餐的自带属性 某些字段不可更改 直接跳过
- :param kwargs:
- :return:
- """
- fields = self.__class__._fields_ordered
- for _field in fields:
- if kwargs.get(_field) is None:
- continue
- if _field in self.__unchargeable_fields:
- continue
- if _field == "subTemplate":
- # 子模版的保存
- self.subTemplate = [MonthlySubTemp.create(**_sub) for _sub in kwargs.get(_field)]
- continue
- setattr(self, _field, kwargs.get(_field))
- return self.save()
- @classmethod
- def create_by_fields(cls, ownerId, **kwargs):
- obj = cls(ownerId = ownerId)
- return obj.update_by_fields(**kwargs)
- class OrderRecordBase(Searchable):
- meta = {
- 'abstract': True,
- }
- openId = StringField(verbose_name = u"微信ID", default = '')
- nickname = StringField(verbose_name = u'用户昵称', default = '')
- devNo = StringField(verbose_name = u"设备ID", default = None)
- devType = StringField(verbose_name = u"设备类型", default = None)
- devTypeName = StringField(verbose_name = u"设备类型名称", default = None)
- devTypeCode = StringField(verbose_name = u"设备类型编码", default = None)
- logicalCode = StringField(verbose_name = u"设备逻辑编码", default = None)
- groupId = StringField(verbose_name = u"设备地址编号", default = None)
- address = StringField(verbose_name = u"设备地址", default = None)
- groupNumber = StringField(verbose_name = u"设备", default = None)
- groupName = StringField(verbose_name = u"交易场地", default = None)
- dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = u'生成时间')
- @property
- def dev_type_name(self):
- if self.devTypeName:
- return self.devTypeName
- if self.devType:
- return self.devType
- return u'未知'
- @property
- def device(self):
- # type:()->DeviceDict
- from apps.web.device.models import Device
- _attr_name = '__device__'
- if hasattr(self, _attr_name):
- return getattr(self, _attr_name)
- else:
- device = Device.get_dev(self.devNo)
- setattr(self, _attr_name, device)
- return device
- @property
- def dev_type_code(self):
- if self.devTypeCode:
- return self.devTypeCode
- device = self.device
- if device:
- return device.devTypeCode
- else:
- return ''
- @property
- def group(self):
- # type:()->GroupDict
- from apps.web.device.models import Group
- _attr_name = '__group__'
- if hasattr(self, _attr_name):
- return getattr(self, _attr_name)
- else:
- group = Group.get_group(self.groupId)
- setattr(self, _attr_name, group)
- return group
- @group.setter
- def group(self, group):
- _attr_name = '__group__'
- setattr(self, _attr_name, group)
- @property
- def owner(self):
- from apps.web.dealer.models import Dealer
- _attr_name = '__my_owner__'
- if hasattr(self, _attr_name):
- return getattr(self, _attr_name)
- else:
- ownerId = getattr(self, 'ownerId', None)
- if not ownerId:
- return None
- else:
- dealer = Dealer.objects(id = ownerId).first()
- setattr(self, _attr_name, dealer)
- return dealer
- @property
- def device_identity_info(self):
- return {
- 'logicalCode': self.logicalCode,
- 'devTypeCode': self.devTypeCode,
- 'devTypeName': self.dev_type_name,
- 'groupName': self.groupName,
- 'groupNumber': self.groupNumber,
- 'address': self.address,
- 'groupId': self.groupId
- }
|