models.py 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import logging
  5. import warnings
  6. from bson import ObjectId
  7. from django.conf import settings
  8. from django.contrib.auth.hashers import make_password, check_password
  9. from mongoengine import StringField, DateTimeField, DynamicDocument, ListField, IntField, BooleanField, DictField, \
  10. ObjectIdField, EmbeddedDocument, EmbeddedDocumentListField, DoesNotExist
  11. from typing import Optional, List, Dict, TYPE_CHECKING
  12. from apilib.monetary import RMB, sum_rmb, AccuracyRMB
  13. from apilib.systypes import IterConstant
  14. from apilib.utils_datetime import to_datetime, get_zero_time
  15. from apilib.utils_json import json_dumps, json_loads
  16. from apilib.utils_sys import memcache_lock, ThreadLock
  17. from apps.web import district
  18. from apps.web.agent.define import AGENT_INCOME_TYPE
  19. from apps.web.common.transaction import WithdrawStatus, WITHDRAW_PAY_TYPE, WithdrawHandler, OrderNoMaker, OrderMainType
  20. from apps.web.constant import Const
  21. from apps.web.core import APP_KEY_DELIMITER, PayAppType, ROLE
  22. from apps.web.core.db import Searchable, StrictDictField, MonetaryField, RoleBaseDocument, PermillageField, \
  23. AccuracyMoneyField, BooleanIntField
  24. from apps.web.core.exceptions import ImproperlyConfigured
  25. from apps.web.core.models import WechatPayApp
  26. from apps.web.core.payment import WithdrawGateway
  27. from apps.web.dealer.define import DEALER_INCOME_TYPE
  28. from apps.web.utils import LimitAttemptsManager
  29. from library.misc import BankAPI
  30. if TYPE_CHECKING:
  31. from apps.web.core.messages.sms import WithdrawSMSProvider
  32. from apps.web.core.payment.wechat import WechatPaymentGateway
  33. from apps.web.core.models import BankCard
  34. from pymongo.results import UpdateResult
  35. from apps.web.device.models import DeviceDict, GroupDict
  36. from apps.web.core import PayAppBase
  37. logger = logging.getLogger(__name__)
  38. class District(object):
  39. memDict = {}
  40. @staticmethod
  41. def load_in_mem(dict_, parentId = None):
  42. for value in dict_:
  43. if value.has_key('children'):
  44. District.memDict[value['value']] = {'text': value['text'], 'parentId': parentId}
  45. District.load_in_mem(value['children'], value['value'])
  46. else:
  47. District.memDict[value['value']] = {'text': value['text'], 'parentId': parentId}
  48. @staticmethod
  49. def get_district(districtId):
  50. if not District.memDict:
  51. District.load_in_mem(district.DISTRICT)
  52. textList = []
  53. area = District.memDict.get(districtId, {})
  54. while area.get('parentId'):
  55. textList.append(area.get('text', ''))
  56. area = District.memDict.get(area.get('parentId', {}))
  57. textList.append(area.get('text', ''))
  58. return ' '.join(textList[::-1])
  59. @staticmethod
  60. def get_area(districtId):
  61. if not District.memDict:
  62. District.load_in_mem(district.DISTRICT)
  63. area = District.memDict.get(districtId, {})
  64. return area.get('text')
  65. @staticmethod
  66. def Is_include(bigId, smallId):
  67. if not District.memDict:
  68. District.load_in_mem(district.DISTRICT)
  69. if bigId == smallId:
  70. return True
  71. parentId = District.memDict.get(smallId).get('parentId')
  72. while parentId is not None:
  73. if parentId == bigId:
  74. return True
  75. parentId = District.memDict.get(parentId).get('parentId')
  76. return False
  77. @staticmethod
  78. def get_subIds(districtId):
  79. if not District.memDict:
  80. District.load_in_mem(district.DISTRICT)
  81. result = []
  82. for k, v in District.memDict.items():
  83. if v['parentId'] == districtId:
  84. result.append(k)
  85. return result
  86. # DeviceWarningField = namedtuple("DeviceWarningField", "warningPart warningStatus warningTime warningDesc warningUart")
  87. #
  88. #
  89. # class DeviceWarning(DeviceWarningField):
  90. #
  91. # def to_cache_dict(self):
  92. # _key = self.warningPart
  93. # _value = {
  94. # "warningStatus": self.warningStatus,
  95. # "warningTime": self.warningTime,
  96. # "warningDesc": self.warningDesc,
  97. # "warningUart": self.warningUart
  98. # }
  99. #
  100. # return {_key: _value}
  101. #
  102. # @classmethod
  103. # def create(cls, **kwargs):
  104. #
  105. # return cls(
  106. # warningPart=kwargs["warningPart"],
  107. # warningStatus=kwargs["warningStatus"],
  108. # warningTime=kwargs["warningTime"],
  109. # warningDesc=kwargs.get("warningDesc", ""),
  110. # warningUart=kwargs.get("warningUart", "")
  111. # )
  112. #
  113. # def to_dict(self):
  114. # return {
  115. # "index": self.warningPart if self.warningPart.isdigit() else 0,
  116. # "warningStatus": self.warningStatus,
  117. # "warningTime": self.warningTime,
  118. # "warningDesc": self.warningDesc
  119. # }
  120. #
  121. # @property
  122. # def isPart(self):
  123. # """
  124. # 是否是部件告警信息
  125. # :return:
  126. # """
  127. # return self.warningPart.isdigit()
  128. #
  129. class FrontendLog(Searchable):
  130. """
  131. 从前端收集到的日志
  132. """
  133. context = StringField(verbose_name = "上下文", default = "")
  134. cookies = StringField(verbose_name = "cookie", default = "")
  135. level = StringField(verbose_name = "日志级别")
  136. content = StringField(verbose_name = "日志内容", min_length = 1)
  137. path = StringField(verbose_name = "哪个页面")
  138. dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = '生成时间')
  139. search_fields = ('content', 'level', 'context', 'path')
  140. meta = {'collection': 'frontend_logs', 'db_alias': 'logdata'}
  141. class CardBank(DynamicDocument):
  142. warnings.warn("CardBank is deprecated", DeprecationWarning)
  143. CardType = StringField(verbose_name = '银行卡类名', unique = True, null = False)
  144. CardName = StringField(verbose_name = '银行卡名称', null = False)
  145. meta = {'collection': 'card_bank', 'db_alias': 'default'}
  146. buffer = {}
  147. buffer_lock = ThreadLock()
  148. @classmethod
  149. def get_collection(cls):
  150. return cls._get_collection()
  151. @staticmethod
  152. def acquire_lock():
  153. CardBank.buffer_lock.acquire_lock()
  154. @staticmethod
  155. def release_lock():
  156. CardBank.buffer_lock.release_lock()
  157. @staticmethod
  158. def try_load_and_acquire_lock():
  159. CardBank.buffer_lock.acquire_lock()
  160. CardBank.load_in_mem()
  161. @staticmethod
  162. def reset_buffer():
  163. try:
  164. CardBank.acquire_lock()
  165. CardBank.buffer = {}
  166. finally:
  167. CardBank.release_lock()
  168. @staticmethod
  169. def load_in_mem():
  170. try:
  171. CardBank.acquire_lock()
  172. if CardBank.buffer:
  173. return
  174. banks = CardBank.objects.all()
  175. for bank in banks:
  176. CardBank.buffer[bank.CardType] = bank.CardName
  177. finally:
  178. CardBank.release_lock()
  179. @staticmethod
  180. def get(cardType):
  181. try:
  182. CardBank.try_load_and_acquire_lock()
  183. if cardType in CardBank.buffer:
  184. return CardBank.buffer[cardType]
  185. else:
  186. return None
  187. finally:
  188. CardBank.release_lock()
  189. @staticmethod
  190. def has(cardType):
  191. try:
  192. CardBank.try_load_and_acquire_lock()
  193. if cardType in CardBank.buffer:
  194. return True
  195. else:
  196. return False
  197. finally:
  198. CardBank.release_lock()
  199. # : 微信支持的银行列表
  200. #: REF https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=24_4
  201. class Banks(DynamicDocument):
  202. CARD_TYPE_MAP = {
  203. 'DC': "储蓄卡",
  204. 'CC': "信用卡",
  205. 'SCC': "准贷记卡",
  206. 'PC': "预付费卡"
  207. }
  208. bankName = StringField(verbose_name = u'银行名称', unique = True, null = False)
  209. bankName2 = StringField(verbose_name = u'银行名称2')
  210. bankCode = StringField(verbose_name = u'银行code', unique = True, null = False)
  211. sn = IntField(verbose_name = u'排序序号', default = 0)
  212. wechatBankCode = StringField(verbose_name = u'微信银行ID', default = '')
  213. patterns = ListField(verbose_name = u'匹配正则', default = [])
  214. meta = {'collection': 'banks', 'db_alias': 'default'}
  215. buffer = {}
  216. buffer_lock = ThreadLock()
  217. def to_dict(self):
  218. return {
  219. 'bankCode': self.bankCode,
  220. 'bankName': self.bankName,
  221. 'bankName2': self.bankName2,
  222. 'wechatBankCode': self.wechatBankCode,
  223. 'patterns': self.patterns
  224. }
  225. @classmethod
  226. def get_bank_info(cls, cardNo):
  227. cls.__load_in_mem()
  228. bank_info = BankAPI().get_bank_card_info(card_no = cardNo)
  229. if bank_info:
  230. bank_code = bank_info['bankCode']
  231. card_type = bank_info['cardType']
  232. if bank_info['bankCode'] in cls.buffer['code']:
  233. bank_name = cls.buffer['code'][bank_code]['bankName']
  234. bank_name2 = cls.buffer['code'][bank_code]['bankName2']
  235. card_type = cls.CARD_TYPE_MAP.get(card_type, card_type)
  236. return {
  237. 'cardNo': cardNo,
  238. 'bankCode': bank_code,
  239. 'cardType': card_type,
  240. 'bankName': bank_name,
  241. 'bankName2': bank_name2
  242. }
  243. else:
  244. return {
  245. 'cardNo': cardNo,
  246. 'bankCode': bank_code,
  247. 'cardType': card_type,
  248. 'bankName': '',
  249. 'bankName2': ''
  250. }
  251. return None
  252. @classmethod
  253. def get_public_banks(cls):
  254. cls.__load_in_mem()
  255. return cls.buffer['public']
  256. @classmethod
  257. def get_personal_banks(cls):
  258. cls.__load_in_mem()
  259. return cls.buffer['personal']
  260. @classmethod
  261. def support_personal(cls, bankName):
  262. cls.__load_in_mem()
  263. return bankName in cls.buffer['personal']
  264. @classmethod
  265. def support_public(cls, bankName):
  266. cls.__load_in_mem()
  267. return bankName in cls.buffer['public']
  268. @classmethod
  269. def get_wechat_bank_code(cls, bankName):
  270. cls.__load_in_mem()
  271. if bankName in cls.buffer['personal']:
  272. return cls.buffer['name'][bankName]['wechatBankCode']
  273. else:
  274. return ''
  275. @classmethod
  276. def get_collection(cls):
  277. return cls._get_collection()
  278. @classmethod
  279. def __acquire_lock(cls):
  280. cls.buffer_lock.acquire_lock()
  281. @classmethod
  282. def __release_lock(cls):
  283. cls.buffer_lock.release_lock()
  284. def reset_buffer(self):
  285. try:
  286. self.__acquire_lock()
  287. self.buffer = {}
  288. finally:
  289. self.__release_lock()
  290. @classmethod
  291. def __load_in_mem(cls):
  292. if cls.buffer:
  293. return
  294. try:
  295. cls.__acquire_lock()
  296. if cls.buffer:
  297. return
  298. cls.buffer = {
  299. 'public': [], # type: list
  300. 'personal': [], # type: list
  301. 'name': {},
  302. 'code': {}
  303. }
  304. banks = cls.objects().order_by('sn')
  305. for bank in banks:
  306. cls.buffer['name'][bank.bankName] = bank.to_dict()
  307. cls.buffer['name'][bank.bankName2] = bank.to_dict()
  308. cls.buffer['code'][bank.bankCode] = bank.to_dict()
  309. cls.buffer['public'].append(bank.bankName)
  310. if bank.bankName2:
  311. cls.buffer['public'].append(bank.bankName2)
  312. if bank.wechatBankCode:
  313. cls.buffer['personal'].append(bank.bankName)
  314. if bank.bankName2:
  315. cls.buffer['personal'].append(bank.bankName2)
  316. finally:
  317. cls.__release_lock()
  318. class FAQ(Searchable):
  319. """
  320. 配置FAQ,首先让经销商|终端用户看到常用问题,找不到问题再联系客服
  321. """
  322. question = StringField(verbose_name = '问题')
  323. answer = StringField(verbose_name = '答案')
  324. target = StringField(verbose_name = '目标 := (Dealer|Agent|EndUser)', default = '*')
  325. devTypeId = StringField(verbose_name = '设备类型ID,默认为普适性问题,为空', default = '')
  326. images = ListField(verbose_name = '辅助说明图像')
  327. videos = ListField(verbose_name = '辅助说明视频')
  328. upvotes = IntField(verbose_name = '该FAQ有帮助')
  329. downvotes = IntField(verbose_name = '该FAQ没有帮助')
  330. managerId = StringField(verbose_name = '管理员ID', null = False)
  331. meta = {'collection': 'faqs', 'db_alias': 'default'}
  332. def to_dict(self):
  333. return {
  334. 'id': str(self.id),
  335. 'question': self.question,
  336. 'answer': self.answer,
  337. 'target': self.target,
  338. 'images': self.images,
  339. 'videos': self.videos,
  340. 'devTypeId': self.devTypeId,
  341. 'upvotes': self.upvotes,
  342. 'downvotes': self.downvotes
  343. }
  344. @classmethod
  345. def get_by_role(cls, role):
  346. #: 目前无用户体系,暂时将匿名用户作为终端用户
  347. role = ROLE.myuser if role == ROLE.anonymoususer else role
  348. return cls.objects(target__in = ['*', role])
  349. class Feature(Searchable):
  350. """
  351. 特性列表的所有特性,由超级管理员配置
  352. (角色,特性名称) 唯一
  353. """
  354. name = StringField(verbose_name = u'特性名称', min_length = 1)
  355. key = StringField(verbose_name = u'具体的特性代号,为英文')
  356. role = StringField(verbose_name = u'特性分配的角色', choices = ROLE.choices())
  357. # managerId = StringField(verbose_name = u'厂商ID')
  358. default = BooleanField(verbose_name = u'默认值')
  359. # by = StringField(verbose_name = u'创建的超级管理员的ID')
  360. desc = StringField(verbose_name = u'描述')
  361. dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = u'生成时间')
  362. search_fields = ('key', 'name')
  363. meta = {
  364. 'indexes': [
  365. {'fields': ['name', 'role'], 'unique': True},
  366. ],
  367. "collection": "features",
  368. "db_alias": "default"
  369. }
  370. @classmethod
  371. def for_dealer(cls, **kwargs): return cls.objects(role = ROLE.dealer, **kwargs)
  372. @classmethod
  373. def for_agent(cls, **kwargs): return cls.objects(role = ROLE.agent, **kwargs)
  374. @classmethod
  375. def for_manager(cls, **kwargs): return cls.objects(role = ROLE.manager, **kwargs)
  376. def to_dict(self):
  377. return {
  378. 'id': str(self.id),
  379. 'role': self.role,
  380. 'name': self.name,
  381. # 'managerId': self.managerId,
  382. # 'by': self.by,
  383. 'key': self.key,
  384. 'default': self.default,
  385. 'desc': self.desc,
  386. 'dateTimeAdded': self.dateTimeAdded
  387. }
  388. def __repr__(self):
  389. return '<Feature key=%s, role=%s>' % (self.key, self.role)
  390. DEFAULT_DEALER_FEATURES = ['show_offline_coins']
  391. class AddressType(Searchable):
  392. value = StringField(verbose_name = "地址值", unique = True) #: e.g. school
  393. label = StringField(verbose_name = "地址名称", default = "") #: e.g. 学校
  394. showWeight = IntField(verbose_name = '显示权重', default = 0)
  395. createdAt = DateTimeField(default = datetime.datetime.now)
  396. meta = {"collection": "AddressType", "db_alias": "default"}
  397. class UserSearchable(RoleBaseDocument):
  398. class Status(IterConstant):
  399. NORMAL = 'normal'
  400. FORBIDDEN = 'forbidden'
  401. ABNORMAL = 'abnormal'
  402. NOWITHDRAW = 'noWithdraw'
  403. username = StringField(verbose_name = "用户名称:=手机号", max_length = 100)
  404. password = StringField(verbose_name = "密码", min_length = 1)
  405. nickname = StringField(verbose_name = '名字,用来标识', default = '')
  406. remarks = StringField(verbose_name = "备注", default = "")
  407. avatar = StringField(verbose_name = u"用户头像", default = "")
  408. activited = BooleanField(verbose_name = "是否激活", default = True)
  409. status = StringField(verbose_name = "账号状态", default = Status.NORMAL)
  410. dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = '添加进来的时间')
  411. last_login = DateTimeField(default = datetime.datetime.now, verbose_name = '最近登录时间')
  412. phoneOS = StringField(verbose_name = u"终端操作系统", default = "")
  413. lastLoginUserAgent = StringField(verbose_name = u"最后一次登录的userAgent", default = "")
  414. smsVendor = StringField(verbose_name = u'sms提供商', default = '')
  415. meta = {
  416. 'abstract': True,
  417. 'indexes': [
  418. {
  419. 'fields': ['$username', '$nickname'],
  420. 'weights': {'username': 5, 'nickname': 5}
  421. }
  422. ]
  423. }
  424. def __str__(self):
  425. return '{}<id={} username={} nickname={}>'.format(
  426. self.__class__.__name__, str(self.id), self.username, self.nickname)
  427. @property
  428. def human_id(self):
  429. return '{}({})'.format(self.username, self.role)
  430. @property
  431. def identity_id(self):
  432. return '{}_{}'.format(self.role, str(self.id))
  433. def to_dict(self, shadow = False):
  434. return {
  435. 'username': self.username if not shadow else '******',
  436. 'nickname': self.nickname,
  437. 'remarks': self.remarks,
  438. 'activited': self.activited,
  439. 'status': self.status,
  440. 'dateTimeAdded': self.dateTimeAdded,
  441. 'last_login': self.last_login,
  442. 'role': self.role,
  443. 'avatar': self.my_avatar
  444. }
  445. @classmethod
  446. def create_user(cls, username, password, **attrs):
  447. user = cls(username = username, **attrs)
  448. user.password = make_password(password)
  449. user.save()
  450. return user
  451. def is_authenticated(self):
  452. return True
  453. def set_password(self, raw_password):
  454. self.password = make_password(raw_password)
  455. self.save()
  456. return self
  457. def check_password(self, raw_password):
  458. return check_password(raw_password, self.password)
  459. def unlock_login(self):
  460. lm = LimitAttemptsManager(identifier = self.username, category = "%sLogin" % self.role)
  461. lm.clear()
  462. @classmethod
  463. def get_last_login1(cls, **kwargs):
  464. return cls.objects(**kwargs).order_by('-last_login').first()
  465. def __check_feature_setup(self):
  466. # type:()->(str, set)
  467. self_features = getattr(self, 'features', None)
  468. if self_features is None:
  469. raise ImproperlyConfigured('no features attr provided')
  470. return self.role, set(self_features)
  471. ## Feature Module
  472. @property
  473. def feature_list(self):
  474. # type:()->List[dict]
  475. role, self_features = self.__check_feature_setup()
  476. features = [_.to_dict() for _ in Feature.objects(role = role)] # type: List[dict]
  477. rv = [
  478. {
  479. 'name': feature['name'],
  480. 'key': feature['key'],
  481. 'value': feature['key'] in self_features
  482. } for feature in features
  483. ]
  484. #: unique to user, special features
  485. # set difference
  486. unique_features = self_features - {_['key'] for _ in features}
  487. rv += [{'name': _, 'key': _, 'value': True} for _ in unique_features]
  488. return rv
  489. def edit_feature_list(self, feature_list, special = None):
  490. # type:(list, Optional[str])->int
  491. self.__check_feature_setup()
  492. features = [_['key'] for _ in feature_list if _['value']]
  493. if special:
  494. features.append(special)
  495. return self.update(set__features = features)
  496. @property
  497. def feature_boolean_map(self):
  498. # type: ()->Dict[str, bool]
  499. features = {}
  500. for _ in self.feature_list:
  501. if _['key']:
  502. features.update({_['key']: _['value']})
  503. return features
  504. def supports(self, feature):
  505. # type: (str)->bool
  506. _, self_features = self.__check_feature_setup()
  507. return feature in self_features
  508. @property
  509. def normal(self):
  510. return self.status in [self.Status.NORMAL, self.Status.NOWITHDRAW]
  511. @property
  512. def forbidden(self):
  513. return self.status == self.Status.FORBIDDEN
  514. @property
  515. def abnormal(self):
  516. return self.status == self.Status.ABNORMAL
  517. @property
  518. def no_withdraw(self):
  519. return self.status == self.Status.NOWITHDRAW
  520. @property
  521. def my_avatar(self):
  522. if self.avatar:
  523. return self.avatar
  524. else:
  525. return ''
  526. @property
  527. def universal_password_login(self):
  528. return getattr(self, '__universal_password_login__', False)
  529. @universal_password_login.setter
  530. def universal_password_login(self, upl):
  531. setattr(self, '__universal_password_login__', upl)
  532. class CapitalUser(UserSearchable):
  533. """
  534. 带资金以及提现用户
  535. """
  536. meta = {
  537. 'abstract': True,
  538. }
  539. INCOME_SOURCE_LIST = list()
  540. INCOME_SOURCE_TO_TYPE = dict()
  541. INCOME_TYPE_LIST = list()
  542. INCOME_TYPE_TO_FIELD = dict()
  543. DEFAULT_WITHDRAW_OPTIONS = {
  544. 'autoWithdrawSwitch': False,
  545. 'autoWithdrawType': "wechat",
  546. 'autoWithdrawMin': RMB(settings.WITHDRAW_MINIMUM).mongo_amount,
  547. 'autoWithdrawStrategy': {'type': 'asWeek', 'value': 3},
  548. 'autoWithdrawBankFee': True
  549. }
  550. ongoingWithdrawList = ListField(field = StringField())
  551. inhandWithdrawList = ListField(field = DictField())
  552. withdrawOptions = DictField(verbose_name = u"提现相关选项", default = DEFAULT_WITHDRAW_OPTIONS)
  553. @classmethod
  554. def income_field_name(cls, income_type):
  555. # type: (str)->str
  556. assert income_type in cls.INCOME_TYPE_LIST, 'not support this income type({})'.format(income_type)
  557. return cls.INCOME_TYPE_TO_FIELD[income_type]
  558. @classmethod
  559. def fund_key(cls, income_type, source_key):
  560. return '{field}.{key}'.format(field = cls.income_field_name(income_type), key = source_key)
  561. def balance_dict(self, income_type):
  562. return dict(getattr(self, self.income_field_name(income_type = income_type)))
  563. def set_balance(self, income_type, source_key, money):
  564. # type: (str, str, RMB)->bool
  565. """
  566. 单元测试使用.初始化值
  567. :param income_type:
  568. :param source_key:
  569. :param money:
  570. :return:
  571. """
  572. assert isinstance(money, RMB), 'money has to be a RMB instance'
  573. assert income_type in self.INCOME_TYPE_LIST, 'not support this source{}'.format(income_type)
  574. assert source_key, 'source key must not be none'
  575. query = {'_id': ObjectId(self.id)}
  576. update = {
  577. '$set': {
  578. '{fund_key}.balance'.format(fund_key = self.fund_key(income_type, source_key)): money.mongo_amount,
  579. '{fund_key}.frozenBalance'.format(fund_key = self.fund_key(income_type, source_key)): RMB(
  580. 0).mongo_amount
  581. },
  582. }
  583. result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult
  584. return bool(result.modified_count == 1)
  585. def incr_fund(self, income_type, source_key, money):
  586. # type: (str, str, RMB)->bool
  587. assert isinstance(money, RMB), 'money has to be a RMB instance'
  588. assert income_type in self.INCOME_TYPE_LIST, 'not support this source'
  589. assert source_key, 'gateway key must not be none'
  590. query = {'_id': ObjectId(self.id)}
  591. update = {
  592. '$inc': {
  593. '{fund_key}.balance'.format(fund_key = self.fund_key(income_type, source_key)): money.mongo_amount
  594. }
  595. }
  596. result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult
  597. return bool(result.modified_count == 1)
  598. def decr_fund(self, income_type, source_key, money):
  599. # type: (str, str, RMB)->int
  600. """
  601. 正常流程不使用该接口,会让账对不上
  602. :param income_type:
  603. :param source_key:
  604. :param money:
  605. :return:
  606. """
  607. assert isinstance(money, RMB), 'money has to be a RMB instance'
  608. assert income_type in self.INCOME_TYPE_LIST, 'not support this source'
  609. assert source_key, 'gateway key must not be none'
  610. query = {'_id': ObjectId(self.id)}
  611. update = {
  612. '$inc':
  613. {
  614. '{fund_key}.balance'.format(fund_key = self.fund_key(income_type, source_key)): (
  615. -money).mongo_amount
  616. }
  617. }
  618. result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult
  619. return bool(result.modified_count == 1)
  620. @classmethod
  621. def prepare_sellet_income(cls, object_id, transaction_id, source, source_key, money, selfAgg=False):
  622. assert isinstance(money, RMB), 'money has to be a RMB instance'
  623. assert source in cls.INCOME_SOURCE_LIST, 'not support this source'
  624. assert source_key, 'source key must not be none'
  625. if money == RMB(0):
  626. logger.debug('{}<id={}> sellet zero.'.format(cls.__name__, object_id))
  627. return
  628. income_type = cls.INCOME_SOURCE_TO_TYPE[source]
  629. query = {
  630. '_id': ObjectId(object_id),
  631. 'ongoingWithdrawList.transaction_id': {
  632. '$ne': transaction_id
  633. }
  634. }
  635. update = {
  636. '$inc': {
  637. '{filed}.{key}.balance'.format(filed=cls.INCOME_TYPE_TO_FIELD[income_type],
  638. key=source_key): money.mongo_amount
  639. },
  640. '$addToSet': {
  641. 'ongoingWithdrawList': {'transaction_id': transaction_id, 'money': money.mongo_amount}
  642. }
  643. }
  644. if selfAgg:
  645. update['$inc'].update({
  646. 'aggregatedIncome.{source}'.format(source=source): money.mongo_amount,
  647. 'incomeMap.{source}'.format(source=source): money.mongo_amount
  648. })
  649. result = cls.get_collection().update_one(query, update, upsert=False) # type: UpdateResult
  650. return bool(result.modified_count == 1)
  651. @classmethod
  652. def commit_sellet_income(cls, object_id, transaction_id):
  653. query = {
  654. '_id': ObjectId(object_id),
  655. 'ongoingWithdrawList': {
  656. '$elemMatch': {
  657. 'transaction_id': transaction_id
  658. }
  659. }
  660. }
  661. update = {
  662. '$pull': {
  663. 'ongoingWithdrawList': {'transaction_id': transaction_id}
  664. }
  665. }
  666. result = cls.get_collection().update_one(query, update, upsert=False) # type: UpdateResult
  667. return bool(result.modified_count == 1)
  668. def sub_balance(self, income_type, source_key=None, only_ledger=True):
  669. # type: (str, str, bool)->RMB
  670. balance_dict = self.balance_dict(income_type=income_type) # type: dict
  671. if not source_key:
  672. balance_list = []
  673. for key, value in balance_dict.iteritems():
  674. if only_ledger:
  675. if WithdrawGateway.is_ledger(key):
  676. balance_list.append(value.balance)
  677. else:
  678. balance_list.append(value.balance)
  679. return sum_rmb(balance_list)
  680. else:
  681. return balance_dict.get(source_key, Balance()).balance
  682. @property
  683. def total_balance(self):
  684. # type: ()->RMB
  685. total = RMB(0)
  686. for income_type in self.INCOME_TYPE_LIST:
  687. total += self.sub_balance(income_type)
  688. return total
  689. def sub_frozen_balance(self, income_type, source_key = None):
  690. # type: (str, str)->RMB
  691. field = self.income_field_name(income_type = income_type)
  692. balance_list = []
  693. for item in self.inhandWithdrawList:
  694. if item['field'] != field:
  695. continue
  696. if source_key and item['key'] != source_key:
  697. continue
  698. balance_list.append(item['value'])
  699. balance_dict = self.balance_dict(income_type = income_type) # type: dict
  700. if not source_key:
  701. for key, value in balance_dict.iteritems():
  702. if WithdrawGateway.is_ledger(key):
  703. balance_list.append(value.frozenBalance)
  704. return sum_rmb(balance_list)
  705. else:
  706. balance_list.append(balance_dict.get(source_key, Balance()).frozenBalance)
  707. return sum_rmb(balance_list)
  708. @property
  709. def total_frozen_balance(self):
  710. # type: ()->RMB
  711. total = RMB(0)
  712. for income_type in self.INCOME_TYPE_LIST:
  713. total += self.sub_frozen_balance(income_type)
  714. return total
  715. def _freeze_balance(self, income_type, money, source_key, transaction_id, freeze_type):
  716. assert source_key, 'gateway is null'
  717. assert transaction_id, 'transaction id is null'
  718. field = self.income_field_name(income_type = income_type)
  719. fund_key = self.fund_key(income_type = income_type, source_key = source_key)
  720. query = {'_id': self.id, '{}.transaction_id'.format(freeze_type): {'$ne': transaction_id}}
  721. update = {
  722. '$inc': {
  723. '{fund_key}.balance'.format(fund_key=fund_key): (-money).mongo_amount
  724. },
  725. '$addToSet': {
  726. freeze_type: {
  727. 'transaction_id': transaction_id,
  728. 'field': field,
  729. 'key': source_key,
  730. 'value': money.mongo_amount
  731. }
  732. }
  733. }
  734. result = self.get_collection().update_one(query, update) # type: UpdateResult
  735. return bool(result.modified_count == 1)
  736. def _clear_frozen_balance(self, transaction_id, freeze_type):
  737. query = {'_id': self.id, '{}.transaction_id'.format(freeze_type): transaction_id}
  738. update = {
  739. '$pull': {
  740. freeze_type:
  741. {
  742. 'transaction_id': transaction_id
  743. }
  744. }
  745. }
  746. result = self.get_collection().update_one(query, update) # type: UpdateResult
  747. return bool(result.modified_count == 1)
  748. def _recover_frozen_balance(self, income_type, money, source_key, transaction_id, freeze_type):
  749. assert source_key, 'gateway is null'
  750. assert transaction_id, 'transaction id is null'
  751. fund_key = self.fund_key(income_type=income_type, source_key=source_key)
  752. query = {'_id': self.id, '{}.transaction_id'.format(freeze_type): transaction_id}
  753. update = {
  754. '$inc': {
  755. '{fund_key}.balance'.format(fund_key=fund_key): money.mongo_amount
  756. },
  757. '$pull': {
  758. freeze_type:
  759. {
  760. 'transaction_id': transaction_id
  761. }
  762. }
  763. }
  764. result = self.get_collection().update_one(query, update) # type: UpdateResult
  765. return bool(result.modified_count == 1)
  766. def freeze_balance(self, income_type, money, source_key, transaction_id): # type:(str, RMB, str, str)->bool
  767. """
  768. 提现的时候冻结经销商的金额
  769. :param source_key:
  770. :param income_type:
  771. :param money:
  772. :param transaction_id:
  773. """
  774. return self._freeze_balance(income_type, money, source_key, transaction_id, "inhandWithdrawList")
  775. def clear_frozen_balance(self, transaction_id): # type:(str)->bool
  776. """
  777. 提现完成 清理冻结
  778. :param transaction_id:
  779. :return:
  780. """
  781. assert transaction_id, 'transaction id is null'
  782. return self._clear_frozen_balance(transaction_id, "inhandWithdrawList")
  783. def recover_frozen_balance(self, income_type, money, source_key, transaction_id): # type:(str, RMB, str, str)->bool
  784. """
  785. 提现遇到问题 直接回滚
  786. :param source_key:
  787. :param income_type:
  788. :param money:
  789. :param transaction_id:
  790. :return:
  791. """
  792. assert source_key, 'gateway is null'
  793. assert transaction_id, 'transaction id is null'
  794. return self._recover_frozen_balance(income_type, money, source_key, transaction_id, "inhandWithdrawList")
  795. def withdraw_source_key(self, pay_app = None):
  796. # type:(PayAppBase)->str
  797. """
  798. 供PaymentGateway调用获取提现网关
  799. :param pay_app:
  800. :return:
  801. """
  802. raise NotImplementedError()
  803. @property
  804. def withdraw_sms_provider(self):
  805. # type: () -> WithdrawSMSProvider
  806. raise NotImplementedError()
  807. @property
  808. def withdraw_sms_phone_number(self):
  809. # type: () -> basestring
  810. raise NotImplementedError()
  811. def new_withdraw_record(self, withdraw_gateway, pay_entity, source_key, income_type, amount, pay_type, manual, recurrent):
  812. # type: (WithdrawGateway, BankCard, str, str, RMB, str, bool, bool) -> WithdrawRecord
  813. raise NotImplementedError()
  814. def new_withdraw_handler(self, record):
  815. # type: (WithdrawRecord) -> WithdrawHandler
  816. raise NotImplementedError()
  817. def get_bound_pay_openid(self, key):
  818. raise NotImplementedError()
  819. def withdraw_bank_card(self, bank_card_no = None):
  820. # type: (str)->BankCard
  821. raise NotImplementedError()
  822. @property
  823. def auto_withdraw_min(self):
  824. return self.withdrawOptions.get('autoWithdrawMin')
  825. @property
  826. def auto_withdraw_type(self):
  827. return self.withdrawOptions.get('autoWithdrawType')
  828. @property
  829. def auto_withdraw_switch(self):
  830. return self.withdrawOptions.get('autoWithdrawSwitch')
  831. @property
  832. def auto_withdraw_strategy(self):
  833. return self.withdrawOptions.get('autoWithdrawStrategy')
  834. @property
  835. def auto_withdraw_bank_fee(self):
  836. return self.withdrawOptions.get('autoWithdrawBankFee', True)
  837. @property
  838. def withdraw_open_id(self):
  839. return getattr(self, '__withdraw_openid__')
  840. @withdraw_open_id.setter
  841. def withdraw_open_id(self, open_id):
  842. setattr(self, '__withdraw_openid__', open_id)
  843. @property
  844. def auto_withdraw_bound_open_id(self):
  845. raise NotImplementedError()
  846. def can_withdraw_today(self):
  847. return True
  848. @property
  849. def current_wallet_withdraw_source_key(self):
  850. logger.debug('get withdraw source key. source = {}'.format(repr(self)))
  851. from apps.web.helpers import AgentCustomizedBrandVisitor
  852. return AgentCustomizedBrandVisitor(factory_type = 'withdraw_source_key', pay_app = None).visit(self)
  853. class WithdrawRefundRecord(Searchable):
  854. withdraw_record_id = ObjectIdField(unique = True)
  855. createdTime = DateTimeField(default = datetime.datetime.now)
  856. role = StringField(choices = ROLE.choices())
  857. meta = {
  858. "collection": "withdraw_refund_record",
  859. "db_alias": "logdata"
  860. }
  861. class WithdrawRecord(Searchable):
  862. class WithdrawPayType(IterConstant):
  863. BANK = 'bank'
  864. WECHAT = 'wechat'
  865. order = StringField(verbose_name = u"订单号", unique = True)
  866. ownerId = StringField(verbose_name = u"提现者对应对象ID")
  867. role = StringField(verbose_name = u'提现者账号类型', choices = ROLE.choices())
  868. name = StringField(verbose_name = u"提现人姓名", default = "") # type: str
  869. phone = StringField(verbose_name = u"持卡人电话", default = "") # type: str
  870. balance = MonetaryField(verbose_name = u"提现人提现时刻余额", default = RMB('0.00')) # type: RMB
  871. incomeType = StringField(verbose_name = u'提现收入类型',
  872. choices = AGENT_INCOME_TYPE.choices() + DEALER_INCOME_TYPE.choices())
  873. withdrawSourceKey = StringField(verbose_name = u'提现来源KEY', default = None)
  874. payType = StringField(verbose_name = u"支付方式(微信,银行卡)", default = "") # type: str
  875. manual = BooleanField(verbose_name = u'是否手动处理', default = False) # type: bool
  876. recurrent = BooleanField(verbose_name = u"是否为自动循环提现", default = False)
  877. status = IntField(verbose_name = u"状态", default = WithdrawStatus.SUBMITTED)
  878. remarks = StringField(verbose_name = u"备注系统内部状态", default = "")
  879. description = StringField(verbose_name = u'给用户看的描述', default = '')
  880. postTime = DateTimeField(verbose_name = u"请求时间", default = datetime.datetime.now)
  881. finishedTime = DateTimeField(verbose_name = u"提现订单完成时间")
  882. amount = MonetaryField(verbose_name = u"提现金额", default = RMB('0.00')) # type: RMB
  883. serviceFee = MonetaryField(verbose_name = u"手续费", default = RMB('0.00')) # type: RMB
  884. actualPay = MonetaryField(verbose_name = u"实际金额", default = RMB('0.00')) # type: RMB
  885. bankTransFee = MonetaryField(verbose_name = u"转账银行费用", default = RMB('0.00')) # type: RMB
  886. refunded = BooleanField(verbose_name = u'是否已经退款了', default = False)
  887. cardUserName = StringField(verbose_name = u"持卡人姓名", default = "")
  888. accountCode = StringField(verbose_name = u"提现银行卡号", default = "")
  889. parentBankName = StringField(verbose_name = u"银行名称", default = "")
  890. subBankName = StringField(verbose_name = u"银行支行名称", default = "")
  891. payAgentId = StringField(verbose_name = u'提现处理平台代理商ID')
  892. withdrawFeeRatio = PermillageField(verbose_name = u'提现费率', default = Const.PLATFORM_DEFAULT_WITHDRAW_FEE_RATIO)
  893. partition = ListField(verbose_name = u'提现费率收入划分', default = [])
  894. withdrawGatewayKey = StringField(verbose_name = u"提现资金池gateway key", required = True)
  895. extras = DictField(verbose_name = u"记录提现额外信息", default = {})
  896. search_fields = ('order', 'phone', 'name')
  897. meta = {
  898. 'indexes': [
  899. {
  900. 'fields': ['ownerId', 'role'],
  901. },
  902. 'status',
  903. '-postTime',
  904. 'payAgentId'
  905. ],
  906. 'collection': 'WithdrawRecord',
  907. 'db_alias': 'default'
  908. }
  909. @classmethod
  910. def get_record(cls, order_no):
  911. return cls.objects(order = order_no).first()
  912. @classmethod
  913. def get_collection(cls):
  914. return cls._get_collection()
  915. @classmethod
  916. def get_succeeded_records(cls, **kwargs):
  917. return cls.objects(status = WithdrawStatus.SUCCEEDED, **kwargs)
  918. @classmethod
  919. def get_processing_via_bank(cls):
  920. return cls.objects(status__in = [WithdrawStatus.PROCESSING, WithdrawStatus.BANK_PROCESSION],
  921. payType = WITHDRAW_PAY_TYPE.BANK, manual = False)
  922. @classmethod
  923. def get_failed_records(cls):
  924. return cls.objects(status = WithdrawStatus.FAILED, refunded = False, manual = False)
  925. @classmethod
  926. def create(cls, withdraw_user, withdraw_gateway, pay_entity, source_key, income_type, pay_type, fund_map,
  927. manual, recurrent, **kwargs):
  928. # type: (CapitalUser, WithdrawGateway, BankCard, str, str, str, dict, bool, bool, dict) -> WithdrawRecord
  929. assert pay_type in (WITHDRAW_PAY_TYPE.WECHAT, WITHDRAW_PAY_TYPE.BANK), 'not support this pay type'
  930. if manual:
  931. assert pay_type == WITHDRAW_PAY_TYPE.BANK, 'pay type must be bank in manual'
  932. withdraw_record_payload = {
  933. 'order': WithdrawRecord.make_no(withdraw_user.role, str(withdraw_user.id)),
  934. 'ownerId': str(withdraw_user.id),
  935. 'role': withdraw_user.role,
  936. 'name': withdraw_user.nickname,
  937. 'phone': withdraw_user.username,
  938. 'balance': withdraw_user.sub_balance(income_type = income_type, source_key = source_key),
  939. 'incomeType': income_type,
  940. 'withdrawSourceKey': source_key,
  941. 'payType': pay_type,
  942. 'manual': manual,
  943. 'amount': fund_map.get('amount'),
  944. 'serviceFee': fund_map.get('serviceFee'),
  945. 'actualPay': fund_map.get('actualPay'),
  946. 'bankTransFee': fund_map.get('bankTransFee'),
  947. 'accountCode': pay_entity.cardNo,
  948. 'cardUserName': pay_entity.holderName,
  949. 'parentBankName': pay_entity.bankName,
  950. 'subBankName': pay_entity.branchName,
  951. 'partition': fund_map.get('partition'),
  952. 'payAgentId': withdraw_gateway.occupantId,
  953. 'withdrawGatewayKey': withdraw_gateway.gateway_key,
  954. 'withdrawFeeRatio': fund_map.get('withdrawFeeRatio'),
  955. 'extras': {'v': 2}
  956. }
  957. if pay_type == WITHDRAW_PAY_TYPE.BANK:
  958. withdraw_record_payload['extras'].update({
  959. 'withdrawBankCard': pay_entity.id
  960. })
  961. if kwargs:
  962. withdraw_record_payload.update(**kwargs)
  963. if hasattr(withdraw_gateway, 'version'):
  964. withdraw_record_payload['extras'].update({
  965. 'gateway_version': getattr(withdraw_gateway, 'version')
  966. })
  967. record = cls(**withdraw_record_payload)
  968. record.save()
  969. return record
  970. @property
  971. def finished(self):
  972. return self.status in WithdrawStatus.finished_status()
  973. def succeed(self, **kwargs):
  974. payload = {
  975. 'status': WithdrawStatus.SUCCEEDED
  976. }
  977. if kwargs:
  978. payload.update(kwargs)
  979. result = self.get_collection().update_one(
  980. filter = {'_id': ObjectId(self.id),
  981. 'status': {'$nin': WithdrawStatus.finished_status()}},
  982. update = {'$set': payload},
  983. upsert = False)
  984. return result.matched_count == 1
  985. def fail(self, remarks = '', description = u'支付网关返回失败'):
  986. result = self.get_collection().update_one(
  987. filter = {'_id': ObjectId(self.id),
  988. 'status': {'$nin': WithdrawStatus.finished_status()}},
  989. update = {'$set': {
  990. 'status': WithdrawStatus.FAILED,
  991. 'remarks': remarks,
  992. 'description': description,
  993. 'finishedTime': datetime.datetime.now()
  994. }},
  995. upsert = False)
  996. return result.matched_count == 1
  997. def revoke(self, remarks = '', description = u'提现失败'):
  998. result = self.get_collection().update_one(
  999. filter = {'_id': ObjectId(self.id),
  1000. 'status': {'$nin': WithdrawStatus.finished_status()}},
  1001. update = {'$set': {
  1002. 'status': WithdrawStatus.CLOSED,
  1003. 'refunded': True,
  1004. 'remarks': remarks,
  1005. 'description': description,
  1006. 'finishedTime': datetime.datetime.now()
  1007. }},
  1008. upsert = False)
  1009. return result.matched_count == 1
  1010. def set_processing(self, remarks = u'提现申请已经受理', description = u'提现申请已经受理', **kwargs):
  1011. extras = self.extras
  1012. extras.update(**kwargs)
  1013. set_payload = {
  1014. 'status': WithdrawStatus.PROCESSING,
  1015. 'remarks': remarks,
  1016. 'description': description,
  1017. 'extras': extras
  1018. }
  1019. result = self.get_collection().update_one(
  1020. filter = {'_id': ObjectId(self.id),
  1021. 'status': {'$nin': WithdrawStatus.finished_status()}},
  1022. update = {'$set': set_payload},
  1023. upsert = False)
  1024. return result.matched_count == 1
  1025. def set_bank_processing(self, **kwargs):
  1026. extras = self.extras
  1027. extras.update(**kwargs)
  1028. set_payload = {
  1029. 'status': WithdrawStatus.BANK_PROCESSION,
  1030. 'description': u'银行正在处理中',
  1031. 'remarks': '',
  1032. 'extras': extras
  1033. }
  1034. result = self.get_collection().update_one(
  1035. filter = {
  1036. '_id': ObjectId(self.id),
  1037. 'status': {
  1038. '$nin': WithdrawStatus.finished_status()
  1039. }
  1040. },
  1041. update = {'$set': set_payload},
  1042. upsert = False)
  1043. return result.matched_count == 1
  1044. @staticmethod
  1045. def make_no(role, user_id):
  1046. # type: (str, str)->str
  1047. assert role in ROLE.choices(), 'no this role'
  1048. return OrderNoMaker.make_order_no_32(identifier = str(user_id),
  1049. main_type = OrderMainType.WITHDRAW,
  1050. sub_type = ROLE.sub_type(role))
  1051. @staticmethod
  1052. def is_my(order_no):
  1053. try:
  1054. return order_no[0] == OrderMainType.WITHDRAW and order_no[1] in ROLE.sub_type_list()
  1055. except Exception as e:
  1056. return False
  1057. @staticmethod
  1058. def process_memcache_lock(order):
  1059. # type: (str)->None
  1060. return memcache_lock('WithdrawRecord_%s' % order, 'processing')
  1061. @property
  1062. def source_key(self):
  1063. if not self.withdrawSourceKey:
  1064. pay_app_type, occupant_id, tokens = WithdrawGateway.parse_gateway_key(self.withdrawGatewayKey)
  1065. appid, mchid = tokens[0], tokens[1]
  1066. app = WechatPayApp(appid = appid, mchid = mchid) # type: WechatPayApp
  1067. app.occupantId = occupant_id
  1068. return APP_KEY_DELIMITER.join(
  1069. [WithdrawGateway.LEDGER_PREFIX, PayAppType.WECHAT, getattr(app, '__source_key__')])
  1070. else:
  1071. return self.withdrawSourceKey
  1072. @property
  1073. def is_new_version(self):
  1074. return self.extras.get('v', 1) == 2
  1075. @classmethod
  1076. def count_today(cls, ownerId, role = ROLE.dealer):
  1077. return cls.objects(
  1078. ownerId = ownerId, role = role,
  1079. status__nin = [WithdrawStatus.CLOSED, WithdrawStatus.SUCCEEDED, WithdrawStatus.REFUND],
  1080. postTime__gte = get_zero_time(datetime.datetime.now())).count()
  1081. class PushRecord(Searchable):
  1082. createdTime = DateTimeField(default = datetime.datetime.now)
  1083. func_name = StringField()
  1084. result = DictField()
  1085. meta = {
  1086. 'collection': 'push_records',
  1087. 'db_alias': 'logdata',
  1088. 'max_documents': 100,
  1089. 'max_size': 2000000
  1090. }
  1091. def __repr__(self): return '<PushRecord func_name=%s>' % (self.func_name,)
  1092. class DealerDoneReportRecord(DynamicDocument):
  1093. reportName = StringField()
  1094. reportDay = StringField()
  1095. dealerIds = ListField()
  1096. expireAt = DateTimeField(default = None)
  1097. meta = {
  1098. 'collection': 'dealer_done_report_record',
  1099. 'db_alias': 'logdata'
  1100. }
  1101. @classmethod
  1102. def update_done_list(cls, report_name, report_day, done_list):
  1103. return cls.objects(reportName = report_name, reportDay = report_day).upsert_one(
  1104. push_all__dealerIds = done_list,
  1105. expireAt = to_datetime(report_day, time_format = "%Y-%m-%d") + datetime.timedelta(days = 7))
  1106. class RefundWithdrawRecord(Searchable):
  1107. """
  1108. 退款提现单,需要记录给经销商/代理商退款的情况
  1109. """
  1110. withdraw_record_id = ObjectIdField(unique = True)
  1111. createdTime = DateTimeField(default = datetime.datetime.now)
  1112. meta = {
  1113. 'abstract': True,
  1114. }
  1115. class ChangeLog(Searchable):
  1116. """
  1117. 在一些敏感的,涉及到需要确保后续可回溯的操作的时候,需要进行记录.
  1118. TODO 这里更加合理的实现,是运用MongoDB的changeStream特性
  1119. 脚本监听,然后过滤处理存储。
  1120. """
  1121. content = StringField()
  1122. event = StringField()
  1123. sender = StringField()
  1124. detail = StrictDictField()
  1125. pre = StrictDictField()
  1126. post = StrictDictField()
  1127. createdTime = DateTimeField(default = datetime.datetime.now)
  1128. meta = {"collection": "change_logs", "db_alias": "logdata"}
  1129. search_fields = ('content', 'event', 'sender')
  1130. def to_dict(self):
  1131. return {
  1132. 'content': self.content,
  1133. 'event': self.event,
  1134. 'sender': self.sender,
  1135. 'detail': self.detail
  1136. }
  1137. class Balance(EmbeddedDocument):
  1138. balance = MonetaryField(verbose_name = '提现收入', default = RMB('0.00'))
  1139. frozenBalance = MonetaryField(verbose_name = '冻结提现收入', default = RMB('0.00'))
  1140. def __repr__(self):
  1141. return "balance = {}, frozenBalance = {}".format(self.balance, self.frozenBalance)
  1142. class AccuracyBalance(EmbeddedDocument):
  1143. balance = AccuracyMoneyField(verbose_name = '提现收入', default = AccuracyRMB('0.00'))
  1144. frozenBalance = AccuracyMoneyField(verbose_name = '冻结提现收入', default = AccuracyRMB('0.00'))
  1145. class TempValues(Searchable):
  1146. """
  1147. 用于存放一些重要信息的临时数据库表,放memcached中不安全,就用这个
  1148. """
  1149. key = StringField(verbose_name = 'key', default = '', unique = True)
  1150. value = StringField(verbose_name = 'value', default = '')
  1151. expireAt = DateTimeField(default = None, verbose_name = u'过期时间')
  1152. meta = {"collection": "temp_values", "db_alias": "logdata"}
  1153. @staticmethod
  1154. def get(key):
  1155. obj = TempValues.objects.filter(__raw__ = {'key': key, '$or': [{'expireAt': {'$exists': False}}, {
  1156. 'expireAt': {'$gte': datetime.datetime.now()}}]}).first()
  1157. if obj:
  1158. return json_loads(obj.value)
  1159. return None
  1160. @staticmethod
  1161. def set(key, value, expire = 60 * 60):
  1162. if isinstance(value, dict):
  1163. _value = json_dumps(value, serialize_type = True)
  1164. else:
  1165. _value = value
  1166. TempValues.objects(key = key).update_one(upsert = True,
  1167. key = key, value = _value, expireAt = datetime.datetime.now() + datetime.timedelta(seconds = expire))
  1168. @staticmethod
  1169. def remove(key):
  1170. try:
  1171. TempValues.objects(key = key).update(
  1172. expireAt = datetime.datetime.now() - datetime.timedelta(seconds = 8 * 60 * 60))
  1173. except Exception, e:
  1174. pass
  1175. class OperatorLog(Searchable):
  1176. """
  1177. 关键用户行为变更日志
  1178. 着重记录用户的并不高频但重要的操作,更改配置项
  1179. """
  1180. class LogLevel(IterConstant):
  1181. INFO = 'INFO'
  1182. NOTICE = 'NOTICE'
  1183. WARNING = 'WARNING'
  1184. CRITICAL = 'CRITICAL'
  1185. EMERGENCY = 'EMERGENCY'
  1186. username = StringField(verbose_name = u'操作员账号')
  1187. operatorId = ObjectIdField(verbose_name=u'操作员关联ID')
  1188. role = StringField(verbose_name = u'操作员角色')
  1189. level = StringField(verbose_name = u'日志级别')
  1190. operatorName = StringField(verbose_name = u'操作名称')
  1191. content = DictField(verbose_name = u'日志内容')
  1192. dateTimeAdded = DateTimeField(verbose_name = u'添加时间', default = datetime.datetime.now)
  1193. meta = {'collection': 'operator_logs', 'db_alias': 'logdata'}
  1194. @classmethod
  1195. def log(cls, user, level, operator_name, content):
  1196. # type: (UserSearchable, basestring, basestring, dict)->None
  1197. try:
  1198. cls(username = user.username,
  1199. role = user.role,
  1200. operatorId = user.id,
  1201. level = level,
  1202. content = content,
  1203. operatorName = operator_name).save()
  1204. except Exception, e:
  1205. logger.exception(e)
  1206. @classmethod
  1207. def log_dev_operation(cls, operator, device, operator_name, content, level=LogLevel.WARNING):
  1208. try:
  1209. content.update({
  1210. 'logicalCode': device.logicalCode,
  1211. 'devNo': device.devNo,
  1212. 'devTypeCode': device.devTypeCode
  1213. })
  1214. return cls(username=operator.username,
  1215. role=operator.role,
  1216. operatorId=operator.id,
  1217. level=level,
  1218. content=content,
  1219. operatorName=operator_name).save()
  1220. except Exception, e:
  1221. logger.exception(e)
  1222. @classmethod
  1223. def record_dev_setting_changes_log(cls, user, operator_name, logicalCode, devTypeCode, content):
  1224. # type: (UserSearchable, basestring, str, str, dict)->Searchable
  1225. try:
  1226. content.update({
  1227. 'logicalCode': logicalCode,
  1228. 'devTypeCode': devTypeCode
  1229. })
  1230. return cls(username = user.username,
  1231. role = user.role,
  1232. operatorId = user.id,
  1233. level = 'INFO',
  1234. content = content,
  1235. operatorName = operator_name).save()
  1236. except Exception, e:
  1237. logger.exception(e)
  1238. class ExceptionLog(Searchable):
  1239. """
  1240. 异常记录
  1241. """
  1242. user = StringField()
  1243. dateTimeAdded = DateTimeField(verbose_name = u'添加时间', default = datetime.datetime.now)
  1244. exception = StringField()
  1245. extra = DictField()
  1246. meta = {'collection': 'exception_log', 'db_alias': 'logdata'}
  1247. @classmethod
  1248. def log(cls, user, exception, extra):
  1249. # type: (str, basestring, dict)->None
  1250. try:
  1251. cls(
  1252. user = user,
  1253. exception = exception,
  1254. extra = extra
  1255. ).save()
  1256. except Exception, e:
  1257. logger.exception(e)
  1258. class MonthlySubTemp(EmbeddedDocument):
  1259. displayName = StringField(verbose_name = u"展示名称", default = "包月套餐")
  1260. price = MonetaryField(verbose_name = u"价格", default = RMB(30))
  1261. numOfMonth = IntField(verbose_name = u"包月的时间 几个月", default = 1)
  1262. def to_dict(self):
  1263. return {
  1264. "displayName": self.displayName,
  1265. "price": str(self.price),
  1266. "numOfMonth": self.numOfMonth
  1267. }
  1268. @classmethod
  1269. def default(cls):
  1270. return cls()
  1271. @classmethod
  1272. def create(cls, **kwargs):
  1273. displayName = kwargs.get("displayName")
  1274. price = kwargs.get("price")
  1275. numOfMonth = kwargs.get("numOfMonth")
  1276. obj = cls.default()
  1277. if displayName is not None:
  1278. obj.displayName = displayName
  1279. if price is not None:
  1280. obj.price = RMB(price)
  1281. if numOfMonth is not None:
  1282. obj.numOfMonth = numOfMonth
  1283. return obj
  1284. # 包月计费模板
  1285. class MonthlyPackageTemp(Searchable):
  1286. """
  1287. 设备类型 和包月计费 拆开 不管什么样的设备类型 次是使用的单位 而每次使用多少 则可以再具体有电量和时间两个基本的计量
  1288. """
  1289. ownerId = StringField(verbose_name = u"包月规则制定的经销商", default = "")
  1290. name = StringField(verbose_name = u"模板名称", default = u"默认模板")
  1291. isDefault = BooleanIntField(verbose_name = u"是否是默认规则", default = 0)
  1292. saleable = BooleanIntField(verbose_name = u"是否开启此模板", default = 0)
  1293. isDelete = BooleanIntField(verbose_name = u"是否被经销商删除", default = 0)
  1294. bothCardAndMobile = BooleanIntField(verbose_name = u"是否通用", default = 0)
  1295. maxCountOfDay = IntField(verbose_name = u"每日的最大的次数", default = 1, min_value = 0)
  1296. maxCountOfMonth = IntField(verbose_name = u"每月的最大次数", default = 30, min_value = 0)
  1297. maxTimeOfCount = IntField(verbose_name = u"每次最大充电量 单位度", default = 240, min_value = 0)
  1298. maxElecOfCount = IntField(verbose_name = u"每次最大的充电时间 单位分钟", default = 1, min_value = 0)
  1299. dateTimeAdded = DateTimeField(verbose_name = u"设置模板的时间", default = datetime.datetime.now)
  1300. dateTimeUpdated = DateTimeField(verbose_name = u"计费模板更新的时间", default = datetime.datetime.now)
  1301. subTemplate = EmbeddedDocumentListField(verbose_name = u"子模版", document_type = MonthlySubTemp,
  1302. default = [MonthlySubTemp.default()])
  1303. meta = {
  1304. "collection": "MonthlyPackageTemp",
  1305. "db_alias": "default"
  1306. }
  1307. __unchargeable_fields = ("id", "ownerId", "dateTimeAdded")
  1308. @classmethod
  1309. def get_package_by_id(cls, _id):
  1310. return cls.objects.filter(id = _id).first()
  1311. @classmethod
  1312. def default_one(cls, ownerId):
  1313. """
  1314. :param ownerId:
  1315. :return:
  1316. """
  1317. obj = cls.objects.filter(ownerId = ownerId, isDefault = 1).first()
  1318. if not obj:
  1319. obj = cls(ownerId = ownerId, isDefault = 1).save()
  1320. return obj
  1321. @classmethod
  1322. def get_template_by_dealer(cls, ownerId):
  1323. """
  1324. 获取经销商的 所有包月模板
  1325. :param ownerId:
  1326. :return:
  1327. """
  1328. objs = cls.objects.filter(ownerId = ownerId, isDelete = 0)
  1329. if not objs:
  1330. return [cls.default_one(ownerId)]
  1331. return objs
  1332. @classmethod
  1333. def get_dealer_package(cls, ownerId, **kwargs):
  1334. return cls.objects.filter(ownerId = ownerId, **kwargs).first()
  1335. def set_default(self):
  1336. """
  1337. 设置为默认
  1338. :return:
  1339. """
  1340. self.__class__.objects.filter(ownerId = self.ownerId, isDefault = 1).update(isDefault = 0)
  1341. self.update(isDefault = 1)
  1342. def to_dict(self):
  1343. result = super(MonthlyPackageTemp, self).to_dict()
  1344. subList = list()
  1345. for _index, _sub in enumerate(self.subTemplate):
  1346. _subResult = _sub.to_dict()
  1347. _subResult["index"] = _index
  1348. subList.append(_subResult)
  1349. result["subTemplate"] = subList
  1350. return result
  1351. def update_by_fields(self, **kwargs):
  1352. """
  1353. 更新 包月套餐的自带属性 某些字段不可更改 直接跳过
  1354. :param kwargs:
  1355. :return:
  1356. """
  1357. fields = self.__class__._fields_ordered
  1358. for _field in fields:
  1359. if kwargs.get(_field) is None:
  1360. continue
  1361. if _field in self.__unchargeable_fields:
  1362. continue
  1363. if _field == "subTemplate":
  1364. # 子模版的保存
  1365. self.subTemplate = [MonthlySubTemp.create(**_sub) for _sub in kwargs.get(_field)]
  1366. continue
  1367. setattr(self, _field, kwargs.get(_field))
  1368. return self.save()
  1369. @classmethod
  1370. def create_by_fields(cls, ownerId, **kwargs):
  1371. obj = cls(ownerId = ownerId)
  1372. return obj.update_by_fields(**kwargs)
  1373. class OrderRecordBase(Searchable):
  1374. meta = {
  1375. 'abstract': True,
  1376. }
  1377. openId = StringField(verbose_name = u"微信ID", default = '')
  1378. nickname = StringField(verbose_name = u'用户昵称', default = '')
  1379. devNo = StringField(verbose_name = u"设备ID", default = None)
  1380. devType = StringField(verbose_name = u"设备类型", default = None)
  1381. devTypeName = StringField(verbose_name = u"设备类型名称", default = None)
  1382. devTypeCode = StringField(verbose_name = u"设备类型编码", default = None)
  1383. logicalCode = StringField(verbose_name = u"设备逻辑编码", default = None)
  1384. groupId = StringField(verbose_name = u"设备地址编号", default = None)
  1385. address = StringField(verbose_name = u"设备地址", default = None)
  1386. groupNumber = StringField(verbose_name = u"设备", default = None)
  1387. groupName = StringField(verbose_name = u"交易场地", default = None)
  1388. dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = u'生成时间')
  1389. @property
  1390. def dev_type_name(self):
  1391. if self.devTypeName:
  1392. return self.devTypeName
  1393. if self.devType:
  1394. return self.devType
  1395. return u'未知'
  1396. @property
  1397. def device(self):
  1398. # type:()->DeviceDict
  1399. from apps.web.device.models import Device
  1400. _attr_name = '__device__'
  1401. if hasattr(self, _attr_name):
  1402. return getattr(self, _attr_name)
  1403. else:
  1404. device = Device.get_dev(self.devNo)
  1405. setattr(self, _attr_name, device)
  1406. return device
  1407. @property
  1408. def dev_type_code(self):
  1409. if self.devTypeCode:
  1410. return self.devTypeCode
  1411. device = self.device
  1412. if device:
  1413. return device.devTypeCode
  1414. else:
  1415. return ''
  1416. @property
  1417. def group(self):
  1418. # type:()->GroupDict
  1419. from apps.web.device.models import Group
  1420. _attr_name = '__group__'
  1421. if hasattr(self, _attr_name):
  1422. return getattr(self, _attr_name)
  1423. else:
  1424. group = Group.get_group(self.groupId)
  1425. setattr(self, _attr_name, group)
  1426. return group
  1427. @group.setter
  1428. def group(self, group):
  1429. _attr_name = '__group__'
  1430. setattr(self, _attr_name, group)
  1431. @property
  1432. def owner(self):
  1433. from apps.web.dealer.models import Dealer
  1434. _attr_name = '__my_owner__'
  1435. if hasattr(self, _attr_name):
  1436. return getattr(self, _attr_name)
  1437. else:
  1438. ownerId = getattr(self, 'ownerId', None)
  1439. if not ownerId:
  1440. return None
  1441. else:
  1442. dealer = Dealer.objects(id = ownerId).first()
  1443. setattr(self, _attr_name, dealer)
  1444. return dealer
  1445. @property
  1446. def device_identity_info(self):
  1447. return {
  1448. 'logicalCode': self.logicalCode,
  1449. 'devTypeCode': self.devTypeCode,
  1450. 'devTypeName': self.dev_type_name,
  1451. 'groupName': self.groupName,
  1452. 'groupNumber': self.groupNumber,
  1453. 'address': self.address,
  1454. 'groupId': self.groupId
  1455. }