models.py 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. """
  4. web.agent.models
  5. ~~~~~~~~~
  6. """
  7. import datetime
  8. import itertools
  9. import logging
  10. import urllib
  11. from collections import namedtuple
  12. import simplejson as json
  13. from bson.objectid import ObjectId
  14. from django.conf import settings
  15. from django.utils.module_loading import import_string
  16. from mongoengine import MapField, LazyReferenceField, IntField
  17. from mongoengine.errors import DoesNotExist
  18. from mongoengine.fields import (StringField, DictField, BooleanField, DateTimeField, EmbeddedDocumentField,
  19. ListField)
  20. from typing import Any, Dict, TYPE_CHECKING, Optional, cast
  21. from apilib.monetary import RMB, sum_rmb, Permillage, Percent
  22. from apps.web.agent.define import AgentConst, AGENT_INCOME_SOURCE, AGENT_INCOME_TYPE
  23. from apps.web.agent.errors import PrimaryAgentDoesNotExist
  24. from apps.web.common.models import WithdrawRecord, CapitalUser, Balance, WithdrawBankCard
  25. from apps.web.common.transaction import WITHDRAW_PAY_TYPE
  26. from apps.web.constant import Const, AppPlatformType, DEALER_CONSUMPTION_AGG_KIND, MoniAppStatus
  27. from apps.web.core import PayAppType, APP_KEY_DELIMITER, ROLE
  28. from apps.web.core.db import Searchable, MonetaryField, StrictDictField, PermillageField, PercentField
  29. from apps.web.core.exceptions import InvalidParameter, NoAgentFound, NoManagerFound
  30. from apps.web.core.messages.sms import agentWithdrawSMSProvider
  31. from apps.web.core.models import WechatManagerApp, WechatAuthApp, BoundOpenInfo, AliApp, WechatPayApp, \
  32. WechatUserManagerApp, WithdrawEntity, WechatUserSubscribeManagerApp, WechatDealerSubscribeManagerApp
  33. from apps.web.core.payment import WithdrawGateway
  34. from apps.web.device.models import DeviceType
  35. from apps.web.exceptions import UserServerException
  36. from apps.web.management.models import Manager
  37. logger = logging.getLogger(__name__)
  38. if TYPE_CHECKING:
  39. from pymongo.results import UpdateResult
  40. from apps.web.common.transaction import WithdrawHandler
  41. from apps.web.core import PayAppBase
  42. AgentDisclaimer = namedtuple("AgentDisclaimer", ["content", "version"])
  43. class Agent(CapitalUser):
  44. """
  45. 代理商, 管理经销商
  46. """
  47. INCOME_SOURCE_LIST = AGENT_INCOME_SOURCE.choices()
  48. INCOME_SOURCE_TO_TYPE = AgentConst.MAP_SOURCE_TO_TYPE
  49. INCOME_TYPE_LIST = AGENT_INCOME_TYPE.choices()
  50. INCOME_TYPE_TO_FIELD = AgentConst.MAP_TYPE_TO_FIELD
  51. #: 默认的收入频道的分布情况
  52. DEFAULT_INCOME_MAP = {
  53. AGENT_INCOME_SOURCE.AD: 0,
  54. AGENT_INCOME_SOURCE.DEALER_WITHDRAW_FEE: 0,
  55. AGENT_INCOME_SOURCE.DEALER_CARD_FEE: 0,
  56. AGENT_INCOME_SOURCE.DEALER_DEVICE_FEE: 0
  57. }
  58. #: 默认管理平台微信公众号授权用户信息
  59. DEFAULT_WECHAT_AUTH_USER_INFO = {
  60. 'avatar': '',
  61. 'sex': 0
  62. }
  63. DEFAULT_PAY_TYPE = {
  64. ROLE.agent: {
  65. AppPlatformType.ALIPAY: PayAppType.ALIPAY,
  66. AppPlatformType.WECHAT: PayAppType.WECHAT,
  67. },
  68. ROLE.dealer: {
  69. AppPlatformType.ALIPAY: PayAppType.ALIPAY,
  70. AppPlatformType.WECHAT: PayAppType.WECHAT,
  71. },
  72. ROLE.myuser: {
  73. AppPlatformType.ALIPAY: PayAppType.ALIPAY,
  74. AppPlatformType.WECHAT: PayAppType.WECHAT,
  75. }
  76. }
  77. DEFAULT_CHECKPOINT = {
  78. 'gerenzhongxin': False, 'yue': False, 'baogaolaoban': False, 'fukuan': False
  79. }
  80. #: 收入相关
  81. deviceBalance = MapField(field = EmbeddedDocumentField(Balance))
  82. trafficBalance = MapField(field = EmbeddedDocumentField(Balance))
  83. adBalance = MapField(field = EmbeddedDocumentField(Balance))
  84. withdrawBalance = MapField(field = EmbeddedDocumentField(Balance))
  85. apiQuotaBalance = MapField(field = EmbeddedDocumentField(Balance))
  86. disableAdBalance = MapField(field = EmbeddedDocumentField(Balance))
  87. incomeMap = DictField(verbose_name = '收入字典', default = DEFAULT_INCOME_MAP)
  88. aggregatedIncome = DictField(verbose_name = '累计收入,只增不减', default = DEFAULT_INCOME_MAP)
  89. #: 代理自定义自己的产品名称和logo
  90. productLogo = StringField(verbose_name = "产品logo", default = "")
  91. productName = StringField(verbose_name = "产品名称", default = "")
  92. #: 公众号相关
  93. gzhServiceQrcodeUrl = StringField(verbose_name = "公众号二维码", default = "", max_length = 256)
  94. gzhServiceLinkUrl = StringField(verbose_name = "公众号链接二维码", default = "")
  95. forceFollowGzh = BooleanField(verbose_name = "是否强制关注公众号", default = False)
  96. forceFollowGzhForDealer = StringField(verbose_name = u'经销商的强制关注开关',
  97. default = 'free') # never:此代理商下的经销商,永远不准强制关注,free:经销商自由允许关注
  98. title = StringField(verbose_name = u'公众号强制关注的时候,显示的title', default = u'为了充分保障您的支付权益,首次使用需要您关注服务公众号辅助使用设备。')
  99. desc = StringField(verbose_name = u'公众号的加粉描述', default = u'关注步骤:手指按在上面二维码上,弹出窗口后,点公众号关注后您再次扫描设备上二维码启动设备。')
  100. #: 用于客服的联系方式
  101. serviceName = StringField(verbose_name = "客服名称", default = "", max_length = 32)
  102. servicePhone = StringField(verbose_name = "客服电话", default = "", max_length = 32)
  103. serviceQrcodeUrl = StringField(verbose_name = "二维码图片链接", default = "", max_length = 256)
  104. #: 特性列表
  105. features = ListField(field = StringField(), verbose_name = u'支持的特性', default = [])
  106. customizedUserGzhAllowable = BooleanField(verbose_name = '用户自定义公众号', default = False)
  107. customizedUserSubGzhAllowable = BooleanField(verbose_name = '用户订阅通知自定义公众号', default = False)
  108. customizedDealerGzhAllowable = BooleanField(verbose_name = '经销商自定义公众号', default = False)
  109. customizedDealerSubGzhAllowable = BooleanField(verbose_name = '经销商订阅通知自定义公众号', default = False)
  110. managerId = StringField(verbose_name = "从属于哪个管理员", null = False)
  111. adShow = BooleanField(verbose_name = "广告收入显示与否的选项", default = False)
  112. # 厂商参与设备运营分成的商户比例 资金池模式
  113. managerProfitShare = PercentField(verbose_name='代理商设备运营分成比例', default = Percent('0.00'))
  114. # 厂商参与设备运营分成的商户比例 暂不支持,但是可以先预留
  115. managerMerProfitShare = PercentField(verbose_name='代理商设备运营分成比例 商户收款', default = Percent('0.00'))
  116. # 提现OPENID映射
  117. payOpenIdMap = MapField(EmbeddedDocumentField(BoundOpenInfo))
  118. #: 用于API接口
  119. openAPI = BooleanField(verbose_name = "是否支持openAPI", default = False)
  120. agentSign = StringField(verbose_name = '唯一签名用于API调用,agent的签名,用于核对对方的签名')
  121. mySign = StringField(verbose_name = '唯一签名用于API调用,我们的签名,用于请求对方URl,对方鉴权')
  122. domain = StringField(verbose_name = '域名调用URL')
  123. wechatLoginAuthApp = EmbeddedDocumentField(verbose_name = '授权APP', document_type = WechatAuthApp)
  124. oldWechatLoginAuthApp = EmbeddedDocumentField(verbose_name = '老的授权APP',
  125. document_type = WechatAuthApp, default = None)
  126. wechatUserManagerialApp = EmbeddedDocumentField(verbose_name = '用户管理APP', document_type = WechatUserManagerApp)
  127. wechatDealerManagerialApp = EmbeddedDocumentField(verbose_name = '经销商管理APP', document_type = WechatManagerApp)
  128. wechatUserSubscribeManagerApp = EmbeddedDocumentField(verbose_name = '用户订阅通知APP',
  129. document_type = WechatUserSubscribeManagerApp)
  130. wechatDealerSubscribeManagerApp = EmbeddedDocumentField(verbose_name = '经销商订阅通知APP',
  131. document_type = WechatDealerSubscribeManagerApp)
  132. # 支付APP配置
  133. customizedAlipayCashflowAllowable = BooleanField(verbose_name = '是否开启自主支付宝收款权限', default = False)
  134. payAppAli = LazyReferenceField(document_type = AliApp, default = None)
  135. customizedWechatCashflowAllowable = BooleanField(verbose_name = '是否开启微信自主收款权限', default = False)
  136. payAppWechat = LazyReferenceField(document_type = WechatPayApp, verbose_name = '微信支付APP(用于提现和支付)', default = None)
  137. featureToggles = DictField(verbose_name = '特性开关', default = {})
  138. # 厂商给代理商的流量卡成本价. 提交经销商的时候,经销商的默认价格从这个继承,代理商也可以更改
  139. annualTrafficCost = MonetaryField(verbose_name = '厂商给代理商的流量卡成本价', default = Const.PLATFORM_DEFAULT_TRAFFIC_COST)
  140. # 平台给厂商的流量卡成本. 添加厂商的时候设置, 添加代理商的时候从厂商配置继承.
  141. # 如果有更改,更改之前的代理商不会修改这个费用,如果修改需要直接修改
  142. trafficCardCost = MonetaryField(verbose_name = '平台给厂商的流量卡成本价', default = Const.PLATFORM_DEFAULT_TRAFFIC_COST)
  143. # 厂商给代理商的提现费率. 代理商给经销商设置的提现费率必须大于该值. 配置资金池的代理商不受限制
  144. withdrawFeeRatio = PermillageField(verbose_name = '厂商给代理商的提现费率下限',
  145. default = Const.PLATFORM_DEFAULT_WITHDRAW_FEE_RATIO,
  146. min_value = Const.MIN_DEALER_WITHDRAW_FEE_RATIO,
  147. max_value = Const.MAX_DEALER_WITHDRAW_FEE_RATIO)
  148. # 平台给厂商的提现费率. 厂商给代理商的提现费率下限必须大于该值. 这个是平台成本价.
  149. withdrawFeeRatioCost = PermillageField(verbose_name = '平台给厂商的提现费率',
  150. default = Const.PLATFORM_DEFAULT_WITHDRAW_FEE_RATIO,
  151. min_value = Const.MIN_DEALER_WITHDRAW_FEE_RATIO,
  152. max_value = Const.MAX_DEALER_WITHDRAW_FEE_RATIO)
  153. bannerList = ListField(field = DictField(), verbose_name = "banner列表", default = [])
  154. #: 大部分情况下,是不允许直接变更支付网关的
  155. #: 手工给其转账的时候把该开关打开
  156. isChangingPaymentGateway = BooleanField(default = False)
  157. payType = DictField(verbose_name = u'各应用支付方式', default = DEFAULT_PAY_TYPE)
  158. moniAppList = ListField(verbose_name = u'监督公众号的清单', default = [])
  159. moniAppCheckPointDict = DictField(verbose_name = u'是否需要弹出监督公众号的代理商检查点', default = DEFAULT_CHECKPOINT)
  160. maxPayLimit = IntField(verbose_name = u'最大充值金额限制', default = 500)
  161. cardWechatInfo = DictField(verbose_name = u'批量导入实体卡绑定内勤微信账号信息', default = {})
  162. boundCardName = StringField(verbose_name = u'默认绑定卡主姓名', default = '')
  163. boundCardPhone = StringField(verbose_name = u'默认绑定卡主手机号', default = '')
  164. withdrawApps = MapField(EmbeddedDocumentField(document_type = WithdrawEntity), default = None)
  165. # 寻找 免责声明的 流程是 代理商--->主代理商--->系统代理商
  166. needDisclaimer = BooleanField(verbose_name = u"代理商是否需要免责声明", default = True)
  167. disclaimer = StringField(verbose_name = u"代理商设置的用户的免责声明", default = "")
  168. disclaimer_version = StringField(verbose_name = u"当前免责声明的版本,声明更新的时候须将版本号更新", default = "v1.0.0")
  169. dealerBankWithdrawFee = BooleanField(verbose_name = u'经销商提现到银行卡的手续费,计算方式', default = False)
  170. meta = {
  171. 'indexes': [
  172. {
  173. 'fields': ['username'],
  174. 'unique': True
  175. }
  176. ],
  177. "collection": "Agent",
  178. "db_alias": "default"
  179. }
  180. search_fields = ('username', 'nickname', 'openId', 'remarks')
  181. def __str__(self):
  182. return 'Agent<id={} username={} nickname={}>'.format(str(self.id), self.username, self.nickname)
  183. @property
  184. def service_phone(self):
  185. return self.servicePhone
  186. def my_pay_type(self, role, gateway_type):
  187. if role in self.payType and gateway_type in self.payType[role]:
  188. return self.payType[role][gateway_type]
  189. else:
  190. return self.DEFAULT_PAY_TYPE[role][gateway_type]
  191. @property
  192. def my_wechat_pay_app(self):
  193. # type: ()->WechatPayApp
  194. if self.customizedWechatCashflowAllowable:
  195. if not self.payAppWechat:
  196. raise Exception(u'第三方支付配置错误(1001)')
  197. else:
  198. my_app = self.payAppWechat.fetch() # type: WechatPayApp
  199. if not my_app.valid:
  200. raise Exception(u'第三方支付配置错误(1002)')
  201. else:
  202. if (str(self.id) == settings.MY_PRIMARY_AGENT_ID) and (not my_app.inhouse):
  203. raise Exception(u'第三方支付配置错误(1003)')
  204. my_app.occupantId = str(self.id)
  205. my_app.occupant = self
  206. return my_app
  207. my_app = WechatPayApp.get_null_app()
  208. my_app.occupantId = str(self.id)
  209. my_app.occupant = self
  210. return my_app
  211. @property
  212. def my_ali_pay_app(self):
  213. # type: ()->AliApp
  214. if self.customizedAlipayCashflowAllowable:
  215. if not self.payAppAli:
  216. raise Exception(u'第三方支付配置错误(1001)')
  217. else:
  218. my_app = self.payAppAli.fetch() # type: AliApp
  219. if not my_app.valid:
  220. raise Exception(u'第三方支付配置错误(1002)')
  221. else:
  222. if (str(self.id) == settings.MY_PRIMARY_AGENT_ID) and (not my_app.inhouse):
  223. raise Exception(u'第三方支付配置错误(1003)')
  224. my_app.occupantId = str(self.id)
  225. my_app.occupant = self
  226. return my_app
  227. my_app = AliApp.get_null_app()
  228. my_app.occupantId = str(self.id)
  229. my_app.occupant = self
  230. return my_app
  231. def to_dict(self, shadow = False):
  232. rv = super(Agent, self).to_dict()
  233. rv.update({
  234. 'id': str(self.id),
  235. 'productLogo': self.product_logo,
  236. 'productName': self.product_name,
  237. 'serviceName': self.serviceName,
  238. 'servicePhone': self.servicePhone,
  239. 'serviceQrcodeUrl': self.serviceQrcodeUrl,
  240. 'gzhServiceQrcodeUrl': self.gzhServiceQrcodeUrl,
  241. 'adShow': self.adShow,
  242. 'forceFollowGzh': self.forceFollowGzh,
  243. 'managerId': self.managerId,
  244. 'isPrimary': self.is_primary,
  245. 'featureList': self.feature_list,
  246. 'annualTrafficCost': self.annualTrafficCost,
  247. 'trafficCardCost': self.trafficCardCost,
  248. 'title': self.title,
  249. 'desc': self.desc,
  250. 'smsVendor': self.smsVendor,
  251. 'dealerBankWithdrawFee': self.dealerBankWithdrawFee,
  252. 'bankWithdrawFee': self.bankWithdrawFee
  253. })
  254. return rv
  255. @classmethod
  256. def filter(cls, manager_id, search_key, page_index, page_size, moniAppId = None, shadow = False):
  257. query = {'managerId': manager_id} if manager_id is not None else {}
  258. if moniAppId:
  259. query.update({'moniAppList': moniAppId})
  260. agents = cls.objects(**query).search(search_key).order_by('-dateTimeAdded')
  261. total = agents.count()
  262. dataList = []
  263. for agent in agents.paginate(pageIndex = page_index, pageSize = page_size): # type: Agent
  264. deviceTypes = DeviceType.objects(agentId = str(agent.id)).all()
  265. if len(deviceTypes):
  266. devTypeList = [_.to_dict() for _ in deviceTypes]
  267. else:
  268. devTypeList = []
  269. if Manager.objects(primeAgentId = str(agent.id)).first():
  270. isPrimary = True
  271. else:
  272. isPrimary = False
  273. zhejiangDict = {}
  274. try:
  275. from apps.web.south_intf.zhejiang_fire import ZhejiangNorther
  276. obj = ZhejiangNorther.objects.get(tokenId = str(agent.id))
  277. ip, port = obj.northPort.split(":")
  278. zhejiangDict = {
  279. 'loginUsername': obj.usernameFromHear,
  280. 'loginPassword': obj.passwordFromHear,
  281. 'mqUsername': obj.mqUser,
  282. 'mqPassword': obj.mqPassword,
  283. 'code': obj.serviceCodeFromNorth,
  284. "northIp": ip,
  285. "northPort": port
  286. }
  287. except Exception:
  288. pass
  289. item = {
  290. 'id': str(agent.id),
  291. 'nickname': agent.nickname,
  292. 'username': '******' if shadow else agent.username,
  293. 'annualTrafficCost': agent.annualTrafficCost,
  294. 'trafficCardCost': agent.trafficCardCost,
  295. 'withdrawFeeRatio': float(agent.withdrawFeeRatio.amount),
  296. 'withdrawFeeRatioCost': float(agent.withdrawFeeRatioCost.amount),
  297. 'managerProfitShare': float(agent.managerProfitShare.amount),
  298. 'dealerTotal': agent.get_dealers().count(),
  299. 'dateTimeAdded': agent.dateTimeAdded,
  300. 'featureList': agent.feature_list,
  301. 'bannerImgList': agent.bannerList,
  302. 'customizedAlipayCashflowAllowable': agent.customizedAlipayCashflowAllowable,
  303. 'customizedWechatCashflowAllowable': agent.customizedWechatCashflowAllowable,
  304. 'customizedDealerGzhAllowable': agent.customizedDealerGzhAllowable,
  305. 'customizedDealerSubGzhAllowable': agent.customizedDealerSubGzhAllowable,
  306. 'customizedUserGzhAllowable': agent.customizedUserGzhAllowable,
  307. 'customizedUserSubGzhAllowable': agent.customizedUserSubGzhAllowable,
  308. 'isPrimary': isPrimary,
  309. 'deviceType': devTypeList,
  310. 'detail': {
  311. 'agentId': str(agent.id),
  312. 'managerId': agent.managerId,
  313. 'features': json.dumps(agent.feature_boolean_map)
  314. },
  315. 'ZJFirePlatform': zhejiangDict,
  316. 'forceFollowGzh': 'yes' if agent.forceFollowGzh else 'no',
  317. 'forceFollowGzhForDealer': agent.forceFollowGzhForDealer,
  318. 'productName': agent.productName,
  319. 'maxPayLimit': agent.maxPayLimit
  320. }
  321. try:
  322. pay_app_ali = agent.my_ali_pay_app # type: Optional[AliApp]
  323. except Exception as e:
  324. pay_app_ali = None
  325. if pay_app_ali: # type
  326. item.update({'aliPayApp': pay_app_ali.to_dict()}) # type: AliApp
  327. else:
  328. item.update({'aliPayApp': AliApp.get_null_app().to_dict()})
  329. try:
  330. pay_app_wechat = agent.my_wechat_pay_app
  331. except Exception as e:
  332. pay_app_wechat = None
  333. if pay_app_wechat:
  334. item.update({'wechatPayApp': pay_app_wechat.to_dict()})
  335. else:
  336. item.update({'wechatPayApp': WechatPayApp.get_null_app().to_dict()})
  337. dealer_manager_app = agent.wechatDealerManagerialApp or WechatManagerApp.get_null_app() # type: WechatManagerApp
  338. item.update({'dealer': dealer_manager_app.to_dict()})
  339. user_manager_app = agent.wechatUserManagerialApp or WechatUserManagerApp.get_null_app() # type: WechatUserManagerApp
  340. item.update({'user': user_manager_app.to_dict()})
  341. user_sub_manager_app = agent.wechatUserSubscribeManagerApp or WechatUserSubscribeManagerApp.get_null_app() # type: WechatUserSubscribeManagerApp
  342. item.update({'user_sub': user_sub_manager_app.to_dict()})
  343. dealer_sub_manager_app = agent.wechatDealerSubscribeManagerApp or WechatDealerSubscribeManagerApp.get_null_app() # type: WechatDealerSubscribeManagerApp
  344. item.update({'dealer_sub': dealer_sub_manager_app.to_dict()})
  345. # 监督号的配置
  346. moniAppList = []
  347. for appId in agent.moniAppList:
  348. try:
  349. moniApp = MoniApp.objects.get(appId = appId)
  350. except Exception, e:
  351. continue
  352. moniAppList.append({'appId': moniApp.appId, 'appName': moniApp.appName})
  353. item.update({'moniApps': moniAppList})
  354. pointList = []
  355. for key, switch in agent.moniAppCheckPointDict.items():
  356. try:
  357. point = MoniAppPoint.objects.get(key = key)
  358. except Exception, e:
  359. continue
  360. pointList.append({'name': point.name, 'key': key, 'switch': switch})
  361. item.update({'pointDict': pointList})
  362. if agent.is_primary:
  363. item.update({'isPrimary': True})
  364. dataList.insert(0, item)
  365. else:
  366. item.update({'isPrimary': False})
  367. dataList.append(item)
  368. return total, dataList
  369. @property
  370. def product_logo(self):
  371. if not self.productLogo:
  372. return self.primary_agent.productLogo
  373. else:
  374. return self.productLogo
  375. @property
  376. def product_name(self):
  377. if not self.productName:
  378. return self.primary_agent.productName
  379. else:
  380. return self.productName
  381. @staticmethod
  382. def get_agent(agentId):
  383. try:
  384. agent = Agent.objects.get(id = ObjectId(agentId)) # type: Agent
  385. return agent.to_dict()
  386. except DoesNotExist:
  387. logger.exception('can not find agent from db,id=%s' % agentId)
  388. return None
  389. def update(self, **kwargs):
  390. # type: (**Any)->int
  391. """
  392. 每次更新的时候确保正在更改支付网关的选项设置为关闭
  393. :param kwargs:
  394. :return:
  395. """
  396. return super(Agent, self).update(**kwargs)
  397. @staticmethod
  398. def update_agent(agentId, valueDict):
  399. """TODO REFACTOR"""
  400. if not valueDict:
  401. return True
  402. try:
  403. agent = Agent.objects(id = agentId).get()
  404. updated = agent.update(**valueDict)
  405. assert updated, u'更新失败'
  406. except Exception as e:
  407. logger.exception('update agent error=%s' % e)
  408. return False
  409. return True
  410. @staticmethod
  411. def record_cookie(agentId, response):
  412. agent = Agent.get_agent(agentId)
  413. if agent is None:
  414. return response
  415. response.set_cookie(key = 'agentLogoUrl',
  416. value = agent['productLogo'],
  417. max_age = 24 * 3600 * 30,
  418. domain = settings.COOKIE_DOMAIN)
  419. pn = urllib.quote(agent['productName'].encode('utf-8'))
  420. response.set_cookie(key = 'agentBrandName', value = pn, max_age = 24 * 3600 * 30,
  421. domain = settings.COOKIE_DOMAIN)
  422. response.set_cookie(key = 'agentId', value = agentId, max_age = 24 * 3600 * 30, domain = settings.COOKIE_DOMAIN)
  423. return response
  424. def put_cookie(self, response):
  425. logo = self.product_logo
  426. if not logo:
  427. logo = '/app/img/logo.png'
  428. response.set_cookie(key = 'agentLogoUrl', value = logo,
  429. max_age = 24 * 3600 * 30,
  430. domain = settings.COOKIE_DOMAIN)
  431. productName = self.product_name
  432. response.set_cookie(key = 'agentBrandName',
  433. value = urllib.quote(productName.encode('utf-8')),
  434. max_age = 24 * 3600 * 30,
  435. domain = settings.COOKIE_DOMAIN)
  436. response.set_cookie(key = 'agentId',
  437. value = str(self.id),
  438. max_age = 24 * 3600 * 30,
  439. domain = settings.COOKIE_DOMAIN)
  440. return response
  441. def get_dealers(self):
  442. """
  443. 获取旗下经销商
  444. :return:
  445. """
  446. Dealer = import_string('apps.web.dealer.models.Dealer')
  447. return Dealer.objects(agentId = str(self.id))
  448. @property
  449. def primary_agent_id(self):
  450. return self.manager.primeAgentId
  451. @property
  452. def manager(self): # type:() -> Manager
  453. if not hasattr(self, '__manager__'):
  454. manager = Manager.objects(id=self.managerId).first()
  455. if not manager:
  456. raise NoManagerFound(manager_id=self.managerId)
  457. setattr(self, '__manager__', manager)
  458. return getattr(self, '__manager__')
  459. @property
  460. def primary_agent(self): # type:() -> Agent
  461. if not hasattr(self, '__my_prime_agent__'):
  462. primeAgent = Agent.objects(id = self.primary_agent_id).first()
  463. if not primeAgent:
  464. raise NoAgentFound(agent_id = self.primary_agent_id)
  465. setattr(self, '__my_prime_agent__', primeAgent)
  466. return getattr(self, '__my_prime_agent__')
  467. @property
  468. def is_primary(self):
  469. """
  470. 不需要去多查询一次数据库 只需要找到manager即可
  471. """
  472. return self.is_equal(self.primary_agent)
  473. @property
  474. def is_in_domain(self):
  475. try:
  476. return self.manager.domain == settings.MY_DOMAIN
  477. except Exception as e:
  478. logger.error('get manager failure. error = %s; id = %s' % (str(e), str(self.id)))
  479. return False
  480. @classmethod
  481. def from_agent(cls, agent, app_type, **kwargs):
  482. param_key = '{app_type}_app'.format(app_type = app_type)
  483. attr_or_func = getattr(agent, param_key)
  484. if hasattr(attr_or_func, '__call__'):
  485. return attr_or_func(**kwargs)
  486. else:
  487. return attr_or_func
  488. @classmethod
  489. def factory(cls, **kwargs):
  490. factory_type = kwargs.pop('factory_type')
  491. if factory_type == 'app':
  492. app_type = kwargs.pop('app_type')
  493. return lambda agent: cls.from_agent(agent = agent, app_type = app_type, **kwargs)
  494. elif factory_type == 'withdraw_source_key':
  495. pay_app = kwargs.pop('pay_app')
  496. return lambda agent: getattr(agent, 'withdraw_source_key')(pay_app)
  497. else:
  498. raise InvalidParameter(u'参数错误')
  499. @staticmethod
  500. def get_inhouse_prime_agent():
  501. # type: ()->Agent
  502. """
  503. 为了方便识别,如果获取默认代理商失败,需要报错为默认代理商找不到
  504. """
  505. try:
  506. return Agent.objects(id = str(settings.MY_PRIMARY_AGENT_ID)).get()
  507. except DoesNotExist:
  508. raise PrimaryAgentDoesNotExist('failed to get default primary agent')
  509. @property
  510. def inhouse_prime_agent(self):
  511. if str(self.id) == str(settings.MY_PRIMARY_AGENT_ID):
  512. return self
  513. else:
  514. return Agent.get_inhouse_prime_agent()
  515. @property
  516. def customizedCashflowAllowable(self):
  517. """
  518. 目前withdrawSourceKey是使用微信商户来标记. 所以资金池场景下仅
  519. 判断微信商户是否支持就可以。在资金池场景下, 必须配置微信支付
  520. :return:
  521. """
  522. return self.customizedWechatCashflowAllowable
  523. def wechat_env_pay_app(self, role = None, pay_app_type = None):
  524. # type: (Optional[None, str], Optional[None, str])->Optional[cast(PayAppBase)]
  525. assert not (role and pay_app_type), 'role and app_type must not have value in the same time.'
  526. if not self.customizedCashflowAllowable:
  527. if pay_app_type:
  528. _pay_app_type = pay_app_type
  529. _role = None
  530. else:
  531. custom = self.payType.get('custom', False)
  532. if custom:
  533. if role in self.payType and AppPlatformType.WECHAT in self.payType[role]:
  534. _pay_app_type = self.payType[role][AppPlatformType.WECHAT]
  535. _role = None
  536. else:
  537. _pay_app_type = None
  538. _role = role
  539. else:
  540. _pay_app_type = None
  541. _role = role
  542. primary_agent = self.primary_agent
  543. if self.is_equal(primary_agent):
  544. return self.inhouse_prime_agent.wechat_env_pay_app(_role, _pay_app_type)
  545. else:
  546. return primary_agent.wechat_env_pay_app(_role, _pay_app_type)
  547. if role:
  548. app_pay_type = self.my_pay_type(role, AppPlatformType.WECHAT)
  549. else:
  550. app_pay_type = pay_app_type
  551. if app_pay_type == PayAppType.WECHAT:
  552. app = self.my_wechat_pay_app # type: WechatPayApp
  553. if not app.enable or not app.valid:
  554. raise Exception(u'系统配置错误(第三方支付)')
  555. return app
  556. raise Exception(u'系统配置错误(第三方支付)')
  557. def alipay_env_pay_app(self, role = None, pay_app_type = None):
  558. assert not (role and pay_app_type), 'role and app_type must not have value in the same time.'
  559. if not self.customizedCashflowAllowable:
  560. # 如果代理商配置了需要的支付类型, 以代理商配置的为准
  561. if pay_app_type:
  562. _pay_app_type = pay_app_type
  563. _role = None
  564. else:
  565. custom = self.payType.get('custom', False)
  566. if custom:
  567. if role in self.payType and AppPlatformType.ALIPAY in self.payType[role]:
  568. _pay_app_type = self.payType[role][AppPlatformType.ALIPAY]
  569. _role = None
  570. else:
  571. _pay_app_type = None
  572. _role = role
  573. else:
  574. _pay_app_type = None
  575. _role = role
  576. primary_agent = self.primary_agent
  577. if self.is_equal(primary_agent):
  578. return self.inhouse_prime_agent.alipay_env_pay_app(_role, _pay_app_type)
  579. else:
  580. return primary_agent.alipay_env_pay_app(_role, _pay_app_type)
  581. if role:
  582. app_pay_type = self.my_pay_type(role, AppPlatformType.ALIPAY)
  583. else:
  584. app_pay_type = pay_app_type
  585. if app_pay_type == PayAppType.ALIPAY:
  586. app = self.my_ali_pay_app # type: AliApp
  587. if not app.enable or not app.valid:
  588. raise Exception(u'系统配置错误(第三方支付)')
  589. return app
  590. raise Exception(u'系统配置错误(第三方支付)')
  591. def _check_wechat_withdraw(self):
  592. my_app = self.my_wechat_pay_app
  593. if not my_app.enable:
  594. raise Exception(u'系统配置错误(三方支付)')
  595. @property
  596. def my_wechat_withdraw_app(self):
  597. my_app = None
  598. if self.withdrawApps:
  599. source_key = self.withdraw_source_key()
  600. if source_key in self.withdrawApps:
  601. my_app = self.withdrawApps[source_key].wechat_app
  602. if not my_app:
  603. my_app = self.my_wechat_pay_app
  604. if (not my_app.valid) or (not my_app.enable):
  605. raise Exception(u'配置错误(第三方支付)')
  606. my_app.occupant = self
  607. my_app.occupantId = str(self.id)
  608. return my_app
  609. @property
  610. def wechat_withdraw_app(self):
  611. if not self.customizedCashflowAllowable:
  612. primary_agent = self.primary_agent
  613. if self.is_equal(primary_agent):
  614. return self.inhouse_prime_agent.wechat_withdraw_app
  615. else:
  616. return primary_agent.wechat_withdraw_app
  617. return self.my_wechat_withdraw_app
  618. def withdraw_source_key(self, pay_app = None):
  619. # type:(PayAppBase)->str
  620. if not pay_app:
  621. my_app = self.wechat_env_pay_app(pay_app_type = PayAppType.WECHAT)
  622. if (not my_app) or (not my_app.valid) or (not my_app.enable):
  623. raise Exception(u'配置错误(第三方支付)')
  624. else:
  625. agent = pay_app.occupant # type: Agent
  626. my_app = agent.my_wechat_pay_app # type: WechatPayApp
  627. if (not my_app) or (not my_app.valid) or (not my_app.enable):
  628. raise Exception(u'配置错误(第三方支付)')
  629. if hasattr(my_app, '__source_key__'):
  630. return APP_KEY_DELIMITER.join(
  631. [WithdrawGateway.LEDGER_PREFIX, my_app.pay_app_type, getattr(my_app, '__source_key__')])
  632. else:
  633. raise AttributeError('no __source_key__ attribute')
  634. # if my_app.inhouse:
  635. # return settings.MY_PRIMARY_AGENT_WALLET_KEY # 所有平台inhouse资金池全部使用固定的资金池KEY
  636. # else:
  637. # return APP_KEY_DELIMITER.join(
  638. # [WithdrawGateway.LEDGER_PREFIX, my_app.pay_app_type, getattr(my_app, '__source_key__')])
  639. @classmethod
  640. def _parse_source_key(cls, source_key):
  641. # type: (str)->tuple
  642. tokens = source_key.split(APP_KEY_DELIMITER)
  643. return True if tokens[0].startswith(WithdrawGateway.LEDGER_PREFIX) else False, tokens[1], tokens[2], tokens[3:]
  644. # if source_key == settings.MY_PRIMARY_AGENT_WALLET_KEY:
  645. # is_ledger, pay_app_type, occupant_id = True, PayAppType.WECHAT, settings.MY_PRIMARY_AGENT_WALLET_KEY
  646. # else:
  647. # tokens = source_key.split(APP_KEY_DELIMITER)
  648. # is_ledger, pay_app_type, occupant_id = True if tokens[0].startswith(
  649. # WithdrawGateway.LEDGER_PREFIX) else False, tokens[1], tokens[2]
  650. @property
  651. def disclaimerVersionFormat(self):
  652. return "{}_{}"
  653. @property
  654. def disclaimerVersion(self):
  655. verFormat = self.disclaimerVersionFormat
  656. return verFormat.format(str(self.id), self.disclaimer_version)
  657. @disclaimerVersion.setter
  658. def disclaimerVersion(self, value):
  659. try:
  660. self.update(disclaimer_version = value)
  661. except Exception as e:
  662. logger.error(
  663. "set disclaimer version error, agent is <{}>, version is <{}>, error is ".format(self, value, e))
  664. raise e
  665. @property
  666. def disclaimerContent(self):
  667. return self.disclaimer
  668. @disclaimerContent.setter
  669. def disclaimerContent(self, value):
  670. try:
  671. self.update(disclaimer = value)
  672. except Exception as e:
  673. logger.error("set disclaimer content error, agent is <{}>. error is <{}>".format(self, e))
  674. raise e
  675. @property
  676. def disclaimerAgent(self):
  677. """
  678. 获取设置了免责声明的代理商
  679. :return:
  680. """
  681. if self.disclaimer:
  682. return self
  683. primeAgent = self.primary_agent
  684. if primeAgent.disclaimer:
  685. return primeAgent
  686. inhousePrimeAgent = self.inhouse_prime_agent
  687. return inhousePrimeAgent
  688. @classmethod
  689. def get_disclaimer(cls, agentId):
  690. """
  691. 获取 代理商的 免责声明
  692. :param agentId:
  693. :return:
  694. """
  695. agent = cls.objects.get(id = agentId)
  696. if not agent.needDisclaimer:
  697. return AgentDisclaimer(content = "", version = agent.disclaimerVersion)
  698. agent = agent.disclaimerAgent
  699. content = agent.disclaimerContent
  700. disclaimer = AgentDisclaimer(content = content, version = agent.disclaimerVersion)
  701. return disclaimer
  702. @classmethod
  703. def withdraw_gateway_list(cls, source_key):
  704. is_ledger, pay_app_type, occupant_id, tokens = cls._parse_source_key(source_key)
  705. if not is_ledger:
  706. return is_ledger, {
  707. 'alipay': WithdrawGateway(AliApp.get_null_app(), False),
  708. 'wechat': WithdrawGateway(WechatPayApp.get_null_app(), False),
  709. 'wechatV3': WithdrawGateway(WechatPayApp.get_null_app(), False)
  710. }
  711. if pay_app_type != PayAppType.WECHAT:
  712. raise UserServerException(u'系统配置错误(第三方支付),请联系平台客服(1001)')
  713. agent = cls.objects(id = occupant_id).first()
  714. if not agent:
  715. raise UserServerException(u'系统配置错误(第三方支付),请联系平台客服(1002)')
  716. if not agent.withdrawApps:
  717. return is_ledger, {
  718. 'alipay': None,
  719. 'wechat': agent.my_wechat_pay_app.new_withdraw_gateway(is_ledger = is_ledger, gateway_version = 'v1'),
  720. 'wechatV3': agent.my_wechat_pay_app.new_withdraw_gateway(is_ledger = is_ledger, gateway_version = 'v3')
  721. }
  722. else:
  723. if source_key not in agent.withdrawApps:
  724. raise UserServerException(u'系统配置错误(第三方支付),请联系平台客服(1003)')
  725. withdraw_entity = agent.withdrawApps[source_key] # type: WithdrawEntity
  726. withdraw_entity.alipay_app.occupantId = str(agent.id)
  727. withdraw_entity.alipay_app.occupant = agent
  728. withdraw_entity.wechat_app.occupantId = str(agent.id)
  729. withdraw_entity.wechat_app.occupant = agent
  730. return is_ledger, {
  731. 'alipay': withdraw_entity.alipay_app.new_withdraw_gateway(is_ledger = is_ledger),
  732. 'wechat': withdraw_entity.wechat_app.new_withdraw_gateway(
  733. is_ledger = is_ledger, gateway_version = 'v1'),
  734. 'wechatV3': withdraw_entity.wechat_app.new_withdraw_gateway(
  735. is_ledger = is_ledger, gateway_version = 'v3')
  736. }
  737. @staticmethod
  738. def get_platform_wechat_manager_app():
  739. agent = Agent.get_inhouse_prime_agent()
  740. return agent.platform_wechat_manager_app
  741. @property
  742. def platform_wechat_manager_app(self):
  743. agent = self.inhouse_prime_agent
  744. app = agent.wechatDealerManagerialApp
  745. if not app:
  746. raise Exception(u'公众号配置错误')
  747. app.occupantId = str(agent.id)
  748. return app
  749. @staticmethod
  750. def get_inhouse_wechat_user_manager_app():
  751. agent = Agent.get_inhouse_prime_agent()
  752. return agent.inhouse_wechat_user_manager_app
  753. @property
  754. def inhouse_wechat_user_manager_app(self):
  755. agent = self.inhouse_prime_agent
  756. app = agent.wechatUserManagerialApp
  757. if not app:
  758. raise Exception(u'公众号配置错误')
  759. app.occupantId = str(agent.id)
  760. return app
  761. @property
  762. def wechat_auth_app(self):
  763. """
  764. 标识用户只用平台的公众号对应APPID. 部分用户由于前期原因配置了自己的
  765. 该接口仅做兼容
  766. :return:
  767. """
  768. if self.wechatLoginAuthApp:
  769. app = self.wechatLoginAuthApp # type: WechatAuthApp
  770. app.occupantId = str(self.id)
  771. return app
  772. else:
  773. primary_agent = self.primary_agent
  774. if self.is_equal(primary_agent):
  775. return self.inhouse_prime_agent.wechat_auth_app
  776. else:
  777. return primary_agent.wechat_auth_app
  778. @property
  779. def wechat_user_manager_app(self):
  780. if self.customizedUserGzhAllowable:
  781. if self.wechatUserManagerialApp:
  782. app = self.wechatUserManagerialApp # type: WechatManagerApp
  783. app.occupantId = str(self.id)
  784. app.occupant = self
  785. return app
  786. else:
  787. raise Exception(u'系统配置错误(wechat_user_manager_app)')
  788. else:
  789. primary_agent = self.primary_agent
  790. if self.is_equal(primary_agent):
  791. inhouse_agent = self.inhouse_prime_agent
  792. if inhouse_agent.customizedUserGzhAllowable:
  793. return inhouse_agent.wechat_user_manager_app
  794. else:
  795. raise Exception(u'系统未配置(wechat_user_manager_app)')
  796. else:
  797. return primary_agent.wechat_user_manager_app
  798. @property
  799. def wechat_user_messager_app(self):
  800. if self.customizedUserGzhAllowable or self.customizedUserSubGzhAllowable:
  801. if self.customizedUserGzhAllowable:
  802. if self.wechatUserManagerialApp:
  803. app = self.wechatUserManagerialApp # type: WechatUserManagerApp
  804. app.occupantId = str(self.id)
  805. app.occupant = self
  806. return app
  807. else:
  808. raise Exception(u'系统配置错误(wechat_user_manager_app)')
  809. if self.customizedUserSubGzhAllowable:
  810. if self.wechatUserSubscribeManagerApp:
  811. app = self.wechatUserSubscribeManagerApp # type: WechatUserSubscribeManagerApp
  812. app.occupantId = str(self.id)
  813. app.occupant = self
  814. return app
  815. else:
  816. raise Exception(u'系统配置错误(wechat_user_subscribe_manager_app)')
  817. else:
  818. primary_agent = self.primary_agent
  819. if self.is_equal(primary_agent):
  820. inhouse_agent = self.inhouse_prime_agent
  821. if inhouse_agent.customizedUserGzhAllowable or inhouse_agent.customizedUserSubGzhAllowable:
  822. return inhouse_agent.wechat_user_messager_app
  823. else:
  824. raise Exception(u'系统未配置(wechat_user_manager_app|wechat_user_subscribe_manager_app)')
  825. else:
  826. return primary_agent.wechat_user_messager_app
  827. @property
  828. def wechat_manager_app(self):
  829. if self.customizedDealerGzhAllowable:
  830. if self.wechatDealerManagerialApp:
  831. app = self.wechatDealerManagerialApp # type: WechatManagerApp
  832. app.occupantId = str(self.id)
  833. return app
  834. else:
  835. raise Exception(u'系统配置错误(wechat_dealer_manager_app, %s)' % str(self.id))
  836. else:
  837. primary_agent = self.primary_agent
  838. if self.is_equal(primary_agent):
  839. return self.inhouse_prime_agent.wechat_manager_app
  840. else:
  841. return primary_agent.wechat_manager_app
  842. @property
  843. def wechat_user_subscribe_manager_app(self):
  844. if self.customizedUserSubGzhAllowable:
  845. if self.wechatUserSubscribeManagerApp:
  846. app = self.wechatUserSubscribeManagerApp # type: WechatManagerApp
  847. app.occupantId = str(self.id)
  848. app.occupant = self
  849. return app
  850. else:
  851. raise Exception(u'系统配置错误(wechat_user_subscribe_manager_app)')
  852. else:
  853. primary_agent = self.primary_agent
  854. if self.is_equal(primary_agent):
  855. inhouse_agent = self.inhouse_prime_agent
  856. if inhouse_agent.customizedUserSubGzhAllowable:
  857. return inhouse_agent.wechat_user_subscribe_manager_app
  858. else:
  859. raise Exception(u'系统未配置(wechat_user_subscribe_manager_app)')
  860. else:
  861. return primary_agent.wechat_user_subscribe_manager_app
  862. @property
  863. def wechat_dealer_subscribe_manager_app(self):
  864. if self.customizedDealerSubGzhAllowable:
  865. if self.wechatDealerSubscribeManagerApp:
  866. app = self.wechatDealerSubscribeManagerApp # type: WechatManagerApp
  867. app.occupantId = str(self.id)
  868. app.occupant = self
  869. return app
  870. else:
  871. raise Exception(u'系统配置错误(wechat_user_manager_app)')
  872. else:
  873. primary_agent = self.primary_agent
  874. if self.is_equal(primary_agent):
  875. return self.inhouse_prime_agent.wechatDealerSubscribeManagerApp
  876. else:
  877. return primary_agent.wechatDealerSubscribeManagerApp
  878. def get_user_sub_template_id_list(self):
  879. if not self.customizedUserSubGzhAllowable:
  880. return []
  881. if not self.wechatUserSubscribeManagerApp.templateIdMap:
  882. return []
  883. else:
  884. try:
  885. return map(lambda _: _['templateId'], self.wechatUserSubscribeManagerApp.templateIdMap.values())
  886. except Exception as e:
  887. logger.info('error e=<{}>'.format(e))
  888. return []
  889. def get_dealer_sub_template_id_list(self):
  890. if not self.customizedDealerSubGzhAllowable:
  891. return []
  892. if not self.wechatUserSubscribeManagerApp.templateIdMap:
  893. return []
  894. else:
  895. try:
  896. return map(lambda _: _['templateId'], self.wechatDealerSubscribeManagerApp.templateIdMap.values())
  897. except Exception as e:
  898. logger.info('error e=<{}>'.format(e))
  899. return []
  900. @property
  901. def alipay_auth_app(self):
  902. if not self.customizedAlipayCashflowAllowable:
  903. primary_agent = self.primary_agent
  904. if self.is_equal(primary_agent):
  905. return self.inhouse_prime_agent.alipay_auth_app
  906. else:
  907. return primary_agent.alipay_auth_app
  908. app = self.my_ali_pay_app # type: AliApp
  909. logger.debug("app id is:{}".format(str(app.id)))
  910. if not app.valid:
  911. raise Exception(u'系统配置错误(第三方支付)')
  912. app.occupantId = str(self.id)
  913. return app
  914. def income_by_date(self, date, specific_source = None):
  915. """
  916. :param date:
  917. :param specific_source:
  918. :return:
  919. """
  920. reports = AgentIncomeReport.objects(agentId = str(self.id), date = date)
  921. if specific_source:
  922. reports = reports.filter(source = specific_source)
  923. return RMB(reports.sum('amount'))
  924. def today_income(self, specific_source = None):
  925. today = datetime.datetime.now().strftime(Const.DATE_FMT)
  926. return self.income_by_date(date = today, specific_source = specific_source)
  927. def yesterday_income(self, specific_source = None):
  928. yesterday = (datetime.datetime.now() - datetime.timedelta(days = 1)).strftime(Const.DATE_FMT)
  929. return self.income_by_date(date = yesterday, specific_source = specific_source)
  930. def aggregate_income(self, specific_source = None):
  931. """
  932. 获取聚合收入
  933. :param specific_source: 特定source
  934. :return:
  935. """
  936. if not specific_source:
  937. return sum_rmb(self.aggregatedIncome.values())
  938. else:
  939. return sum_rmb([v for k, v in self.aggregatedIncome.items() if k == specific_source])
  940. def _single_month_income(self, year, month, specific_source):
  941. if not specific_source:
  942. reports = AgentIncomeReport.objects(agentId = str(self.id), date__startswith = '%d-%02d' % (year, month))
  943. else:
  944. reports = AgentIncomeReport.objects(agentId = str(self.id), date__startswith = '%d-%02d' % (year, month),
  945. source = specific_source)
  946. return RMB(reports.sum('amount'))
  947. def current_month_income(self, specific_source = None):
  948. now = datetime.datetime.now()
  949. return self._single_month_income(now.year, now.month, specific_source)
  950. def monthly_income(self, specific_source = None):
  951. """
  952. 获得月报表
  953. :param specific_source:
  954. :return:
  955. """
  956. now = datetime.datetime.now()
  957. years = {_ for _ in range(2017, now.year + 1)}
  958. months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
  959. return {(year, month): self._single_month_income(year, month, specific_source)
  960. for year, month in itertools.product(years, months)}
  961. def is_equal(self, agent):
  962. return str(self.id) == str(agent.id)
  963. @property
  964. def supported_device_types(self):
  965. result = DeviceType.all(str(self.id))
  966. if result:
  967. return result
  968. primary_agent = self.primary_agent
  969. if not self.is_equal(primary_agent):
  970. return primary_agent.supported_device_types
  971. else:
  972. return []
  973. @property
  974. def feature_boolean_map(self):
  975. # type: ()->Dict[str, bool]
  976. features = super(Agent, self).feature_boolean_map
  977. if self.customizedWechatCashflowAllowable:
  978. features.update({
  979. 'show_withdraw_management': True
  980. })
  981. return features
  982. @property
  983. def hide_consume_kinds_dealer(self):
  984. rv = []
  985. if 'hiddenUsedTime' in self.features:
  986. rv.append(DEALER_CONSUMPTION_AGG_KIND.DURATION)
  987. if 'hidden_coins' in self.features:
  988. rv.append(DEALER_CONSUMPTION_AGG_KIND.COIN)
  989. left_features = set(self.features) - {'hiddenUsedTime', 'hidden_coins'}
  990. for feature in left_features:
  991. if feature.startswith('hide_') and feature.endswith('_for_dealer'):
  992. kind = feature.replace('hide_', '').replace('_for_dealer', '')
  993. if kind in DEALER_CONSUMPTION_AGG_KIND.choices():
  994. rv.append(kind)
  995. return rv
  996. @property
  997. def hide_consume_kinds_user(self):
  998. rv = []
  999. if 'hiddenUsedTime' in self.features:
  1000. rv.append(DEALER_CONSUMPTION_AGG_KIND.DURATION)
  1001. if 'hidden_coins' in self.features:
  1002. rv.append(DEALER_CONSUMPTION_AGG_KIND.COIN)
  1003. left_features = set(self.features) - {'hiddenUsedTime', 'hidden_coins'}
  1004. for feature in left_features:
  1005. if feature.startswith('hide_') and feature.endswith('_for_user'):
  1006. kind = feature.replace('hide_', '').replace('_for_user', '')
  1007. if kind in DEALER_CONSUMPTION_AGG_KIND.choices():
  1008. rv.append(kind)
  1009. return rv
  1010. @property
  1011. def withdraw_sms_provider(self):
  1012. return agentWithdrawSMSProvider
  1013. @property
  1014. def withdraw_sms_phone_number(self):
  1015. return str(self.username)
  1016. def incr_income(self, source, source_key, money):
  1017. # type: (str, str, RMB)->bool
  1018. assert isinstance(money, RMB), 'money has to be a RMB instance'
  1019. assert source in AGENT_INCOME_SOURCE.choices(), 'not support this source'
  1020. assert source_key, 'source key must not be none'
  1021. income_type = AgentConst.MAP_SOURCE_TO_TYPE[source]
  1022. query = {'_id': ObjectId(self.id)}
  1023. update = {
  1024. '$inc': {
  1025. 'aggregatedIncome.{source}'.format(source=source): money.mongo_amount,
  1026. 'incomeMap.{source}'.format(source=source): money.mongo_amount,
  1027. '{filed}.{key}.balance'.format(filed=AgentConst.MAP_TYPE_TO_FIELD[income_type],
  1028. key=source_key): money.mongo_amount
  1029. },
  1030. }
  1031. result = self.get_collection().update_one(query, update, upsert=False) # type: UpdateResult
  1032. return bool(result.modified_count == 1)
  1033. def decr_income(self, source, source_key, money): # type:(str, str, RMB) -> bool
  1034. """
  1035. 扣除代理商的收益
  1036. 必须由调用方保证不会调用重入
  1037. """
  1038. return self.incr_income(source, source_key, -money)
  1039. def new_withdraw_record(self, withdraw_gateway, pay_entity, source_key, income_type, amount, pay_type, manual,
  1040. recurrent):
  1041. # type: (WithdrawGateway, WithdrawBankCard, str, str, RMB, str, bool, bool) -> WithdrawRecord
  1042. if income_type == AGENT_INCOME_TYPE.DEALER_WITHDRAW_FEE:
  1043. withdraw_fee_ratio = Permillage('0.00') # type: Permillage
  1044. else:
  1045. withdraw_fee_ratio = Const.PLATFORM_DEFAULT_WITHDRAW_FEE_RATIO # type: Permillage
  1046. service_fee = amount * withdraw_fee_ratio.as_ratio # type: RMB
  1047. withdraw_agent = withdraw_gateway.occupant # type: Agent
  1048. has_bank_fee = withdraw_agent.dealerBankWithdrawFee and \
  1049. self.bankWithdrawFee and \
  1050. pay_type == WITHDRAW_PAY_TYPE.BANK
  1051. if has_bank_fee:
  1052. bank_trans_fee = min(RMB('25.00'), max(RMB('0.10'), amount * Permillage('1').as_ratio))
  1053. else:
  1054. bank_trans_fee = RMB('0.00')
  1055. actual_pay = amount - service_fee - bank_trans_fee # type: RMB
  1056. return WithdrawRecord.create(self,
  1057. withdraw_gateway = withdraw_gateway,
  1058. pay_entity = pay_entity,
  1059. source_key = source_key,
  1060. income_type = income_type,
  1061. pay_type = pay_type,
  1062. fund_map = {
  1063. 'amount': amount,
  1064. 'serviceFee': service_fee,
  1065. 'actualPay': actual_pay,
  1066. 'bankTransFee': bank_trans_fee,
  1067. 'withdrawFeeRatio': withdraw_fee_ratio,
  1068. 'partition': []
  1069. },
  1070. manual = manual,
  1071. recurrent = recurrent)
  1072. def new_withdraw_handler(self, record):
  1073. # type: (WithdrawRecord) -> WithdrawHandler
  1074. from apps.web.agent.withdraw import AgentWithdrawHandler
  1075. return AgentWithdrawHandler(self, record)
  1076. def get_bound_pay_openid(self, key):
  1077. # type: (str)->str
  1078. pay_openid_map = self.payOpenIdMap # type: dict
  1079. bound = pay_openid_map.get(key, BoundOpenInfo(openId = '')) # type: BoundOpenInfo
  1080. return str(bound.openId)
  1081. def set_bound_pay_openid(self, key, **payload):
  1082. # type: (str, Dict)->None
  1083. self.payOpenIdMap[key] = BoundOpenInfo(**payload)
  1084. def get_online_moni_app(self):
  1085. apps = MoniApp.objects.filter(appId__in = self.moniAppList, status=MoniAppStatus.ADDING).order_by('-priority')
  1086. if apps.count() == 0:
  1087. return {}
  1088. app = apps.first() # type: MoniApp
  1089. return {
  1090. 'appId': app.appId,
  1091. 'secret': app.secret,
  1092. 'title': app.title,
  1093. 'appName': app.appName,
  1094. 'desc': app.desc
  1095. }
  1096. @property
  1097. def agentIds(self):
  1098. # 自身 有agentId 配置的有公众号的,返回自身的id, 没有配置公众号的, 找到厂商的祝代理商的id 以及自身的id返回
  1099. if hasattr(self, "wechatUserManagerialApp") and self.wechatUserManagerialApp:
  1100. return [str(self.id)]
  1101. manager = Manager.objects.get(id = self.managerId)
  1102. agent = Agent.objects.get(id = str(manager.primeAgentId))
  1103. return [str(agent.id), str(self.id)]
  1104. def is_my_product_user(self, user):
  1105. return str(self.id) == user.productAgentId
  1106. @property
  1107. def my_avatar(self):
  1108. if self.avatar:
  1109. return self.avatar
  1110. logo = self.product_logo
  1111. if not logo:
  1112. logo = ''
  1113. return logo
  1114. @property
  1115. def abnormal(self):
  1116. return self.status == self.Status.ABNORMAL
  1117. @property
  1118. def deviceIncomeShow(self):
  1119. """
  1120. 兼容之前数据库字段的方式
  1121. """
  1122. if "deviceIncomeShow" in self.features:
  1123. return True
  1124. return getattr(self, '_deviceIncomeShow', False)
  1125. @deviceIncomeShow.setter
  1126. def deviceIncomeShow(self, value):
  1127. """
  1128. 兼容之前的数据库 防止报错
  1129. """
  1130. setattr(self, '_deviceIncomeShow', value)
  1131. class AgentIncomeReport(Searchable):
  1132. agentId = StringField(verbose_name = '代理商ID')
  1133. detail = StrictDictField(verbose_name = '详情', default = {})
  1134. source = StringField(verbose_name = '收入来源')
  1135. amount = MonetaryField(verbose_name = '数额', default = RMB(0))
  1136. mchid = StringField(verbose_name = '资金账号')
  1137. date = StringField(verbose_name = '日期', default = lambda: datetime.datetime.now().strftime(Const.DATE_FMT))
  1138. dateTimeAdded = DateTimeField(verbose_name = '生成时间', default = datetime.datetime.now)
  1139. meta = {"collection": "agent_income_reports", "db_alias": "report"}
  1140. def __repr__(self):
  1141. return '<AgentIncomeReport agentId=%s, source=%s, date=%s>' % (self.agentId, self.source, self.date)
  1142. @property
  1143. def title(self):
  1144. if self.source == AGENT_INCOME_SOURCE.DEALER_WITHDRAW_FEE:
  1145. return u'提现收益-经销商({}) 提现金额({})'.format(str(self.detail.get('name', '')), str(self.detail.get('withdrawAmount', '')))
  1146. elif self.source == AGENT_INCOME_SOURCE.DEALER_CARD_FEE:
  1147. return u'流量卡收益-经销商(%s) 充值(%s)' % (self.detail.get('name', ''), self.detail.get('sum_of_price', ''))
  1148. elif self.source == AGENT_INCOME_SOURCE.AD:
  1149. return u'广告收益-广告(%s) 设备(%s) 经销商(%s)' \
  1150. % (self.detail.get('adId', ''), self.detail.get('logicalCode', ''), self.detail.get('dealer', ''))
  1151. elif self.source == AGENT_INCOME_SOURCE.DEALER_DEVICE_FEE:
  1152. return u'设备收益-经销商(%s) 设备(%s) 地址(%s)' \
  1153. % (self.detail.get('name', ''), self.detail.get('logicalCode', ''), self.detail.get('groupName', ''))
  1154. elif self.source == AGENT_INCOME_SOURCE.INSURANCE:
  1155. return u'保险收益-经销商({}) 地址({})'.format(self.detail.get('name', ''), self.detail.get('groupName', ''))
  1156. @classmethod
  1157. def get_income_list(cls, **filters):
  1158. if 'endDate' in filters:
  1159. filters['date__lt'] = filters.pop('endDate')
  1160. return cls.objects(**filters).order_by('-createdTime')
  1161. class MoniApp(Searchable):
  1162. # 公众号的原始属性 其中appToken用于微信验证服务器 由我们自行设置
  1163. appName = StringField(verbose_name=u"公众号的名称", default="")
  1164. appid = StringField(verbose_name=u"appId", unique=True)
  1165. rawAppId = StringField(verbose_name=u"公众号的微信号", unique=True)
  1166. secret = StringField(verbose_name=u"秘钥", default="")
  1167. appToken = StringField(verbose_name="token", default="")
  1168. appType = StringField(verbose_name="公众号类型", default="wechat")
  1169. agentId = StringField(verbose_name=u"公众号所属的Agent")
  1170. priority = IntField(verbose_name="加粉的数量", default=0)
  1171. status = IntField(verbose_name="当前的状态", default=MoniAppStatus.ADDING, choices=MoniAppStatus.choices())
  1172. desc = StringField(verbose_name='展示描述', default = '')
  1173. title = StringField(verbose_name=u'展示加粉的说明title', default="")
  1174. # TODO 需要将agentId 以及status 设置为联合索引确保唯一性
  1175. meta = {"collection": "moni_app", "db_alias": "default"}
  1176. def __str__(self):
  1177. return "<{}>-<{}>".format(self.appName, self.rawAppId)
  1178. @property
  1179. def appId(self):
  1180. return self.appid
  1181. @property
  1182. def occupantId(self):
  1183. return self.agentId
  1184. @classmethod
  1185. def get_app_by_raw(cls, rawAppId):
  1186. try:
  1187. app = cls.objects.get(rawAppId=rawAppId)
  1188. except DoesNotExist:
  1189. return None
  1190. return app
  1191. @classmethod
  1192. def get_app_by_agent(cls, agentId):
  1193. """
  1194. 获取代理商设置的 moniApp
  1195. 找到加粉符合条件的最少的一个
  1196. """
  1197. agentId = str(agentId)
  1198. return cls.objects.filter(agentId=agentId, status=MoniAppStatus.ADDING).first()
  1199. @classmethod
  1200. def get_inhouse_app(cls):
  1201. return cls.get_app_by_agent(agentId=settings.MY_PRIMARY_AGENT_ID)
  1202. def to_dict(self):
  1203. return {
  1204. 'appid': self.appid,
  1205. 'secret': self.secret,
  1206. 'title': self.title,
  1207. 'appName': self.appName,
  1208. 'desc': self.desc
  1209. }
  1210. @classmethod
  1211. def subscribe(cls, app):
  1212. cls.objects.filter(appid=str(app.appid)).update(inc__priority=1)
  1213. @classmethod
  1214. def unSubscribe(cls, app):
  1215. cls.objects.filter(appid=str(app.appid)).update(dec__priority=1)
  1216. class MoniAppPoint(Searchable):
  1217. key = StringField(verbose_name = 'key', default = '')
  1218. name = StringField(verbose_name = 'name', default = '')
  1219. desc = StringField(verbose_name = 'desc', default = '')
  1220. meta = {"collection": "moni_app_point", "db_alias": "default"}