models.py 73 KB

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