models.py 86 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632
  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. RefundSubType
  21. from apps.web.constant import Const
  22. from apps.web.core import ROLE
  23. from apps.web.core.db import Searchable, StrictDictField, MonetaryField, RoleBaseDocument, PermillageField, \
  24. AccuracyMoneyField, BooleanIntField, BaseDocument
  25. from apps.web.core.exceptions import ImproperlyConfigured
  26. from apps.web.core.payment import WithdrawGateway, PaymentGateway
  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. from apps.web.user.models import RechargeRecord
  36. from apps.web.dealer.models import DealerRechargeRecord
  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. bankCode = StringField(verbose_name = u'银行code', unique = True, null = False)
  209. bankName = StringField(verbose_name = u'银行名称', unique = True, null = False)
  210. sn = IntField(verbose_name = u'排序序号', default = 0)
  211. wechatBankCode = StringField(verbose_name = u'微信银行ID', default = '')
  212. patterns = ListField(verbose_name = u'匹配正则', default = [])
  213. meta = {'collection': 'banks', 'db_alias': 'default'}
  214. buffer = {}
  215. buffer_lock = ThreadLock()
  216. def to_dict(self):
  217. return {
  218. 'bankCode': self.bankCode,
  219. 'bankName': self.bankName,
  220. 'wechatBankCode': self.wechatBankCode,
  221. 'patterns': self.patterns
  222. }
  223. @classmethod
  224. def get_bank_info(cls, cardNo):
  225. cls.__load_in_mem()
  226. bank_info = BankAPI().get_bank_card_info(card_no = cardNo)
  227. if bank_info:
  228. bank_code = bank_info['bankCode']
  229. card_type = bank_info['cardType']
  230. if bank_info['bankCode'] in cls.buffer['code']:
  231. bank_name = cls.buffer['code'][bank_code]['bankName']
  232. bank_name2 = cls.buffer['code'][bank_code]['bankName2']
  233. card_type = cls.CARD_TYPE_MAP.get(card_type, card_type)
  234. return {
  235. 'cardNo': cardNo,
  236. 'bankCode': bank_code,
  237. 'cardType': card_type,
  238. 'bankName': bank_name,
  239. 'bankName2': bank_name2
  240. }
  241. else:
  242. return {
  243. 'cardNo': cardNo,
  244. 'bankCode': bank_code,
  245. 'cardType': card_type,
  246. 'bankName': '',
  247. 'bankName2': ''
  248. }
  249. return None
  250. @classmethod
  251. def get_public_banks(cls):
  252. cls.__load_in_mem()
  253. return cls.buffer['public']
  254. @classmethod
  255. def get_personal_banks(cls):
  256. cls.__load_in_mem()
  257. return cls.buffer['personal']
  258. @classmethod
  259. def support_personal(cls, bankName):
  260. cls.__load_in_mem()
  261. return bankName in cls.buffer['personal']
  262. @classmethod
  263. def support_public(cls, bankName):
  264. cls.__load_in_mem()
  265. return bankName in cls.buffer['public']
  266. @classmethod
  267. def get_wechat_bank_code(cls, bankName):
  268. cls.__load_in_mem()
  269. if bankName in cls.buffer['personal']:
  270. return cls.buffer['name'][bankName]['wechatBankCode']
  271. else:
  272. return ''
  273. @classmethod
  274. def get_collection(cls):
  275. return cls._get_collection()
  276. @classmethod
  277. def __acquire_lock(cls):
  278. cls.buffer_lock.acquire_lock()
  279. @classmethod
  280. def __release_lock(cls):
  281. cls.buffer_lock.release_lock()
  282. def reset_buffer(self):
  283. try:
  284. self.__acquire_lock()
  285. self.buffer = {}
  286. finally:
  287. self.__release_lock()
  288. @classmethod
  289. def __load_in_mem(cls):
  290. if cls.buffer:
  291. return
  292. try:
  293. cls.__acquire_lock()
  294. if cls.buffer:
  295. return
  296. cls.buffer = {
  297. 'public': [], # type: list
  298. 'personal': [], # type: list
  299. 'name': {},
  300. 'code': {}
  301. }
  302. banks = cls.objects().order_by('sn')
  303. for bank in banks:
  304. cls.buffer['name'][bank.bankName] = bank.to_dict()
  305. cls.buffer['name'][bank.bankName2] = bank.to_dict()
  306. cls.buffer['code'][bank.bankCode] = bank.to_dict()
  307. cls.buffer['public'].append(bank.bankName)
  308. if bank.bankName2:
  309. cls.buffer['public'].append(bank.bankName2)
  310. if bank.wechatBankCode:
  311. cls.buffer['personal'].append(bank.bankName)
  312. if bank.bankName2:
  313. cls.buffer['personal'].append(bank.bankName2)
  314. finally:
  315. cls.__release_lock()
  316. class FAQ(Searchable):
  317. """
  318. 配置FAQ,首先让经销商|终端用户看到常用问题,找不到问题再联系客服
  319. """
  320. question = StringField(verbose_name = '问题')
  321. answer = StringField(verbose_name = '答案')
  322. target = StringField(verbose_name = '目标 := (Dealer|Agent|EndUser)', default = '*')
  323. devTypeId = StringField(verbose_name = '设备类型ID,默认为普适性问题,为空', default = '')
  324. images = ListField(verbose_name = '辅助说明图像')
  325. videos = ListField(verbose_name = '辅助说明视频')
  326. upvotes = IntField(verbose_name = '该FAQ有帮助')
  327. downvotes = IntField(verbose_name = '该FAQ没有帮助')
  328. managerId = StringField(verbose_name = '管理员ID', null = False)
  329. meta = {'collection': 'faqs', 'db_alias': 'default'}
  330. def to_dict(self):
  331. return {
  332. 'id': str(self.id),
  333. 'question': self.question,
  334. 'answer': self.answer,
  335. 'target': self.target,
  336. 'images': self.images,
  337. 'videos': self.videos,
  338. 'devTypeId': self.devTypeId,
  339. 'upvotes': self.upvotes,
  340. 'downvotes': self.downvotes
  341. }
  342. @classmethod
  343. def get_by_role(cls, role):
  344. #: 目前无用户体系,暂时将匿名用户作为终端用户
  345. role = ROLE.myuser if role == ROLE.anonymoususer else role
  346. return cls.objects(target__in = ['*', role])
  347. class Feature(Searchable):
  348. """
  349. 特性列表的所有特性,由超级管理员配置
  350. (角色,特性名称) 唯一
  351. """
  352. name = StringField(verbose_name = u'特性名称', min_length = 1)
  353. key = StringField(verbose_name = u'具体的特性代号,为英文')
  354. role = StringField(verbose_name = u'特性分配的角色', choices = ROLE.choices())
  355. # managerId = StringField(verbose_name = u'厂商ID')
  356. default = BooleanField(verbose_name = u'默认值')
  357. # by = StringField(verbose_name = u'创建的超级管理员的ID')
  358. desc = StringField(verbose_name = u'描述')
  359. dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = u'生成时间')
  360. search_fields = ('key', 'name')
  361. meta = {
  362. 'indexes': [
  363. {'fields': ['name', 'role'], 'unique': True},
  364. ],
  365. "collection": "features",
  366. "db_alias": "default"
  367. }
  368. @classmethod
  369. def for_dealer(cls, **kwargs): return cls.objects(role = ROLE.dealer, **kwargs)
  370. @classmethod
  371. def for_agent(cls, **kwargs): return cls.objects(role = ROLE.agent, **kwargs)
  372. @classmethod
  373. def for_manager(cls, **kwargs): return cls.objects(role = ROLE.manager, **kwargs)
  374. def to_dict(self):
  375. return {
  376. 'id': str(self.id),
  377. 'role': self.role,
  378. 'name': self.name,
  379. # 'managerId': self.managerId,
  380. # 'by': self.by,
  381. 'key': self.key,
  382. 'default': self.default,
  383. 'desc': self.desc,
  384. 'dateTimeAdded': self.dateTimeAdded
  385. }
  386. def __repr__(self):
  387. return '<Feature key=%s, role=%s>' % (self.key, self.role)
  388. DEFAULT_DEALER_FEATURES = ['show_offline_coins']
  389. class AddressType(Searchable):
  390. value = StringField(verbose_name = "地址值", unique = True) #: e.g. school
  391. label = StringField(verbose_name = "地址名称", default = "") #: e.g. 学校
  392. showWeight = IntField(verbose_name = '显示权重', default = 0)
  393. createdAt = DateTimeField(default = datetime.datetime.now)
  394. meta = {"collection": "AddressType", "db_alias": "default"}
  395. class UserSearchable(RoleBaseDocument):
  396. class Status(IterConstant):
  397. NORMAL = 'normal'
  398. FORBIDDEN = 'forbidden'
  399. ABNORMAL = 'abnormal'
  400. NOWITHDRAW = 'noWithdraw'
  401. username = StringField(verbose_name = "用户名称:=手机号", max_length = 100)
  402. password = StringField(verbose_name = "密码", min_length = 1)
  403. nickname = StringField(verbose_name = '名字,用来标识', default = '')
  404. remarks = StringField(verbose_name = "备注", default = "")
  405. avatar = StringField(verbose_name = u"用户头像", default = "")
  406. activited = BooleanField(verbose_name = "是否激活", default = True)
  407. status = StringField(verbose_name = "账号状态", default = Status.NORMAL)
  408. dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = '添加进来的时间')
  409. last_login = DateTimeField(default = datetime.datetime.now, verbose_name = '最近登录时间')
  410. phoneOS = StringField(verbose_name = u"终端操作系统", default = "")
  411. lastLoginUserAgent = StringField(verbose_name = u"最后一次登录的userAgent", default = "")
  412. smsVendor = StringField(verbose_name = u'sms提供商', default = '')
  413. meta = {
  414. 'abstract': True,
  415. 'indexes': [
  416. {
  417. 'fields': ['$username', '$nickname'],
  418. 'weights': {'username': 5, 'nickname': 5}
  419. }
  420. ]
  421. }
  422. def __str__(self):
  423. return '{}<id={} username={} nickname={}>'.format(
  424. self.__class__.__name__, str(self.id), self.username, self.nickname)
  425. @property
  426. def human_id(self):
  427. return '{}({})'.format(self.username, self.role)
  428. @property
  429. def identity_id(self):
  430. return '{}_{}'.format(self.role, str(self.id))
  431. def to_dict(self, shadow = False):
  432. return {
  433. 'username': self.username if not shadow else '******',
  434. 'nickname': self.nickname,
  435. 'remarks': self.remarks,
  436. 'activited': self.activited,
  437. 'status': self.status,
  438. 'dateTimeAdded': self.dateTimeAdded,
  439. 'last_login': self.last_login,
  440. 'role': self.role,
  441. 'avatar': self.my_avatar
  442. }
  443. @classmethod
  444. def create_user(cls, username, password, **attrs):
  445. user = cls(username = username, **attrs)
  446. user.password = make_password(password)
  447. user.save()
  448. return user
  449. def is_authenticated(self):
  450. return True
  451. def set_password(self, raw_password):
  452. self.password = make_password(raw_password)
  453. self.save()
  454. return self
  455. def check_password(self, raw_password):
  456. return check_password(raw_password, self.password)
  457. def unlock_login(self):
  458. lm = LimitAttemptsManager(identifier = self.username, category = "%sLogin" % self.role)
  459. lm.clear()
  460. @classmethod
  461. def get_last_login1(cls, **kwargs):
  462. return cls.objects(**kwargs).order_by('-last_login').first()
  463. def __check_feature_setup(self):
  464. # type:()->(str, set)
  465. self_features = getattr(self, 'features', None)
  466. if self_features is None:
  467. raise ImproperlyConfigured('no features attr provided')
  468. return self.role, set(self_features)
  469. ## Feature Module
  470. @property
  471. def feature_list(self):
  472. # type:()->List[dict]
  473. role, self_features = self.__check_feature_setup()
  474. features = [_.to_dict() for _ in Feature.objects(role = role)] # type: List[dict]
  475. rv = [
  476. {
  477. 'name': feature['name'],
  478. 'key': feature['key'],
  479. 'value': feature['key'] in self_features
  480. } for feature in features
  481. ]
  482. #: unique to user, special features
  483. # set difference
  484. unique_features = self_features - {_['key'] for _ in features}
  485. rv += [{'name': _, 'key': _, 'value': True} for _ in unique_features]
  486. return rv
  487. def edit_feature_list(self, feature_list, special = None):
  488. # type:(list, Optional[str])->int
  489. self.__check_feature_setup()
  490. features = [_['key'] for _ in feature_list if _['value']]
  491. if special:
  492. features.append(special)
  493. return self.update(set__features = features)
  494. @property
  495. def feature_boolean_map(self):
  496. # type: ()->Dict[str, bool]
  497. features = {}
  498. for _ in self.feature_list:
  499. if _['key']:
  500. features.update({_['key']: _['value']})
  501. return features
  502. def supports(self, feature):
  503. # type: (str)->bool
  504. _, self_features = self.__check_feature_setup()
  505. return feature in self_features
  506. @property
  507. def normal(self):
  508. return self.status in [self.Status.NORMAL, self.Status.NOWITHDRAW]
  509. @property
  510. def forbidden(self):
  511. return self.status == self.Status.FORBIDDEN
  512. @property
  513. def abnormal(self):
  514. return self.status == self.Status.ABNORMAL
  515. @property
  516. def no_withdraw(self):
  517. return self.status == self.Status.NOWITHDRAW
  518. @property
  519. def my_avatar(self):
  520. if self.avatar:
  521. return self.avatar
  522. else:
  523. return ''
  524. @property
  525. def universal_password_login(self):
  526. return getattr(self, '__universal_password_login__', False)
  527. @universal_password_login.setter
  528. def universal_password_login(self, upl):
  529. setattr(self, '__universal_password_login__', upl)
  530. class CapitalUser(UserSearchable):
  531. """
  532. 带资金以及提现用户
  533. """
  534. meta = {
  535. 'abstract': True,
  536. }
  537. INCOME_SOURCE_LIST = list()
  538. INCOME_SOURCE_TO_TYPE = dict()
  539. INCOME_TYPE_LIST = list()
  540. INCOME_TYPE_TO_FIELD = dict()
  541. DEFAULT_WITHDRAW_OPTIONS = {
  542. 'autoWithdrawSwitch': False,
  543. 'autoWithdrawType': "wechat",
  544. 'autoWithdrawMin': RMB(settings.WITHDRAW_MINIMUM).mongo_amount,
  545. 'autoWithdrawStrategy': {'type': 'asWeek', 'value': 3},
  546. 'autoWithdrawBankFee': True,
  547. 'alipay': {
  548. 'realName': '',
  549. 'loginId': ''
  550. },
  551. 'wechat': {
  552. 'realName': ''
  553. }
  554. }
  555. ongoingWithdrawList = ListField(field = StringField())
  556. inhandWithdrawList = ListField(field = DictField())
  557. withdrawOptions = DictField(verbose_name = u"提现相关选项", default = DEFAULT_WITHDRAW_OPTIONS)
  558. bankWithdrawFee = BooleanField(verbose_name = u"银行卡提现手续开关(资金池代理商开关和这个开关为双开关)", default = True)
  559. @property
  560. def is_new_join(self):
  561. one_month_before = datetime.datetime.now() - datetime.timedelta(days = 90)
  562. if self.dateTimeAdded > one_month_before:
  563. return True
  564. else:
  565. return False
  566. @classmethod
  567. def income_field_name(cls, income_type):
  568. # type: (str)->str
  569. assert income_type in cls.INCOME_TYPE_LIST, 'not support this income type({})'.format(income_type)
  570. return cls.INCOME_TYPE_TO_FIELD[income_type]
  571. @classmethod
  572. def fund_key(cls, income_type, source_key):
  573. return '{field}.{key}'.format(field = cls.income_field_name(income_type), key = source_key)
  574. def balance_dict(self, income_type):
  575. return dict(getattr(self, self.income_field_name(income_type = income_type)))
  576. def set_balance(self, income_type, source_key, money):
  577. # type: (str, str, RMB)->bool
  578. """
  579. 单元测试使用.初始化值
  580. :param income_type:
  581. :param source_key:
  582. :param money:
  583. :return:
  584. """
  585. assert isinstance(money, RMB), 'money has to be a RMB instance'
  586. assert income_type in self.INCOME_TYPE_LIST, 'not support this source{}'.format(income_type)
  587. assert source_key, 'source key must not be none'
  588. query = {'_id': ObjectId(self.id)}
  589. update = {
  590. '$set': {
  591. '{fund_key}.balance'.format(fund_key = self.fund_key(income_type, source_key)): money.mongo_amount,
  592. '{fund_key}.frozenBalance'.format(fund_key = self.fund_key(income_type, source_key)): RMB(
  593. 0).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 incr_fund(self, income_type, source_key, money):
  599. # type: (str, str, RMB)->bool
  600. assert isinstance(money, RMB), 'money has to be a RMB instance'
  601. assert income_type in self.INCOME_TYPE_LIST, 'not support this source'
  602. assert source_key, 'gateway key must not be none'
  603. query = {'_id': ObjectId(self.id)}
  604. update = {
  605. '$inc': {
  606. '{fund_key}.balance'.format(fund_key = self.fund_key(income_type, source_key)): money.mongo_amount
  607. }
  608. }
  609. result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult
  610. return bool(result.modified_count == 1)
  611. def decr_fund(self, income_type, source_key, money):
  612. # type: (str, str, RMB)->int
  613. """
  614. 正常流程不使用该接口,会让账对不上
  615. :param income_type:
  616. :param source_key:
  617. :param money:
  618. :return:
  619. """
  620. assert isinstance(money, RMB), 'money has to be a RMB instance'
  621. assert income_type in self.INCOME_TYPE_LIST, 'not support this source'
  622. assert source_key, 'gateway key must not be none'
  623. query = {'_id': ObjectId(self.id)}
  624. update = {
  625. '$inc':
  626. {
  627. '{fund_key}.balance'.format(fund_key = self.fund_key(income_type, source_key)): (
  628. -money).mongo_amount
  629. }
  630. }
  631. result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult
  632. return bool(result.modified_count == 1)
  633. @classmethod
  634. def prepare_sellet_income(cls, object_id, transaction_id, source, source_key, money, selfAgg=False):
  635. assert isinstance(money, RMB), 'money has to be a RMB instance'
  636. assert source in cls.INCOME_SOURCE_LIST, 'not support this source'
  637. assert source_key, 'source key must not be none'
  638. if money == RMB(0):
  639. logger.debug('{}<id={}> sellet zero.'.format(cls.__name__, object_id))
  640. return
  641. income_type = cls.INCOME_SOURCE_TO_TYPE[source]
  642. query = {
  643. '_id': ObjectId(object_id),
  644. 'ongoingWithdrawList.transaction_id': {
  645. '$ne': transaction_id
  646. }
  647. }
  648. update = {
  649. '$inc': {
  650. '{filed}.{key}.balance'.format(filed=cls.INCOME_TYPE_TO_FIELD[income_type],
  651. key=source_key): money.mongo_amount
  652. },
  653. '$addToSet': {
  654. 'ongoingWithdrawList': {'transaction_id': transaction_id, 'money': money.mongo_amount}
  655. }
  656. }
  657. if selfAgg:
  658. update['$inc'].update({
  659. 'aggregatedIncome.{source}'.format(source=source): money.mongo_amount,
  660. 'incomeMap.{source}'.format(source=source): money.mongo_amount
  661. })
  662. result = cls.get_collection().update_one(query, update, upsert=False) # type: UpdateResult
  663. return bool(result.modified_count == 1)
  664. @classmethod
  665. def commit_sellet_income(cls, object_id, transaction_id):
  666. query = {
  667. '_id': ObjectId(object_id),
  668. 'ongoingWithdrawList': {
  669. '$elemMatch': {
  670. 'transaction_id': transaction_id
  671. }
  672. }
  673. }
  674. update = {
  675. '$pull': {
  676. 'ongoingWithdrawList': {'transaction_id': transaction_id}
  677. }
  678. }
  679. result = cls.get_collection().update_one(query, update, upsert=False) # type: UpdateResult
  680. return bool(result.modified_count == 1)
  681. def sub_balance(self, income_type, source_key = None, only_ledger = True):
  682. # type: (str, str, bool)->RMB
  683. balance_dict = self.balance_dict(income_type = income_type) # type: dict
  684. if not source_key:
  685. balance_list = []
  686. for key, value in balance_dict.iteritems():
  687. if only_ledger:
  688. if WithdrawGateway.is_ledger(key):
  689. balance_list.append(value.balance)
  690. else:
  691. balance_list.append(value.balance)
  692. return sum_rmb(balance_list)
  693. else:
  694. return balance_dict.get(source_key, Balance()).balance
  695. @property
  696. def total_balance(self):
  697. # type: ()->RMB
  698. total = RMB(0)
  699. for income_type in self.INCOME_TYPE_LIST:
  700. total += self.sub_balance(income_type)
  701. return total
  702. def sub_frozen_balance(self, income_type, source_key = None):
  703. # type: (str, str)->RMB
  704. field = self.income_field_name(income_type = income_type)
  705. balance_list = []
  706. for item in self.inhandWithdrawList:
  707. if item['field'] != field:
  708. continue
  709. if source_key and item['key'] != source_key:
  710. continue
  711. balance_list.append(item['value'])
  712. balance_dict = self.balance_dict(income_type = income_type) # type: dict
  713. if not source_key:
  714. for key, value in balance_dict.iteritems():
  715. if WithdrawGateway.is_ledger(key):
  716. balance_list.append(value.frozenBalance)
  717. return sum_rmb(balance_list)
  718. else:
  719. balance_list.append(balance_dict.get(source_key, Balance()).frozenBalance)
  720. return sum_rmb(balance_list)
  721. @property
  722. def total_frozen_balance(self):
  723. # type: ()->RMB
  724. total = RMB(0)
  725. for income_type in self.INCOME_TYPE_LIST:
  726. total += self.sub_frozen_balance(income_type)
  727. return total
  728. def re_freeze_balance(self, income_type, money, source_key, transaction_id): # type:(str, RMB, str, str)->bool
  729. """
  730. 某种情况下, 即使反馈成功, 后续也会退单, 这个时候手工处理, 重新设置经销商inhand列表, 但是不在扣除金额(已经在成功的时候扣除了).
  731. :param source_key:
  732. :param income_type:
  733. :param money:
  734. :param transaction_id:
  735. """
  736. assert source_key, 'gateway is null'
  737. assert transaction_id, 'transaction id is null'
  738. field = self.income_field_name(income_type = income_type)
  739. query = {'_id': self.id, 'inhandWithdrawList.transaction_id': {'$ne': transaction_id}}
  740. update = {
  741. '$addToSet': {
  742. 'inhandWithdrawList': {
  743. 'transaction_id': transaction_id,
  744. 'field': field,
  745. 'key': source_key,
  746. 'value': money.mongo_amount
  747. }
  748. }
  749. }
  750. result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult
  751. return bool(result.modified_count == 1)
  752. def _freeze_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. field = self.income_field_name(income_type = income_type)
  756. fund_key = self.fund_key(income_type = income_type, source_key = source_key)
  757. query = {'_id': self.id, '{}.transaction_id'.format(freeze_type): {'$ne': transaction_id}}
  758. update = {
  759. '$inc': {
  760. '{fund_key}.balance'.format(fund_key=fund_key): (-money).mongo_amount
  761. },
  762. '$addToSet': {
  763. freeze_type: {
  764. 'transaction_id': transaction_id,
  765. 'field': field,
  766. 'key': source_key,
  767. 'value': money.mongo_amount
  768. }
  769. }
  770. }
  771. result = self.get_collection().update_one(query, update) # type: UpdateResult
  772. return bool(result.modified_count == 1)
  773. def _clear_frozen_balance(self, transaction_id, freeze_type):
  774. query = {'_id': self.id, '{}.transaction_id'.format(freeze_type): transaction_id}
  775. update = {
  776. '$pull': {
  777. freeze_type:
  778. {
  779. 'transaction_id': transaction_id
  780. }
  781. }
  782. }
  783. result = self.get_collection().update_one(query, update) # type: UpdateResult
  784. return bool(result.modified_count == 1)
  785. def _recover_frozen_balance(self, income_type, money, source_key, transaction_id, freeze_type):
  786. assert source_key, 'gateway is null'
  787. assert transaction_id, 'transaction id is null'
  788. fund_key = self.fund_key(income_type=income_type, source_key=source_key)
  789. query = {'_id': self.id, '{}.transaction_id'.format(freeze_type): transaction_id}
  790. update = {
  791. '$inc': {
  792. '{fund_key}.balance'.format(fund_key=fund_key): money.mongo_amount
  793. },
  794. '$pull': {
  795. freeze_type:
  796. {
  797. 'transaction_id': transaction_id
  798. }
  799. }
  800. }
  801. result = self.get_collection().update_one(query, update) # type: UpdateResult
  802. return bool(result.modified_count == 1)
  803. def freeze_balance(self, income_type, money, source_key, transaction_id): # type:(str, RMB, str, str)->bool
  804. """
  805. 提现的时候冻结经销商的金额
  806. :param source_key:
  807. :param income_type:
  808. :param money:
  809. :param transaction_id:
  810. """
  811. return self._freeze_balance(income_type, money, source_key, transaction_id, "inhandWithdrawList")
  812. def clear_frozen_balance(self, transaction_id): # type:(str)->bool
  813. """
  814. 提现完成 清理冻结
  815. :param transaction_id:
  816. :return:
  817. """
  818. assert transaction_id, 'transaction id is null'
  819. return self._clear_frozen_balance(transaction_id, "inhandWithdrawList")
  820. def recover_frozen_balance(self, income_type, money, source_key, transaction_id): # type:(str, RMB, str, str)->bool
  821. """
  822. 提现遇到问题 直接回滚
  823. :param source_key:
  824. :param income_type:
  825. :param money:
  826. :param transaction_id:
  827. :return:
  828. """
  829. assert source_key, 'gateway is null'
  830. assert transaction_id, 'transaction id is null'
  831. return self._recover_frozen_balance(income_type, money, source_key, transaction_id, "inhandWithdrawList")
  832. def withdraw_source_key(self, pay_app = None):
  833. # type:(PayAppBase)->str
  834. """
  835. 供PaymentGateway调用获取提现网关
  836. :param pay_app:
  837. :return:
  838. """
  839. raise NotImplementedError()
  840. @property
  841. def withdraw_sms_provider(self):
  842. # type: () -> WithdrawSMSProvider
  843. raise NotImplementedError()
  844. @property
  845. def withdraw_sms_phone_number(self):
  846. # type: () -> basestring
  847. raise NotImplementedError()
  848. def check_withdraw_min_fee(self, income_type, pay_type, amount):
  849. raise NotImplementedError()
  850. def new_withdraw_record(self, withdraw_gateway, pay_entity, source_key, income_type, amount, pay_type, manual,
  851. recurrent):
  852. # type: (WithdrawGateway, WithdrawBankCard, str, str, RMB, str, bool, bool) -> WithdrawRecord
  853. raise NotImplementedError()
  854. def new_withdraw_handler(self, record):
  855. # type: (WithdrawRecord) -> WithdrawHandler
  856. raise NotImplementedError()
  857. def get_bound_pay_openid(self, key):
  858. raise NotImplementedError()
  859. def withdraw_bank_card(self, accountCode):
  860. # type: (str)->WithdrawBankCard
  861. return WithdrawBankCard.objects(ownerId = str(self.id), role = self.role, accountCode = accountCode).first()
  862. @property
  863. def withdraw_bank_cards(self):
  864. banks = WithdrawBankCard.objects(ownerId = str(self.id), role = self.role).all()
  865. return [{
  866. 'accountCode': bank.accountCode,
  867. 'bankName': bank.bankName
  868. } for bank in banks]
  869. @property
  870. def auto_withdraw_min(self):
  871. return self.withdrawOptions.get('autoWithdrawMin')
  872. @property
  873. def auto_withdraw_type(self):
  874. return self.withdrawOptions.get('autoWithdrawType')
  875. @property
  876. def auto_withdraw_switch(self):
  877. return self.withdrawOptions.get('autoWithdrawSwitch')
  878. @property
  879. def auto_withdraw_strategy(self):
  880. return self.withdrawOptions.get('autoWithdrawStrategy', {})
  881. @property
  882. def auto_withdraw_bank_fee(self):
  883. return self.withdrawOptions.get('autoWithdrawBankFee', True)
  884. @property
  885. def withdraw_open_id(self):
  886. return getattr(self, '__withdraw_openid__')
  887. @withdraw_open_id.setter
  888. def withdraw_open_id(self, open_id):
  889. setattr(self, '__withdraw_openid__', open_id)
  890. @property
  891. def withdraw_alipay_config(self):
  892. return self.withdrawOptions.get('alipay', {})
  893. @property
  894. def withdraw_alipay_login_id(self):
  895. return self.withdraw_alipay_config.get('loginId', '')
  896. @property
  897. def withdraw_alipay_real_name(self):
  898. return self.withdraw_alipay_config.get('realName', '')
  899. @property
  900. def withdraw_wechat_config(self):
  901. return self.withdrawOptions.get('wechat', {})
  902. @property
  903. def withdraw_wechat_real_name(self):
  904. return self.withdraw_wechat_config.get('realName', '') or self.nickname
  905. @property
  906. def auto_withdraw_bound_open_id(self):
  907. raise NotImplementedError()
  908. def can_withdraw_today(self, amount):
  909. from apps.web.core.exceptions import ServiceException
  910. count, total = WithdrawRecord.count_today(ownerId = str(self.id), role = self.role)
  911. if count >= 10:
  912. raise ServiceException(
  913. {
  914. 'result': 0, 'description': u"今日提现次数超限",
  915. 'payload': {}
  916. })
  917. if not self.supports('in_withdraw_whitelist') and self.is_new_join:
  918. if amount + total > RMB(500):
  919. raise ServiceException(
  920. {
  921. 'result': 0, 'description': u"今日提现金额超限",
  922. 'payload': {}
  923. })
  924. else:
  925. if total + amount > RMB(settings.WITHDRAW_MAXIMUM):
  926. raise ServiceException(
  927. {
  928. 'result': 0,
  929. 'description': u"今日提现金额超限",
  930. 'payload': {}
  931. })
  932. @property
  933. def current_wallet_withdraw_source_key(self):
  934. logger.debug('get withdraw source key. source = {}'.format(repr(self)))
  935. from apps.web.helpers import AgentCustomizedBrandVisitor
  936. return AgentCustomizedBrandVisitor(factory_type = 'withdraw_source_key', pay_app = None).visit(self)
  937. def withdraw_support(self, source_key):
  938. from apps.web.agent.models import Agent
  939. is_ledger, agent, withdraw_gateway_list = Agent.withdraw_gateway_list(source_key)
  940. if not is_ledger:
  941. return {
  942. 'wechat': {
  943. 'support': False,
  944. },
  945. 'alipay': {
  946. 'support': False,
  947. },
  948. 'bank': {
  949. 'support': False,
  950. }
  951. }
  952. rv = {
  953. 'wechat': {
  954. 'support':
  955. withdraw_gateway_list['wechat'].support_withdraw and not withdraw_gateway_list[
  956. 'wechat'].manual_withdraw,
  957. 'realName': self.withdraw_wechat_real_name
  958. },
  959. 'alipay': {
  960. 'support': withdraw_gateway_list['alipay'].support_withdraw and not withdraw_gateway_list[
  961. 'wechat'].manual_withdraw,
  962. 'realName': self.withdraw_alipay_real_name,
  963. 'loginId': self.withdraw_alipay_login_id
  964. },
  965. 'bank': {
  966. 'support': withdraw_gateway_list['wechat'].support_withdraw_bank or withdraw_gateway_list[
  967. 'alipay'].support_withdraw_bank,
  968. 'cards': self.withdraw_bank_cards,
  969. 'transFee': agent.dealerBankWithdrawFee and self.bankWithdrawFee
  970. }
  971. }
  972. return rv
  973. def set_withdraw_alipay(self, loginId, realName):
  974. self.update(withdrawOptions__alipay = {'loginId': loginId, 'realName': realName})
  975. def set_withdraw_wechat(self, realName):
  976. self.update(withdrawOptions__wechat = {'realName': realName})
  977. class WithdrawRefundRecord(Searchable):
  978. withdraw_record_id = ObjectIdField(unique = True)
  979. createdTime = DateTimeField(default = datetime.datetime.now)
  980. role = StringField(choices = ROLE.choices())
  981. meta = {
  982. "collection": "withdraw_refund_record",
  983. "db_alias": "logdata"
  984. }
  985. class WithdrawRecord(Searchable):
  986. class WithdrawPayType(IterConstant):
  987. BANK = 'bank'
  988. WECHAT = 'wechat'
  989. order = StringField(verbose_name = u"订单号", unique = True)
  990. ownerId = StringField(verbose_name = u"提现者对应对象ID")
  991. role = StringField(verbose_name = u'提现者账号类型', choices = ROLE.choices())
  992. name = StringField(verbose_name = u"提现人姓名", default = "") # type: str
  993. phone = StringField(verbose_name = u"持卡人电话", default = "") # type: str
  994. balance = MonetaryField(verbose_name = u"提现人提现时刻余额", default = RMB('0.00')) # type: RMB
  995. incomeType = StringField(verbose_name = u'提现收入类型',
  996. choices = AGENT_INCOME_TYPE.choices() + DEALER_INCOME_TYPE.choices())
  997. withdrawSourceKey = StringField(verbose_name = u'提现来源KEY', default = None)
  998. payType = StringField(verbose_name = u"支付方式(微信,银行卡)", default = "") # type: str
  999. manual = BooleanField(verbose_name = u'是否手动处理', default = False) # type: bool
  1000. recurrent = BooleanField(verbose_name = u"是否为自动循环提现", default = False)
  1001. status = IntField(verbose_name = u"状态", default = WithdrawStatus.SUBMITTED)
  1002. remarks = StringField(verbose_name = u"备注系统内部状态", default = "")
  1003. description = StringField(verbose_name = u'给用户看的描述', default = '')
  1004. postTime = DateTimeField(verbose_name = u"请求时间", default = datetime.datetime.now)
  1005. finishedTime = DateTimeField(verbose_name = u"提现订单完成时间")
  1006. amount = MonetaryField(verbose_name = u"提现金额", default = RMB('0.00')) # type: RMB
  1007. serviceFee = MonetaryField(verbose_name = u"手续费", default = RMB('0.00')) # type: RMB
  1008. actualPay = MonetaryField(verbose_name = u"实际金额", default = RMB('0.00')) # type: RMB
  1009. bankTransFee = MonetaryField(verbose_name = u"转账银行费用", default = RMB('0.00')) # type: RMB
  1010. refunded = BooleanField(verbose_name = u'是否已经退款了', default = False)
  1011. cardUserName = StringField(verbose_name = u"持卡人姓名", default = "")
  1012. accountCode = StringField(verbose_name = u"提现银行卡号", default = "")
  1013. parentBankName = StringField(verbose_name = u"银行名称", default = "")
  1014. subBankName = StringField(verbose_name = u"银行支行名称", default = "")
  1015. payAgentId = StringField(verbose_name = u'提现处理平台代理商ID')
  1016. withdrawFeeRatio = PermillageField(verbose_name = u'提现费率', default = Const.PLATFORM_DEFAULT_WITHDRAW_FEE_RATIO)
  1017. partition = ListField(verbose_name = u'提现费率收入划分', default = [])
  1018. withdrawGatewayKey = StringField(verbose_name = u"提现资金池gateway key", required = True)
  1019. extras = DictField(verbose_name = u"记录提现额外信息", default = {})
  1020. search_fields = ('order', 'phone', 'name')
  1021. meta = {
  1022. 'indexes': [
  1023. {
  1024. 'fields': ['ownerId', 'role'],
  1025. },
  1026. 'status',
  1027. '-postTime',
  1028. 'payAgentId'
  1029. ],
  1030. 'collection': 'WithdrawRecord',
  1031. 'db_alias': 'default'
  1032. }
  1033. @classmethod
  1034. def get_record(cls, order_no):
  1035. return cls.objects(order = order_no).first()
  1036. @classmethod
  1037. def get_collection(cls):
  1038. return cls._get_collection()
  1039. @classmethod
  1040. def get_succeeded_records(cls, **kwargs):
  1041. return cls.objects(status = WithdrawStatus.SUCCEEDED, **kwargs)
  1042. @classmethod
  1043. def get_processing_via_bank(cls):
  1044. return cls.objects(status__in = [WithdrawStatus.PROCESSING, WithdrawStatus.BANK_PROCESSION],
  1045. payType = WITHDRAW_PAY_TYPE.BANK, manual = False)
  1046. @classmethod
  1047. def get_processing_via_v3(cls):
  1048. return cls.objects(status__in = [WithdrawStatus.PROCESSING], payType = WITHDRAW_PAY_TYPE.WECHAT, manual = False)
  1049. @classmethod
  1050. def get_failed_records(cls):
  1051. return cls.objects(status = WithdrawStatus.FAILED, refunded = False, manual = False)
  1052. @classmethod
  1053. def create(cls, withdraw_user, withdraw_gateway, pay_entity, source_key, income_type, pay_type, fund_map,
  1054. manual, recurrent, **kwargs):
  1055. # type: (CapitalUser, WithdrawGateway, WithdrawBankCard, str, str, str, dict, bool, bool, dict) -> WithdrawRecord
  1056. assert pay_type in (
  1057. WITHDRAW_PAY_TYPE.WECHAT, WITHDRAW_PAY_TYPE.BANK, WITHDRAW_PAY_TYPE.ALIPAY), 'not support this pay type'
  1058. if manual:
  1059. assert pay_type == WITHDRAW_PAY_TYPE.BANK, 'pay type must be bank in manual'
  1060. withdraw_record_payload = {
  1061. 'order': WithdrawRecord.make_no(withdraw_user.role, str(withdraw_user.id)),
  1062. 'ownerId': str(withdraw_user.id),
  1063. 'role': withdraw_user.role,
  1064. 'name': withdraw_user.nickname,
  1065. 'phone': withdraw_user.username,
  1066. 'balance': withdraw_user.sub_balance(income_type = income_type, source_key = source_key),
  1067. 'incomeType': income_type,
  1068. 'withdrawSourceKey': source_key,
  1069. 'payType': pay_type,
  1070. 'manual': manual,
  1071. 'amount': fund_map.get('amount'),
  1072. 'serviceFee': fund_map.get('serviceFee'),
  1073. 'actualPay': fund_map.get('actualPay'),
  1074. 'bankTransFee': fund_map.get('bankTransFee'),
  1075. 'accountCode': pay_entity.accountCode,
  1076. 'cardUserName': pay_entity.accountName,
  1077. 'parentBankName': pay_entity.bankName,
  1078. 'subBankName': pay_entity.branchBankName,
  1079. 'partition': fund_map.get('partition'),
  1080. 'payAgentId': withdraw_gateway.occupantId,
  1081. 'withdrawGatewayKey': withdraw_gateway.gateway_key,
  1082. 'withdrawFeeRatio': fund_map.get('withdrawFeeRatio'),
  1083. 'extras': {'v': 2}
  1084. }
  1085. if pay_type == WITHDRAW_PAY_TYPE.BANK:
  1086. withdraw_record_payload['extras'].update({
  1087. 'withdrawBankCard': pay_entity.id
  1088. })
  1089. if kwargs:
  1090. withdraw_record_payload.update(**kwargs)
  1091. if hasattr(withdraw_gateway, 'version'):
  1092. withdraw_record_payload['extras'].update({
  1093. 'gateway_version': getattr(withdraw_gateway, 'version')
  1094. })
  1095. record = cls(**withdraw_record_payload)
  1096. record.save()
  1097. return record
  1098. @property
  1099. def finished(self):
  1100. return self.status in WithdrawStatus.finished_status()
  1101. def succeed(self, **kwargs):
  1102. payload = {
  1103. 'status': WithdrawStatus.SUCCEEDED
  1104. }
  1105. if kwargs and 'extra' in kwargs:
  1106. extra = kwargs.pop('extra')
  1107. for key, value in extra.iteritems():
  1108. payload.update({
  1109. 'extra.{}'.format(key): value
  1110. })
  1111. if kwargs:
  1112. payload.update(kwargs)
  1113. result = self.get_collection().update_one(
  1114. filter = {'_id': ObjectId(self.id),
  1115. 'status': {'$nin': WithdrawStatus.finished_status()}},
  1116. update = {'$set': payload},
  1117. upsert = False)
  1118. return result.matched_count == 1
  1119. def fail(self, remarks = '', description = u'支付网关返回失败'):
  1120. result = self.get_collection().update_one(
  1121. filter = {'_id': ObjectId(self.id),
  1122. 'status': {'$nin': WithdrawStatus.finished_status()}},
  1123. update = {'$set': {
  1124. 'status': WithdrawStatus.FAILED,
  1125. 'remarks': remarks,
  1126. 'description': description,
  1127. 'finishedTime': datetime.datetime.now()
  1128. }},
  1129. upsert = False)
  1130. return result.matched_count == 1
  1131. def revoke(self, remarks = '', description = u'提现失败'):
  1132. result = self.get_collection().update_one(
  1133. filter = {'_id': ObjectId(self.id),
  1134. 'status': {'$nin': WithdrawStatus.finished_status()}},
  1135. update = {'$set': {
  1136. 'status': WithdrawStatus.CLOSED,
  1137. 'refunded': True,
  1138. 'remarks': remarks,
  1139. 'description': description,
  1140. 'finishedTime': datetime.datetime.now()
  1141. }},
  1142. upsert = False)
  1143. return result.matched_count == 1
  1144. def set_processing(self, remarks = u'提现申请已经受理', description = u'提现申请已经受理', **kwargs):
  1145. extras = self.extras
  1146. extras.update(**kwargs)
  1147. set_payload = {
  1148. 'status': WithdrawStatus.PROCESSING,
  1149. 'remarks': remarks,
  1150. 'description': description,
  1151. 'extras': extras
  1152. }
  1153. result = self.get_collection().update_one(
  1154. filter = {'_id': ObjectId(self.id),
  1155. 'status': {'$nin': WithdrawStatus.finished_status()}},
  1156. update = {'$set': set_payload},
  1157. upsert = False)
  1158. return result.matched_count == 1
  1159. def set_bank_processing(self, **kwargs):
  1160. extras = self.extras
  1161. extras.update(**kwargs)
  1162. set_payload = {
  1163. 'status': WithdrawStatus.BANK_PROCESSION,
  1164. 'description': u'银行正在处理中',
  1165. 'remarks': '',
  1166. 'extras': extras
  1167. }
  1168. result = self.get_collection().update_one(
  1169. filter = {
  1170. '_id': ObjectId(self.id),
  1171. 'status': {
  1172. '$nin': WithdrawStatus.finished_status()
  1173. }
  1174. },
  1175. update = {'$set': set_payload},
  1176. upsert = False)
  1177. return result.matched_count == 1
  1178. @staticmethod
  1179. def make_no(role, user_id):
  1180. # type: (str, str)->str
  1181. assert role in ROLE.choices(), 'no this role'
  1182. return OrderNoMaker.make_order_no_32(identifier = str(user_id),
  1183. main_type = OrderMainType.WITHDRAW,
  1184. sub_type = ROLE.sub_type(role))
  1185. @staticmethod
  1186. def is_my(order_no):
  1187. try:
  1188. if order_no[0] == OrderMainType.WITHDRAW and order_no[1] in ROLE.sub_type_list():
  1189. return True
  1190. elif order_no[14] == OrderMainType.WITHDRAW and order_no[15] in ROLE.sub_type_list():
  1191. return True
  1192. else:
  1193. return False
  1194. except Exception as e:
  1195. return False
  1196. @staticmethod
  1197. def process_memcache_lock(order):
  1198. # type: (str)->None
  1199. return memcache_lock('WithdrawRecord_%s' % order, 'processing')
  1200. @classmethod
  1201. def count_today(cls, ownerId, role = ROLE.dealer):
  1202. count = 0
  1203. total = RMB(0)
  1204. for item in cls.objects(
  1205. ownerId = ownerId, role = role,
  1206. status__nin = [WithdrawStatus.CLOSED, WithdrawStatus.REFUND],
  1207. postTime__gte = get_zero_time(datetime.datetime.now())).all():
  1208. count += 1
  1209. total += item.amount
  1210. return count, total
  1211. class PushRecord(Searchable):
  1212. createdTime = DateTimeField(default = datetime.datetime.now)
  1213. func_name = StringField()
  1214. result = DictField()
  1215. meta = {
  1216. 'collection': 'push_records',
  1217. 'db_alias': 'logdata',
  1218. 'max_documents': 100,
  1219. 'max_size': 2000000
  1220. }
  1221. def __repr__(self): return '<PushRecord func_name=%s>' % (self.func_name,)
  1222. class DealerDoneReportRecord(DynamicDocument):
  1223. reportName = StringField()
  1224. reportDay = StringField()
  1225. dealerIds = ListField()
  1226. expireAt = DateTimeField(default = None)
  1227. meta = {
  1228. 'collection': 'dealer_done_report_record',
  1229. 'db_alias': 'logdata'
  1230. }
  1231. @classmethod
  1232. def update_done_list(cls, report_name, report_day, done_list):
  1233. return cls.objects(reportName = report_name, reportDay = report_day).upsert_one(
  1234. push_all__dealerIds = done_list,
  1235. expireAt = to_datetime(report_day, time_format = "%Y-%m-%d") + datetime.timedelta(days = 7))
  1236. class RefundWithdrawRecord(Searchable):
  1237. """
  1238. 退款提现单,需要记录给经销商/代理商退款的情况
  1239. """
  1240. withdraw_record_id = ObjectIdField(unique = True)
  1241. createdTime = DateTimeField(default = datetime.datetime.now)
  1242. meta = {
  1243. 'abstract': True,
  1244. }
  1245. class ChangeLog(Searchable):
  1246. """
  1247. 在一些敏感的,涉及到需要确保后续可回溯的操作的时候,需要进行记录.
  1248. TODO 这里更加合理的实现,是运用MongoDB的changeStream特性
  1249. 脚本监听,然后过滤处理存储。
  1250. """
  1251. content = StringField()
  1252. event = StringField()
  1253. sender = StringField()
  1254. detail = StrictDictField()
  1255. pre = StrictDictField()
  1256. post = StrictDictField()
  1257. createdTime = DateTimeField(default = datetime.datetime.now)
  1258. meta = {"collection": "change_logs", "db_alias": "logdata"}
  1259. search_fields = ('content', 'event', 'sender')
  1260. def to_dict(self):
  1261. return {
  1262. 'content': self.content,
  1263. 'event': self.event,
  1264. 'sender': self.sender,
  1265. 'detail': self.detail
  1266. }
  1267. class Balance(EmbeddedDocument):
  1268. balance = MonetaryField(verbose_name = '提现收入', default = RMB('0.00'))
  1269. frozenBalance = MonetaryField(verbose_name = '冻结提现收入', default = RMB('0.00'))
  1270. def __repr__(self):
  1271. return "balance = {}, frozenBalance = {}".format(self.balance, self.frozenBalance)
  1272. class AccuracyBalance(EmbeddedDocument):
  1273. balance = AccuracyMoneyField(verbose_name = '提现收入', default = AccuracyRMB('0.00'))
  1274. frozenBalance = AccuracyMoneyField(verbose_name = '冻结提现收入', default = AccuracyRMB('0.00'))
  1275. class TempValues(Searchable):
  1276. """
  1277. 用于存放一些重要信息的临时数据库表,放memcached中不安全,就用这个
  1278. """
  1279. key = StringField(verbose_name = 'key', default = '', unique = True)
  1280. value = StringField(verbose_name = 'value', default = '')
  1281. expireAt = DateTimeField(default = None, verbose_name = u'过期时间')
  1282. meta = {"collection": "temp_values", "db_alias": "logdata"}
  1283. @staticmethod
  1284. def get(key):
  1285. obj = TempValues.objects.filter(__raw__ = {'key': key, '$or': [{'expireAt': {'$exists': False}}, {
  1286. 'expireAt': {'$gte': datetime.datetime.now()}}]}).first()
  1287. if obj:
  1288. return json_loads(obj.value)
  1289. return None
  1290. @staticmethod
  1291. def set(key, value, expire = 60 * 60):
  1292. if isinstance(value, dict):
  1293. _value = json_dumps(value, serialize_type = True)
  1294. else:
  1295. _value = value
  1296. TempValues.objects(key = key).update_one(upsert = True,
  1297. key = key, value = _value, expireAt = datetime.datetime.now() + datetime.timedelta(seconds = expire))
  1298. @staticmethod
  1299. def remove(key):
  1300. try:
  1301. TempValues.objects(key = key).update(
  1302. expireAt = datetime.datetime.now() - datetime.timedelta(seconds = 8 * 60 * 60))
  1303. except Exception, e:
  1304. pass
  1305. class OperatorLog(Searchable):
  1306. """
  1307. 关键用户行为变更日志
  1308. 着重记录用户的并不高频但重要的操作,更改配置项
  1309. """
  1310. class LogLevel(IterConstant):
  1311. INFO = 'INFO'
  1312. NOTICE = 'NOTICE'
  1313. WARNING = 'WARNING'
  1314. CRITICAL = 'CRITICAL'
  1315. EMERGENCY = 'EMERGENCY'
  1316. username = StringField(verbose_name = u'操作员账号')
  1317. operatorId = ObjectIdField(verbose_name=u'操作员关联ID')
  1318. role = StringField(verbose_name = u'操作员角色')
  1319. level = StringField(verbose_name = u'日志级别')
  1320. operatorName = StringField(verbose_name = u'操作名称')
  1321. content = DictField(verbose_name = u'日志内容')
  1322. dateTimeAdded = DateTimeField(verbose_name = u'添加时间', default = datetime.datetime.now)
  1323. meta = {'collection': 'operator_logs', 'db_alias': 'logdata'}
  1324. @classmethod
  1325. def log(cls, user, level, operator_name, content):
  1326. # type: (UserSearchable, basestring, basestring, dict)->None
  1327. try:
  1328. cls(username = user.username,
  1329. role = user.role,
  1330. operatorId = user.id,
  1331. level = level,
  1332. content = content,
  1333. operatorName = operator_name).save()
  1334. except Exception, e:
  1335. logger.exception(e)
  1336. @classmethod
  1337. def log_dev_operation(cls, operator, device, operator_name, content, level=LogLevel.WARNING):
  1338. try:
  1339. content.update({
  1340. 'logicalCode': device.logicalCode,
  1341. 'devNo': device.devNo,
  1342. 'devTypeCode': device.devTypeCode
  1343. })
  1344. return cls(username=operator.username,
  1345. role=operator.role,
  1346. operatorId=operator.id,
  1347. level=level,
  1348. content=content,
  1349. operatorName=operator_name).save()
  1350. except Exception, e:
  1351. logger.exception(e)
  1352. @classmethod
  1353. def record_dev_setting_changes_log(cls, user, operator_name, logicalCode, devTypeCode, content):
  1354. # type: (UserSearchable, basestring, str, str, dict)->Searchable
  1355. try:
  1356. content.update({
  1357. 'logicalCode': logicalCode,
  1358. 'devTypeCode': devTypeCode
  1359. })
  1360. return cls(username = user.username,
  1361. role = user.role,
  1362. operatorId = user.id,
  1363. level = 'INFO',
  1364. content = content,
  1365. operatorName = operator_name).save()
  1366. except Exception, e:
  1367. logger.exception(e)
  1368. class ExceptionLog(Searchable):
  1369. """
  1370. 异常记录
  1371. """
  1372. user = StringField()
  1373. dateTimeAdded = DateTimeField(verbose_name = u'添加时间', default = datetime.datetime.now)
  1374. exception = StringField()
  1375. extra = DictField()
  1376. meta = {'collection': 'exception_log', 'db_alias': 'logdata'}
  1377. @classmethod
  1378. def log(cls, user, exception, extra):
  1379. # type: (str, basestring, dict)->None
  1380. try:
  1381. cls(
  1382. user = user,
  1383. exception = exception,
  1384. extra = extra
  1385. ).save()
  1386. except Exception, e:
  1387. logger.exception(e)
  1388. class MonthlySubTemp(EmbeddedDocument):
  1389. displayName = StringField(verbose_name = u"展示名称", default = "包月套餐")
  1390. price = MonetaryField(verbose_name = u"价格", default = RMB(30))
  1391. numOfMonth = IntField(verbose_name = u"包月的时间 几个月", default = 1)
  1392. def to_dict(self):
  1393. return {
  1394. "displayName": self.displayName,
  1395. "price": str(self.price),
  1396. "numOfMonth": self.numOfMonth
  1397. }
  1398. @classmethod
  1399. def default(cls):
  1400. return cls()
  1401. @classmethod
  1402. def create(cls, **kwargs):
  1403. displayName = kwargs.get("displayName")
  1404. price = kwargs.get("price")
  1405. numOfMonth = kwargs.get("numOfMonth")
  1406. obj = cls.default()
  1407. if displayName is not None:
  1408. obj.displayName = displayName
  1409. if price is not None:
  1410. obj.price = RMB(price)
  1411. if numOfMonth is not None:
  1412. obj.numOfMonth = numOfMonth
  1413. return obj
  1414. # 包月计费模板
  1415. class MonthlyPackageTemp(Searchable):
  1416. """
  1417. 设备类型 和包月计费 拆开 不管什么样的设备类型 次是使用的单位 而每次使用多少 则可以再具体有电量和时间两个基本的计量
  1418. """
  1419. ownerId = StringField(verbose_name = u"包月规则制定的经销商", default = "")
  1420. name = StringField(verbose_name = u"模板名称", default = u"默认模板")
  1421. isDefault = BooleanIntField(verbose_name = u"是否是默认规则", default = 0)
  1422. saleable = BooleanIntField(verbose_name = u"是否开启此模板", default = 0)
  1423. isDelete = BooleanIntField(verbose_name = u"是否被经销商删除", default = 0)
  1424. bothCardAndMobile = BooleanIntField(verbose_name = u"是否通用", default = 0)
  1425. maxCountOfDay = IntField(verbose_name = u"每日的最大的次数", default = 1, min_value = 0)
  1426. maxCountOfMonth = IntField(verbose_name = u"每月的最大次数", default = 30, min_value = 0)
  1427. maxTimeOfCount = IntField(verbose_name = u"每次最大充电量 单位度", default = 240, min_value = 0)
  1428. maxElecOfCount = IntField(verbose_name = u"每次最大的充电时间 单位分钟", default = 1, min_value = 0)
  1429. dateTimeAdded = DateTimeField(verbose_name = u"设置模板的时间", default = datetime.datetime.now)
  1430. dateTimeUpdated = DateTimeField(verbose_name = u"计费模板更新的时间", default = datetime.datetime.now)
  1431. subTemplate = EmbeddedDocumentListField(verbose_name = u"子模版", document_type = MonthlySubTemp,
  1432. default = [MonthlySubTemp.default()])
  1433. meta = {
  1434. "collection": "MonthlyPackageTemp",
  1435. "db_alias": "default"
  1436. }
  1437. __unchargeable_fields = ("id", "ownerId", "dateTimeAdded")
  1438. @classmethod
  1439. def get_package_by_id(cls, _id):
  1440. return cls.objects.filter(id = _id).first()
  1441. @classmethod
  1442. def default_one(cls, ownerId):
  1443. """
  1444. :param ownerId:
  1445. :return:
  1446. """
  1447. obj = cls.objects.filter(ownerId = ownerId, isDefault = 1).first()
  1448. if not obj:
  1449. obj = cls(ownerId = ownerId, isDefault = 1).save()
  1450. return obj
  1451. @classmethod
  1452. def get_template_by_dealer(cls, ownerId):
  1453. """
  1454. 获取经销商的 所有包月模板
  1455. :param ownerId:
  1456. :return:
  1457. """
  1458. objs = cls.objects.filter(ownerId = ownerId, isDelete = 0)
  1459. if not objs:
  1460. return [cls.default_one(ownerId)]
  1461. return objs
  1462. @classmethod
  1463. def get_dealer_package(cls, ownerId, **kwargs):
  1464. return cls.objects.filter(ownerId = ownerId, **kwargs).first()
  1465. def set_default(self):
  1466. """
  1467. 设置为默认
  1468. :return:
  1469. """
  1470. self.__class__.objects.filter(ownerId = self.ownerId, isDefault = 1).update(isDefault = 0)
  1471. self.update(isDefault = 1)
  1472. def to_dict(self):
  1473. result = super(MonthlyPackageTemp, self).to_dict()
  1474. subList = list()
  1475. for _index, _sub in enumerate(self.subTemplate):
  1476. _subResult = _sub.to_dict()
  1477. _subResult["index"] = _index
  1478. subList.append(_subResult)
  1479. result["subTemplate"] = subList
  1480. return result
  1481. def update_by_fields(self, **kwargs):
  1482. """
  1483. 更新 包月套餐的自带属性 某些字段不可更改 直接跳过
  1484. :param kwargs:
  1485. :return:
  1486. """
  1487. fields = self.__class__._fields_ordered
  1488. for _field in fields:
  1489. if kwargs.get(_field) is None:
  1490. continue
  1491. if _field in self.__unchargeable_fields:
  1492. continue
  1493. if _field == "subTemplate":
  1494. # 子模版的保存
  1495. self.subTemplate = [MonthlySubTemp.create(**_sub) for _sub in kwargs.get(_field)]
  1496. continue
  1497. setattr(self, _field, kwargs.get(_field))
  1498. return self.save()
  1499. @classmethod
  1500. def create_by_fields(cls, ownerId, **kwargs):
  1501. obj = cls(ownerId = ownerId)
  1502. return obj.update_by_fields(**kwargs)
  1503. class OrderRecordBase(Searchable):
  1504. meta = {
  1505. 'abstract': True,
  1506. }
  1507. openId = StringField(verbose_name = u"微信ID", default = '')
  1508. nickname = StringField(verbose_name = u'用户昵称', default = '')
  1509. devNo = StringField(verbose_name = u"设备ID", default = None)
  1510. devType = StringField(verbose_name = u"设备类型", default = None)
  1511. devTypeName = StringField(verbose_name = u"设备类型名称", default = None)
  1512. devTypeCode = StringField(verbose_name = u"设备类型编码", default = None)
  1513. logicalCode = StringField(verbose_name = u"设备逻辑编码", default = None)
  1514. groupId = StringField(verbose_name = u"设备地址编号", default = None)
  1515. address = StringField(verbose_name = u"设备地址", default = None)
  1516. groupNumber = StringField(verbose_name = u"设备", default = None)
  1517. groupName = StringField(verbose_name = u"交易场地", default = None)
  1518. dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = u'生成时间')
  1519. @property
  1520. def dev_type_name(self):
  1521. if self.devTypeName:
  1522. return self.devTypeName
  1523. if self.devType:
  1524. return self.devType
  1525. return u'未知'
  1526. @property
  1527. def device(self):
  1528. # type:()->DeviceDict
  1529. from apps.web.device.models import Device
  1530. _attr_name = '__device__'
  1531. if hasattr(self, _attr_name):
  1532. return getattr(self, _attr_name)
  1533. else:
  1534. device = Device.get_dev_by_logicalCode(self.logicalCode)
  1535. setattr(self, _attr_name, device)
  1536. return device
  1537. @device.setter
  1538. def device(self, value):
  1539. setattr(self, '__device__', value)
  1540. @property
  1541. def dev_type_code(self):
  1542. if self.devTypeCode:
  1543. return self.devTypeCode
  1544. device = self.device
  1545. if device:
  1546. return device.devTypeCode
  1547. else:
  1548. return ''
  1549. @property
  1550. def group(self):
  1551. # type:()->GroupDict
  1552. from apps.web.device.models import Group
  1553. _attr_name = '__group__'
  1554. if hasattr(self, _attr_name):
  1555. return getattr(self, _attr_name)
  1556. else:
  1557. group = Group.get_group(self.groupId)
  1558. setattr(self, _attr_name, group)
  1559. return group
  1560. @group.setter
  1561. def group(self, group):
  1562. _attr_name = '__group__'
  1563. setattr(self, _attr_name, group)
  1564. @property
  1565. def owner(self):
  1566. from apps.web.dealer.models import Dealer
  1567. _attr_name = '__my_owner__'
  1568. if hasattr(self, _attr_name):
  1569. return getattr(self, _attr_name)
  1570. else:
  1571. ownerId = getattr(self, 'ownerId', None)
  1572. if not ownerId:
  1573. return None
  1574. else:
  1575. dealer = Dealer.objects(id = ownerId).first()
  1576. setattr(self, _attr_name, dealer)
  1577. return dealer
  1578. @property
  1579. def device_identity_info(self):
  1580. return {
  1581. 'logicalCode': self.logicalCode,
  1582. 'devTypeCode': self.devTypeCode,
  1583. 'devTypeName': self.dev_type_name,
  1584. 'groupName': self.groupName,
  1585. 'groupNumber': self.groupNumber,
  1586. 'address': self.address,
  1587. 'groupId': self.groupId
  1588. }
  1589. class WithdrawBanks(BaseDocument):
  1590. CARD_TYPE_MAP = {
  1591. 'DC': "借记卡",
  1592. 'CC': "信用卡",
  1593. 'SCC': "准贷记卡",
  1594. 'PC': "预付费卡"
  1595. }
  1596. code = StringField(verbose_name = u'编号', unique = True, null = False)
  1597. name = StringField(verbose_name = u'名称', null = False)
  1598. bankAbbrevCode = StringField(verbose_name = u'银行编码')
  1599. wechatBankCode = StringField(verbose_name = u'微信银行ID', default = '')
  1600. meta = {'collection': 'withdraw_banks', 'db_alias': 'default'}
  1601. buffer = None
  1602. buffer_lock = ThreadLock()
  1603. def to_dict(self):
  1604. return {
  1605. 'code': self.code,
  1606. 'name': self.name,
  1607. 'wechatBankCode': self.wechatBankCode,
  1608. 'bankAbbrevCode': self.bankAbbrevCode
  1609. }
  1610. @classmethod
  1611. def get_wechat_bank_code(cls, bankName):
  1612. cls.__load_in_mem()
  1613. if bankName in cls.buffer['wechat']:
  1614. return cls.buffer['wechat'][bankName].get('wechatBankCode', '')
  1615. else:
  1616. return ''
  1617. @classmethod
  1618. def get_wechat_support_banks(cls, keyWord):
  1619. cls.__load_in_mem()
  1620. rv = []
  1621. for item in cls.buffer['wechat'].values():
  1622. if keyWord:
  1623. if keyWord in item['name']:
  1624. rv.append({
  1625. 'code': item['code'], 'name': item['name']
  1626. })
  1627. else:
  1628. rv.append({
  1629. 'code': item['code'], 'name': item['name']
  1630. })
  1631. return rv
  1632. @classmethod
  1633. def get_by_bank_abbrev_code(cls, bankAbbrevCode):
  1634. cls.__load_in_mem()
  1635. if bankAbbrevCode in cls.buffer['bankAbbrevCode']:
  1636. bank_info = cls.buffer['bankAbbrevCode'][bankAbbrevCode]
  1637. return {
  1638. 'bankName': bank_info['name']
  1639. }
  1640. else:
  1641. return None
  1642. @classmethod
  1643. def get_all_banks(cls, keyWord):
  1644. cls.__load_in_mem()
  1645. rv = []
  1646. for item in cls.buffer['all'].values():
  1647. if keyWord:
  1648. if keyWord in item['name']:
  1649. rv.append({
  1650. 'code': item['code'], 'name': item['name']
  1651. })
  1652. else:
  1653. rv.append({
  1654. 'code': item['code'], 'name': item['name']
  1655. })
  1656. return rv
  1657. @classmethod
  1658. def support(cls, bankName):
  1659. cls.__load_in_mem()
  1660. return bankName in cls.buffer['all']
  1661. @classmethod
  1662. def get_collection(cls):
  1663. return cls._get_collection()
  1664. @classmethod
  1665. def __acquire_lock(cls):
  1666. cls.buffer_lock.acquire_lock()
  1667. @classmethod
  1668. def __release_lock(cls):
  1669. cls.buffer_lock.release_lock()
  1670. def reset_buffer(self):
  1671. try:
  1672. self.__acquire_lock()
  1673. self.buffer = None
  1674. finally:
  1675. self.__release_lock()
  1676. @classmethod
  1677. def __load_in_mem(cls):
  1678. if cls.buffer:
  1679. return
  1680. try:
  1681. cls.__acquire_lock()
  1682. if cls.buffer:
  1683. return
  1684. cls.buffer = {
  1685. 'all': {},
  1686. 'wechat': {},
  1687. 'bankAbbrevCode': {}
  1688. }
  1689. banks = cls.objects().all()
  1690. for bank in banks:
  1691. payload = bank.to_dict()
  1692. cls.buffer['all'][bank.name] = payload
  1693. if bank.wechatBankCode:
  1694. cls.buffer['wechat'][bank.name] = payload
  1695. if bank.bankAbbrevCode:
  1696. cls.buffer['bankAbbrevCode'][bank.bankAbbrevCode] = payload
  1697. finally:
  1698. cls.__release_lock()
  1699. class WithdrawDistrict(BaseDocument):
  1700. code = StringField(verbose_name = u'编号', unique = True, null = False)
  1701. name = StringField(verbose_name = u'名称', null = False)
  1702. parent = StringField(verbose_name = u'父级编号', null = False)
  1703. meta = {
  1704. "collection": "withdraw_district",
  1705. "db_alias": "default"
  1706. }
  1707. @classmethod
  1708. def get_area(cls, province = None):
  1709. parent = '0000' if not province else province
  1710. return [
  1711. {
  1712. 'code': item.code,
  1713. 'name': item.name
  1714. } for item in cls.objects(parent = parent).order_by('-code')
  1715. ]
  1716. class WithdrawBranchBanks(Searchable):
  1717. code = StringField(verbose_name = u'编号', unique = True, null = False)
  1718. name = StringField(verbose_name = u'名称', null = False)
  1719. provinceCode = StringField(verbose_name = u'省编号', null = False)
  1720. cityCode = StringField(verbose_name = u'城市编号', null = False)
  1721. bankCode = StringField(verbose_name = u'银行编号', null = False)
  1722. cnapsCode = StringField(verbose_name = u'联行号')
  1723. search_fields = ('name',)
  1724. meta = {
  1725. "collection": "withdraw_branch_banks",
  1726. "db_alias": "default"
  1727. }
  1728. class WithdrawBankCard(DynamicDocument):
  1729. """
  1730. 提现银行卡信息
  1731. """
  1732. class AccountType(object):
  1733. PERSONAL = 'personal'
  1734. PUBLIC = 'public'
  1735. ownerId = StringField(verbose_name = u"提现者ID", null = False)
  1736. role = StringField(verbose_name = u'提现角色', null = False)
  1737. accountCode = StringField(verbose_name = u"银行卡账号", null = False)
  1738. accountName = StringField(verbose_name = u'持卡人姓名')
  1739. accountType = StringField(verbose_name = u'卡类型(个人账号,对公账号)', default = AccountType.PERSONAL)
  1740. bankCode = StringField(verbose_name = u'银行ID', default = '')
  1741. bankName = StringField(verbose_name = u"银行名称", default = "")
  1742. phone = StringField(verbose_name = u'银行预留电话', default = "")
  1743. # 对公提现需要提供省市支行名称或者联行号
  1744. province = StringField(verbose_name = u"省名称", default = None)
  1745. provinceCode = StringField(verbose_name = u"省编码", default = None)
  1746. city = StringField(verbose_name = u"市名称", default = None)
  1747. cityCode = StringField(verbose_name = u"市编码", default = None)
  1748. branchBankName = StringField(verbose_name = u"银行支行名称", default = None)
  1749. branchBankCode = StringField(verbose_name = u"银行支行编码", default = None)
  1750. cnapsCode = StringField(verbose_name = u"联行号", default = None)
  1751. dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = u'添加进来的时间')
  1752. manual = BooleanField(verbose_name = u"该卡是否仅手动提现", default = False)
  1753. meta = {"collection": "withdraw_bank_card", "db_alias": "default"}
  1754. @classmethod
  1755. def get_collection(cls):
  1756. return cls._get_collection()
  1757. def to_dict(self):
  1758. if self.accountType == self.AccountType.PERSONAL:
  1759. return {
  1760. 'id': str(self.id),
  1761. 'accountCode': self.accountCode,
  1762. 'accountName': self.accountName,
  1763. 'accountType': self.accountType,
  1764. 'bankName': self.bankName,
  1765. 'bankCode': self.bankCode,
  1766. 'phone': self.phone,
  1767. 'isPublic': False
  1768. }
  1769. else:
  1770. return {
  1771. 'id': str(self.id),
  1772. 'accountCode': self.accountCode,
  1773. 'accountName': self.accountName,
  1774. 'accountType': self.accountType,
  1775. 'bankName': self.bankName,
  1776. 'bankCode': self.bankCode,
  1777. 'phone': self.phone,
  1778. 'province': self.province,
  1779. 'provinceCode': self.provinceCode,
  1780. 'city': self.city,
  1781. 'cityCode': self.cityCode,
  1782. 'branchBankName': self.branchBankName,
  1783. 'branchBankCode': self.branchBankCode,
  1784. 'cnapsCode': self.cnapsCode,
  1785. 'isPublic': True
  1786. }
  1787. @classmethod
  1788. def new_personal_withdraw_bank_card(
  1789. cls, ownerId, role, accountName, accountCode, bankName, phone, id = None):
  1790. if id:
  1791. return cls.objects(id = id).upsert_one(
  1792. ownerId = ownerId, role = role, bankName = bankName,
  1793. accountType = cls.AccountType.PERSONAL, accountName = accountName,
  1794. accountCode = accountCode, phone = phone)
  1795. else:
  1796. return cls.objects(ownerId = ownerId, role = role, accountCode = accountCode).upsert_one(
  1797. ownerId = ownerId, role = role, bankName = bankName,
  1798. accountType = cls.AccountType.PERSONAL, accountName = accountName,
  1799. accountCode = accountCode, phone = phone)
  1800. @classmethod
  1801. def new_public_withdraw_bank_card(cls, ownerId, role, accountName, accountCode, bankCode, bankName,
  1802. provinceCode, province, cityCode, city, branchBankCode, branchBankName,
  1803. cnapsCode, phone, id = None):
  1804. if id:
  1805. return cls.objects(id = id).upsert_one(
  1806. ownerId = ownerId, role = role, bankCode = bankCode, bankName = bankName,
  1807. accountType = cls.AccountType.PUBLIC, accountName = accountName, accountCode = accountCode,
  1808. phone = phone, provinceCode = provinceCode, province = province,
  1809. cityCode = cityCode, city = city, branchBankCode = branchBankCode,
  1810. branchBankName = branchBankName, cnapsCode = cnapsCode)
  1811. else:
  1812. return cls.objects(ownerId = ownerId, role = role, accountCode = accountCode).upsert_one(
  1813. ownerId = ownerId, role = role, bankCode = bankCode, bankName = bankName,
  1814. accountType = cls.AccountType.PUBLIC, accountName = accountName, accountCode = accountCode,
  1815. phone = phone, provinceCode = provinceCode, province = province,
  1816. cityCode = cityCode, city = city, branchBankCode = branchBankCode,
  1817. branchBankName = branchBankName, cnapsCode = cnapsCode)
  1818. class RefundOrderBase(Searchable):
  1819. meta = {
  1820. 'abstract': True
  1821. }
  1822. class Status(object):
  1823. """ 退款单的状态 正常流程顺序是从上至下 """
  1824. CREATED = 'created' # 创建
  1825. PROCESSING = 'processing' # 申请中
  1826. FAILURE = 'failure' # 申请失败 (彻底失败 需要重新发起)
  1827. SUCCESS = 'success' # 退款成功 (异步结果)
  1828. CLOSED = 'closed' # 退款失败 (异步结果 不需要重新发起)
  1829. NOORDER = 'noOrder' # 单提交错误
  1830. # 订单常见的状态
  1831. # created ---> processing ---> success (申请 然后接受成功)
  1832. # created ---> failure (直接申请失败)
  1833. # created ---> processing ---> closed (申请成功 回调失败)
  1834. # created ---> processing ---> failure (申请成功 回调失败 不可以重试)
  1835. rechargeObjId = ObjectIdField(verbose_name = u'对应充值单')
  1836. payAppType = StringField(verbose_name = u'支付应用类型', default = None)
  1837. # refundSeq = IntField(verbose_name=u'多次退款,计算退款序列号', default=1)
  1838. orderNo = StringField(verbose_name = u'商户退款订单号', default = '')
  1839. errorCode = StringField(verbose_name = u"错误代码", default = "")
  1840. errorDesc = StringField(verbose_name = u"错误描述", default = "")
  1841. money = MonetaryField(verbose_name = u"退款的金钱数额", default = RMB('0.00'))
  1842. status = StringField(verbose_name = u'订单状态', default = Status.CREATED)
  1843. datetimeAdded = DateTimeField(verbose_name = u"退款创建时间", default = None)
  1844. datetimeUpdated = DateTimeField(verbose_name = u"退款更新时间")
  1845. finishedTime = DateTimeField(verbose_name = u"退款到账时间")
  1846. extraInfo = DictField(verbose_name = u'额外信息')
  1847. tradeRefundNo = StringField(verbose_name = u"交易机构退款单号", default = None)
  1848. retryCount = IntField(verbose_name = u"重试次数", default = 0)
  1849. def succeed(self, finishedTime, **kwargs): # type:(datetime.datetime, dict) -> bool
  1850. """
  1851. 退款成功
  1852. :param finishedTime:
  1853. :param kwargs:
  1854. :return:
  1855. """
  1856. payload = {
  1857. 'status': self.Status.SUCCESS,
  1858. 'datetimeUpdated': datetime.datetime.now(),
  1859. 'finishedTime': finishedTime
  1860. }
  1861. if kwargs:
  1862. payload.update(kwargs)
  1863. result = self.get_collection().update_one(
  1864. filter = {
  1865. '_id': ObjectId(self.id),
  1866. 'status': {
  1867. '$nin': [self.Status.SUCCESS, self.Status.CLOSED]
  1868. }},
  1869. update = {'$set': payload},
  1870. upsert = False)
  1871. matched = (result.matched_count == 1)
  1872. if matched:
  1873. self.reload()
  1874. return matched
  1875. def fail(self, errorCode = "", errorDesc = "", **kwargs): # type:(str, unicode, dict) -> bool
  1876. """
  1877. 更新为失败状态 表示退款申请失败 这种情况下 可能就需要售后进行介入
  1878. 1. 订单申请发起时候即失败
  1879. 2. 订单申请通过 但是回调的时候明确失败 并且是不可重试的失败
  1880. 3. 此状态即表示订单没退款 理论上可以重新发起退款
  1881. """
  1882. payload = {
  1883. 'status': self.Status.FAILURE,
  1884. 'datetimeUpdated': datetime.datetime.now(),
  1885. 'errorCode': errorCode,
  1886. 'errorDesc': errorDesc
  1887. }
  1888. if kwargs:
  1889. payload.update(kwargs)
  1890. _filter = {
  1891. '_id': ObjectId(self.id),
  1892. 'status': {
  1893. '$nin': [self.Status.CLOSED, self.Status.SUCCESS]
  1894. }
  1895. }
  1896. result = self.get_collection().update_one(
  1897. filter = _filter,
  1898. update = {'$set': payload},
  1899. upsert = False)
  1900. return result.matched_count == 1
  1901. def no_order(self, errorCode = "", errorDesc = ""): # type:(basestring, basestring) -> bool
  1902. """
  1903. 查询状态无此订单
  1904. :param errorCode:
  1905. :param errorDesc:
  1906. :return:
  1907. """
  1908. payload = {
  1909. 'status': self.Status.NOORDER,
  1910. 'datetimeUpdated': datetime.datetime.now(),
  1911. 'errorCode': errorCode,
  1912. 'errorDesc': errorDesc
  1913. }
  1914. _filter = {
  1915. '_id': ObjectId(self.id),
  1916. 'status': {
  1917. '$nin': [self.Status.CLOSED, self.Status.SUCCESS]
  1918. }
  1919. }
  1920. result = self.get_collection().update_one(
  1921. filter = _filter,
  1922. update = {'$set': payload},
  1923. upsert = False)
  1924. return result.matched_count == 1
  1925. def closed(self, errorCode = "", errorDesc = "", **kwargs): # type:(basestring, basestring, dict) -> bool
  1926. """
  1927. 退款申请成功了 表示合法的退款申请 但是由于某些原因业务进行不下去
  1928. """
  1929. payload = {
  1930. 'status': self.Status.CLOSED,
  1931. 'datetimeUpdated': datetime.datetime.now(),
  1932. 'errorCode': errorCode,
  1933. 'errorDesc': errorDesc
  1934. }
  1935. if kwargs:
  1936. payload.update(kwargs)
  1937. _filter = {
  1938. '_id': ObjectId(self.id),
  1939. 'status': {
  1940. '$nin': [self.Status.CLOSED, self.Status.SUCCESS]
  1941. }
  1942. }
  1943. result = self.get_collection().update_one(
  1944. filter = _filter,
  1945. update = {'$set': payload},
  1946. upsert = False)
  1947. matched = (result.matched_count == 1)
  1948. if matched:
  1949. self.reload()
  1950. return matched
  1951. def processing(self): # type:() -> bool
  1952. payload = {
  1953. 'status': self.Status.PROCESSING,
  1954. 'datetimeUpdated': datetime.datetime.now()
  1955. }
  1956. _filter = {
  1957. '_id': ObjectId(self.id),
  1958. 'status': {
  1959. '$nin': [self.Status.CLOSED, self.Status.SUCCESS]
  1960. }
  1961. }
  1962. result = self.get_collection().update_one(
  1963. filter = _filter,
  1964. update = {'$set': payload},
  1965. upsert = False)
  1966. return result.matched_count == 1
  1967. def retry_processing(self, changeOrderNo): # type:(bool)->bool
  1968. """
  1969. 只有订单处于FAILURE以及NO_ORDER状态的单才会重新调度
  1970. :param changeOrderNo:
  1971. :return:
  1972. """
  1973. RefundOrderHistory.issue(self)
  1974. if changeOrderNo:
  1975. identifier = self.orderNo[16:29]
  1976. orderNo = OrderNoMaker.make_order_no_32(
  1977. identifier = identifier,
  1978. main_type = OrderMainType.REFUND,
  1979. sub_type = RefundSubType.REFUND)
  1980. _payload = {
  1981. 'orderNo': orderNo,
  1982. 'status': self.Status.PROCESSING,
  1983. 'datetimeUpdated': datetime.datetime.now()
  1984. }
  1985. else:
  1986. _payload = {
  1987. 'status': self.Status.PROCESSING,
  1988. 'datetimeUpdated': datetime.datetime.now()
  1989. }
  1990. _filter = {
  1991. '_id': ObjectId(self.id),
  1992. 'status': {
  1993. '$in': [self.Status.FAILURE, self.Status.NOORDER]
  1994. }
  1995. }
  1996. result = self.get_collection().update_one(
  1997. filter = _filter,
  1998. update = {'$set': _payload, '$inc': {'retryCount': int(1)}},
  1999. upsert = False)
  2000. return result.matched_count == 1
  2001. @property
  2002. def is_no_order(self):
  2003. return self.status == self.Status.NOORDER
  2004. @property
  2005. def is_fail(self):
  2006. """ 是否订单失败 """
  2007. return self.status == self.Status.FAILURE
  2008. @property
  2009. def is_closed(self):
  2010. """ 退款单是否已经关闭 目前这个状态没有用 适用于用户手动取消退款申请 """
  2011. return self.status == self.Status.CLOSED
  2012. @property
  2013. def is_success(self):
  2014. """ 退款已经成功 """
  2015. return self.status == self.Status.SUCCESS
  2016. @property
  2017. def is_created(self):
  2018. return self.status == self.Status.CREATED
  2019. @property
  2020. def is_apply(self):
  2021. """ 是否已经发出退款申请 """
  2022. return self.status in [self.Status.CREATED, self.Status.PROCESSING]
  2023. @property
  2024. def is_processing(self):
  2025. return self.status == self.Status.PROCESSING
  2026. @property
  2027. def is_successful(self):
  2028. """ 保留旧的方法 """
  2029. return self.status in [self.Status.SUCCESS, self.Status.PROCESSING]
  2030. @classmethod
  2031. def get_record(cls, **kwargs):
  2032. return cls.objects(**kwargs).first()
  2033. @property
  2034. def refund_income_order(self):
  2035. return None
  2036. @property
  2037. def pay_app_type(self):
  2038. raise AttributeError('must implement pay_app_type.')
  2039. @property
  2040. def pay_sub_order(self):
  2041. # type: ()->Optional[DealerRechargeRecord,RechargeRecord]
  2042. raise AttributeError('must implement pay_sub_order.')
  2043. @pay_sub_order.setter
  2044. def pay_sub_order(self, order):
  2045. raise AttributeError('must implement pay_sub_order setter.')
  2046. @property
  2047. def my_payment_gateway(self):
  2048. if not hasattr(self, '__payment_gateway__'):
  2049. _payment_gateway = PaymentGateway.clone_from_order(self.pay_sub_order)
  2050. setattr(self, '__payment_gateway__', _payment_gateway)
  2051. return getattr(self, '__payment_gateway__')
  2052. @property
  2053. def notify_url(self):
  2054. raise AttributeError('must implement notify_url.')
  2055. class RefundOrderHistory(Searchable):
  2056. """
  2057. 用户退款失败历史记录
  2058. """
  2059. className = StringField(verbose_name = u'对应退款单表名')
  2060. refId = ObjectIdField(verbose_name = u'对应退款单')
  2061. orderNo = StringField(verbose_name = u'原单号', default = '')
  2062. errorCode = StringField(verbose_name = u"错误代码", default = "")
  2063. errorDesc = StringField(verbose_name = u"错误描述", default = "")
  2064. datetimeAdded = DateTimeField(verbose_name = u"创建时间", default = None)
  2065. datetimeUpdated = DateTimeField(verbose_name = u"退款更新时间")
  2066. finishedTime = DateTimeField(verbose_name = u"退款到账时间")
  2067. tradeRefundNo = StringField(verbose_name = u"交易机构退款单号", default = None)
  2068. meta = {
  2069. "collection": "refund_order_history",
  2070. "db_alias": "logdata",
  2071. }
  2072. @classmethod
  2073. def issue(cls, order):
  2074. # type:(RefundOrderBase)->RefundOrderHistory
  2075. return cls(
  2076. className = order.__class__.__name__,
  2077. refId = order.id,
  2078. orderNo = order.orderNo,
  2079. errorCode = order.errorCode,
  2080. errorDesc = order.errorDesc,
  2081. datetimeAdded = datetime.datetime.now(),
  2082. datetimeUpdated = order.datetimeUpdated,
  2083. finishedTime = order.finishedTime,
  2084. tradeRefundNo = order.tradeRefundNo).save()