models.py 103 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. """
  4. web.dealer.models
  5. ~~~~~~~~~
  6. """
  7. import datetime
  8. import logging
  9. import uuid
  10. from operator import itemgetter
  11. from bson.objectid import ObjectId
  12. from django.conf import settings
  13. from django.core.cache import cache
  14. from mongoengine import EmbeddedDocument, StringField, BooleanField, EmbeddedDocumentField, MapField, DynamicDocument, \
  15. DateTimeField, Document, IntField, ListField, DictField, FloatField, ObjectIdField, EmbeddedDocumentListField, \
  16. LazyReferenceField, DynamicEmbeddedDocument, GenericLazyReferenceField
  17. from typing import Dict, AnyStr, Any, List, Optional, TYPE_CHECKING
  18. from apilib.monetary import RMB, Percent, sum_rmb, Permillage
  19. from apilib.systypes import IterConstant, StrEnum
  20. from apilib.utils_string import cn, md5
  21. from apilib.utils_url import add_query
  22. from apps.web.agent.models import Agent, MoniAppPoint
  23. from apps.web.common.models import UserSearchable, MonthlyPackageTemp
  24. from apps.web.common.models import WithdrawRecord, CapitalUser, Balance
  25. from apps.web.common.transaction import OrderNoMaker, OrderMainType, DealerPaySubType, WITHDRAW_PAY_TYPE, RefundSubType
  26. from apps.web.constant import TYPE_ADJUST_USER_VIRTUAL_CARD, DEALER_CONSUMPTION_AGG_KIND, \
  27. DEALER_CONSUMPTION_AGG_KIND_TRANSLATION, Const, APP_TYPE, AppPlatformType
  28. from apps.web.core import PayAppType, ROLE, APP_KEY_DELIMITER
  29. from apps.web.core.db import Searchable, MonetaryField, PercentField, PermillageField
  30. from apps.web.core.exceptions import UpdateError, ServiceException, InvalidParameter, MerchantError
  31. from apps.web.dealer.constant import TodoDone, TodoTypeEnum, LinkageSwitchEnum
  32. from apps.web.core.messages.sms import dealerWithdrawSMSProvider, dealerMonitorWithdrawSMSProvider
  33. from apps.web.core.models import BoundOpenInfo, BankCard, SwapPayApp
  34. from apps.web.core.payment import WithdrawGateway
  35. from apps.web.dealer.define import DEALER_INCOME_SOURCE, DEALER_INCOME_TYPE, DealerConst, \
  36. DEALER_INCOME_SOURCE_TRANSLATION
  37. from apps.web.device.models import Device, Group, GroupDict, DeviceType
  38. from apps.web.helpers import get_app, get_user_manager_agent
  39. from apps.web.utils import concat_dealer_access_entry_url, concat_front_end_url, concat_server_end_url
  40. if TYPE_CHECKING:
  41. from apps.web.common.transaction import WithdrawHandler
  42. from apps.web.core.payment import PaymentGateway
  43. from apps.web.core import PayAppBase
  44. logger = logging.getLogger(__name__)
  45. class DealerCacheMgr():
  46. pass
  47. class DealerAddr(EmbeddedDocument):
  48. id = StringField(verbose_name = 'id', default = str(uuid.uuid4()))
  49. default = BooleanField(verbose_name = u'是否默认地址', default = False)
  50. name = StringField(verbose_name = 'name', default = '')
  51. tel = StringField(verbose_name = 'telno', default = '')
  52. addr = StringField(verbose_name = 'addr', default = '')
  53. class ApiAppInfo(DynamicDocument):
  54. people = StringField(verbose_name=u"联系人", default='')
  55. tel = StringField(verbose_name=u"联系电话", default='')
  56. callbackUrl = StringField(verbose_name=u"回调地址Url", default='')
  57. apiDeviceMax = IntField(verbose_name=u'API最大接入数量', default=0)
  58. apiDevicePerCost = MonetaryField(verbose_name=u'API配额单价', default=RMB(50))
  59. meta = {
  60. 'collection': 'api_app_info',
  61. 'db_alias': 'default'
  62. }
  63. class DisableAdPlan(DynamicDocument):
  64. cycle = IntField(verbose_name=u'单次购买周期(单位:月)', default=12)
  65. disableAdCost = MonetaryField(verbose_name=u'纯净版单价', default=RMB(20))
  66. meta = {
  67. 'collection': 'disable_ad_plan',
  68. 'db_alias': 'default'
  69. }
  70. class ConfigTemplate(EmbeddedDocument):
  71. rechargeDiscount = ListField(verbose_name=u'优惠充值模板', default=[])
  72. cardRechargeDiscount = ListField(verbose_name=u'卡优惠充值模板', default=[])
  73. class DealerLinkageSwitch(DynamicEmbeddedDocument):
  74. """
  75. 经销商相关开关项
  76. """
  77. chargeInsurance = BooleanField(default=False, verbose_name=u"充电险开关")
  78. agentProxyServicePhone = BooleanField(verbose_name=u'代理商是否接管客服', default=False)
  79. def to_dict(self):
  80. return {"chargeInsurance": self.chargeInsurance, 'agentProxyServicePhone': self.agentProxyServicePhone}
  81. class Dealer(CapitalUser):
  82. """
  83. 经销商
  84. """
  85. INCOME_SOURCE_LIST = DEALER_INCOME_SOURCE.choices()
  86. INCOME_SOURCE_TO_TYPE = DealerConst.MAP_SOURCE_TO_TYPE
  87. INCOME_TYPE_LIST = DEALER_INCOME_TYPE.choices()
  88. INCOME_TYPE_TO_FIELD = DealerConst.MAP_TYPE_TO_FIELD
  89. #: 默认管理平台微信公众号授权用户信息
  90. DEFAULT_WECHAT_AUTH_USER_INFO = {
  91. 'avatar': '',
  92. 'sex': 0
  93. }
  94. DEFAULT_CHECKPOINT = {
  95. 'gerenzhongxin': False,
  96. 'yue': False,
  97. 'baogaolaoban': False,
  98. 'fukuan': False
  99. }
  100. DEFAULT_PAY_TYPE = {
  101. ROLE.myuser: {
  102. AppPlatformType.ALIPAY: PayAppType.ALIPAY,
  103. AppPlatformType.WECHAT: PayAppType.WECHAT,
  104. AppPlatformType.JD: PayAppType.JD_AGGR,
  105. AppPlatformType.WECHAT_MINI: PayAppType.WECHAT_MINI
  106. }
  107. }
  108. CACHE_KEY = 'dealer-info-{id}'
  109. CacheMgr = DealerCacheMgr
  110. wechat = StringField(verbose_name = "微信", default = "")
  111. description = StringField(verbose_name = "描述", default = "")
  112. phone = StringField(verbose_name = "电话", default = "")
  113. # 相当于 经销商的资金池
  114. deviceBalance = MapField(EmbeddedDocumentField(document_type=Balance))
  115. # 经销商获取的分账收益 在消费订单结束的时候进行记录
  116. ledgerBalance = MapField(EmbeddedDocumentField(document_type=Balance))
  117. adBalance = MapField(EmbeddedDocumentField(document_type=Balance))
  118. payOpenIdMap = MapField(EmbeddedDocumentField(BoundOpenInfo))
  119. cardNo = StringField(verbose_name = "银行卡号", default = "")
  120. bankName = StringField(verbose_name = "银行名称", default = "")
  121. cardHolder = StringField(verbose_name = "持卡人名称", default = "")
  122. isRead = StringField(verbose_name = "是否已经读了消息", default = "N")
  123. noPassReason = StringField(verbose_name = "未通过原因", default = "")
  124. cibMerchant = BooleanField(verbose_name = "是否立即生效", default = False)
  125. # 推送消息
  126. managerialAppId = StringField(verbose_name = "auth,notify app id", default = "")
  127. managerialOpenId = StringField(verbose_name = "后台,接受推送消息", default = "")
  128. wechatAuthUserInfo = DictField(verbose_name = "经销商授权后获取的用户信息", default = DEFAULT_WECHAT_AUTH_USER_INFO)
  129. managerialWechatBoundTime = DateTimeField(verbose_name = "绑定微信的时间")
  130. agentId = StringField(verbose_name = "从属于哪个代理商", null = False)
  131. # 上级代理商设置的经营分成比例策略
  132. agentProfitShare = PercentField(verbose_name='代理商设备运营分成比例', default = Percent('0.00'))
  133. agentMerProfitShare = PercentField(verbose_name='代理商设备运营分成比例 商户收款', default = Percent('0.00'))
  134. #: 默认选项|开关
  135. #: 选项
  136. adShow = BooleanField(verbose_name = "广告显示与否的选项", default = True)
  137. noAdPolicy = StringField(verbose_name = "不打开广告情况下的显示策略(notshow,noshow,banner)", default = 'noshow')
  138. beforeCharge = BooleanField(verbose_name = "使用前必须充值开关", default = False)
  139. noRecharge = BooleanField(verbose_name = "不需要充值功能", default = False)
  140. defaultWashConfig = DictField(verbose_name = "与经销商绑定的washconfig", default = {})
  141. #: 通知选项
  142. withdrawlNotify = BooleanField(verbose_name = "提现通知", default = False)
  143. offlineNotifySwitch = BooleanField(verbose_name = "设备离线通知开关", default = False)
  144. offlineNotifyTime = StringField(verbose_name = '设备离线通知时间', default = '')
  145. dailyIncomeReportPushSwitch = BooleanField(verbose_name = '是否开启次日9点推送昨日收益报表', default = False)
  146. newUserPaymentOrderPushSwitch = BooleanField(verbose_name = '是否开启', default = False)
  147. devFaultPushDealerSwitch = BooleanField(verbose_name = '设备故障通知开关,是否推送给经销商', default = False)
  148. devFaultPushUserSwitch = BooleanField(verbose_name = '设备故障通知开关,是否推送给用户', default = False)
  149. #: 用于客服的联系方式
  150. serviceName = StringField(verbose_name = "客服名称", default = "", max_length = 32)
  151. servicePhone = StringField(verbose_name = "客服电话", default = "", max_length = 32)
  152. qrcodeUrl = StringField(verbose_name = "二维码图片链接", default = "", max_length = 256)
  153. # 提现费率. 由代理商设置
  154. withdrawFeeRatio = PermillageField(
  155. verbose_name = '提现费率',
  156. default = Const.PLATFORM_DEFAULT_WITHDRAW_FEE_RATIO,
  157. min_value = Const.MIN_DEALER_WITHDRAW_FEE_RATIO,
  158. max_value = Const.MAX_DEALER_WITHDRAW_FEE_RATIO
  159. )
  160. # 流量卡年费
  161. annualTrafficCost = MonetaryField(default = Const.PLATFORM_DEFAULT_TRAFFIC_COST, verbose_name = '流量卡年费')
  162. # 该值由平台调整. 可以单独调整某些经销商的成本价格和卡年费
  163. trafficCardCost = MonetaryField(verbose_name = u'平台提供给经销商的流量卡成本价', default = None)
  164. limitAdLocation = BooleanField(default = False, verbose_name = u'是否限制广告领取的地域')
  165. features = ListField(verbose_name = u'支持的特性', default = [])
  166. defaultDiscountConfig = DictField(verbose_name = u"经销商默认优惠充值套餐", default = Const.DEFAULT_DISCOUNT_RULE)
  167. defaultCardDiscountConfig = DictField(verbose_name = u"经销商默认卡优惠充值套餐", default = Const.DEFAULT_DISCOUNT_RULE)
  168. supportedIncomeAggregate = ListField(verbose_name = u'支持的收入聚合', default = DEALER_INCOME_SOURCE.choices())
  169. supportedConsumptionAggregate = ListField(verbose_name=u'支持的消费聚合', default=DEALER_CONSUMPTION_AGG_KIND.choices())
  170. supportedConsumptionShow = ListField(verbose_name=u"用户端的消费信息显示", default=[])
  171. bottomAd = DictField(verbose_name = u"底部banner", default = {})
  172. pushBrokerUrl = StringField(verbose_name = u'经销商订阅的消息推送URL', default = settings.DEFAULT_DEALER_PUSH_BROKER_URL)
  173. limitDevNum = IntField(verbose_name = u'限制经销商最大设备数量', default = 9999)
  174. isPurePartner = BooleanField(verbose_name = u'是否为纯合伙人', default = False)
  175. userCount = IntField(verbose_name = u'总的用户数量', default = 0)
  176. moniAppCheckPointDict = DictField(verbose_name = u'是否需要弹出监督公众号的经销商检查点', default = DEFAULT_CHECKPOINT)
  177. forceFollowGzh = StringField(verbose_name=u'是否强制关注公众号', default='agent')#以代理商为准:'agent',强制关注:'yes',不允许强制关注:'no',‘永不关注’:‘never’,‘关不关注无所谓’:free
  178. devCount = IntField(verbose_name = u'设备数量,方便统计查询', default = 0)
  179. maxPackagePrice = MonetaryField(verbose_name = u"最大套餐价格", default = RMB(100.00))
  180. expressAddrList = EmbeddedDocumentListField(verbose_name = u'经销商的收货地址', document_type = DealerAddr)
  181. showServicePhone = BooleanField(verbose_name = u'是否允许使用服务电话', default = False)
  182. isShowBanner = BooleanField(verbose_name = u'是否显示代理商banner', default = True)
  183. hasTempPackage = BooleanField(verbose_name = u'是否启用临时套餐', default = False)
  184. freeModeDisplayedIdle = BooleanField(verbose_name = u'免费地址下端口状态显示为空闲', default = False)
  185. domain = StringField(verbose_name = u'api推送地址url', default = '')
  186. payAppType = StringField(verbose_name = u'商户类型', default = None)
  187. monitorPhone = StringField(verbose_name = u"提现审批员的手机号", default = "", max_length = 32)
  188. currencyCoins = BooleanField(verbose_name = u'金币是否通用(deprecated)', default = True)
  189. currencyMode = StringField(verbose_name = u'金币通用模式(allYes, allNo, asGroup)', default = None)
  190. # 有种情况下比如同一个手机号在两个代理商下申请的都有经销商 商户其实只用申请一个就行了
  191. bindMerSrc = StringField(verbose_name=u"共用的商户经销商ID")
  192. # 联动开关
  193. linkageSwitch = EmbeddedDocumentField(verbose_name=u"开关集合", document_type=DealerLinkageSwitch, default=DealerLinkageSwitch)
  194. apiInfo = LazyReferenceField(verbose_name=u'api应用信息', document_type=ApiAppInfo, default=None)
  195. disableAdPlan = LazyReferenceField(verbose_name=u'纯净版信息', document_type=DisableAdPlan, default=None)
  196. # 配置模板集合
  197. templateSet = EmbeddedDocumentField(verbose_name=u"开关集合", document_type=ConfigTemplate, default=ConfigTemplate)
  198. meta = {
  199. 'indexes': [
  200. {
  201. 'fields': ['username', 'agentId'], 'unique': True
  202. },
  203. ],
  204. "collection": "Dealer",
  205. "db_alias": "default"
  206. }
  207. search_fields = ('username', 'nickname', 'remarks')
  208. def __str__(self):
  209. return '{}<id={} username={} nickname={} agentId={}>'.format(
  210. self.__class__.__name__, str(self.id), self.username, self.nickname, self.agentId)
  211. @property
  212. def currency_mode(self):
  213. if not self.currencyMode:
  214. if self.currencyCoins:
  215. return 'allYes'
  216. else:
  217. return 'allNo'
  218. else:
  219. return self.currencyMode
  220. @property
  221. def bossId(self):
  222. return self.id
  223. @property
  224. def myBoss(self):
  225. return self
  226. # 覆写. 需要对ruleDict参数进行一次转换
  227. def save(self, force_insert = False, validate = True, clean = True,
  228. write_concern = None, cascade = None, cascade_kwargs = None,
  229. _refs = None, save_condition = None, **kwargs):
  230. if 'defaultDiscountConfig' in kwargs:
  231. newRuleDict = {}
  232. for k, v in self.format_default_discount.items():
  233. newRuleDict[k.replace('.', '-')] = v
  234. self.defaultDiscountConfig = newRuleDict
  235. if "defaultCardDiscountConfig" in kwargs:
  236. newCardRuleDict = {}
  237. for k, v in self.format_card_discount.items():
  238. newCardRuleDict[k.replace('.', '-')] = v
  239. self.defaultCardDiscountConfig = newCardRuleDict
  240. return super(Dealer, self).save(**kwargs)
  241. def update(self, **kwargs):
  242. ruleDict = kwargs.pop('defaultDiscountConfig', None)
  243. if ruleDict is not None:
  244. newRuleDict = {}
  245. for k, v in ruleDict.items():
  246. newRuleDict[k.replace('.', '-')] = v
  247. kwargs.update({'defaultDiscountConfig': newRuleDict})
  248. cardRuleDict = kwargs.pop('defaultCardDiscountConfig', None)
  249. if cardRuleDict is not None:
  250. newCardRuleDict = {}
  251. for k, v in cardRuleDict.items():
  252. newCardRuleDict[k.replace('.', '-')] = v
  253. kwargs.update({'defaultCardDiscountConfig': newCardRuleDict})
  254. updated = super(Dealer, self).update(**kwargs)
  255. if not updated:
  256. raise UpdateError('failed to update dealer query kwargs(%s)' % (kwargs,))
  257. else:
  258. return updated
  259. @property
  260. def permissionList(self):
  261. return []
  262. @property
  263. def virtualCardOnlyCardSwitch(self):
  264. """
  265. 虚拟卡仅用于 实体卡的开关
  266. TODO zjl 这个是临时加的一个开关,目的在于不修改前台 达到 虚拟卡只用于实体卡而不能微信扫码使用的目的
  267. 等虚拟卡这一块儿整改的时候直接将这个去掉
  268. :return:
  269. """
  270. return "virtualCardOnlyCardSwitch" in self.features
  271. @property
  272. def switches(self):
  273. return {
  274. 'adShow': self.adShow,
  275. 'beforeCharge': self.beforeCharge,
  276. 'noRecharge': self.noRecharge,
  277. 'withdrawlNotify': self.withdrawlNotify,
  278. 'offlineNotify': self.offlineNotifySwitch,
  279. 'dailyIncomeReportPushSwitch': self.dailyIncomeReportPushSwitch,
  280. 'newUserPaymentOrderPushSwitch': self.newUserPaymentOrderPushSwitch,
  281. 'devFaultPushDealerSwitch': self.devFaultPushDealerSwitch,
  282. 'devFaultPushUserSwitch': self.devFaultPushUserSwitch,
  283. 'virtualCardOnlyCardSwitch': self.virtualCardOnlyCardSwitch
  284. }
  285. def query_feature_by_list(self, queryList):
  286. # type:(list)->dict
  287. dealerFeatures = self.feature_boolean_map
  288. resultFeatures = {}
  289. agent = Agent.objects.get(id=self.agentId) # type: Agent
  290. agent_features = agent.feature_boolean_map
  291. for query in queryList:
  292. if query in dealerFeatures:
  293. resultFeatures[query] = dealerFeatures[query]
  294. else:
  295. if query in agent_features:
  296. resultFeatures[query] = agent_features[query]
  297. else:
  298. resultFeatures[query] = False
  299. return resultFeatures
  300. def get_feature(self, feature_name):
  301. return self.query_feature_by_list([feature_name])
  302. def to_dict(self, shadow = False):
  303. rv = super(Dealer, self).to_dict(shadow = shadow)
  304. pointList = []
  305. for key, switch in self.moniAppCheckPointDict.items():
  306. try:
  307. point = MoniAppPoint.objects.get(key = key)
  308. except Exception as e:
  309. continue
  310. pointList.append({'name': point.name, 'key': key, 'switch': switch})
  311. rv.update({
  312. 'id': str(self.id),
  313. 'deviceBalance': self.sub_balance(DEALER_INCOME_TYPE.DEVICE_INCOME),
  314. 'adBalance': self.sub_balance(DEALER_INCOME_TYPE.AD_INCOME),
  315. "ledgerBalance": self.sub_balance(DEALER_INCOME_TYPE.LEDGER_CONSUME),
  316. 'balance': self.total_balance,
  317. 'wechat': self.wechat,
  318. 'description': self.description,
  319. 'managerialOpenId': self.managerialOpenId,
  320. 'managerialAppId': self.managerialAppId,
  321. 'agentId': self.agentId,
  322. 'bottomAd': self.bottomAd,
  323. 'featureList': self.feature_list,
  324. 'withdrawFeeRatio': self.withdrawFeeRatio,
  325. 'annualTrafficCost': self.annualTrafficCost,
  326. 'defaultDiscountConfig': self.defaultDiscountConfig,
  327. 'pushBrokerUrl': self.pushBrokerUrl,
  328. 'isPurePartner': self.isPurePartner,
  329. 'pointDict': pointList,
  330. 'forceFollowGzh': self.forceFollowGzh,
  331. 'smsVendor': self.smsVendor,
  332. 'hasTempPackage': self.hasTempPackage,
  333. 'freeModeDisplayedIdle': self.freeModeDisplayedIdle,
  334. 'offlineNotifySwitch': self.offlineNotifySwitch,
  335. 'offlineNotifyTime': self.offlineNotifyTime,
  336. 'currencyMode': self.currency_mode,
  337. 'supportedConsumptionShow': self.supportedConsumptionShow,
  338. })
  339. if hasattr(self, 'displayTempPackage'):
  340. rv.update({"displayTempPackage": self.displayTempPackage})
  341. rv.update(self.switches)
  342. return rv
  343. @classmethod
  344. def all(cls):
  345. return [dealer.to_dict() for dealer in cls.objects.all()]
  346. @staticmethod
  347. def cache_key(ownerId):
  348. # type:(Optional[ObjectId, str])->str
  349. return Dealer.CACHE_KEY.format(id = str(ownerId))
  350. @staticmethod
  351. def get_dealer(ownerId): # type:(ObjectId)->Optional[DealerDict, None]
  352. if not ownerId:
  353. return None
  354. dealer = cache.get(Dealer.cache_key(ownerId)) # type: dict
  355. if dealer:
  356. return DealerDict(dealer)
  357. else:
  358. dealer = Dealer.objects(id = ownerId).first() # type: Dealer
  359. if dealer:
  360. value = dealer.to_dict()
  361. cache.set(Dealer.cache_key(ownerId), value)
  362. return DealerDict(value)
  363. else:
  364. logger.error('can not find dealer from db,id=%s' % ownerId)
  365. return None
  366. @staticmethod
  367. def invalid_cache(ownerId):
  368. # type:(Optional[ObjectId, str])->None
  369. cache.delete(Dealer.cache_key(ownerId))
  370. @staticmethod
  371. def update_dealer(ownerId, **valueDict):
  372. # type:(ObjectId, **Any)->bool
  373. try:
  374. if 'noRecharge' in valueDict and valueDict['noRecharge']:
  375. valueDict.update({'beforeCharge': False})
  376. elif 'beforeCharge' in valueDict and valueDict['beforeCharge']:
  377. valueDict.update({'noRecharge': False})
  378. Dealer.objects(id = ObjectId(ownerId)).update(**valueDict)
  379. except Exception as e:
  380. logger.exception('update dealer info error=%s' % e)
  381. return False
  382. Dealer.invalid_cache(ownerId)
  383. return True
  384. @property
  385. def isManagerialOpenIdBound(self):
  386. return self.managerialOpenId != ''
  387. def get_bound_pay_openid(self, key):
  388. # type: (str)->str
  389. pay_openid_map = self.payOpenIdMap # type: dict
  390. bound = pay_openid_map.get(key, BoundOpenInfo(openId = '')) # type: BoundOpenInfo
  391. return bound.openId
  392. def set_bound_pay_openid(self, key, **payload):
  393. # type: (str, Dict)->None
  394. self.payOpenIdMap[key] = BoundOpenInfo(**payload)
  395. def get_bound_pay_info(self, key):
  396. pay_openid_map = self.payOpenIdMap
  397. bound = pay_openid_map.get(key, BoundOpenInfo(openId = ''))
  398. return bound.to_detail_dict()
  399. def set_bound_pay_info(self, key, **payload):
  400. bound = BoundOpenInfo(**payload)
  401. self.payOpenIdMap[key] = bound
  402. return
  403. def isAutoWithdrawOpenIdBound(self, key):
  404. pay_openid_map = self.payOpenIdMap # type: dict
  405. bound = pay_openid_map.get(key, BoundOpenInfo(openId = ''))
  406. return bound.openId != "" and bound.nickname != ""
  407. @property
  408. def format_default_discount(self):
  409. tmp_rule_dict = {}
  410. for k, v in self.defaultDiscountConfig.items():
  411. tmp_rule_dict[k.replace('-', '.')] = v
  412. return tmp_rule_dict
  413. @property
  414. def format_card_discount(self):
  415. card_rule_dict = dict()
  416. for k, v in self.defaultCardDiscountConfig.items():
  417. card_rule_dict[k.replace("-", ".")] = v
  418. return card_rule_dict
  419. @staticmethod
  420. def get_dealers(agentId):
  421. dealers = Dealer.get_collection().find({'agentId': agentId})
  422. return [str(dealer['_id']) for dealer in dealers]
  423. @staticmethod
  424. def get_dealer_from_groupId(groupId):
  425. group = Group.get_group(groupId)
  426. if group is not None:
  427. dealer = Dealer.objects(id = group['ownerId']).first()
  428. else:
  429. group = Group.objects(id = groupId).first()
  430. dealer = Dealer.objects(id = group.ownerId).first()
  431. return dealer
  432. def get_own_devices(self):
  433. groupIds = Group.get_group_ids_of_dealer(str(self.id))
  434. devNoList = Device.get_devNos_by_group(groupIds)
  435. return Device.get_dev_by_nos(devNoList).values()
  436. @property
  437. def income_aggregate_source(self):
  438. # type: ()->Dict[str, AnyStr]
  439. """
  440. e.g. {'ad': u'广告'}
  441. :return:
  442. """
  443. supports = [item for item in self.supportedIncomeAggregate if item != 'ad']
  444. g = itemgetter(*supports)
  445. return dict(zip(supports, g(DEALER_INCOME_SOURCE_TRANSLATION)))
  446. @property
  447. def consumption_aggregate_source(self):
  448. # type: ()->Dict[str, AnyStr]
  449. """
  450. e.g. {'elect': u'电量'}
  451. :return:
  452. """
  453. g = itemgetter(*self.supportedConsumptionAggregate)
  454. return dict(zip(self.supportedConsumptionAggregate, g(DEALER_CONSUMPTION_AGG_KIND_TRANSLATION)))
  455. def set_agent_profit_share(self, share):
  456. # type: (Percent)->int
  457. """
  458. :param share:
  459. :return:
  460. """
  461. updated = self.update(agentProfitShare = share.mongo_amount)
  462. return updated
  463. def set_agent_mer_profit_share(self, share):
  464. # type: (Percent)->int
  465. """
  466. :param share:
  467. :return:
  468. """
  469. updated = self.update(agentMerProfitShare = share.mongo_amount)
  470. return updated
  471. @classmethod
  472. def set_traffic_costs_multiply(cls, dealer_ids, cost):
  473. # type: (List[ObjectId], RMB)->int
  474. updated = cls.get_collection().update_many({'_id': {'$in': dealer_ids}},
  475. {'$set': {'annualTrafficCost': cost.mongo_amount}},
  476. upsert = False)
  477. return updated
  478. @property
  479. def login_url(self):
  480. # type:()->str
  481. return concat_dealer_access_entry_url(agentId = self.agentId)
  482. @property
  483. def withdraw_sms_provider(self):
  484. if self.monitorPhone:
  485. return dealerMonitorWithdrawSMSProvider
  486. else:
  487. return dealerWithdrawSMSProvider
  488. @property
  489. def withdraw_sms_phone_number(self):
  490. if self.monitorPhone:
  491. return str(self.monitorPhone)
  492. else:
  493. return str(self.username)
  494. @property
  495. def auto_withdraw_bound_open_id(self):
  496. from apps.web.helpers import get_wechat_auth_bridge
  497. auth_bridge = get_wechat_auth_bridge(source = self, app_type = APP_TYPE.WECHAT_WITHDRAW)
  498. return self.get_bound_pay_openid(auth_bridge.bound_openid_key)
  499. @property
  500. def auto_withdraw_bank_account(self):
  501. return self.withdrawOptions.get('bankAccount', None)
  502. def record_income(self, source, source_key, money):
  503. # type: (str, str, RMB)->bool
  504. assert isinstance(money, RMB), 'money has to be a RMB instance'
  505. assert source in DEALER_INCOME_SOURCE.choices(), 'not support this source'
  506. assert source_key, 'source key must not be none'
  507. income_type = DealerConst.MAP_SOURCE_TO_TYPE[source]
  508. return self.incr_fund(income_type, source_key, money)
  509. def new_withdraw_handler(self, record):
  510. # type: (WithdrawRecord) -> WithdrawHandler
  511. from apps.web.dealer.withdraw import DealerWithdrawHandler
  512. return DealerWithdrawHandler(self, record)
  513. def new_withdraw_record(self, withdraw_gateway, pay_entity, source_key, income_type, amount, pay_type, manual, recurrent):
  514. """
  515. 创建自动提现的单子
  516. :param withdraw_gateway: 提现网关 WechatPaymentGateway
  517. :param pay_entity: 银行卡 WithdrawBankCard
  518. :param income_type: 收益类型 str
  519. :param amount: 提现金额(非实际到账) RMB
  520. :param pay_type: 提现的方式 str
  521. :param manual: 手动提现 Bool
  522. :param recurrent: 是否自动提现 Bool
  523. :return: WithdrawRecord
  524. """
  525. agent = Agent.objects(id = self.agentId).first() # type: Agent
  526. if not agent:
  527. logger.error('agent is None, agentId={}'.format(self.agentId))
  528. raise ServiceException(
  529. {
  530. 'result': 0,
  531. 'description': u'系统繁忙,请稍后再试',
  532. 'payload': {}
  533. })
  534. agentFeeRation = agent.withdrawFeeRatio
  535. managerFeeRatio = agent.withdrawFeeRatioCost
  536. dealerFeeRatio = self.withdrawFeeRatio
  537. # 微信或者支付宝平台服务费
  538. serviceFee = amount * (dealerFeeRatio.as_ratio)
  539. # 计算微信转银行卡手续费
  540. has_bank_fee = False
  541. if pay_type == WITHDRAW_PAY_TYPE.BANK:
  542. if recurrent:
  543. has_bank_fee = self.auto_withdraw_bank_fee
  544. else:
  545. withdraw_agent = withdraw_gateway.occupant # type: Agent
  546. has_bank_fee = withdraw_agent.dealerBankWithdrawFee and self.bankWithdrawFee
  547. if has_bank_fee:
  548. bank_trans_fee = min(RMB('25.00'), max(RMB('0.10'), amount * Permillage('1').as_ratio))
  549. else:
  550. bank_trans_fee = RMB('0.00')
  551. actualPay = amount - serviceFee - bank_trans_fee
  552. if actualPay < RMB(settings.WITHDRAW_MINIMUM):
  553. logger.error('amount is not enough.')
  554. raise ServiceException(
  555. {
  556. 'result': 0,
  557. 'description': u"提现实际到账金额不能少于{}元".format(RMB(settings.WITHDRAW_MINIMUM)),
  558. 'payload': {}
  559. }
  560. )
  561. if dealerFeeRatio > managerFeeRatio:
  562. earned = amount * ((dealerFeeRatio - managerFeeRatio).as_ratio)
  563. if earned < RMB(0):
  564. earned = RMB(0)
  565. elif earned > serviceFee:
  566. earned = serviceFee
  567. # 分给代理商
  568. agentEarned = RMB(0)
  569. if dealerFeeRatio > agentFeeRation:
  570. agentEarned = amount * ((dealerFeeRatio - agentFeeRation).as_ratio)
  571. if agentEarned < RMB(0):
  572. agent_earned = RMB(0)
  573. elif agentEarned > earned:
  574. agentEarned = earned
  575. # 代理商分完如果还有 继续分给厂商
  576. managerEarned = (earned - agentEarned)
  577. if managerEarned < RMB(0):
  578. managerEarned = RMB(0)
  579. else:
  580. agentEarned = RMB(0)
  581. managerEarned = RMB(0)
  582. partition = [
  583. {
  584. 'role': ROLE.agent,
  585. 'id': self.agentId,
  586. 'cost': agentFeeRation.mongo_amount,
  587. 'earned': agentEarned.mongo_amount
  588. },
  589. {
  590. 'role': ROLE.manager,
  591. 'id': str(agent.primary_agent.id),
  592. 'cost': managerFeeRatio.mongo_amount,
  593. 'earned': managerEarned.mongo_amount
  594. }
  595. ]
  596. return WithdrawRecord.create(
  597. self,
  598. withdraw_gateway = withdraw_gateway,
  599. pay_entity = pay_entity,
  600. source_key = source_key,
  601. income_type = income_type,
  602. pay_type = pay_type,
  603. fund_map = {
  604. 'amount': amount,
  605. 'serviceFee': serviceFee,
  606. 'bankTransFee': bank_trans_fee,
  607. 'actualPay': actualPay,
  608. 'withdrawFeeRatio': dealerFeeRatio,
  609. 'partition': partition
  610. },
  611. manual = manual,
  612. recurrent = recurrent
  613. )
  614. def get_addr_list(self):
  615. result = []
  616. for obj in self.expressAddrList:
  617. if not obj.default:
  618. result.append({'tel':obj.tel,'name':obj.name,'addr':obj.addr,'id':str(obj.id),'default':obj.default})
  619. else:
  620. result.insert(0, {'tel':obj.tel,'name':obj.name,'addr':obj.addr,'id':str(obj.id),'default':obj.default})
  621. return result
  622. @classmethod
  623. def get_auto_withdraw_dealers(cls):
  624. dealers = list()
  625. for _dealer in cls.objects.filter(withdrawOptions__autoWithdrawSwitch = True): # type: Dealer
  626. if "autoWithdraw" in _dealer.features:
  627. dealers.append(_dealer)
  628. return dealers
  629. @classmethod
  630. def get_income_balance_list(cls, dealer, incomeType, minNum=0):
  631. """
  632. 计算经销商在 每个收益来源下面的可提现余额 以及 手续费
  633. :param dealer:
  634. :param incomeType:
  635. :param minNum: 过滤金额
  636. :return: list --> (sourceKey, amount) 来源 当前账户余额
  637. """
  638. assert incomeType in DEALER_INCOME_TYPE.choices(), 'not support this income type'
  639. balanceDict = getattr(dealer, DealerConst.MAP_TYPE_TO_FIELD[incomeType])
  640. withdrawInfoList = list()
  641. for sourceKey, item in balanceDict.items():
  642. if not WithdrawGateway.is_ledger(sourceKey):
  643. continue
  644. balance = item.balance
  645. if balance <= RMB(0):
  646. continue
  647. if balance < RMB(minNum):
  648. continue
  649. withdrawInfoList.append((sourceKey, balance))
  650. return withdrawInfoList
  651. @classmethod
  652. def factory(cls, **kwargs):
  653. factory_type = kwargs.pop('factory_type')
  654. if factory_type == 'app':
  655. app_type = kwargs.pop('app_type')
  656. return lambda dealer: cls.from_dealer(dealer = dealer, app_type = app_type, **kwargs)
  657. else:
  658. raise InvalidParameter(u'参数错误')
  659. @classmethod
  660. def from_dealer(cls, dealer, app_type, **kwargs):
  661. appKey = "{}_app".format(app_type)
  662. attr_or_func = getattr(dealer, appKey)
  663. if callable(attr_or_func):
  664. return attr_or_func(**kwargs)
  665. else:
  666. return attr_or_func
  667. @property
  668. def my_avatar(self):
  669. if self.avatar:
  670. return self.avatar
  671. if self.isManagerialOpenIdBound and self.wechatAuthUserInfo:
  672. return self.wechatAuthUserInfo.get('avatar', '')
  673. return ''
  674. def withdraw_source_key(self, pay_app = None):
  675. # type:(PayAppBase)->str
  676. """
  677. 经销商提现资金池KEY是虚拟的, 仅仅对应账号的资金统计值
  678. :param pay_app:
  679. :return:
  680. """
  681. return APP_KEY_DELIMITER.join(
  682. [WithdrawGateway.NO_LEDGER_PREFIX, pay_app.pay_app_type, getattr(pay_app, '__gateway_key__')])
  683. def handle_with_partners(self, partners):
  684. # type:(list) -> list
  685. """
  686. 处理 合伙人 完成一些参数校验
  687. :param partners:
  688. :return:
  689. """
  690. if not partners:
  691. return list()
  692. new_partners = list(partners)
  693. if self.payByMerchant:
  694. return self.handle_with_merchant_partner(new_partners)
  695. else:
  696. return self.handle_with_normal_partner(new_partners)
  697. def handle_with_merchant_partner(self, partners):
  698. # type:(list) -> list
  699. """
  700. 处理 合伙人 完成一些参数校验 不存在的情况下不要新建
  701. :param partners:
  702. :return:
  703. """
  704. mobileMap = {_partner['tel']: _partner for _partner in partners}
  705. newPartners = list()
  706. dealers = Dealer.objects.filter(username__in=mobileMap.keys(), agentId=self.agentId)
  707. # 总人数鉴定
  708. if len(dealers) != len(partners):
  709. notRegisterDealers = set(mobileMap.keys()) - set([_.username for _ in dealers])
  710. raise ServiceException({"result": 0, "description": u"合伙人尚未注册,请在注册合伙人 {} 为经销商!".format(notRegisterDealers)})
  711. # 同一个代理商下 经销商username唯一
  712. for _dealer in dealers:
  713. if _dealer == self:
  714. raise ServiceException({"result": 0, "description": u'合伙人<{}>不能是经销商本人'.format(_dealer.username)})
  715. # 分成不为0的商户必须 配有京东APP收款
  716. if not _dealer.payAppJDAggre and int(mobileMap[_dealer.username]["percent"]) > 0:
  717. raise ServiceException({"result": 0, "description": u'合伙人<{}>必须开通商户号'.format(_dealer.username)})
  718. # 将ID添加进去
  719. _partner = mobileMap.get(str(_dealer.username))
  720. _partner.update({"id": str(_dealer.id)})
  721. newPartners.append(_partner.copy())
  722. # TODO 合伙人添加的时候 应该是只是添加 而并不是有权限去修改诸如昵称一类的信息
  723. return newPartners
  724. def handle_with_normal_partner(self, partners):
  725. # type:(list) -> list
  726. """
  727. 处理 合伙人 完成一些参数校验
  728. 对于普通的合伙人校验逻辑还是和之前的一样 有则更新 没有创建
  729. :param partners:
  730. :return:
  731. """
  732. from apps.web.dealer.utils import create_partner, update_partner
  733. mobileList = [_partner['tel'] for _partner in partners]
  734. dealers = Dealer.objects.filter(username__in = mobileList, agentId = self.agentId)
  735. partnerDict = {}
  736. for dealer in dealers:
  737. if dealer == self:
  738. raise ServiceException({"result": 0, "description": u'合伙人<{}>不能是经销商本人'.format(dealer.username)})
  739. partnerDict[dealer.username] = dealer
  740. for partner in partners:
  741. if partner['tel'] not in partnerDict:
  742. newDealer = create_partner(
  743. username = partner['tel'],
  744. password = md5("1234567"),
  745. agentId = self.agentId,
  746. nickname = partner['name'],
  747. annualTrafficCost = self.annualTrafficCost,
  748. agentProfitShare = self.agentProfitShare
  749. )
  750. partner['id'] = str(newDealer.id)
  751. else:
  752. dealerId = str(partnerDict[partner['tel']].id)
  753. updated = update_partner(
  754. dealerId = ObjectId(dealerId),
  755. agentId = self.agentId,
  756. nickname = partner['name'],
  757. agentProfitShare = self.agentProfitShare
  758. )
  759. if not updated:
  760. raise ServiceException({"result": 0, "description": u"合伙人(%s)信息更新失败" % partnerDict[partner['tel']]})
  761. partner['id'] = dealerId
  762. partner['percent'] = str(partner['percent'])
  763. return partners
  764. @classmethod
  765. def get_cooperative_dealer_ids(cls, dealerId):
  766. rcds = Group.get_collection().find({'partnerList.id': dealerId})
  767. dealer_ids = set([dealerId])
  768. for rcd in rcds: # type: Group
  769. dealer_ids.add(rcd['ownerId'])
  770. return list(dealer_ids)
  771. @classmethod
  772. def get_cooperative_group_ids(cls, dealerId):
  773. gs = Group.get_collection().find({'partnerList.id': dealerId})
  774. groupIds = []
  775. for _ in gs:
  776. groupIds.append(str(_['_id']))
  777. gsMe = Group.get_collection().find({'ownerId': dealerId})
  778. for _ in gsMe:
  779. groupIds.append(str(_['_id']))
  780. return groupIds
  781. @property
  782. def is_inhouse_wallet(self):
  783. if self.payByMerchant: # 经销商配置了自己商户号的,不能自动充值
  784. return False
  785. app = get_app(source = self, app_type = APP_TYPE.WECHAT_ENV_PAY, role = ROLE.myuser)
  786. return getattr(app, 'inhouse', False)
  787. @property
  788. def defaultMonthlyPackage(self):
  789. """
  790. 默认的包月套餐的列表
  791. :return:
  792. """
  793. return MonthlyPackageTemp.default_one(ownerId=str(self.id))
  794. @property
  795. def monthlyPackage(self):
  796. """
  797. 所有的包月套餐的列表
  798. :return:
  799. """
  800. return MonthlyPackageTemp.get_template_by_dealer(str(self.id))
  801. def isJosEnable(self, clientEnv="wechat"):
  802. """
  803. 判断该经销商是否能够使用京东的拉新活动
  804. # 由于分账的原因 目前只能够走 使用我们自身资金池的用户
  805. :param clientEnv: 客户端的环境
  806. :return:
  807. """
  808. if self.payByMerchant:
  809. # 开启商户的经销商 不能使用
  810. return False
  811. from apps.web.helpers import get_app
  812. if clientEnv == "wechat":
  813. app_type = APP_TYPE.WECHAT_ENV_PAY
  814. elif clientEnv == "jd":
  815. app_type = APP_TYPE.JD_ENV_PAY
  816. else:
  817. return None
  818. app = get_app(source=self, app_type=app_type, role=ROLE.myuser)
  819. # 首先判断是否是 我们自己的资金池 判断方式为 app的agent为平台agent
  820. # 再其次判断是否是京东支付 否则用户记账会记在不通的平台上 容易被经销商发现
  821. if app.occupantId == settings.MY_PRIMARY_AGENT_ID and app.pay_app_type == PayAppType.JD_AGGR:
  822. return app
  823. # 不满足条件的 都不允许使用
  824. return None
  825. def limit_filter_date(self, startTime, endTime):
  826. # type:(str, str)->(str, str)
  827. if not startTime:
  828. startTime = datetime.datetime.now().strftime('%Y-%m-%d')
  829. if not endTime:
  830. endTime = datetime.datetime.now().strftime('%Y-%m-%d')
  831. if str(self.id) == '5d132407003048494763a51b':
  832. # 港德的隐藏数据需求
  833. if startTime < '2021-01-01':
  834. startTime = '2021-01-01'
  835. if endTime < '2021-01-01':
  836. startTime = '2021-01-01'
  837. endTime = '2020-12-31'
  838. else:
  839. if startTime > endTime:
  840. endTime = startTime
  841. return startTime, endTime
  842. @classmethod
  843. def limit_filter_year(cls, dealerId, year):
  844. if dealerId == '5d132407003048494763a51b':
  845. # 港德的隐藏数据需求
  846. if int(year) < 2021:
  847. return None
  848. return year
  849. def get_currency_group_ids(self, group, groups = None):
  850. # type: (Optional[Group, GroupDict], List[Optional[Group, GroupDict]])->list
  851. dealer_dict = DealerDict(self.to_dict()) # type: DealerDict
  852. return dealer_dict.get_currency_group_ids(group, groups)
  853. @property
  854. def ad_show(self):
  855. if self.adShow:
  856. return 'show'
  857. else:
  858. return self.noAdPolicy
  859. @property
  860. def minProfitShare(self):
  861. """
  862. 经销商 所有的地址中 经销商的最低的分成比例
  863. :return:
  864. """
  865. profitShare = Percent('100')
  866. for _group in Group.objects.filter(ownerId=str(self.id)):
  867. if not _group.partnerList:
  868. continue
  869. for _partner in _group.partnerList:
  870. _profitShare = Percent(_partner["percent"])
  871. if _profitShare < profitShare:
  872. profitShare = _profitShare
  873. return profitShare
  874. def rent_freeze_balance(self, income_type, money, source_key, transaction_id): # type:(str, RMB, str, str)->bool
  875. """
  876. 出租设备的冻结订单
  877. :param income_type:
  878. :param money:
  879. :param source_key:
  880. :param transaction_id:
  881. :return:
  882. """
  883. assert source_key, 'gateway is null'
  884. assert transaction_id, 'transaction id is null'
  885. orderType = "inHandRentOrderList"
  886. field = self.income_field_name(income_type = income_type)
  887. fund_key = self.fund_key(income_type = income_type, source_key = source_key)
  888. query = {'_id': self.id, '{}.transaction_id'.format(orderType): {'$ne': transaction_id}}
  889. update = {
  890. '$inc': {
  891. '{fund_key}.balance'.format(fund_key = fund_key): (-money).mongo_amount
  892. },
  893. '$addToSet': {
  894. '{}'.format(orderType): {
  895. 'transaction_id': transaction_id,
  896. 'field': field,
  897. 'key': source_key,
  898. 'value': money.mongo_amount
  899. }
  900. }
  901. }
  902. result = self.get_collection().update_one(query, update, upsert = False)
  903. return bool(result.modified_count == 1)
  904. def rent_clear_frozen_balance(self, transaction_id): # type:(str)->bool
  905. """
  906. 订单扣款完毕 清除冻结信息
  907. :param transaction_id:
  908. :return:
  909. """
  910. assert transaction_id, 'transaction id is null'
  911. orderType = "inHandRentOrderList"
  912. query = {'_id': self.id, '{}.transaction_id'.format(orderType): transaction_id}
  913. update = {
  914. '$pull': {
  915. '{}'.format(orderType):
  916. {
  917. 'transaction_id': transaction_id
  918. }
  919. }
  920. }
  921. result = self.get_collection().update_one(query, update, upsert = False)
  922. return bool(result.modified_count == 1)
  923. def rent_recover_frozen_balance(self, income_type, money, source_key, transaction_id): # type:(str, RMB, str, str)->bool
  924. """
  925. 回滚冻结的支付订单余额
  926. :param source_key:
  927. :param income_type:
  928. :param money:
  929. :param transaction_id:
  930. :return:
  931. """
  932. assert source_key, 'gateway is null'
  933. assert transaction_id, 'transaction id is null'
  934. orderType = "inHandRentOrderList"
  935. fund_key = self.fund_key(income_type = income_type, source_key = source_key)
  936. query = {'_id': self.id, '{}.transaction_id'.format(orderType): transaction_id}
  937. update = {
  938. '$inc': {
  939. '{fund_key}.balance'.format(fund_key = fund_key): money.mongo_amount
  940. },
  941. '$pull': {
  942. '{}'.format(orderType):
  943. {
  944. 'transaction_id': transaction_id
  945. }
  946. }
  947. }
  948. result = self.get_collection().update_one(query, update, upsert = False)
  949. return bool(result.modified_count == 1)
  950. def get_linkage_switch(self):
  951. """
  952. 获取经销商的两级联动开关设置
  953. """
  954. return {
  955. LinkageSwitchEnum.CHARGE_INSURANCE: self.linkageSwitch.chargeInsurance
  956. }
  957. def turn_on(self, category):
  958. """
  959. 打开保险的开关
  960. """
  961. # 打开保险前的前置检查
  962. if category == LinkageSwitchEnum.CHARGE_INSURANCE:
  963. if not self.is_inhouse_wallet:
  964. return False
  965. self.linkageSwitch.chargeInsurance = True
  966. return bool(self.update(linkageSwitch__chargeInsurance=self.linkageSwitch.chargeInsurance))
  967. def turn_off(self, category):
  968. """
  969. 关掉保险的开关
  970. """
  971. if category == LinkageSwitchEnum.CHARGE_INSURANCE:
  972. self.linkageSwitch.chargeInsurance = False
  973. return bool(self.update(linkageSwitch__chargeInsurance=self.linkageSwitch.chargeInsurance))
  974. def get_all_api_devs(self):
  975. devList = Device.get_devs_by_ownerId(str(self.id))
  976. return filter(lambda _: _.isApi == True, devList)
  977. @property
  978. def api_quota_info(self):
  979. totalQuota = self.api_app.apiDeviceMax
  980. devs = self.get_all_api_devs()
  981. usedQuota = len(devs)
  982. remainingQuota = totalQuota - usedQuota if totalQuota - usedQuota > 0 else 0
  983. return {
  984. 'totalQuota': totalQuota,
  985. 'usedQuota': usedQuota,
  986. 'remainingQuota': remainingQuota
  987. }
  988. @property
  989. def api_app(self):
  990. if self.apiInfo:
  991. return self.apiInfo.fetch()
  992. else:
  993. return ApiAppInfo()
  994. def update_api_app(self, **payload):
  995. if not self.apiInfo:
  996. self.apiInfo = ApiAppInfo().save()
  997. self.save()
  998. apiInfo = self.apiInfo.fetch() # type: ApiAppInfo
  999. apiInfo.update(**payload)
  1000. @property
  1001. def disable_ad_plan(self):
  1002. if self.disableAdPlan: # type: DisableAdPlan
  1003. return self.disableAdPlan.fetch()
  1004. else:
  1005. return DisableAdPlan()
  1006. def update_disable_ad_plan(self, **payload):
  1007. if not self.disableAdPlan:
  1008. self.disableAdPlan = DisableAdPlan().save()
  1009. self.save()
  1010. disable_ad_plan = self.disableAdPlan.fetch() # type: DisableAdPlan
  1011. disable_ad_plan.update(**payload)
  1012. def disable_ad_quota_info(self):
  1013. all_devs = Device.get_devs_by_ownerId(str(self.id))
  1014. now = datetime.datetime.now()
  1015. configured_filter = lambda _: _.disableADExpireDate and _.disableADExpireDate > now
  1016. expiring_soon_filter = lambda _: _.disableADExpireDate and (_.disableADExpireDate - now).total_seconds() <= 60 * 60 * 24 * 15
  1017. configured = len(filter(configured_filter, all_devs))
  1018. expiringSoon= len(filter(expiring_soon_filter, all_devs))
  1019. notConfigured = len(Device.get_devs_by_ownerId(str(self.id))) - configured
  1020. return {
  1021. 'configured': configured,
  1022. 'expiringSoon': expiringSoon,
  1023. 'notConfigured': notConfigured
  1024. }
  1025. def query_home_page_layout(self):
  1026. menuDict = {}
  1027. menu_features = []
  1028. for menu, default_value in Const.MAIN_MENU_LIST.iteritems():
  1029. if default_value:
  1030. menu_features.append('hide_{}'.format(menu))
  1031. else:
  1032. menu_features.append(menu)
  1033. menuDict[menu] = default_value
  1034. homeDataDict = {}
  1035. home_data_features = ['show_ad_income', 'show_offline_coins']
  1036. for menu, default_value in Const.HOME_PAGE_DATA_LIST.iteritems():
  1037. homeDataDict[menu] = default_value
  1038. queryResult = self.query_feature_by_list(menu_features + home_data_features)
  1039. for k, v in queryResult.items():
  1040. if k in home_data_features:
  1041. if k == 'show_ad_income':
  1042. homeDataDict['today_ad_income'] = v
  1043. elif k == 'show_offline_coins':
  1044. homeDataDict['offline_coins'] = v
  1045. else:
  1046. if 'hide_' in k:
  1047. if v:
  1048. menu_name = k.replace('hide_', '')
  1049. menuDict[menu_name] = False
  1050. else:
  1051. menu_name = k
  1052. menuDict[menu_name] = v
  1053. return menuDict, homeDataDict
  1054. @property
  1055. def service_phone(self):
  1056. # 如果代理商接管客服电话,就显示代理商的服务电话
  1057. if self.linkageSwitch.agentProxyServicePhone is True:
  1058. agent = Agent.objects(id=self.agentId).first() # type: Optional[Agent]
  1059. if agent and agent.service_phone:
  1060. return agent.service_phone
  1061. elif self.servicePhone:
  1062. return self.servicePhone
  1063. else:
  1064. return self.username
  1065. @property
  1066. def can_withdraw_today(self):
  1067. if self.supports('in_withdraw_whitelist'):
  1068. return True
  1069. if self.devCount >= 5:
  1070. return True
  1071. return WithdrawRecord.count_today(ownerId = str(self.id), role = self.role) <= 2
  1072. @property
  1073. def my_agent(self):
  1074. # type:()->Agent
  1075. if not hasattr(self, '__my_agent__'):
  1076. my_agent = Agent.objects(id=self.agentId).first()
  1077. setattr(self, '__my_agent__', my_agent)
  1078. return getattr(self, '__my_agent__')
  1079. @property
  1080. def force_follow_gzh(self):
  1081. if self.forceFollowGzh == 'agent':
  1082. isNeedFollow = self.my_agent.forceFollowGzh
  1083. else:
  1084. isNeedFollow = True if self.forceFollowGzh == 'yes' else False
  1085. return isNeedFollow
  1086. @property
  1087. def show_auth_window(self):
  1088. showAuth = 0 if self.supports('not_show_auth_window') else 1
  1089. if showAuth == 0:
  1090. return False
  1091. else:
  1092. return True
  1093. @property
  1094. def productAgent(self):
  1095. if not hasattr(self, '__product_agent__'):
  1096. agent = get_user_manager_agent(self)
  1097. setattr(self, '__product_agent__', agent)
  1098. return getattr(self, '__product_agent__')
  1099. def freeze_ledger_balance(self, money, source_key, transaction_id):
  1100. """
  1101. 消费订单分账前的冻结
  1102. """
  1103. income_type = DEALER_INCOME_TYPE.DEVICE_INCOME
  1104. self._freeze_balance(income_type, money, source_key, transaction_id, "inhandLedger")
  1105. def clear_ledger_balance(self, transaction_id):
  1106. """
  1107. 分账记录收益完成 回滚
  1108. """
  1109. self._clear_frozen_balance(transaction_id, "inhandLedger")
  1110. class DealerDict(dict):
  1111. """
  1112. 经销商的缓存表示
  1113. """
  1114. def __repr__(self):
  1115. return '<DealerDict id=%s>' % (self.get('groupId', 'unknown'),)
  1116. @property
  1117. def v(self):
  1118. return dict(self)
  1119. @property
  1120. def id(self):
  1121. return self.get('id')
  1122. @property
  1123. def agentId(self):
  1124. return self.get('agentId')
  1125. @property
  1126. def entity(self):
  1127. if hasattr(self, '__entity__'):
  1128. return getattr(self, '__entity__')
  1129. dealer = Dealer.objects(id = self.id).first()
  1130. if not dealer:
  1131. raise Exception('no such dealer<id={}>'.format(self.id))
  1132. else:
  1133. setattr(self, '__entity__', dealer)
  1134. return dealer
  1135. @property
  1136. def currency_mode(self):
  1137. if 'currencyMode' not in self:
  1138. Dealer.invalid_cache(self.id)
  1139. dealer = Dealer.get_dealer(self.id) # type: DealerDict
  1140. self.update(dealer.v)
  1141. return self.get('currencyMode')
  1142. @property
  1143. def supportedConsumptionShow(self):
  1144. """
  1145. 客户端的 消费消息显示开关
  1146. """
  1147. if 'supportedConsumptionShow' not in self:
  1148. Dealer.invalid_cache(self.id)
  1149. dealer = Dealer.get_dealer(self.id)
  1150. self.update(dealer.v)
  1151. return self["supportedConsumptionShow"]
  1152. def is_currency(self, left_group, right_group):
  1153. # type: (GroupDict, GroupDict)->bool
  1154. if left_group.ownerId != right_group.ownerId:
  1155. return False
  1156. if self.currency_mode == 'allNo':
  1157. return False
  1158. if self.currency_mode == 'allYes':
  1159. return True
  1160. if left_group.currencyGroup and right_group.currencyGroup and (
  1161. left_group.currencyGroup == right_group.currencyGroup):
  1162. return True
  1163. else:
  1164. return False
  1165. def get_currency_group_ids(self, group, groups = None):
  1166. # type: (Optional[Group, GroupDict], List[Optional[Group, GroupDict]])->list
  1167. if self.currency_mode == 'allNo':
  1168. return []
  1169. else:
  1170. if not groups:
  1171. groups = Group.get_groups_of_dealer(str(self.id))
  1172. if self.currency_mode == 'allYes':
  1173. rv = []
  1174. for item in groups: # type: GroupDict
  1175. if item.groupId == group.groupId:
  1176. continue
  1177. rv.append(item.groupId)
  1178. return rv
  1179. else:
  1180. if not group.currencyGroup:
  1181. return []
  1182. else:
  1183. rv = []
  1184. for item in groups: # type: GroupDict
  1185. if item.groupId == group.groupId:
  1186. continue
  1187. if item.currencyGroup == group.currencyGroup:
  1188. rv.append(item.groupId)
  1189. return rv
  1190. @property
  1191. def servicePhone(self):
  1192. return self['servicePhone']
  1193. @property
  1194. def my_agent(self):
  1195. if '__my_agent__' not in self:
  1196. my_agent = Agent.objects(id=self.agentId).first()
  1197. self['__my_agent__'] = my_agent
  1198. return self.get('__my_agent__')
  1199. @property
  1200. def username(self):
  1201. return self['username']
  1202. @property
  1203. def nickname(self):
  1204. return self['nickname']
  1205. # 经销商商户相关信息
  1206. class Merchant(DynamicDocument):
  1207. """
  1208. TODO 经销商商户相关信息. 升级需要, 下个版本后删除
  1209. """
  1210. ownerId = StringField(verbose_name = "经销商ID", default = "")
  1211. merchantType = StringField(verbose_name = "商户类型", default = "")
  1212. merchantName = StringField(verbose_name = "持卡人姓名", default = "")
  1213. provinceID = StringField(verbose_name = "省id", default = "")
  1214. provinceName = StringField(verbose_name = "省名称", default = "")
  1215. cityID = StringField(verbose_name = "市id", default = "")
  1216. cityName = StringField(verbose_name = "市名称", default = "")
  1217. address = StringField(verbose_name = "详细地址", default = "")
  1218. localLicensePhoto = StringField(verbose_name = "营业执照", default = "")
  1219. legalPerson = StringField(verbose_name = "负责人姓名", default = "")
  1220. idCode = StringField(verbose_name = "身份证", default = "")
  1221. email = StringField(verbose_name = "邮箱地址", default = "")
  1222. principalMobile = StringField(verbose_name = "商店名称", default = "")
  1223. validateCode = StringField(verbose_name = "商店名称", default = "")
  1224. localIndentityPhoto1 = StringField(verbose_name = "身份证第一面", default = "")
  1225. localIndentityPhoto2 = StringField(verbose_name = "身份证第二面", default = "")
  1226. accountCode = StringField(verbose_name = "公司账号银行账号", default = "")
  1227. parentBankName = StringField(verbose_name = "银行名称", default = "")
  1228. subBankName = StringField(verbose_name = "银行支行名称", default = "")
  1229. cardType = StringField(verbose_name = "卡类型", default = "")
  1230. bankCardType = StringField(verbose_name = "银行卡内部卡类型", default = "")
  1231. bankProvinceID = StringField(verbose_name = "开户地区省id", default = "")
  1232. bankProvinceName = StringField(verbose_name = "开户地区省name", default = "")
  1233. bankCityID = StringField(verbose_name = "开户地区市id", default = "")
  1234. bankCityName = StringField(verbose_name = "开户地区市name", default = "")
  1235. cnapsCode = StringField(verbose_name = "开户地区市name", default = "")
  1236. localAccountPhoto = StringField(verbose_name = "开户许可证", default = "")
  1237. getPicUrl = StringField(verbose_name = "URl", default = "")
  1238. dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = '添加进来的时间')
  1239. manual = BooleanField(verbose_name = u"该卡是否仅手动提现", default = False)
  1240. accountType = StringField(verbose_name = u'卡类型(个人账号,对公账号)', default = BankCard.AccountType.PERSONAL)
  1241. # allowed = BooleanField(verbose_name=u"是否是提现的卡", default=False)
  1242. meta = {"collection": "Merchant", "db_alias": "default"}
  1243. @classmethod
  1244. def get_collection(cls):
  1245. return cls._get_collection()
  1246. class UpscoreRecord(Document):
  1247. logicalCode = StringField(verbose_name = "设备逻辑编号", default = '')
  1248. devNo = StringField(verbose_name = "设备编号")
  1249. devType = StringField(verbose_name = "设备类型", default = '')
  1250. ownerId = StringField(verbose_name = "所有者", default = "")
  1251. time = DateTimeField(verbose_name = "时间", default = datetime.datetime.now)
  1252. score = IntField(verbose_name = "上分数量", default = 0)
  1253. groupName = StringField(verbose_name = "上分分组名称", default = "")
  1254. address = StringField(verbose_name = "上分设备地址", default = "")
  1255. remark = StringField(verbose_name = "备注", default = "")
  1256. package = DictField(verbose_name = "套餐", default = {})
  1257. type = StringField(verbose_name = '上分类型(套餐上分,远程充卡),默认为空,表示上分', default = '')
  1258. meta = {"collection": "UpscoreRecord", "db_alias": "logdata",
  1259. # "shard_key":("ownerId",)
  1260. }
  1261. @classmethod
  1262. def get_collection(cls):
  1263. return cls._get_collection()
  1264. class AdjustUserVirtualCardRecord(Document):
  1265. cardId = StringField(verbose_name = "虚拟卡ID")
  1266. cardNo = StringField(verbose_name = "虚拟卡卡号")
  1267. adjustDays = IntField(verbose_name = "调整天数", default = 0)
  1268. beforeAdjust = DateTimeField(verbose_name = "调整前的过期时间")
  1269. adjustQuota = ListField(verbose_name = "调整的额度", default = [])
  1270. oldQuota = ListField(verbose_name = "调整之前的额度", default = [])
  1271. operator = StringField(verbose_name = "操作人")
  1272. dealerId = StringField(verbose_name = "卡的管理者")
  1273. adjustType = StringField(verbose_name = "调整方式", choices = TYPE_ADJUST_USER_VIRTUAL_CARD.choices())
  1274. dateTimeAdded = DateTimeField(verbose_name = "添加时间", default = datetime.datetime.now)
  1275. meta = {'collection': 'adjust_user_virtual_card_record', 'db_alias': 'logdata'}
  1276. @classmethod
  1277. def get_collection(cls):
  1278. return cls._get_collection()
  1279. class DealerRechargeRecord(Searchable):
  1280. """
  1281. 经销商充值记录
  1282. """
  1283. class PayState(IterConstant):
  1284. UnPaid = 'UnPaid'
  1285. Paid = 'Paid'
  1286. Failure = 'Failure'
  1287. Fake = 'Fake'
  1288. Cancel = 'Cancel'
  1289. Quit = 'Quit'
  1290. Close = 'Close'
  1291. class ProductType(IterConstant):
  1292. SimCard = 'WF4801' # 流量卡充值
  1293. ApiCost = 'WF48A' # API接入
  1294. DisableAd = 'WF48D' # 禁用广告接入
  1295. AutoSimCard = 'WF4802' # 流量卡自动充值
  1296. ManualSimCard = 'WF4803' # 流量卡线下充值
  1297. SimOrderVerify = 'WF5801' # 流量卡充值对账后重新分账
  1298. ProductDesc = {
  1299. ProductType.SimCard: cn(u'流量卡充值'),
  1300. ProductType.AutoSimCard: cn(u'流量卡自动充值'),
  1301. ProductType.ManualSimCard: cn(u'流量卡线下充值'),
  1302. ProductType.ApiCost: cn(u'API配额充值'),
  1303. ProductType.DisableAd: cn(u'纯净计划'),
  1304. ProductType.SimOrderVerify: cn(u'流量卡充值对账'),
  1305. }
  1306. MapSubType = {
  1307. DealerPaySubType.AUTO_SIM_CARD: ProductType.AutoSimCard,
  1308. DealerPaySubType.MANUAL_SIM_CARD: ProductType.ManualSimCard,
  1309. DealerPaySubType.SIM_CARD: ProductType.SimCard,
  1310. DealerPaySubType.API_COST: ProductType.ApiCost,
  1311. DealerPaySubType.DISABLE_AD: ProductType.DisableAd,
  1312. DealerPaySubType.SIM_ORDER_VERIFY: ProductType.SimOrderVerify
  1313. }
  1314. MY_MAIN_TYPE = OrderMainType.PAY
  1315. orderNo = StringField(verbose_name = u"订单号", unique = True)
  1316. wxOrderNo = StringField(verbose_name = u"渠道订单号. 聚合商户,是聚合平台商户订单号;直连商户则是微信或支付宝订单号", default = "")
  1317. transactionId = StringField(verbose_name = u"微信或者支付宝订单号", default = "")
  1318. product = StringField(verbose_name = u'产品名称', default = ProductType.SimCard)
  1319. items = ListField(verbose_name = u"充值商品", null = False)
  1320. name = StringField(verbose_name = u'商品名称', default = '')
  1321. dealerId = StringField(verbose_name = u"经销商ID", null = False)
  1322. nickname = StringField(verbose_name = u"昵称", default = '')
  1323. totalFee = IntField(verbose_name = u"订单金额(以分为单位)", min_value = 0, null = False)
  1324. settleInfo = DictField(verbose_name = u'结算信息', default = {})
  1325. status = StringField(verbose_name = u"充值结果", default = PayState.UnPaid)
  1326. description = StringField(verbose_name = u"订单错误结果描述,一般为第三方错误码", default = "")
  1327. createdTime = DateTimeField(default = datetime.datetime.now, verbose_name = u"生成时间")
  1328. finishedTime = DateTimeField(default = None, verbose_name = u"支付完成时间")
  1329. attachParas = DictField(verbose_name = u"消费模型信息", default = {})
  1330. gateway = StringField(verbose_name = u'支付网关类型', default = '')
  1331. payAppType = StringField(verbose_name = u'支付应用类型', default = '')
  1332. payGatewayKey = StringField(verbose_name = u'支付网关key', default = '')
  1333. withdrawSourceKey = StringField(verbose_name = u'提现网关source key', default = '')
  1334. subject = StringField(verbose_name = u'商品标题', default = '')
  1335. extraInfo = DictField(verbose_name = u"订单本身模型信息", default = None)
  1336. meta = {'collection': 'dealer_recharge_record', 'db_alias': 'default',
  1337. 'indexes': [
  1338. {
  1339. 'fields': ['dealerId', 'product']
  1340. },
  1341. 'orderNo', 'wxOrderNo', 'transactionId',
  1342. # 'name'
  1343. ]}
  1344. search_fields = ('wxOrderNo', 'name')
  1345. def __repr__(self):
  1346. return '<DealerRechargeRecord id=%s>' % (str(self.id))
  1347. @classmethod
  1348. def get_product(cls, sub_type):
  1349. return {
  1350. 'type': cls.MapSubType[sub_type],
  1351. 'desc': cls.ProductDesc[cls.MapSubType[sub_type]]
  1352. }
  1353. @classmethod
  1354. def issue(cls, sub_type, payment_gateway, user, identifier=None, **payload):
  1355. # type: (str, PaymentGateway, CapitalUser, basestring, Dict)->DealerRechargeRecord
  1356. payload.update({
  1357. 'product': cls.get_product(sub_type)['type'],
  1358. 'gateway': payment_gateway.gateway_type,
  1359. 'payAppType': payment_gateway.pay_app_type,
  1360. 'payGatewayKey': payment_gateway.gateway_key,
  1361. 'subject': cls.get_product(sub_type)['desc'],
  1362. 'createdTime': datetime.datetime.now()
  1363. })
  1364. payload.update({
  1365. 'withdrawSourceKey': payment_gateway.withdraw_source_key()
  1366. })
  1367. if 'extraInfo' not in payload:
  1368. payload['extraInfo'] = {}
  1369. if payment_gateway.pay_app_type == PayAppType.SAOBEI:
  1370. payload['extraInfo'].update({
  1371. 'payTime': str(datetime.datetime.now().strftime('%Y%m%d%H%M%S'))
  1372. })
  1373. payload['status'] = cls.PayState.UnPaid
  1374. identifier = identifier if identifier else str(user.id)
  1375. payload['orderNo'] = OrderNoMaker.make_order_no_32(
  1376. identifier = identifier, main_type = cls.MY_MAIN_TYPE, sub_type = sub_type)
  1377. record = cls(**payload)
  1378. record.save()
  1379. return record
  1380. @classmethod
  1381. def get_record(cls, order_no, dealer_id = None):
  1382. # type: (str, str)->Optional[DealerRechargeRecord]
  1383. # TODO: 分片需要带上片键
  1384. record = cls.objects(orderNo = order_no).first()
  1385. return record
  1386. def succeed(self, wxOrderNo, **kwargs):
  1387. # 这里和数据库强相关, 通过判断是否正确更新记录
  1388. payload = {
  1389. 'wxOrderNo': wxOrderNo
  1390. }
  1391. if kwargs:
  1392. payload.update(kwargs)
  1393. payload.update({'finishedTime': datetime.datetime.now()})
  1394. payload.update({'status': self.PayState.Paid})
  1395. result = DealerRechargeRecord.get_collection().update_one(
  1396. filter = {'_id': ObjectId(self.id), 'status': {'$ne': self.PayState.Paid}},
  1397. update = {'$set': payload},
  1398. upsert = False
  1399. )
  1400. return result.matched_count == 1
  1401. def fail(self, **kwargs):
  1402. self.status = self.PayState.Failure
  1403. self.finishedTime = datetime.datetime.now()
  1404. for key, value in kwargs.items():
  1405. setattr(self, key, value)
  1406. self.save()
  1407. return self
  1408. def cancel(self):
  1409. result = DealerRechargeRecord.get_collection().update_one(
  1410. filter = {'_id': ObjectId(self.id), 'status': self.PayState.UnPaid},
  1411. update = {'$set': {'status': self.PayState.Cancel,
  1412. 'finishedTime': datetime.datetime.now()}},
  1413. upsert = False
  1414. )
  1415. return result.matched_count == 1
  1416. def quit(self):
  1417. self.status = self.PayState.Quit
  1418. self.save()
  1419. return self
  1420. def close(self):
  1421. self.status = self.PayState.Close
  1422. self.finishedTime = datetime.datetime.now()
  1423. return self
  1424. def is_success(self):
  1425. return self.status == self.PayState.Paid
  1426. def is_fail(self):
  1427. return self.status == self.PayState.Failure
  1428. def is_cancel(self):
  1429. return self.status == self.PayState.Cancel
  1430. def is_unpay(self):
  1431. return self.status == self.PayState.UnPaid
  1432. def is_close(self):
  1433. return self.status == self.PayState.Close
  1434. @property
  1435. def sum_of_price(self):
  1436. # type: ()->RMB
  1437. return sum_rmb([RMB(_['price']) for _ in self.items])
  1438. def get_body(self):
  1439. return self.ProductDesc.get(self.product)
  1440. @property
  1441. def fen_total_fee(self):
  1442. return self.totalFee
  1443. @property
  1444. def my_gateway(self):
  1445. return self.gateway
  1446. @property
  1447. def pay_app_type(self):
  1448. return self.payAppType
  1449. @property
  1450. def pay_gateway_key(self):
  1451. return self.payGatewayKey
  1452. @property
  1453. def withdraw_source_key(self):
  1454. return self.withdrawSourceKey
  1455. class RefundDealerRechargeRecord(Searchable):
  1456. class Status(object):
  1457. """ 退款单的状态 正常流程顺序是从上至下 """
  1458. CREATED = 'created' # 创建
  1459. PROCESSING = 'processing' # 申请中
  1460. FAILURE = 'failure' # 申请失败 (彻底失败 需要重新发起)
  1461. SUCCESS = 'success' # 退款成功 (异步结果)
  1462. CLOSED = 'closed' # 退款失败 (异步结果 不需要重新发起)
  1463. # 订单常见的状态
  1464. # created ---> processing ---> success (申请 然后接受成功)
  1465. # created ---> failure (直接申请失败)
  1466. # created ---> processing ---> closed (申请成功 回调失败)
  1467. # created ---> processing ---> failure (申请成功 回调失败 不可以重试)
  1468. rechargeObjId = ObjectIdField(verbose_name = u'充值单')
  1469. orderNo = StringField(verbose_name = u'商户退款订单号', default = '')
  1470. # refundSeq = IntField(verbose_name=u'多次退款,计算退款序列号', default=1)
  1471. errorCode = StringField(verbose_name = u"错误代码", default = "")
  1472. errorDesc = StringField(verbose_name = u"错误描述", default = "")
  1473. money = MonetaryField(verbose_name = u"退款的金钱数额", default = RMB('0.00'))
  1474. status = StringField(verbose_name = u'订单状态', default = 'created')
  1475. datetimeAdded = DateTimeField(verbose_name = u"退款创建时间", default = datetime.datetime.now)
  1476. datetimeUpdated = DateTimeField(verbose_name = u"退款更新时间")
  1477. finishedTime = DateTimeField(verbose_name = u"退款到账时间")
  1478. tradeRefundNo = StringField(verbose_name = u"交易机构退款单号", default = "")
  1479. meta = {
  1480. 'collection': 'refund_dealer_recharge_record',
  1481. 'db_alias': 'default'
  1482. }
  1483. @classmethod
  1484. def issue(cls, order, refundCash):
  1485. # type:(DealerRechargeRecord, RMB)->RefundDealerRechargeRecord
  1486. refund_order_no = OrderNoMaker.make_order_no_32(
  1487. identifier = order.orderNo,
  1488. main_type = OrderMainType.REFUND,
  1489. sub_type = RefundSubType.REFUND)
  1490. return cls(
  1491. rechargeObjId = order.id,
  1492. # refundSeq=next_seq,
  1493. orderNo = refund_order_no,
  1494. money = refundCash,
  1495. status = cls.Status.CREATED).save()
  1496. def succeed(self, tradeRefundNo, finishedTime): # type:(Optional[str], datetime.datetime) -> bool
  1497. """ 更新为成功状态 已经成功退款 """
  1498. result = self.__class__.get_collection().update_one(
  1499. {
  1500. 'orderNo': self.orderNo,
  1501. 'status': self.Status.PROCESSING
  1502. },
  1503. {
  1504. '$set': {
  1505. 'status': self.Status.SUCCESS,
  1506. 'datetimeUpdated': datetime.datetime.now(),
  1507. 'tradeRefundNo': tradeRefundNo,
  1508. 'finishedTime': finishedTime
  1509. }
  1510. }
  1511. )
  1512. return result.matched_count == 1
  1513. def fail(self, errorCode = "", errorDesc = ""): # type:(str, unicode) -> bool
  1514. """
  1515. 更新为失败状态 表示退款申请失败 这种情况下 可能就需要售后进行介入
  1516. 1. 订单申请发起时候即失败
  1517. 2. 订单申请通过 但是回调的时候明确失败 并且是不可重试的失败
  1518. 3. 此状态即表示订单没退款 理论上可以重新发起退款
  1519. """
  1520. return self.__class__.objects.filter(
  1521. orderNo = self.orderNo,
  1522. status__in = [self.Status.PROCESSING, self.Status.CREATED]
  1523. ).update(
  1524. status = self.Status.FAILURE,
  1525. datetimeUpdated = datetime.datetime.now(),
  1526. errorCode = errorCode,
  1527. errorDesc = errorDesc
  1528. )
  1529. def closed(self, tradeRefundNo, errorCode = "", errorDesc = ""): # type:(str, str, str) -> bool
  1530. """
  1531. 退款申请成功了 表示合法的退款申请 但是由于某些原因业务进行不下去
  1532. """
  1533. result = self.__class__.objects.filter(
  1534. orderNo = self.orderNo,
  1535. status = self.Status.PROCESSING
  1536. ).update(
  1537. status = self.Status.CLOSED,
  1538. datetimeUpdated = datetime.datetime.now(),
  1539. tradeRefundNo = tradeRefundNo,
  1540. errorCode = errorCode,
  1541. errorDesc = errorDesc
  1542. )
  1543. return result
  1544. def processing(self): # type:() -> bool
  1545. result = self.__class__.objects.filter(
  1546. orderNo = self.orderNo,
  1547. status__in = [self.Status.CLOSED, self.Status.CREATED]
  1548. ).update(
  1549. status = self.Status.PROCESSING,
  1550. datetimeUpdated = datetime.datetime.now()
  1551. )
  1552. return result
  1553. @property
  1554. def is_fail(self):
  1555. """ 是否订单失败 """
  1556. return self.status == self.Status.FAILURE
  1557. @property
  1558. def is_closed(self):
  1559. """ 退款单是否已经关闭 目前这个状态没有用 适用于用户手动取消退款申请 """
  1560. return self.status == self.Status.CLOSED
  1561. @property
  1562. def is_success(self):
  1563. """ 退款已经成功 """
  1564. return self.status == self.Status.SUCCESS
  1565. @property
  1566. def is_apply(self):
  1567. """ 是否已经发出退款申请 """
  1568. return self.status in [self.Status.CREATED, self.Status.PROCESSING]
  1569. @property
  1570. def is_processing(self):
  1571. return self.status == self.Status.PROCESSING
  1572. @property
  1573. def is_successful(self):
  1574. """ 保留旧的方法 """
  1575. return self.status in [self.Status.SUCCESS, self.Status.PROCESSING]
  1576. @property
  1577. def pay_order(self):
  1578. # type: ()->DealerRechargeRecord
  1579. if not hasattr(self, '__pay_order__'):
  1580. pay_order = DealerRechargeRecord.objects(id = str(self.rechargeObjId)).first()
  1581. setattr(self, '__pay_order__', pay_order)
  1582. return getattr(self, '__pay_order__')
  1583. @pay_order.setter
  1584. def pay_order(self, order):
  1585. setattr(self, '__pay_order__', order)
  1586. @classmethod
  1587. def get_record(cls, order_no):
  1588. return cls.objects(orderNo = order_no).first()
  1589. class OnSale(DynamicDocument):
  1590. """
  1591. 优惠活动管理
  1592. """
  1593. dealerId = StringField(verbose_name = "经销商ID", null = False)
  1594. name = StringField(verbose_name = '活动名称', default = '')
  1595. showSite = IntField(verbose_name = "展现的位置", default = 0) # 展现的位置0,表示扫码后的套餐页面。
  1596. logicalCodeList = ListField(verbose_name = "需要推送的设备", null = False)
  1597. onsaleType = StringField(verbose_name = '活动类型', default = '')
  1598. detailDict = DictField(verbose_name = '活动详细数据', default = {})
  1599. desc = StringField(verbose_name = '描述', default = '')
  1600. img = StringField(verbose_name = '创意图片', default = '')
  1601. onClickUrl = StringField(verbose_name = '回调地址', default = '')
  1602. startTime = DateTimeField(verbose_name = '起始时间', default = datetime.datetime.now)
  1603. endTime = DateTimeField(verbose_name = '结束时间', default = datetime.datetime.now)
  1604. status = StringField(verbose_name = '状态', default = 'start')
  1605. #: 展现类型,onlyOne,点击后就不弹出。forever,表示即使点击过,还是一直弹出,web:表示是页面,不是图片
  1606. showType = StringField(verbose_name = '状态', default = 'onlyOne')
  1607. onsaleTypeDict = {
  1608. u'首次使用送金币': {
  1609. 'onClickUrl': '/user/promotion/getCoins',
  1610. 'desc': u'适用所有设备类型,用户首次扫码的时候,允许客户领金币进行体验,本次活动领过金币的用户,下次无法领取金币。',
  1611. 'expression': "",
  1612. 'img': '/app/img/marketing/first_give_coins.jpg',
  1613. 'showType': 'onlyOne'
  1614. },
  1615. u'首次免费按摩': {
  1616. 'onClickUrl': '/user/promotion/getDuration',
  1617. 'desc': u'适用按摩坐垫以及足疗机,用户首次扫码的时候,允许客户直接启动设备体验,本次活动体验过的用户,下次无法再体验。',
  1618. 'expression': "dev['devType']['code'] in ['100110','100111','100112','100113','100119','100900']",
  1619. 'img': '/app/img/marketing/first_free.jpg',
  1620. 'showType': 'onlyOne'
  1621. },
  1622. u'优惠充值大放送': {
  1623. 'onClickUrl': '/user/onsaleRecharge',
  1624. 'desc': u'适用所有设备类型,用户优惠充值,活动期间始终弹出此推广创意,即使用户已经充值优惠过了。(用户点击推广活动后,会跳转到对应设备的充值页面。需要提前在优惠设置中,把对应的地址下的充值套餐配置好)',
  1625. 'expression': "",
  1626. 'img': '/app/img/marketing/recharegeOnsale.jpg',
  1627. 'showType': 'forever'
  1628. },
  1629. u'手机实名免费用': {
  1630. 'onClickUrl': '/user/inputMobile',
  1631. 'desc': u'首次验证手机号后,可以免费使用设备',
  1632. 'expression': "",
  1633. 'img': '/app/img/marketing/inputMobile.jpg',
  1634. 'showType': 'onlyOne-web'
  1635. },
  1636. #u"京东新人1分购": {
  1637. # "onClickUrl": "/user/onsaleJdNewUser",
  1638. # "desc": u"",
  1639. # "expression": "",
  1640. # "img": "https://resource.washpayer.com/uploaded/marketing/jd.jpg",
  1641. # "showType": "onlyOne"
  1642. #},
  1643. u'包年包月优惠多多': {
  1644. 'onClickUrl': '/user/onsaleTicketList',
  1645. 'desc': u'适用所有设备类型,用户优惠充值,活动期间始终弹出此推广创意,即使用户已经充值优惠过了。(用户点击推广活动后,会跳转到对应设备的充值页面。需要提前在优惠设置中,把对应的地址下的充值套餐配置好)',
  1646. 'expression': "",
  1647. 'img': '/app/img/marketing/recharegeOnsale.jpg',
  1648. 'showType': 'forever'
  1649. },
  1650. }
  1651. def to_dict(self):
  1652. return {
  1653. 'id': str(self.id),
  1654. 'name': self.name,
  1655. 'showSite': self.showSite,
  1656. 'logicalCodeList': self.logicalCodeList,
  1657. 'onsaleType': self.onsaleType,
  1658. 'onClickUrl': self.onClickUrl,
  1659. 'desc': self.desc,
  1660. 'detailDict': self.detailDict,
  1661. 'img': self.img,
  1662. 'startTime': self.startTime.strftime("%Y-%m-%d"),
  1663. 'endTime': self.endTime.strftime("%Y-%m-%d"),
  1664. 'status': self.status
  1665. }
  1666. # 优惠活动访问记录
  1667. class OnSaleRecord(DynamicDocument):
  1668. onsaleId = StringField(verbose_name = "经销商ID", null = False)
  1669. userId = StringField(verbose_name = "用户ID", null = False)
  1670. nickName = StringField(verbose_name = "用户昵称", null = False)
  1671. addedTime = DateTimeField(verbose_name = "访问时间", default = datetime.datetime.now)
  1672. onsaleDetail = DictField(verbose_name = "活动详情数据", default = {})
  1673. meta = {
  1674. "collection": "on_sale_record",
  1675. "db_alias": "logdata",
  1676. }
  1677. #: 经销商创建卡卷
  1678. class VirtualCard(Searchable):
  1679. cardName = StringField(verbose_name = "卡名称", default = "")
  1680. ownerId = StringField(verbose_name = "卡的发布老板", default = "")
  1681. groupIds = ListField(verbose_name = "可用使用卡的地址", default = []) # 空表示没有地址,*表示所有地址下可用
  1682. devTypeList = ListField(verbose_name = "支持的设备类型清单", default = []) # 存放设备类型的ID
  1683. price = MonetaryField(verbose_name = "卡的售价", default = RMB('0.00'))
  1684. createTime = DateTimeField(verbose_name = "创建时间", default = datetime.datetime.now)
  1685. periodDays = FloatField(verbose_name = "卡的可用天数", default = 30.0)
  1686. expiredTime = DateTimeField(verbose_name="卡卷售卖下架时间", default=lambda : datetime.datetime.now() + datetime.timedelta(days=365))
  1687. dayQuota = ListField(verbose_name = "日限额度", default = []) # {'unit':u'次','count':2}
  1688. quota = ListField(verbose_name = "总的额度", default = [])
  1689. userLimit = IntField(verbose_name = "用户数量限制", default = 10)
  1690. userDesc = StringField(verbose_name = "用户侧的描述", default = "")
  1691. dealerDesc = StringField(verbose_name = "经销商侧描述", default = "")
  1692. status = IntField(verbose_name = "卡状态", default = 1) # -1:删除;0:停止发售;1:开始发售
  1693. # 作为内置参数,接口不暴露出来 使用的时候后台脚本修改, 修改脚本名称为 adjust_virtual_card_need_renew
  1694. needRenewMax = IntField(verbose_name = "过期后可续卡天数", default=10)
  1695. needRenewMin = IntField(verbose_name = "过期前可续卡天数", default=10)
  1696. # 暂停发售之后是否可以继续续卡
  1697. renewIgnoreStatus = BooleanField(verbose_name = "暂停发售之后是否可以继续续卡", default = False)
  1698. # 坤元虚拟卡特有:功率
  1699. power = IntField(verbose_name = "包月卡功率限制", default = 0)
  1700. meta = {
  1701. "collection": "VirtualCard",
  1702. "db_alias": "default",
  1703. }
  1704. @property
  1705. def is_expired(self):
  1706. return self.expiredTime <= datetime.datetime.now()
  1707. @property
  1708. def description(self):
  1709. """
  1710. 用于描述虚拟卡的信息
  1711. :return:
  1712. """
  1713. try:
  1714. qStr = u"额度:"
  1715. for item in self.quota:
  1716. qStr += "{} {}".format(item.get("count"), item.get("unit"))
  1717. tStr = u"时间:{}天".format(self.periodDays)
  1718. return "{}\t{}".format(qStr, tStr)
  1719. except Exception:
  1720. return None
  1721. @classmethod
  1722. def get_last(cls, ownerId):
  1723. return cls.objects.filter(ownerId=ownerId).order_by("-id").first()
  1724. @property
  1725. def groups(self):
  1726. if '*' in self.groupIds:
  1727. groups = '*'
  1728. else:
  1729. groups = [{'groupName': grp['groupName'], 'address': grp['address'], 'groupId': grp['groupId']} for grp in
  1730. Group.get_groups_by_group_ids(self.groupIds).values()]
  1731. return groups
  1732. @property
  1733. def devTypes(self):
  1734. devs = DeviceType.objects.filter(id__in=self.devTypeList).only("majorDeviceType", "name")
  1735. devTypes = list()
  1736. for _dev in devs: # type: DeviceType
  1737. devTypes.append({"devTypeName": _dev.name, "majorDeviceType": _dev.majorDeviceType})
  1738. return devTypes
  1739. def to_dict(self):
  1740. return {
  1741. "id": str(self.id),
  1742. "cardId": str(self.id),
  1743. "cardName": self.cardName,
  1744. "price": self.price,
  1745. "periodDays": self.periodDays,
  1746. "createTime": self.createTime,
  1747. "expiredTime": self.expiredTime,
  1748. "userDesc": self.userDesc,
  1749. "dealerDesc": self.dealerDesc,
  1750. "status": self.status,
  1751. 'isExpired': self.is_expired,
  1752. 'power': self.power,
  1753. "quota": self.quota,
  1754. "dayQuota": self.dayQuota,
  1755. "groups": self.groups,
  1756. "devTypes": self.devTypes,
  1757. 'userLimit': self.userLimit
  1758. }
  1759. class ElecPriceTemplate(Searchable):
  1760. ownerId = StringField(verbose_name = "卡的发布老板", default = "")
  1761. name = StringField(verbose_name = "卡的发布老板", default = "")
  1762. priceList = ListField(verbose_name = "48分时价格", default = [])
  1763. class ItemType(Searchable):
  1764. ownerId = StringField(verbose_name = "ownerId", default = "")
  1765. title = StringField(verbose_name = "标题", default = "")
  1766. desc = StringField(verbose_name = "描述", default = "")
  1767. picUrl = StringField(verbose_name = "图片", default = "")
  1768. price = IntField(verbose_name = "价格,分", default = 1)
  1769. class SubAccount(UserSearchable):
  1770. """
  1771. 子账号表
  1772. """
  1773. agentId = StringField(verbose_name = "agentId", min_length = 1)
  1774. phone = StringField(verbose_name = "电话", default = "")
  1775. bossId = ObjectIdField(verbose_name = "主账号ID")
  1776. permissionList = ListField(verbose_name = "菜单清单", default = [])
  1777. meta = {
  1778. 'collection': 'sub_account',
  1779. 'db_alias': 'default',
  1780. 'indexes': [
  1781. {
  1782. 'fields': ['username', 'bossId'], 'unique': True
  1783. },
  1784. {
  1785. 'fields': ['username', 'agentId'], 'unique': True
  1786. }
  1787. ],
  1788. }
  1789. def __str__(self):
  1790. return '{}<id={} username={} nickname={} agentId={}>'.format(
  1791. self.__class__.__name__, str(self.id), self.username, self.nickname, self.agentId)
  1792. @property
  1793. def myBoss(self):
  1794. # type:()->Dealer
  1795. if hasattr(self, '_myBoss'):
  1796. return getattr(self, '_myBoss')
  1797. else:
  1798. myBoss = Dealer.objects.get(id = self.bossId)
  1799. setattr(self, '_myBoss', myBoss)
  1800. return myBoss
  1801. def query_feature_by_list(self, queryList):
  1802. return self.myBoss.query_feature_by_list(queryList)
  1803. def get_feature(self, feature_name):
  1804. return self.myBoss.get_feature(feature_name)
  1805. def query_home_page_layout(self):
  1806. return self.myBoss.query_home_page_layout()
  1807. def to_dict(self):
  1808. value = self.myBoss.to_dict()
  1809. value.update(
  1810. {
  1811. 'username': self.username,
  1812. 'nickname': self.nickname,
  1813. 'role': ROLE.subaccount
  1814. }
  1815. )
  1816. return value
  1817. @property
  1818. def switches(self):
  1819. return self.myBoss.switches
  1820. @property
  1821. def isManagerialOpenIdBound(self):
  1822. return self.myBoss.isManagerialOpenIdBound
  1823. def get_bound_pay_openid(self, key):
  1824. return self.myBoss.get_bound_pay_openid(key)
  1825. @property
  1826. def format_default_discount(self):
  1827. return self.myBoss.format_default_discount
  1828. def get_own_devices(self):
  1829. groupIds = Group.get_group_ids_of_dealer(str(self.bossId))
  1830. devNoList = Device.get_devNos_by_group(groupIds)
  1831. return Device.get_dev_by_nos(devNoList).values()
  1832. @property
  1833. def income_aggregate_source(self):
  1834. # type: ()->Dict[str, AnyStr]
  1835. """
  1836. e.g. {'ad': u'广告'}
  1837. :return:
  1838. """
  1839. g = itemgetter(*self.myBoss.supportedIncomeAggregate)
  1840. return dict(zip(self.myBoss.supportedIncomeAggregate, g(DEALER_INCOME_SOURCE_TRANSLATION)))
  1841. @property
  1842. def consumption_aggregate_source(self):
  1843. # type: ()->Dict[str, AnyStr]
  1844. """
  1845. e.g. {'elect': u'电量'}
  1846. :return:
  1847. """
  1848. g = itemgetter(*self.myBoss.supportedConsumptionAggregate)
  1849. return dict(zip(self.myBoss.supportedConsumptionAggregate, g(DEALER_CONSUMPTION_AGG_KIND_TRANSLATION)))
  1850. def set_agent_profit_share(self, share):
  1851. # type: (Percent)->int
  1852. """
  1853. :param share:
  1854. :return:
  1855. """
  1856. updated = self.myBoss.update(agentProfitShare = share.mongo_amount)
  1857. return updated
  1858. @property
  1859. def adShow(self):
  1860. return self.myBoss.adShow
  1861. @property
  1862. def ad_show(self):
  1863. return self.myBoss.ad_show
  1864. @property
  1865. def pushBrokerUrl(self):
  1866. return self.myBoss.pushBrokerUrl
  1867. @property
  1868. def isPurePartner(self):
  1869. return self.myBoss.isPurePartner
  1870. @property
  1871. def wechat(self):
  1872. return self.myBoss.wechat
  1873. @property
  1874. def description(self):
  1875. return self.myBoss.description
  1876. @property
  1877. def cardNo(self):
  1878. return self.myBoss.cardNo
  1879. @property
  1880. def bankName(self):
  1881. return self.myBoss.bankName
  1882. @property
  1883. def cardHolder(self):
  1884. return self.myBoss.cardHolder
  1885. @property
  1886. def isRead(self):
  1887. return self.myBoss.isRead
  1888. @property
  1889. def noPassReason(self):
  1890. return self.myBoss.noPassReason
  1891. @property
  1892. def cibMerchant(self):
  1893. return self.myBoss.cibMerchant
  1894. @property
  1895. def managerialAppId(self):
  1896. return self.myBoss.managerialAppId
  1897. @property
  1898. def managerialOpenId(self):
  1899. return self.myBoss.managerialOpenId
  1900. @property
  1901. def wechatAuthUserInfo(self):
  1902. return self.myBoss.wechatAuthUserInfo
  1903. @property
  1904. def managerialWechatBoundTime(self):
  1905. return self.myBoss.managerialWechatBoundTime
  1906. @property
  1907. def agentProfitShare(self):
  1908. return self.myBoss.agentProfitShare
  1909. @property
  1910. def beforeCharge(self):
  1911. return self.myBoss.beforeCharge
  1912. @property
  1913. def noRecharge(self):
  1914. return self.myBoss.noRecharge
  1915. @property
  1916. def defaultWashConfig(self):
  1917. return self.myBoss.defaultWashConfig
  1918. @property
  1919. def withdrawlNotify(self):
  1920. return self.myBoss.withdrawlNotify
  1921. @property
  1922. def offlineNotify(self):
  1923. return self.myBoss.offlineNotifySwitch
  1924. @property
  1925. def dailyIncomeReportPushSwitch(self):
  1926. return self.myBoss.dailyIncomeReportPushSwitch
  1927. @property
  1928. def newUserPaymentOrderPushSwitch(self):
  1929. return self.myBoss.newUserPaymentOrderPushSwitch
  1930. @property
  1931. def devFaultPushDealerSwitch(self):
  1932. return self.myBoss.devFaultPushDealerSwitch
  1933. @property
  1934. def devFaultPushUserSwitch(self):
  1935. return self.myBoss.devFaultPushUserSwitch
  1936. @property
  1937. def currency_mode(self):
  1938. return self.myBoss.currency_mode
  1939. @property
  1940. def serviceName(self):
  1941. return self.myBoss.serviceName
  1942. @property
  1943. def servicePhone(self):
  1944. return self.myBoss.servicePhone
  1945. @property
  1946. def qrcodeUrl(self):
  1947. return self.myBoss.qrcodeUrl
  1948. @property
  1949. def withdrawFeeRatio(self):
  1950. return self.myBoss.withdrawFeeRatio
  1951. @property
  1952. def annualTrafficCost(self):
  1953. return self.myBoss.annualTrafficCost
  1954. @property
  1955. def limitAdLocation(self):
  1956. return self.myBoss.limitAdLocation
  1957. @property
  1958. def defaultDiscountConfig(self):
  1959. return self.myBoss.defaultDiscountConfig
  1960. @property
  1961. def supportedIncomeAggregate(self):
  1962. return self.myBoss.supportedIncomeAggregate
  1963. @property
  1964. def supportedConsumptionAggregate(self):
  1965. return self.myBoss.supportedConsumptionAggregate
  1966. @property
  1967. def bottomAd(self):
  1968. return self.myBoss.bottomAd
  1969. @property
  1970. def limitDevNum(self):
  1971. return self.myBoss.limitDevNum
  1972. @property
  1973. def feature_boolean_map(self):
  1974. return self.myBoss.feature_boolean_map
  1975. @property
  1976. def features(self):
  1977. return self.myBoss.features
  1978. @property
  1979. def normal(self):
  1980. my_boss = self.myBoss # type: Dealer
  1981. if not my_boss.normal:
  1982. return my_boss.normal
  1983. else:
  1984. return self.status == self.Status.NORMAL
  1985. @property
  1986. def forbidden(self):
  1987. my_boss = self.myBoss # type: Dealer
  1988. if my_boss.forbidden:
  1989. return my_boss.forbidden
  1990. else:
  1991. return self.status == self.Status.FORBIDDEN
  1992. @property
  1993. def abnormal(self):
  1994. my_boss = self.myBoss # type: Dealer
  1995. if my_boss.abnormal:
  1996. return my_boss.abnormal
  1997. else:
  1998. return self.status == self.Status.ABNORMAL
  1999. @property
  2000. def no_withdraw(self):
  2001. return False
  2002. @property
  2003. def is_inhouse_wallet(self):
  2004. return False
  2005. def limit_filter_date(self, startTime, endTime):
  2006. my_boss = self.myBoss # type: Dealer
  2007. return my_boss.limit_filter_date(startTime, endTime)
  2008. @property
  2009. def service_phone(self):
  2010. my_boss = self.myBoss # type: Dealer
  2011. return my_boss.service_phone
  2012. class Complaint(Searchable):
  2013. openId = StringField(verbose_name = 'openId', default = '')
  2014. logicalCode = StringField(verbose_name = 'logicalCode', default = '')
  2015. groupName = StringField(verbose_name = 'groupName', default = '')
  2016. orderNo = StringField(verbose_name = 'orderNo', default = '')
  2017. handledStatus = StringField(verbose_name = 'status', default = 'init') # init/notice/warning/forbidden
  2018. reason = StringField(verbose_name = 'reason', default = '')
  2019. dateTimeAdded = DateTimeField(verbose_name = u'添加进来的时间', default = datetime.datetime.now)
  2020. handledTime = DateTimeField(verbose_name = u'添加进来的时间', default = datetime.datetime.now)
  2021. meta = {'collection': 'complaint', 'db_alias': 'logdata'}
  2022. # 发送给经销商的消息
  2023. class DealerMessage(Searchable):
  2024. ownerId = StringField(verbose_name = 'ownerId', default = '')
  2025. fromUser = StringField(verbose_name = 'fromUser', default = '')
  2026. messageType = StringField(verbose_name = 'messageType', default = '')
  2027. title = StringField(verbose_name = 'title', default = '')
  2028. desc = StringField(verbose_name = 'desc', default = '')
  2029. read = BooleanField(verbose_name = 'status', default = False)
  2030. dateTimeAdded = DateTimeField(verbose_name = u'添加时间', default = datetime.datetime.now)
  2031. readTime = DateTimeField(verbose_name = u'添加时间', default = datetime.datetime.now)
  2032. relatedInfo = DictField(verbose_name = u'关联信息,便于查找,比如投诉信息', default = {})
  2033. meta = {'collection': 'dealer_message', 'db_alias': 'logdata'}
  2034. # 经销商的换货单
  2035. class ExchangeOrder(Searchable):
  2036. factoryId = ObjectIdField(verbose_name = 'factoryId', default = '')
  2037. agentId = ObjectIdField(verbose_name = 'agentId', default = '')
  2038. ownerId = ObjectIdField(verbose_name = 'ownerId', default = '')
  2039. dateTimeAdded = DateTimeField(verbose_name = 'dateTimeAdded', default = datetime.datetime.now())
  2040. dealerStatus = StringField(verbose_name = u'经销商侧状态', default = '') # 比如已提单、已发货等
  2041. partIds = ListField(verbose_name = u'换货单', default = [])
  2042. pics = ListField(verbose_name = u'用户上传的图片', default = [])
  2043. dealerWords = StringField(verbose_name = u'经销商留言', default = '')
  2044. factoryAddr = DictField(verbose_name = u'厂家维修地址', default = {})
  2045. dealerOrderNo = StringField(verbose_name = u'经销商的快递单号', default = '')
  2046. factoryStatus = StringField(verbose_name = u'厂家侧的状态', default = '') # 比如已发货、已同意换货等
  2047. factoryOrderNo = StringField(verbose_name = u'厂家的快递单号', default = '')
  2048. dealerAddr = DictField(verbose_name = u'经销商地址', default = {})
  2049. factoryWords = StringField(verbose_name = u'厂家留言', default = '')
  2050. more = StringField(verbose_name = u'备注', default = '')
  2051. meta = {'collection': 'exchange_order', 'db_alias': 'default'}
  2052. search_fields = ('dealerWords', 'factoryWords', 'factoryOrderNo', 'dealerOrderNo')
  2053. def make_status_info(self):
  2054. dealerInfo = ''
  2055. factoryInfo = ''
  2056. if self.dealerStatus == 'created':
  2057. dealerInfo = u'经销商申请换板'
  2058. elif self.dealerStatus == 'sended':
  2059. dealerInfo = u'经销商已发货'
  2060. if self.factoryStatus == 'agreed':
  2061. factoryInfo = u'厂家同意换货'
  2062. elif self.factoryStatus == 'disagreed':
  2063. factoryInfo = u'厂家不同意'
  2064. elif self.factoryStatus == 'sended':
  2065. factoryInfo = u'售后中心已发货'
  2066. elif self.factoryStatus == 'closed':
  2067. factoryInfo = u'成功关闭'
  2068. elif self.factoryStatus == '':
  2069. factoryInfo = u'待确认'
  2070. if dealerInfo and factoryInfo:
  2071. return '%s,%s' % (dealerInfo, factoryInfo)
  2072. elif dealerInfo:
  2073. return dealerInfo
  2074. else:
  2075. return factoryInfo
  2076. # 卡的上分记录表
  2077. class UpCardScoreRecord(Searchable):
  2078. cardId = StringField(verbose_name = "cardId", default = '')
  2079. cardNo = StringField(verbose_name = "cardNo", default = '')
  2080. ownerId = StringField(verbose_name = "所有者", default = "")
  2081. dateTimeAdded = DateTimeField(verbose_name = "时间", default = datetime.datetime.now)
  2082. score = FloatField(verbose_name = "上分数量", default = 0.0)
  2083. address = StringField(verbose_name = "绑定地址", default = "")
  2084. remark = StringField(verbose_name = "备注", default = "")
  2085. meta = {"collection": "UpCardScoreRecord", "db_alias": "logdata"}
  2086. @classmethod
  2087. def get_collection(cls):
  2088. return cls._get_collection()
  2089. class PermissionInfo(Searchable): # 权限模版
  2090. pid = StringField(verbose_name = 'pid', null = True, auto_incre = True)
  2091. key = StringField(verbose_name = 'key', unique = True, null = False)
  2092. value = BooleanField(verbose_name = 'value', default = False)
  2093. name = StringField(verbose_name = '权限名称', default = '')
  2094. desc = StringField(verbose_name = '权限详情', default = '')
  2095. dateTimeAdded = DateTimeField(verbose_name = '添加时间', default = datetime.datetime.now)
  2096. meta = {
  2097. 'collection': 'permission_info',
  2098. 'db_alias': 'default',
  2099. }
  2100. @classmethod
  2101. def get_permissions(cls):
  2102. def run(head):
  2103. if head.count() == 0:
  2104. return
  2105. res = {}
  2106. for item in head:
  2107. value = run(all_Permission.filter(pid = str(item.id))) # 结果为None or dict
  2108. res[item.key] = value or item.value
  2109. return res
  2110. all_Permission = cls.objects.all()
  2111. head = all_Permission.filter(pid = None)
  2112. return run(head)
  2113. class PermissionRole(Searchable): # 成员表
  2114. AUTHORIZE_TYPE = ['dealer_to_dealer', 'dealer_to_subAccount']
  2115. roleName = StringField(verbose_name='角色名称', null=True, default='')
  2116. operId = StringField(verbose_name='操作人ID')
  2117. dealerId = StringField(verbose_name='经销商ID')
  2118. subAccountId = StringField(verbose_name='子账号ID')
  2119. permissionRuleId = StringField(verbose_name='权限配置')
  2120. dateTimeAdded = DateTimeField(verbose_name='添加时间', default=datetime.datetime.now)
  2121. lastModifiedTime = DateTimeField(verbose_name='最后一次操作时间', default=datetime.datetime.now)
  2122. authorizeType = StringField(verbose_name='授权关系',choices = AUTHORIZE_TYPE)
  2123. isActive = BooleanField(verbose_name='激活', default=False)
  2124. meta = {
  2125. 'collection': 'permission_role',
  2126. 'db_alias': 'default',
  2127. }
  2128. @staticmethod
  2129. def add_dealer_role(oper_id, dealerId):
  2130. rule = PermissionRule.objects.create(ruleName=str(uuid.uuid4()), dealerId=dealerId,
  2131. permissionDict=PermissionInfo.get_permissions())
  2132. models = {
  2133. 'roleName': None,
  2134. 'operId': oper_id,
  2135. 'dealerId': dealerId,
  2136. 'permissionRuleId': str(rule.id),
  2137. 'authorizeType': 'dealer_to_dealer',
  2138. }
  2139. return PermissionRole.objects.create(**models)
  2140. @staticmethod
  2141. def add_subAccount_role(oper_id, dealerId):
  2142. rule = PermissionRule.objects.create(ruleName=str(uuid.uuid4()), dealerId=dealerId,
  2143. permissionDict=PermissionInfo.get_permissions())
  2144. models = {
  2145. 'roleName': None,
  2146. 'operId': oper_id,
  2147. 'dealerId': dealerId,
  2148. 'permissionRuleId': str(rule.id),
  2149. 'authorizeType': 'dealer_to_subAccount',
  2150. }
  2151. return PermissionRole.objects.create(**models)
  2152. def save(self, **kw):
  2153. self.lastModifiedTime = datetime.datetime.now()
  2154. return super(PermissionRole, self).save(**kw)
  2155. @classmethod
  2156. def get_role_permission(cls, dealerId, operId):
  2157. cacheKey = '{}-{}'.format(dealerId, operId)
  2158. permissionRule = cache.get(cacheKey)
  2159. if not permissionRule:
  2160. role = cls.objects.filter(dealerId=dealerId, operId=operId, isActive=True).first()
  2161. permissionRule = {}
  2162. if role:
  2163. permissionRule = PermissionRule.objects.get(id=role.permissionRuleId).permissionDict
  2164. cache.set(cacheKey, permissionRule)
  2165. return permissionRule
  2166. @classmethod
  2167. def delete_role_permission(cls, operIds, dealerId):
  2168. cls.objects.filter(operId__in=operIds, dealerId=dealerId).update(isActive=False)
  2169. for operId in operIds:
  2170. cacheKey = '{}-{}'.format(dealerId, operId)
  2171. cache.delete(cacheKey)
  2172. @classmethod
  2173. def get_auth_to_dealer(cls, dealerId):
  2174. """
  2175. 获取 本账号授权的经销商
  2176. :param dealerId:
  2177. :return:
  2178. """
  2179. return cls.objects.filter(dealerId=dealerId, authorizeType='dealer_to_dealer', isActive=True).values_list(
  2180. 'operId')
  2181. @classmethod
  2182. def get_auth_to_sub(cls, dealerId):
  2183. """
  2184. 获取 本账号授权的子账号
  2185. :param dealerId:
  2186. :return:
  2187. """
  2188. return cls.objects.filter(dealerId=dealerId, authorizeType='dealer_to_subAccount', isActive=True).values_list(
  2189. 'operId')
  2190. @classmethod
  2191. def get_is_auth_list(cls, operId, authorizeType):
  2192. """
  2193. :param operId: 操作人ID
  2194. :param authorizeType: 授权类型
  2195. :return:
  2196. """
  2197. return cls.objects.filter(operId=str(operId), authorizeType=authorizeType, isActive=True).values_list('dealerId')
  2198. class PermissionRule(Searchable): # 权限配置表
  2199. ruleName = StringField(verbose_name = '配置名称')
  2200. dealerId = StringField(verbose_name = '经销商')
  2201. permissionDict = DictField(verbose_name = '权限内容')
  2202. dateTimeAdded = DateTimeField(verbose_name = '添加时间', default = datetime.datetime.now)
  2203. lastModifiedTime = DateTimeField(verbose_name = '最后一次操作时间', default = datetime.datetime.now)
  2204. meta = {
  2205. 'collection': 'permission_rule',
  2206. 'db_alias': 'default',
  2207. }
  2208. def get_permissionDict(self):
  2209. def update_permission(base_dict, update_dict):
  2210. result = {}
  2211. for key, value in base_dict.items():
  2212. if isinstance(value, dict):
  2213. res = update_permission(value, update_dict[key])
  2214. result[key] = res
  2215. else:
  2216. # print key,value,update_dict
  2217. if not isinstance(update_dict, dict):
  2218. result[key] = update_dict
  2219. else:
  2220. if key in update_dict:
  2221. result[key] = update_dict[key]
  2222. else:
  2223. result[key] = value
  2224. return result
  2225. return update_permission(PermissionInfo.get_permissions(), self.permissionDict)
  2226. def save(self, **kw):
  2227. self.lastModifiedTime = datetime.datetime.now()
  2228. return super(PermissionRule, self).save(**kw)
  2229. class TodoMessage(Searchable):
  2230. title = StringField(verbose_name=u"标题")
  2231. content = StringField(verbose_name=u"内容")
  2232. type = IntField(verbose_name=u"待办的种类", choices=TodoTypeEnum.choices())
  2233. link = StringField(verbose_name=u"跳转地址")
  2234. done = IntField(verbose_name=u"待办状态", choices=TodoDone.choices(), default=TodoDone.INIT)
  2235. ownerId = StringField(verbose_name=u"经销商")
  2236. expiredTime = DateTimeField(verbose_name=u"信息过期时间")
  2237. dateTimeAdded = DateTimeField(verbose_name=u"信息添加的时间", default=datetime.datetime.now())
  2238. popOnlyOnce = BooleanField(verbose_name=u"登录后台只显示一次", default=True)
  2239. def to_dict(self):
  2240. return {
  2241. "id": self.id,
  2242. "title": self.title,
  2243. 'content': self.content,
  2244. "type": self.type,
  2245. "link": self.link,
  2246. "status": self.done,
  2247. "ownerId": self.ownerId,
  2248. 'popOnlyOnce': self.popOnlyOnce
  2249. }
  2250. def has_done(self):
  2251. self.update(done=TodoDone.DONE)
  2252. @property
  2253. def checkModel(self):
  2254. for _ in TodoTypeEnum:
  2255. if _ == self.type:
  2256. return _._load_todoCls()
  2257. @classmethod
  2258. def get_todo_message(cls, user, typeList = TodoTypeEnum.choices()):
  2259. """
  2260. 获取所有未完成的任务
  2261. """
  2262. items = cls.objects.filter(
  2263. ownerId = str(user.id),
  2264. done__in = [TodoDone.INIT, TodoDone.ING],
  2265. type__in = typeList,
  2266. expiredTime__gt = datetime.datetime.now())
  2267. dataList = list()
  2268. for _m in items: # type: TodoMessage
  2269. checkModel = _m.checkModel
  2270. # 进行一次任务检查 查看任务是否完成以及是否需要强制执行
  2271. hasDone, force = checkModel.check_has_done(_m)
  2272. if hasDone:
  2273. _m.has_done()
  2274. continue
  2275. data = _m.to_dict()
  2276. data["force"] = force
  2277. dataList.append(data)
  2278. return dataList
  2279. @classmethod
  2280. def merchant_message(cls, user):
  2281. # type: (RoleBaseDocument)->dict
  2282. if user.role == ROLE.dealer:
  2283. messages = cls.get_todo_message(user, [TodoTypeEnum.MER_TODO.code])
  2284. return messages[0] if len(messages) > 0 else {}
  2285. else:
  2286. return {}
  2287. @classmethod
  2288. def sim_expire_message(cls, ownerId, expireCount):
  2289. params = {
  2290. 'type': 'simCard',
  2291. 'redirect': concat_front_end_url(uri = '/app/deviceCard.html')
  2292. }
  2293. link = add_query(concat_server_end_url(uri = '/dealer/wechat/entry'), params)
  2294. msg = cls(
  2295. id = "1",
  2296. title = u"流量卡续费提醒",
  2297. content = u"您当前有{}台设备流量卡需要续费,为了避免影响设备的正常运行,请您尽快续费(已经充值请忽略)".format(expireCount),
  2298. type = TodoTypeEnum.SMS_TODO.code,
  2299. link = link,
  2300. ownerId = ownerId,
  2301. done = int(TodoDone.ING),
  2302. expiredTime = datetime.datetime.now() + datetime.timedelta(days = 365),
  2303. popOnlyOnce = False
  2304. ).to_dict()
  2305. return msg