# -*- coding: utf-8 -*- # !/usr/bin/env python """ web.agent.models ~~~~~~~~~ """ import datetime import itertools import logging import urllib from collections import namedtuple import simplejson as json from bson.objectid import ObjectId from django.conf import settings from django.utils.module_loading import import_string from mongoengine import MapField, LazyReferenceField, IntField from mongoengine.errors import DoesNotExist from mongoengine.fields import (StringField, DictField, BooleanField, DateTimeField, EmbeddedDocumentField, ListField) from typing import Any, Dict, TYPE_CHECKING, Optional, cast from apilib.monetary import RMB, sum_rmb, Permillage, Percent from apps.web.agent.define import AgentConst, AGENT_INCOME_SOURCE, AGENT_INCOME_TYPE from apps.web.agent.errors import PrimaryAgentDoesNotExist from apps.web.common.models import WithdrawRecord, CapitalUser, Balance, WithdrawBankCard from apps.web.common.transaction import WITHDRAW_PAY_TYPE from apps.web.constant import Const, AppPlatformType, DEALER_CONSUMPTION_AGG_KIND, MoniAppStatus from apps.web.core import PayAppType, APP_KEY_DELIMITER, ROLE from apps.web.core.db import Searchable, MonetaryField, StrictDictField, PermillageField, PercentField from apps.web.core.exceptions import InvalidParameter, NoAgentFound, NoManagerFound from apps.web.core.messages.sms import agentWithdrawSMSProvider from apps.web.core.models import WechatManagerApp, WechatAuthApp, BoundOpenInfo, AliApp, WechatPayApp, \ WechatUserManagerApp, WithdrawEntity, WechatUserSubscribeManagerApp, WechatDealerSubscribeManagerApp from apps.web.core.payment import WithdrawGateway from apps.web.device.models import DeviceType from apps.web.exceptions import UserServerException from apps.web.management.models import Manager logger = logging.getLogger(__name__) if TYPE_CHECKING: from pymongo.results import UpdateResult from apps.web.common.transaction import WithdrawHandler from apps.web.core import PayAppBase AgentDisclaimer = namedtuple("AgentDisclaimer", ["content", "version"]) class Agent(CapitalUser): """ 代理商, 管理经销商 """ INCOME_SOURCE_LIST = AGENT_INCOME_SOURCE.choices() INCOME_SOURCE_TO_TYPE = AgentConst.MAP_SOURCE_TO_TYPE INCOME_TYPE_LIST = AGENT_INCOME_TYPE.choices() INCOME_TYPE_TO_FIELD = AgentConst.MAP_TYPE_TO_FIELD #: 默认的收入频道的分布情况 DEFAULT_INCOME_MAP = { AGENT_INCOME_SOURCE.AD: 0, AGENT_INCOME_SOURCE.DEALER_WITHDRAW_FEE: 0, AGENT_INCOME_SOURCE.DEALER_CARD_FEE: 0, AGENT_INCOME_SOURCE.DEALER_DEVICE_FEE: 0 } #: 默认管理平台微信公众号授权用户信息 DEFAULT_WECHAT_AUTH_USER_INFO = { 'avatar': '', 'sex': 0 } DEFAULT_PAY_TYPE = { ROLE.agent: { AppPlatformType.ALIPAY: PayAppType.ALIPAY, AppPlatformType.WECHAT: PayAppType.WECHAT, }, ROLE.dealer: { AppPlatformType.ALIPAY: PayAppType.ALIPAY, AppPlatformType.WECHAT: PayAppType.WECHAT, }, ROLE.myuser: { AppPlatformType.ALIPAY: PayAppType.ALIPAY, AppPlatformType.WECHAT: PayAppType.WECHAT, } } DEFAULT_CHECKPOINT = { 'gerenzhongxin': False, 'yue': False, 'baogaolaoban': False, 'fukuan': False } #: 收入相关 deviceBalance = MapField(field = EmbeddedDocumentField(Balance)) trafficBalance = MapField(field = EmbeddedDocumentField(Balance)) adBalance = MapField(field = EmbeddedDocumentField(Balance)) withdrawBalance = MapField(field = EmbeddedDocumentField(Balance)) apiQuotaBalance = MapField(field = EmbeddedDocumentField(Balance)) disableAdBalance = MapField(field = EmbeddedDocumentField(Balance)) incomeMap = DictField(verbose_name = '收入字典', default = DEFAULT_INCOME_MAP) aggregatedIncome = DictField(verbose_name = '累计收入,只增不减', default = DEFAULT_INCOME_MAP) #: 代理自定义自己的产品名称和logo productLogo = StringField(verbose_name = "产品logo", default = "") productName = StringField(verbose_name = "产品名称", default = "") #: 公众号相关 gzhServiceQrcodeUrl = StringField(verbose_name = "公众号二维码", default = "", max_length = 256) gzhServiceLinkUrl = StringField(verbose_name = "公众号链接二维码", default = "") forceFollowGzh = BooleanField(verbose_name = "是否强制关注公众号", default = False) forceFollowGzhForDealer = StringField(verbose_name = u'经销商的强制关注开关', default = 'free') # never:此代理商下的经销商,永远不准强制关注,free:经销商自由允许关注 title = StringField(verbose_name = u'公众号强制关注的时候,显示的title', default = u'为了充分保障您的支付权益,首次使用需要您关注服务公众号辅助使用设备。') desc = StringField(verbose_name = u'公众号的加粉描述', default = u'关注步骤:手指按在上面二维码上,弹出窗口后,点公众号关注后您再次扫描设备上二维码启动设备。') #: 用于客服的联系方式 serviceName = StringField(verbose_name = "客服名称", default = "", max_length = 32) servicePhone = StringField(verbose_name = "客服电话", default = "", max_length = 32) serviceQrcodeUrl = StringField(verbose_name = "二维码图片链接", default = "", max_length = 256) #: 特性列表 features = ListField(field = StringField(), verbose_name = u'支持的特性', default = []) customizedUserGzhAllowable = BooleanField(verbose_name = '用户自定义公众号', default = False) customizedUserSubGzhAllowable = BooleanField(verbose_name = '用户订阅通知自定义公众号', default = False) customizedDealerGzhAllowable = BooleanField(verbose_name = '经销商自定义公众号', default = False) customizedDealerSubGzhAllowable = BooleanField(verbose_name = '经销商订阅通知自定义公众号', default = False) managerId = StringField(verbose_name = "从属于哪个管理员", null = False) adShow = BooleanField(verbose_name = "广告收入显示与否的选项", default = False) # 厂商参与设备运营分成的商户比例 资金池模式 managerProfitShare = PercentField(verbose_name='代理商设备运营分成比例', default = Percent('0.00')) # 厂商参与设备运营分成的商户比例 暂不支持,但是可以先预留 managerMerProfitShare = PercentField(verbose_name='代理商设备运营分成比例 商户收款', default = Percent('0.00')) # 提现OPENID映射 payOpenIdMap = MapField(EmbeddedDocumentField(BoundOpenInfo)) #: 用于API接口 openAPI = BooleanField(verbose_name = "是否支持openAPI", default = False) agentSign = StringField(verbose_name = '唯一签名用于API调用,agent的签名,用于核对对方的签名') mySign = StringField(verbose_name = '唯一签名用于API调用,我们的签名,用于请求对方URl,对方鉴权') domain = StringField(verbose_name = '域名调用URL') wechatLoginAuthApp = EmbeddedDocumentField(verbose_name = '授权APP', document_type = WechatAuthApp) oldWechatLoginAuthApp = EmbeddedDocumentField(verbose_name = '老的授权APP', document_type = WechatAuthApp, default = None) wechatUserManagerialApp = EmbeddedDocumentField(verbose_name = '用户管理APP', document_type = WechatUserManagerApp) wechatDealerManagerialApp = EmbeddedDocumentField(verbose_name = '经销商管理APP', document_type = WechatManagerApp) wechatUserSubscribeManagerApp = EmbeddedDocumentField(verbose_name = '用户订阅通知APP', document_type = WechatUserSubscribeManagerApp) wechatDealerSubscribeManagerApp = EmbeddedDocumentField(verbose_name = '经销商订阅通知APP', document_type = WechatDealerSubscribeManagerApp) # 支付APP配置 customizedAlipayCashflowAllowable = BooleanField(verbose_name = '是否开启自主支付宝收款权限', default = False) payAppAli = LazyReferenceField(document_type = AliApp, default = None) customizedWechatCashflowAllowable = BooleanField(verbose_name = '是否开启微信自主收款权限', default = False) payAppWechat = LazyReferenceField(document_type = WechatPayApp, verbose_name = '微信支付APP(用于提现和支付)', default = None) featureToggles = DictField(verbose_name = '特性开关', default = {}) # 厂商给代理商的流量卡成本价. 提交经销商的时候,经销商的默认价格从这个继承,代理商也可以更改 annualTrafficCost = MonetaryField(verbose_name = '厂商给代理商的流量卡成本价', default = Const.PLATFORM_DEFAULT_TRAFFIC_COST) # 平台给厂商的流量卡成本. 添加厂商的时候设置, 添加代理商的时候从厂商配置继承. # 如果有更改,更改之前的代理商不会修改这个费用,如果修改需要直接修改 trafficCardCost = MonetaryField(verbose_name = '平台给厂商的流量卡成本价', default = Const.PLATFORM_DEFAULT_TRAFFIC_COST) # 厂商给代理商的提现费率. 代理商给经销商设置的提现费率必须大于该值. 配置资金池的代理商不受限制 withdrawFeeRatio = PermillageField(verbose_name = '厂商给代理商的提现费率下限', default = Const.PLATFORM_DEFAULT_WITHDRAW_FEE_RATIO, min_value = Const.MIN_DEALER_WITHDRAW_FEE_RATIO, max_value = Const.MAX_DEALER_WITHDRAW_FEE_RATIO) # 平台给厂商的提现费率. 厂商给代理商的提现费率下限必须大于该值. 这个是平台成本价. withdrawFeeRatioCost = PermillageField(verbose_name = '平台给厂商的提现费率', default = Const.PLATFORM_DEFAULT_WITHDRAW_FEE_RATIO, min_value = Const.MIN_DEALER_WITHDRAW_FEE_RATIO, max_value = Const.MAX_DEALER_WITHDRAW_FEE_RATIO) bannerList = ListField(field = DictField(), verbose_name = "banner列表", default = []) #: 大部分情况下,是不允许直接变更支付网关的 #: 手工给其转账的时候把该开关打开 isChangingPaymentGateway = BooleanField(default = False) payType = DictField(verbose_name = u'各应用支付方式', default = DEFAULT_PAY_TYPE) moniAppList = ListField(verbose_name = u'监督公众号的清单', default = []) moniAppCheckPointDict = DictField(verbose_name = u'是否需要弹出监督公众号的代理商检查点', default = DEFAULT_CHECKPOINT) maxPayLimit = IntField(verbose_name = u'最大充值金额限制', default = 500) cardWechatInfo = DictField(verbose_name = u'批量导入实体卡绑定内勤微信账号信息', default = {}) boundCardName = StringField(verbose_name = u'默认绑定卡主姓名', default = '') boundCardPhone = StringField(verbose_name = u'默认绑定卡主手机号', default = '') withdrawApps = MapField(EmbeddedDocumentField(document_type = WithdrawEntity), default = None) # 寻找 免责声明的 流程是 代理商--->主代理商--->系统代理商 needDisclaimer = BooleanField(verbose_name = u"代理商是否需要免责声明", default = True) disclaimer = StringField(verbose_name = u"代理商设置的用户的免责声明", default = "") disclaimer_version = StringField(verbose_name = u"当前免责声明的版本,声明更新的时候须将版本号更新", default = "v1.0.0") dealerBankWithdrawFee = BooleanField(verbose_name = u'经销商提现到银行卡的手续费,计算方式', default = False) meta = { 'indexes': [ { 'fields': ['username'], 'unique': True } ], "collection": "Agent", "db_alias": "default" } search_fields = ('username', 'nickname', 'openId', 'remarks') def __str__(self): return 'Agent'.format(str(self.id), self.username, self.nickname) @property def service_phone(self): return self.servicePhone def my_pay_type(self, role, gateway_type): if role in self.payType and gateway_type in self.payType[role]: return self.payType[role][gateway_type] else: return self.DEFAULT_PAY_TYPE[role][gateway_type] @property def my_wechat_pay_app(self): # type: ()->WechatPayApp if self.customizedWechatCashflowAllowable: if not self.payAppWechat: raise Exception(u'第三方支付配置错误(1001)') else: my_app = self.payAppWechat.fetch() # type: WechatPayApp if not my_app.valid: raise Exception(u'第三方支付配置错误(1002)') else: if (str(self.id) == settings.MY_PRIMARY_AGENT_ID) and (not my_app.inhouse): raise Exception(u'第三方支付配置错误(1003)') my_app.occupantId = str(self.id) my_app.occupant = self return my_app my_app = WechatPayApp.get_null_app() my_app.occupantId = str(self.id) my_app.occupant = self return my_app @property def my_ali_pay_app(self): # type: ()->AliApp if self.customizedAlipayCashflowAllowable: if not self.payAppAli: raise Exception(u'第三方支付配置错误(1001)') else: my_app = self.payAppAli.fetch() # type: AliApp if not my_app.valid: raise Exception(u'第三方支付配置错误(1002)') else: if (str(self.id) == settings.MY_PRIMARY_AGENT_ID) and (not my_app.inhouse): raise Exception(u'第三方支付配置错误(1003)') my_app.occupantId = str(self.id) my_app.occupant = self return my_app my_app = AliApp.get_null_app() my_app.occupantId = str(self.id) my_app.occupant = self return my_app def to_dict(self, shadow = False): rv = super(Agent, self).to_dict() rv.update({ 'id': str(self.id), 'productLogo': self.product_logo, 'productName': self.product_name, 'serviceName': self.serviceName, 'servicePhone': self.servicePhone, 'serviceQrcodeUrl': self.serviceQrcodeUrl, 'gzhServiceQrcodeUrl': self.gzhServiceQrcodeUrl, 'adShow': self.adShow, 'forceFollowGzh': self.forceFollowGzh, 'managerId': self.managerId, 'isPrimary': self.is_primary, 'featureList': self.feature_list, 'annualTrafficCost': self.annualTrafficCost, 'trafficCardCost': self.trafficCardCost, 'title': self.title, 'desc': self.desc, 'smsVendor': self.smsVendor, 'dealerBankWithdrawFee': self.dealerBankWithdrawFee, 'bankWithdrawFee': self.bankWithdrawFee }) return rv @classmethod def filter(cls, manager_id, search_key, page_index, page_size, moniAppId = None, shadow = False): query = {'managerId': manager_id} if manager_id is not None else {} if moniAppId: query.update({'moniAppList': moniAppId}) agents = cls.objects(**query).search(search_key).order_by('-dateTimeAdded') total = agents.count() dataList = [] for agent in agents.paginate(pageIndex = page_index, pageSize = page_size): # type: Agent deviceTypes = DeviceType.objects(agentId = str(agent.id)).all() if len(deviceTypes): devTypeList = [_.to_dict() for _ in deviceTypes] else: devTypeList = [] if Manager.objects(primeAgentId = str(agent.id)).first(): isPrimary = True else: isPrimary = False zhejiangDict = {} try: from apps.web.south_intf.zhejiang_fire import ZhejiangNorther obj = ZhejiangNorther.objects.get(tokenId = str(agent.id)) ip, port = obj.northPort.split(":") zhejiangDict = { 'loginUsername': obj.usernameFromHear, 'loginPassword': obj.passwordFromHear, 'mqUsername': obj.mqUser, 'mqPassword': obj.mqPassword, 'code': obj.serviceCodeFromNorth, "northIp": ip, "northPort": port } except Exception as e: logger.error("agent filter got an error = {}".format(e)) pass item = { 'id': str(agent.id), 'nickname': agent.nickname, 'username': '******' if shadow else agent.username, 'annualTrafficCost': agent.annualTrafficCost, 'trafficCardCost': agent.trafficCardCost, 'withdrawFeeRatio': float(agent.withdrawFeeRatio.amount), 'withdrawFeeRatioCost': float(agent.withdrawFeeRatioCost.amount), 'managerProfitShare': float(agent.managerProfitShare.amount), 'dealerTotal': agent.get_dealers().count(), 'dateTimeAdded': agent.dateTimeAdded, 'featureList': agent.feature_list, 'bannerImgList': agent.bannerList, 'customizedAlipayCashflowAllowable': agent.customizedAlipayCashflowAllowable, 'customizedWechatCashflowAllowable': agent.customizedWechatCashflowAllowable, 'customizedDealerGzhAllowable': agent.customizedDealerGzhAllowable, 'customizedDealerSubGzhAllowable': agent.customizedDealerSubGzhAllowable, 'customizedUserGzhAllowable': agent.customizedUserGzhAllowable, 'customizedUserSubGzhAllowable': agent.customizedUserSubGzhAllowable, 'isPrimary': isPrimary, 'deviceType': devTypeList, 'detail': { 'agentId': str(agent.id), 'managerId': agent.managerId, 'features': json.dumps(agent.feature_boolean_map) }, 'ZJFirePlatform': zhejiangDict, 'forceFollowGzh': 'yes' if agent.forceFollowGzh else 'no', 'forceFollowGzhForDealer': agent.forceFollowGzhForDealer, 'productName': agent.productName, 'maxPayLimit': agent.maxPayLimit } try: pay_app_ali = agent.my_ali_pay_app # type: Optional[AliApp] except Exception as e: pay_app_ali = None if pay_app_ali: # type item.update({'aliPayApp': pay_app_ali.to_dict()}) # type: AliApp else: item.update({'aliPayApp': AliApp.get_null_app().to_dict()}) try: pay_app_wechat = agent.my_wechat_pay_app except Exception as e: pay_app_wechat = None if pay_app_wechat: item.update({'wechatPayApp': pay_app_wechat.to_dict()}) else: item.update({'wechatPayApp': WechatPayApp.get_null_app().to_dict()}) dealer_manager_app = agent.wechatDealerManagerialApp or WechatManagerApp.get_null_app() # type: WechatManagerApp item.update({'dealer': dealer_manager_app.to_dict()}) user_manager_app = agent.wechatUserManagerialApp or WechatUserManagerApp.get_null_app() # type: WechatUserManagerApp item.update({'user': user_manager_app.to_dict()}) user_sub_manager_app = agent.wechatUserSubscribeManagerApp or WechatUserSubscribeManagerApp.get_null_app() # type: WechatUserSubscribeManagerApp item.update({'user_sub': user_sub_manager_app.to_dict()}) dealer_sub_manager_app = agent.wechatDealerSubscribeManagerApp or WechatDealerSubscribeManagerApp.get_null_app() # type: WechatDealerSubscribeManagerApp item.update({'dealer_sub': dealer_sub_manager_app.to_dict()}) # 监督号的配置 moniAppList = [] for appId in agent.moniAppList: try: moniApp = MoniApp.objects.get(appId = appId) except Exception, e: continue moniAppList.append({'appId': moniApp.appId, 'appName': moniApp.appName}) item.update({'moniApps': moniAppList}) pointList = [] for key, switch in agent.moniAppCheckPointDict.items(): try: point = MoniAppPoint.objects.get(key = key) except Exception, e: continue pointList.append({'name': point.name, 'key': key, 'switch': switch}) item.update({'pointDict': pointList}) if agent.is_primary: item.update({'isPrimary': True}) dataList.insert(0, item) else: item.update({'isPrimary': False}) dataList.append(item) return total, dataList @property def product_logo(self): if not self.productLogo: return self.primary_agent.productLogo else: return self.productLogo @property def product_name(self): if not self.productName: return self.primary_agent.productName else: return self.productName @staticmethod def get_agent(agentId): try: agent = Agent.objects.get(id = ObjectId(agentId)) # type: Agent return agent.to_dict() except DoesNotExist: logger.exception('can not find agent from db,id=%s' % agentId) return None def update(self, **kwargs): # type: (**Any)->int """ 每次更新的时候确保正在更改支付网关的选项设置为关闭 :param kwargs: :return: """ return super(Agent, self).update(**kwargs) @staticmethod def update_agent(agentId, valueDict): """TODO REFACTOR""" if not valueDict: return True try: agent = Agent.objects(id = agentId).get() updated = agent.update(**valueDict) assert updated, u'更新失败' except Exception as e: logger.exception('update agent error=%s' % e) return False return True @staticmethod def record_cookie(agentId, response): agent = Agent.get_agent(agentId) if agent is None: return response response.set_cookie(key = 'agentLogoUrl', value = agent['productLogo'], max_age = 24 * 3600 * 30, domain = settings.COOKIE_DOMAIN) pn = urllib.quote(agent['productName'].encode('utf-8')) response.set_cookie(key = 'agentBrandName', value = pn, max_age = 24 * 3600 * 30, domain = settings.COOKIE_DOMAIN) response.set_cookie(key = 'agentId', value = agentId, max_age = 24 * 3600 * 30, domain = settings.COOKIE_DOMAIN) return response def put_cookie(self, response): logo = self.product_logo if not logo: logo = '/app/img/logo.png' response.set_cookie(key = 'agentLogoUrl', value = logo, max_age = 24 * 3600 * 30, domain = settings.COOKIE_DOMAIN) productName = self.product_name response.set_cookie(key = 'agentBrandName', value = urllib.quote(productName.encode('utf-8')), max_age = 24 * 3600 * 30, domain = settings.COOKIE_DOMAIN) response.set_cookie(key = 'agentId', value = str(self.id), max_age = 24 * 3600 * 30, domain = settings.COOKIE_DOMAIN) return response def get_dealers(self): """ 获取旗下经销商 :return: """ Dealer = import_string('apps.web.dealer.models.Dealer') return Dealer.objects(agentId = str(self.id)) @property def primary_agent_id(self): return self.manager.primeAgentId @property def manager(self): # type:() -> Manager if not hasattr(self, '__manager__'): manager = Manager.objects(id=self.managerId).first() if not manager: raise NoManagerFound(manager_id=self.managerId) setattr(self, '__manager__', manager) return getattr(self, '__manager__') @property def primary_agent(self): # type:() -> Agent if not hasattr(self, '__my_prime_agent__'): primeAgent = Agent.objects(id = self.primary_agent_id).first() if not primeAgent: raise NoAgentFound(agent_id = self.primary_agent_id) setattr(self, '__my_prime_agent__', primeAgent) return getattr(self, '__my_prime_agent__') @property def is_primary(self): """ 不需要去多查询一次数据库 只需要找到manager即可 """ return self.is_equal(self.primary_agent) @property def is_in_domain(self): try: return self.manager.domain == settings.MY_DOMAIN except Exception as e: logger.error('get manager failure. error = %s; id = %s' % (str(e), str(self.id))) return False @classmethod def from_agent(cls, agent, app_type, **kwargs): param_key = '{app_type}_app'.format(app_type = app_type) attr_or_func = getattr(agent, param_key) if hasattr(attr_or_func, '__call__'): return attr_or_func(**kwargs) else: return attr_or_func @classmethod def factory(cls, **kwargs): factory_type = kwargs.pop('factory_type') if factory_type == 'app': app_type = kwargs.pop('app_type') return lambda agent: cls.from_agent(agent = agent, app_type = app_type, **kwargs) elif factory_type == 'withdraw_source_key': pay_app = kwargs.pop('pay_app') return lambda agent: getattr(agent, 'withdraw_source_key')(pay_app) else: raise InvalidParameter(u'参数错误') @staticmethod def get_inhouse_prime_agent(): # type: ()->Agent """ 为了方便识别,如果获取默认代理商失败,需要报错为默认代理商找不到 """ try: return Agent.objects(id = str(Agent.inhouse_prime_agent_id())).get() except DoesNotExist: raise PrimaryAgentDoesNotExist('failed to get default primary agent') @property def inhouse_prime_agent(self): if str(self.id) == str(self.inhouse_prime_agent_id()): return self else: return Agent.get_inhouse_prime_agent() @staticmethod def inhouse_prime_agent_id(): return str(settings.MY_PRIMARY_AGENT_ID) @property def customizedCashflowAllowable(self): """ 目前withdrawSourceKey是使用微信商户来标记. 所以资金池场景下仅 判断微信商户是否支持就可以。在资金池场景下, 必须配置微信支付 :return: """ return self.customizedWechatCashflowAllowable def wechat_env_pay_app(self, role = None, pay_app_type = None): # type: (Optional[None, str], Optional[None, str])->Optional[cast(PayAppBase)] assert not (role and pay_app_type), 'role and app_type must not have value in the same time.' if not self.customizedCashflowAllowable: if pay_app_type: _pay_app_type = pay_app_type _role = None else: custom = self.payType.get('custom', False) if custom: if role in self.payType and AppPlatformType.WECHAT in self.payType[role]: _pay_app_type = self.payType[role][AppPlatformType.WECHAT] _role = None else: _pay_app_type = None _role = role else: _pay_app_type = None _role = role primary_agent = self.primary_agent if self.is_equal(primary_agent): return self.inhouse_prime_agent.wechat_env_pay_app(_role, _pay_app_type) else: return primary_agent.wechat_env_pay_app(_role, _pay_app_type) if role: app_pay_type = self.my_pay_type(role, AppPlatformType.WECHAT) else: app_pay_type = pay_app_type if app_pay_type == PayAppType.WECHAT: app = self.my_wechat_pay_app # type: WechatPayApp if not app.enable or not app.valid: raise Exception(u'系统配置错误(第三方支付)') return app raise Exception(u'系统配置错误(第三方支付)') def alipay_env_pay_app(self, role = None, pay_app_type = None): assert not (role and pay_app_type), 'role and app_type must not have value in the same time.' if not self.customizedCashflowAllowable: # 如果代理商配置了需要的支付类型, 以代理商配置的为准 if pay_app_type: _pay_app_type = pay_app_type _role = None else: custom = self.payType.get('custom', False) if custom: if role in self.payType and AppPlatformType.ALIPAY in self.payType[role]: _pay_app_type = self.payType[role][AppPlatformType.ALIPAY] _role = None else: _pay_app_type = None _role = role else: _pay_app_type = None _role = role primary_agent = self.primary_agent if self.is_equal(primary_agent): return self.inhouse_prime_agent.alipay_env_pay_app(_role, _pay_app_type) else: return primary_agent.alipay_env_pay_app(_role, _pay_app_type) if role: app_pay_type = self.my_pay_type(role, AppPlatformType.ALIPAY) else: app_pay_type = pay_app_type if app_pay_type == PayAppType.ALIPAY: app = self.my_ali_pay_app # type: AliApp if not app.enable or not app.valid: raise Exception(u'系统配置错误(第三方支付)') return app raise Exception(u'系统配置错误(第三方支付)') def _check_wechat_withdraw(self): my_app = self.my_wechat_pay_app if not my_app.enable: raise Exception(u'系统配置错误(三方支付)') @property def my_wechat_withdraw_app(self): my_app = None if self.withdrawApps: source_key = self.withdraw_source_key() if source_key in self.withdrawApps: my_app = self.withdrawApps[source_key].wechat_app if not my_app: my_app = self.my_wechat_pay_app if (not my_app.valid) or (not my_app.enable): raise Exception(u'配置错误(第三方支付)') my_app.occupant = self my_app.occupantId = str(self.id) return my_app @property def wechat_withdraw_app(self): if not self.customizedCashflowAllowable: primary_agent = self.primary_agent if self.is_equal(primary_agent): return self.inhouse_prime_agent.wechat_withdraw_app else: return primary_agent.wechat_withdraw_app return self.my_wechat_withdraw_app def withdraw_source_key(self, pay_app = None): # type:(PayAppBase)->str if not pay_app: my_app = self.wechat_env_pay_app(pay_app_type = PayAppType.WECHAT) if (not my_app) or (not my_app.valid) or (not my_app.enable): raise Exception(u'配置错误(第三方支付)') else: agent = pay_app.occupant # type: Agent my_app = agent.my_wechat_pay_app # type: WechatPayApp if (not my_app) or (not my_app.valid) or (not my_app.enable): raise Exception(u'配置错误(第三方支付)') if hasattr(my_app, '__source_key__'): return APP_KEY_DELIMITER.join( [WithdrawGateway.LEDGER_PREFIX, my_app.pay_app_type, getattr(my_app, '__source_key__')]) else: raise AttributeError('no __source_key__ attribute') # if my_app.inhouse: # return settings.MY_PRIMARY_AGENT_WALLET_KEY # 所有平台inhouse资金池全部使用固定的资金池KEY # else: # return APP_KEY_DELIMITER.join( # [WithdrawGateway.LEDGER_PREFIX, my_app.pay_app_type, getattr(my_app, '__source_key__')]) @classmethod def _parse_source_key(cls, source_key): # type: (str)->tuple tokens = source_key.split(APP_KEY_DELIMITER) return True if tokens[0].startswith(WithdrawGateway.LEDGER_PREFIX) else False, tokens[1], tokens[2], tokens[3:] # if source_key == settings.MY_PRIMARY_AGENT_WALLET_KEY: # is_ledger, pay_app_type, occupant_id = True, PayAppType.WECHAT, settings.MY_PRIMARY_AGENT_WALLET_KEY # else: # tokens = source_key.split(APP_KEY_DELIMITER) # is_ledger, pay_app_type, occupant_id = True if tokens[0].startswith( # WithdrawGateway.LEDGER_PREFIX) else False, tokens[1], tokens[2] @property def disclaimerVersionFormat(self): return "{}_{}" @property def disclaimerVersion(self): verFormat = self.disclaimerVersionFormat return verFormat.format(str(self.id), self.disclaimer_version) @disclaimerVersion.setter def disclaimerVersion(self, value): try: self.update(disclaimer_version = value) except Exception as e: logger.error( "set disclaimer version error, agent is <{}>, version is <{}>, error is ".format(self, value, e)) raise e @property def disclaimerContent(self): return self.disclaimer @disclaimerContent.setter def disclaimerContent(self, value): try: self.update(disclaimer = value) except Exception as e: logger.error("set disclaimer content error, agent is <{}>. error is <{}>".format(self, e)) raise e @property def disclaimerAgent(self): """ 获取设置了免责声明的代理商 :return: """ if self.disclaimer: return self primeAgent = self.primary_agent if primeAgent.disclaimer: return primeAgent inhousePrimeAgent = self.inhouse_prime_agent return inhousePrimeAgent @classmethod def get_disclaimer(cls, agentId): """ 获取 代理商的 免责声明 :param agentId: :return: """ agent = cls.objects.get(id = agentId) if not agent.needDisclaimer: return AgentDisclaimer(content = "", version = agent.disclaimerVersion) agent = agent.disclaimerAgent content = agent.disclaimerContent disclaimer = AgentDisclaimer(content = content, version = agent.disclaimerVersion) return disclaimer @classmethod def withdraw_gateway_list(cls, source_key): is_ledger, pay_app_type, occupant_id, tokens = cls._parse_source_key(source_key) if not is_ledger: return is_ledger, None, { 'alipay': WithdrawGateway(AliApp.get_null_app()), 'wechat': WithdrawGateway(WechatPayApp.get_null_app()), 'wechatV3': WithdrawGateway(WechatPayApp.get_null_app()) } agent = cls.objects(id = occupant_id).first() if not agent: raise UserServerException(u'系统配置错误(第三方支付),请联系平台客服(1002)') if pay_app_type != PayAppType.WECHAT: raise UserServerException(u'系统配置错误(第三方支付),请联系平台客服(1001)') if not agent.withdrawApps: return is_ledger, agent, { 'alipay': WithdrawGateway(AliApp.get_null_app()), 'wechat': agent.my_wechat_pay_app.new_withdraw_gateway(gateway_version = 'v1'), 'wechatV3': agent.my_wechat_pay_app.new_withdraw_gateway(gateway_version = 'v3') } else: if source_key not in agent.withdrawApps: raise UserServerException(u'系统配置错误(第三方支付),请联系平台客服(1003)') withdraw_entity = agent.withdrawApps[source_key] # type: WithdrawEntity pay_rv = { 'alipay': WithdrawGateway(AliApp.get_null_app()) } if withdraw_entity.alipay_app: withdraw_entity.alipay_app.occupantId = str(agent.id) withdraw_entity.alipay_app.occupant = agent pay_rv['alipay'] = withdraw_entity.alipay_app.new_withdraw_gateway() # 微信提现必须配置, 接口可能不支持提现, 但是手工提现以微信为准 withdraw_entity.wechat_app.occupantId = str(agent.id) withdraw_entity.wechat_app.occupant = agent pay_rv.update({ 'wechat': withdraw_entity.wechat_app.new_withdraw_gateway(gateway_version = 'v1'), 'wechatV3': withdraw_entity.wechat_app.new_withdraw_gateway(gateway_version = 'v3') }) return is_ledger, agent, pay_rv @staticmethod def get_platform_wechat_manager_app(): agent = Agent.get_inhouse_prime_agent() return agent.platform_wechat_manager_app @property def platform_wechat_manager_app(self): agent = self.inhouse_prime_agent app = agent.wechatDealerManagerialApp if not app: raise Exception(u'公众号配置错误') app.occupantId = str(agent.id) return app @staticmethod def get_inhouse_wechat_user_manager_app(): agent = Agent.get_inhouse_prime_agent() return agent.inhouse_wechat_user_manager_app @property def inhouse_wechat_user_manager_app(self): agent = self.inhouse_prime_agent app = agent.wechatUserManagerialApp if not app: raise Exception(u'公众号配置错误') app.occupantId = str(agent.id) return app @property def wechat_auth_app(self): """ 标识用户只用平台的公众号对应APPID. 部分用户由于前期原因配置了自己的 该接口仅做兼容 :return: """ if self.wechatLoginAuthApp: app = self.wechatLoginAuthApp # type: WechatAuthApp app.occupantId = str(self.id) return app else: primary_agent = self.primary_agent if self.is_equal(primary_agent): return self.inhouse_prime_agent.wechat_auth_app else: return primary_agent.wechat_auth_app @property def wechat_user_manager_app(self): if self.customizedUserGzhAllowable: if self.wechatUserManagerialApp: app = self.wechatUserManagerialApp # type: WechatManagerApp app.occupantId = str(self.id) app.occupant = self return app else: raise Exception(u'系统配置错误(wechat_user_manager_app)') else: primary_agent = self.primary_agent if self.is_equal(primary_agent): inhouse_agent = self.inhouse_prime_agent if inhouse_agent.customizedUserGzhAllowable: return inhouse_agent.wechat_user_manager_app else: raise Exception(u'系统未配置(wechat_user_manager_app)') else: return primary_agent.wechat_user_manager_app @property def wechat_user_messager_app(self): if self.customizedUserGzhAllowable or self.customizedUserSubGzhAllowable: if self.customizedUserGzhAllowable: if self.wechatUserManagerialApp: app = self.wechatUserManagerialApp # type: WechatUserManagerApp app.occupantId = str(self.id) app.occupant = self return app else: raise Exception(u'系统配置错误(wechat_user_manager_app)') if self.customizedUserSubGzhAllowable: if self.wechatUserSubscribeManagerApp: app = self.wechatUserSubscribeManagerApp # type: WechatUserSubscribeManagerApp app.occupantId = str(self.id) app.occupant = self return app else: raise Exception(u'系统配置错误(wechat_user_subscribe_manager_app)') else: primary_agent = self.primary_agent if self.is_equal(primary_agent): inhouse_agent = self.inhouse_prime_agent if inhouse_agent.customizedUserGzhAllowable or inhouse_agent.customizedUserSubGzhAllowable: return inhouse_agent.wechat_user_messager_app else: raise Exception(u'系统未配置(wechat_user_manager_app|wechat_user_subscribe_manager_app)') else: return primary_agent.wechat_user_messager_app @property def wechat_manager_app(self): if self.customizedDealerGzhAllowable: if self.wechatDealerManagerialApp: app = self.wechatDealerManagerialApp # type: WechatManagerApp app.occupantId = str(self.id) return app else: raise Exception(u'系统配置错误(wechat_dealer_manager_app, %s)' % str(self.id)) else: primary_agent = self.primary_agent if self.is_equal(primary_agent): return self.inhouse_prime_agent.wechat_manager_app else: return primary_agent.wechat_manager_app @property def wechat_user_subscribe_manager_app(self): if self.customizedUserSubGzhAllowable: if self.wechatUserSubscribeManagerApp: app = self.wechatUserSubscribeManagerApp # type: WechatManagerApp app.occupantId = str(self.id) app.occupant = self return app else: raise Exception(u'系统配置错误(wechat_user_subscribe_manager_app)') else: primary_agent = self.primary_agent if self.is_equal(primary_agent): inhouse_agent = self.inhouse_prime_agent if inhouse_agent.customizedUserSubGzhAllowable: return inhouse_agent.wechat_user_subscribe_manager_app else: raise Exception(u'系统未配置(wechat_user_subscribe_manager_app)') else: return primary_agent.wechat_user_subscribe_manager_app @property def wechat_dealer_subscribe_manager_app(self): if self.customizedDealerSubGzhAllowable: if self.wechatDealerSubscribeManagerApp: app = self.wechatDealerSubscribeManagerApp # type: WechatManagerApp app.occupantId = str(self.id) app.occupant = self return app else: raise Exception(u'系统配置错误(wechat_user_manager_app)') else: primary_agent = self.primary_agent if self.is_equal(primary_agent): return self.inhouse_prime_agent.wechatDealerSubscribeManagerApp else: return primary_agent.wechatDealerSubscribeManagerApp def get_user_sub_template_id_list(self): if not self.customizedUserSubGzhAllowable: return [] if not self.wechatUserSubscribeManagerApp.templateIdMap: return [] else: try: return map(lambda _: _['templateId'], self.wechatUserSubscribeManagerApp.templateIdMap.values()) except Exception as e: logger.info('error e=<{}>'.format(e)) return [] def get_dealer_sub_template_id_list(self): if not self.customizedDealerSubGzhAllowable: return [] if not self.wechatUserSubscribeManagerApp.templateIdMap: return [] else: try: return map(lambda _: _['templateId'], self.wechatDealerSubscribeManagerApp.templateIdMap.values()) except Exception as e: logger.info('error e=<{}>'.format(e)) return [] @property def alipay_auth_app(self): if not self.customizedAlipayCashflowAllowable: primary_agent = self.primary_agent if self.is_equal(primary_agent): return self.inhouse_prime_agent.alipay_auth_app else: return primary_agent.alipay_auth_app app = self.my_ali_pay_app # type: AliApp logger.debug("app id is:{}".format(str(app.id))) if not app.valid: raise Exception(u'系统配置错误(第三方支付)') app.occupantId = str(self.id) return app def income_by_date(self, date, specific_source = None): """ :param date: :param specific_source: :return: """ reports = AgentIncomeReport.objects(agentId = str(self.id), date = date) if specific_source: reports = reports.filter(source = specific_source) return RMB(reports.sum('amount')) def today_income(self, specific_source = None): today = datetime.datetime.now().strftime(Const.DATE_FMT) return self.income_by_date(date = today, specific_source = specific_source) def yesterday_income(self, specific_source = None): yesterday = (datetime.datetime.now() - datetime.timedelta(days = 1)).strftime(Const.DATE_FMT) return self.income_by_date(date = yesterday, specific_source = specific_source) def aggregate_income(self, specific_source = None): """ 获取聚合收入 :param specific_source: 特定source :return: """ if not specific_source: return sum_rmb(self.aggregatedIncome.values()) else: return sum_rmb([v for k, v in self.aggregatedIncome.items() if k == specific_source]) def _single_month_income(self, year, month, specific_source): if not specific_source: reports = AgentIncomeReport.objects(agentId = str(self.id), date__startswith = '%d-%02d' % (year, month)) else: reports = AgentIncomeReport.objects(agentId = str(self.id), date__startswith = '%d-%02d' % (year, month), source = specific_source) return RMB(reports.sum('amount')) def current_month_income(self, specific_source = None): now = datetime.datetime.now() return self._single_month_income(now.year, now.month, specific_source) def monthly_income(self, specific_source = None): """ 获得月报表 :param specific_source: :return: """ now = datetime.datetime.now() years = {_ for _ in range(2017, now.year + 1)} months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] return {(year, month): self._single_month_income(year, month, specific_source) for year, month in itertools.product(years, months)} def is_equal(self, agent): return str(self.id) == str(agent.id) @property def supported_device_types(self): result = DeviceType.all(str(self.id)) if result: return result primary_agent = self.primary_agent if not self.is_equal(primary_agent): return primary_agent.supported_device_types else: return [] @property def feature_boolean_map(self): # type: ()->Dict[str, bool] features = super(Agent, self).feature_boolean_map if self.customizedWechatCashflowAllowable: features.update({ 'show_withdraw_management': True }) return features @property def hide_consume_kinds_dealer(self): rv = [] if 'hiddenUsedTime' in self.features: rv.append(DEALER_CONSUMPTION_AGG_KIND.DURATION) if 'hidden_coins' in self.features: rv.append(DEALER_CONSUMPTION_AGG_KIND.COIN) left_features = set(self.features) - {'hiddenUsedTime', 'hidden_coins'} for feature in left_features: if feature.startswith('hide_') and feature.endswith('_for_dealer'): kind = feature.replace('hide_', '').replace('_for_dealer', '') if kind in DEALER_CONSUMPTION_AGG_KIND.choices(): rv.append(kind) return rv @property def hide_consume_kinds_user(self): rv = [] if 'hiddenUsedTime' in self.features: rv.append(DEALER_CONSUMPTION_AGG_KIND.DURATION) if 'hidden_coins' in self.features: rv.append(DEALER_CONSUMPTION_AGG_KIND.COIN) left_features = set(self.features) - {'hiddenUsedTime', 'hidden_coins'} for feature in left_features: if feature.startswith('hide_') and feature.endswith('_for_user'): kind = feature.replace('hide_', '').replace('_for_user', '') if kind in DEALER_CONSUMPTION_AGG_KIND.choices(): rv.append(kind) return rv @property def withdraw_sms_provider(self): return agentWithdrawSMSProvider @property def withdraw_sms_phone_number(self): return str(self.username) def incr_income(self, source, source_key, money): # type: (str, str, RMB)->bool assert isinstance(money, RMB), 'money has to be a RMB instance' assert source in AGENT_INCOME_SOURCE.choices(), 'not support this source' assert source_key, 'source key must not be none' income_type = AgentConst.MAP_SOURCE_TO_TYPE[source] query = {'_id': ObjectId(self.id)} update = { '$inc': { 'aggregatedIncome.{source}'.format(source=source): money.mongo_amount, 'incomeMap.{source}'.format(source=source): money.mongo_amount, '{filed}.{key}.balance'.format(filed=AgentConst.MAP_TYPE_TO_FIELD[income_type], key=source_key): money.mongo_amount }, } result = self.get_collection().update_one(query, update, upsert=False) # type: UpdateResult return bool(result.modified_count == 1) def decr_income(self, source, source_key, money): # type:(str, str, RMB) -> bool """ 扣除代理商的收益 必须由调用方保证不会调用重入 """ return self.incr_income(source, source_key, -money) def check_withdraw_min_fee(self, income_type, pay_type, amount): if pay_type == WITHDRAW_PAY_TYPE.BANK: minimum = RMB(settings.WITHDRAW_MINIMUM) else: if income_type == AGENT_INCOME_TYPE.AD: minimum = RMB(settings.AD_WITHDRAW_MINIMUM) elif income_type == AGENT_INCOME_TYPE.DEALER_WITHDRAW_FEE: minimum = RMB(settings.SIM_INCOME_WITHDRAW_MINIMUM) else: minimum = RMB(settings.WITHDRAW_MINIMUM) if amount < minimum: raise ServiceException( {'result': 0, 'description': u"提现实际到账金额不能少于%s元" % (minimum,), 'payload': {}}) def new_withdraw_record(self, withdraw_gateway, pay_entity, source_key, income_type, amount, pay_type, manual, recurrent): # type: (WithdrawGateway, WithdrawBankCard, str, str, RMB, str, bool, bool) -> WithdrawRecord if income_type in [AGENT_INCOME_TYPE.DEALER_WITHDRAW_FEE, AGENT_INCOME_TYPE.AD]: withdraw_fee_ratio = Permillage('0.00') # type: Permillage else: withdraw_fee_ratio = Const.PLATFORM_DEFAULT_WITHDRAW_FEE_RATIO # type: Permillage service_fee = amount * withdraw_fee_ratio.as_ratio # type: RMB withdraw_agent = withdraw_gateway.occupant # type: Agent has_bank_fee = withdraw_agent.dealerBankWithdrawFee and \ self.bankWithdrawFee and \ pay_type == WITHDRAW_PAY_TYPE.BANK if has_bank_fee: bank_trans_fee = min(RMB('25.00'), max(RMB('0.10'), amount * Permillage('1').as_ratio)) else: bank_trans_fee = RMB('0.00') actual_pay = amount - service_fee - bank_trans_fee # type: RMB self.check_withdraw_min_fee(income_type, pay_type, actual_pay) return WithdrawRecord.create(self, withdraw_gateway = withdraw_gateway, pay_entity = pay_entity, source_key = source_key, income_type = income_type, pay_type = pay_type, fund_map = { 'amount': amount, 'serviceFee': service_fee, 'actualPay': actual_pay, 'bankTransFee': bank_trans_fee, 'withdrawFeeRatio': withdraw_fee_ratio, 'partition': [] }, manual = manual, recurrent = recurrent) def new_withdraw_handler(self, record): # type: (WithdrawRecord) -> WithdrawHandler from apps.web.agent.withdraw import AgentWithdrawHandler return AgentWithdrawHandler(self, record) def get_bound_pay_openid(self, key): # type: (str)->str pay_openid_map = self.payOpenIdMap # type: dict bound = pay_openid_map.get(key, BoundOpenInfo(openId = '')) # type: BoundOpenInfo return str(bound.openId) def set_bound_pay_openid(self, key, **payload): # type: (str, Dict)->None self.payOpenIdMap[key] = BoundOpenInfo(**payload) def get_online_moni_app(self): apps = MoniApp.objects.filter(appId__in = self.moniAppList, status=MoniAppStatus.ADDING).order_by('-priority') if apps.count() == 0: return {} app = apps.first() # type: MoniApp return { 'appId': app.appId, 'secret': app.secret, 'title': app.title, 'appName': app.appName, 'desc': app.desc } @property def agentIds(self): # 自身 有agentId 配置的有公众号的,返回自身的id, 没有配置公众号的, 找到厂商的祝代理商的id 以及自身的id返回 if hasattr(self, "wechatUserManagerialApp") and self.wechatUserManagerialApp: return [str(self.id)] manager = Manager.objects.get(id = self.managerId) agent = Agent.objects.get(id = str(manager.primeAgentId)) return [str(agent.id), str(self.id)] def is_my_product_user(self, user): return str(self.id) == user.productAgentId @property def my_avatar(self): if self.avatar: return self.avatar logo = self.product_logo if not logo: logo = '' return logo @property def abnormal(self): return self.status == self.Status.ABNORMAL @property def deviceIncomeShow(self): """ 兼容之前数据库字段的方式 """ if "deviceIncomeShow" in self.features: return True return getattr(self, '_deviceIncomeShow', False) @deviceIncomeShow.setter def deviceIncomeShow(self, value): """ 兼容之前的数据库 防止报错 """ setattr(self, '_deviceIncomeShow', value) class AgentIncomeReport(Searchable): agentId = StringField(verbose_name = '代理商ID') detail = StrictDictField(verbose_name = '详情', default = {}) source = StringField(verbose_name = '收入来源') amount = MonetaryField(verbose_name = '数额', default = RMB(0)) mchid = StringField(verbose_name = '资金账号') date = StringField(verbose_name = '日期', default = lambda: datetime.datetime.now().strftime(Const.DATE_FMT)) dateTimeAdded = DateTimeField(verbose_name = '生成时间', default = datetime.datetime.now) meta = {"collection": "agent_income_reports", "db_alias": "report"} def __repr__(self): return '' % (self.agentId, self.source, self.date) @property def title(self): if self.source == AGENT_INCOME_SOURCE.DEALER_WITHDRAW_FEE: return u'提现收益-经销商({}) 提现金额({})'.format(str(self.detail.get('name', '')), str(self.detail.get('withdrawAmount', ''))) elif self.source == AGENT_INCOME_SOURCE.DEALER_CARD_FEE: return u'流量卡收益-经销商(%s) 充值(%s)' % (self.detail.get('name', ''), self.detail.get('sum_of_price', '')) elif self.source == AGENT_INCOME_SOURCE.AD: return u'广告收益-广告(%s) 设备(%s) 经销商(%s)' \ % (self.detail.get('adId', ''), self.detail.get('logicalCode', ''), self.detail.get('dealer', '')) elif self.source == AGENT_INCOME_SOURCE.DEALER_DEVICE_FEE: return u'设备收益-经销商(%s) 设备(%s) 地址(%s)' \ % (self.detail.get('name', ''), self.detail.get('logicalCode', ''), self.detail.get('groupName', '')) elif self.source == AGENT_INCOME_SOURCE.INSURANCE: return u'保险收益-经销商({}) 地址({})'.format(self.detail.get('name', ''), self.detail.get('groupName', '')) @classmethod def get_income_list(cls, **filters): if 'endDate' in filters: filters['date__lt'] = filters.pop('endDate') return cls.objects(**filters).order_by('-createdTime') class MoniApp(Searchable): # 公众号的原始属性 其中appToken用于微信验证服务器 由我们自行设置 appName = StringField(verbose_name=u"公众号的名称", default="") appid = StringField(verbose_name=u"appId", unique=True) rawAppId = StringField(verbose_name=u"公众号的微信号", unique=True) secret = StringField(verbose_name=u"秘钥", default="") appToken = StringField(verbose_name="token", default="") appType = StringField(verbose_name="公众号类型", default="wechat") agentId = StringField(verbose_name=u"公众号所属的Agent") priority = IntField(verbose_name="加粉的数量", default=0) status = IntField(verbose_name="当前的状态", default=MoniAppStatus.ADDING, choices=MoniAppStatus.choices()) desc = StringField(verbose_name='展示描述', default = '') title = StringField(verbose_name=u'展示加粉的说明title', default="") # TODO 需要将agentId 以及status 设置为联合索引确保唯一性 meta = {"collection": "moni_app", "db_alias": "default"} def __str__(self): return "<{}>-<{}>".format(self.appName, self.rawAppId) @property def appId(self): return self.appid @property def occupantId(self): return self.agentId @classmethod def get_app_by_raw(cls, rawAppId): try: app = cls.objects.get(rawAppId=rawAppId) except DoesNotExist: return None return app @classmethod def get_app_by_agent(cls, agentId): """ 获取代理商设置的 moniApp 找到加粉符合条件的最少的一个 """ agentId = str(agentId) return cls.objects.filter(agentId=agentId, status=MoniAppStatus.ADDING).first() @classmethod def get_inhouse_app(cls): return cls.get_app_by_agent(agentId=settings.MY_PRIMARY_AGENT_ID) def to_dict(self): return { 'appid': self.appid, 'secret': self.secret, 'title': self.title, 'appName': self.appName, 'desc': self.desc } @classmethod def subscribe(cls, app): cls.objects.filter(appid=str(app.appid)).update(inc__priority=1) @classmethod def unSubscribe(cls, app): cls.objects.filter(appid=str(app.appid)).update(dec__priority=1) class MoniAppPoint(Searchable): key = StringField(verbose_name = 'key', default = '') name = StringField(verbose_name = 'name', default = '') desc = StringField(verbose_name = 'desc', default = '') meta = {"collection": "moni_app_point", "db_alias": "default"}