# -*- coding: utf-8 -*- # !/usr/bin/env python """ web.dealer.models ~~~~~~~~~ """ import datetime import logging import uuid from operator import itemgetter from bson.objectid import ObjectId from django.conf import settings from django.core.cache import cache from mongoengine import EmbeddedDocument, StringField, BooleanField, EmbeddedDocumentField, MapField, DynamicDocument, \ DateTimeField, Document, IntField, ListField, DictField, FloatField, ObjectIdField, EmbeddedDocumentListField, \ LazyReferenceField, DynamicEmbeddedDocument from typing import Dict, AnyStr, Any, List, Optional, TYPE_CHECKING from apilib.monetary import RMB, Percent, sum_rmb, Permillage from apilib.systypes import IterConstant from apilib.utils_string import cn from apilib.utils_url import add_query from apps.web.agent.models import Agent, MoniAppPoint from apps.web.common.models import UserSearchable, MonthlyPackageTemp from apps.web.common.models import WithdrawRecord, CapitalUser, Balance from apps.web.common.transaction import OrderNoMaker, OrderMainType, DealerPaySubType, WITHDRAW_PAY_TYPE, RefundSubType from apps.web.constant import TYPE_ADJUST_USER_VIRTUAL_CARD, DEALER_CONSUMPTION_AGG_KIND, \ DEALER_CONSUMPTION_AGG_KIND_TRANSLATION, Const, APP_TYPE from apps.web.core import PayAppType, ROLE, APP_KEY_DELIMITER from apps.web.core.db import Searchable, MonetaryField, PercentField, PermillageField from apps.web.core.exceptions import UpdateError, ServiceException, InvalidParameter from apps.web.core.messages.sms import dealerWithdrawSMSProvider, dealerMonitorWithdrawSMSProvider from apps.web.core.models import BoundOpenInfo from apps.web.core.payment import WithdrawGateway from apps.web.dealer.constant import TodoDone, TodoTypeEnum, LinkageSwitchEnum from apps.web.dealer.define import DEALER_INCOME_SOURCE, DEALER_INCOME_TYPE, DealerConst, \ DEALER_INCOME_SOURCE_TRANSLATION from apps.web.device.models import Device, Group, GroupDict, DeviceType from apps.web.helpers import get_app, get_user_manager_agent from apps.web.utils import concat_dealer_access_entry_url, concat_front_end_url, concat_server_end_url if TYPE_CHECKING: from apps.web.common.transaction import WithdrawHandler from apps.web.core.payment import PaymentGateway from apps.web.core import PayAppBase logger = logging.getLogger(__name__) class DealerCacheMgr(): pass class DealerAddr(EmbeddedDocument): id = StringField(verbose_name = 'id', default = str(uuid.uuid4())) default = BooleanField(verbose_name = u'是否默认地址', default = False) name = StringField(verbose_name = 'name', default = '') tel = StringField(verbose_name = 'telno', default = '') addr = StringField(verbose_name = 'addr', default = '') class ApiAppInfo(DynamicDocument): people = StringField(verbose_name=u"联系人", default='') tel = StringField(verbose_name=u"联系电话", default='') callbackUrl = StringField(verbose_name=u"回调地址Url", default='') apiDeviceMax = IntField(verbose_name=u'API最大接入数量', default=0) apiDevicePerCost = MonetaryField(verbose_name=u'API配额单价', default=RMB(50)) meta = { 'collection': 'api_app_info', 'db_alias': 'default' } class DisableAdPlan(DynamicDocument): cycle = IntField(verbose_name=u'单次购买周期(单位:月)', default=12) disableAdCost = MonetaryField(verbose_name=u'纯净版单价', default=RMB(20)) meta = { 'collection': 'disable_ad_plan', 'db_alias': 'default' } class ConfigTemplate(EmbeddedDocument): rechargeDiscount = ListField(verbose_name=u'优惠充值模板', default=[]) cardRechargeDiscount = ListField(verbose_name=u'卡优惠充值模板', default=[]) class DealerLinkageSwitch(DynamicEmbeddedDocument): """ 经销商相关开关项 """ chargeInsurance = BooleanField(default=False, verbose_name=u"充电险开关") agentProxyServicePhone = BooleanField(verbose_name=u'代理商是否接管客服', default=False) def to_dict(self): return {"chargeInsurance": self.chargeInsurance, 'agentProxyServicePhone': self.agentProxyServicePhone} class Dealer(CapitalUser): """ 经销商 """ INCOME_SOURCE_LIST = DEALER_INCOME_SOURCE.choices() INCOME_SOURCE_TO_TYPE = DealerConst.MAP_SOURCE_TO_TYPE INCOME_TYPE_LIST = DEALER_INCOME_TYPE.choices() INCOME_TYPE_TO_FIELD = DealerConst.MAP_TYPE_TO_FIELD #: 默认管理平台微信公众号授权用户信息 DEFAULT_WECHAT_AUTH_USER_INFO = { 'avatar': '', 'sex': 0 } DEFAULT_CHECKPOINT = { 'gerenzhongxin': False, 'yue': False, 'baogaolaoban': False, 'fukuan': False } CACHE_KEY = 'dealer-info-{id}' CacheMgr = DealerCacheMgr wechat = StringField(verbose_name = "微信", default = "") description = StringField(verbose_name = "描述", default = "") phone = StringField(verbose_name = "电话", default = "") # 相当于 经销商的资金池 deviceBalance = MapField(EmbeddedDocumentField(document_type=Balance)) # 经销商获取的分账收益 在消费订单结束的时候进行记录 ledgerBalance = MapField(EmbeddedDocumentField(document_type=Balance)) adBalance = MapField(EmbeddedDocumentField(document_type=Balance)) payOpenIdMap = MapField(EmbeddedDocumentField(BoundOpenInfo)) isRead = StringField(verbose_name = "是否已经读了消息", default = "N") noPassReason = StringField(verbose_name = "未通过原因", default = "") cibMerchant = BooleanField(verbose_name = "是否立即生效", default = False) # 推送消息 managerialAppId = StringField(verbose_name = "auth,notify app id", default = "") managerialOpenId = StringField(verbose_name = "后台,接受推送消息", default = "") wechatAuthUserInfo = DictField(verbose_name = "经销商授权后获取的用户信息", default = DEFAULT_WECHAT_AUTH_USER_INFO) managerialWechatBoundTime = DateTimeField(verbose_name = "绑定微信的时间") agentId = StringField(verbose_name = "从属于哪个代理商", null = False) # 上级代理商设置的经营分成比例策略 agentProfitShare = PercentField(verbose_name='代理商设备运营分成比例', default = Percent('0.00')) agentMerProfitShare = PercentField(verbose_name='代理商设备运营分成比例 商户收款', default = Percent('0.00')) #: 默认选项|开关 #: 选项 adShow = BooleanField(verbose_name = "广告显示与否的选项", default = True) noAdPolicy = StringField(verbose_name = "不打开广告情况下的显示策略(notshow,noshow,banner)", default = 'noshow') beforeCharge = BooleanField(verbose_name = "使用前必须充值开关", default = False) noRecharge = BooleanField(verbose_name = "不需要充值功能", default = False) defaultWashConfig = DictField(verbose_name = "与经销商绑定的washconfig", default = {}) #: 通知选项 withdrawlNotify = BooleanField(verbose_name = "提现通知", default = False) offlineNotifySwitch = BooleanField(verbose_name = "设备离线通知开关", default = False) offlineNotifyTime = StringField(verbose_name = '设备离线通知时间', default = '') dailyIncomeReportPushSwitch = BooleanField(verbose_name = '是否开启次日9点推送昨日收益报表', default = False) newUserPaymentOrderPushSwitch = BooleanField(verbose_name = '是否开启', default = False) devFaultPushDealerSwitch = BooleanField(verbose_name = '设备故障通知开关,是否推送给经销商', default = False) devFaultPushUserSwitch = BooleanField(verbose_name = '设备故障通知开关,是否推送给用户', default = False) #: 用于客服的联系方式 serviceName = StringField(verbose_name = "客服名称", default = "", max_length = 32) servicePhone = StringField(verbose_name = "客服电话", default = "", max_length = 32) qrcodeUrl = StringField(verbose_name = "二维码图片链接", default = "", max_length = 256) # 提现费率. 由代理商设置 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 ) # 流量卡年费 annualTrafficCost = MonetaryField(default = Const.PLATFORM_DEFAULT_TRAFFIC_COST, verbose_name = '流量卡年费') # 该值由平台调整. 可以单独调整某些经销商的成本价格和卡年费 trafficCardCost = MonetaryField(verbose_name = u'平台提供给经销商的流量卡成本价', default = None) limitAdLocation = BooleanField(default = False, verbose_name = u'是否限制广告领取的地域') features = ListField(verbose_name = u'支持的特性', default = []) defaultDiscountConfig = DictField(verbose_name = u"经销商默认优惠充值套餐", default = Const.DEFAULT_DISCOUNT_RULE) defaultCardDiscountConfig = DictField(verbose_name = u"经销商默认卡优惠充值套餐", default = Const.DEFAULT_DISCOUNT_RULE) supportedIncomeAggregate = ListField(verbose_name = u'支持的收入聚合', default = DEALER_INCOME_SOURCE.choices()) supportedConsumptionAggregate = ListField(verbose_name=u'支持的消费聚合', default=DEALER_CONSUMPTION_AGG_KIND.choices()) supportedConsumptionShow = ListField(verbose_name=u"用户端的消费信息显示", default=[]) bottomAd = DictField(verbose_name = u"底部banner", default = {}) pushBrokerUrl = StringField(verbose_name = u'经销商订阅的消息推送URL', default = settings.DEFAULT_DEALER_PUSH_BROKER_URL) limitDevNum = IntField(verbose_name = u'限制经销商最大设备数量', default = 9999) isPurePartner = BooleanField(verbose_name = u'是否为纯合伙人', default = False) userCount = IntField(verbose_name = u'总的用户数量', default = 0) moniAppCheckPointDict = DictField(verbose_name = u'是否需要弹出监督公众号的经销商检查点', default = DEFAULT_CHECKPOINT) forceFollowGzh = StringField(verbose_name=u'是否强制关注公众号', default='agent')#以代理商为准:'agent',强制关注:'yes',不允许强制关注:'no',‘永不关注’:‘never’,‘关不关注无所谓’:free devCount = IntField(verbose_name = u'设备数量,方便统计查询', default = 0) maxPackagePrice = MonetaryField(verbose_name = u"最大套餐价格", default = RMB(100.00)) expressAddrList = EmbeddedDocumentListField(verbose_name = u'经销商的收货地址', document_type = DealerAddr) showServicePhone = BooleanField(verbose_name = u'是否允许使用服务电话', default = False) isShowBanner = BooleanField(verbose_name = u'是否显示代理商banner', default = True) hasTempPackage = BooleanField(verbose_name = u'是否启用临时套餐', default = False) freeModeDisplayedIdle = BooleanField(verbose_name = u'免费地址下端口状态显示为空闲', default = False) domain = StringField(verbose_name = u'api推送地址url', default = '') monitorPhone = StringField(verbose_name = u"提现审批员的手机号", default = "", max_length = 32) currencyCoins = BooleanField(verbose_name = u'金币是否通用(deprecated)', default = True) currencyMode = StringField(verbose_name = u'金币通用模式(allYes, allNo, asGroup)', default = None) # 有种情况下比如同一个手机号在两个代理商下申请的都有经销商 商户其实只用申请一个就行了 bindMerSrc = StringField(verbose_name=u"共用的商户经销商ID") # 联动开关 linkageSwitch = EmbeddedDocumentField(verbose_name=u"开关集合", document_type=DealerLinkageSwitch, default=DealerLinkageSwitch) apiInfo = LazyReferenceField(verbose_name=u'api应用信息', document_type=ApiAppInfo, default=None) disableAdPlan = LazyReferenceField(verbose_name=u'纯净版信息', document_type=DisableAdPlan, default=None) # 配置模板集合 templateSet = EmbeddedDocumentField(verbose_name=u"开关集合", document_type=ConfigTemplate, default=ConfigTemplate) meta = { 'indexes': [ { 'fields': ['username', 'agentId'], 'unique': True }, ], "collection": "Dealer", "db_alias": "default" } search_fields = ('username', 'nickname', 'remarks') def __str__(self): return '{}'.format( self.__class__.__name__, str(self.id), self.username, self.nickname, self.agentId) @property def currency_mode(self): if not self.currencyMode: if self.currencyCoins: return 'allYes' else: return 'allNo' else: return self.currencyMode @property def bossId(self): return self.id @property def myBoss(self): return self # 覆写. 需要对ruleDict参数进行一次转换 def save(self, force_insert = False, validate = True, clean = True, write_concern = None, cascade = None, cascade_kwargs = None, _refs = None, save_condition = None, **kwargs): if 'defaultDiscountConfig' in kwargs: newRuleDict = {} for k, v in self.format_default_discount.items(): newRuleDict[k.replace('.', '-')] = v self.defaultDiscountConfig = newRuleDict if "defaultCardDiscountConfig" in kwargs: newCardRuleDict = {} for k, v in self.format_card_discount.items(): newCardRuleDict[k.replace('.', '-')] = v self.defaultCardDiscountConfig = newCardRuleDict return super(Dealer, self).save(**kwargs) def update(self, **kwargs): try: ruleDict = kwargs.pop('defaultDiscountConfig', None) if ruleDict is not None: newRuleDict = {} for k, v in ruleDict.items(): newRuleDict[k.replace('.', '-')] = v kwargs.update({'defaultDiscountConfig': newRuleDict}) cardRuleDict = kwargs.pop('defaultCardDiscountConfig', None) if cardRuleDict is not None: newCardRuleDict = {} for k, v in cardRuleDict.items(): newCardRuleDict[k.replace('.', '-')] = v kwargs.update({'defaultCardDiscountConfig': newCardRuleDict}) updated = super(Dealer, self).update(**kwargs) if not updated: raise UpdateError('failed to update dealer query kwargs(%s)' % (kwargs,)) else: return updated finally: self.invalid_cache(str(self.id)) @property def permissionList(self): return [] @property def virtualCardOnlyCardSwitch(self): """ 虚拟卡仅用于 实体卡的开关 TODO zjl 这个是临时加的一个开关,目的在于不修改前台 达到 虚拟卡只用于实体卡而不能微信扫码使用的目的 等虚拟卡这一块儿整改的时候直接将这个去掉 :return: """ return "virtualCardOnlyCardSwitch" in self.features @property def switches(self): return { 'adShow': self.adShow, 'beforeCharge': self.beforeCharge, 'noRecharge': self.noRecharge, 'withdrawlNotify': self.withdrawlNotify, 'offlineNotify': self.offlineNotifySwitch, 'dailyIncomeReportPushSwitch': self.dailyIncomeReportPushSwitch, 'newUserPaymentOrderPushSwitch': self.newUserPaymentOrderPushSwitch, 'devFaultPushDealerSwitch': self.devFaultPushDealerSwitch, 'devFaultPushUserSwitch': self.devFaultPushUserSwitch, 'virtualCardOnlyCardSwitch': self.virtualCardOnlyCardSwitch } def query_feature_by_list(self, queryList): # type:(list)->dict dealerFeatures = self.feature_boolean_map resultFeatures = {} agent = Agent.objects.get(id=self.agentId) # type: Agent agent_features = agent.feature_boolean_map for query in queryList: if query in dealerFeatures: resultFeatures[query] = dealerFeatures[query] else: if query in agent_features: resultFeatures[query] = agent_features[query] else: resultFeatures[query] = False return resultFeatures def get_feature(self, feature_name): return self.query_feature_by_list([feature_name]) def to_dict(self, shadow = False): rv = super(Dealer, self).to_dict(shadow = shadow) pointList = [] for key, switch in self.moniAppCheckPointDict.items(): try: point = MoniAppPoint.objects.get(key = key) except Exception as e: continue pointList.append({'name': point.name, 'key': key, 'switch': switch}) rv.update({ 'id': str(self.id), 'deviceBalance': self.sub_balance(DEALER_INCOME_TYPE.DEVICE_INCOME), 'adBalance': self.sub_balance(DEALER_INCOME_TYPE.AD_INCOME), "ledgerBalance": self.sub_balance(DEALER_INCOME_TYPE.LEDGER_CONSUME), 'balance': self.total_balance, 'wechat': self.wechat, 'description': self.description, 'managerialOpenId': self.managerialOpenId, 'managerialAppId': self.managerialAppId, 'agentId': self.agentId, 'bottomAd': self.bottomAd, 'featureList': self.feature_list, 'withdrawFeeRatio': self.withdrawFeeRatio, 'annualTrafficCost': self.annualTrafficCost, 'defaultDiscountConfig': self.defaultDiscountConfig, 'pushBrokerUrl': self.pushBrokerUrl, 'isPurePartner': self.isPurePartner, 'pointDict': pointList, 'forceFollowGzh': self.forceFollowGzh, 'smsVendor': self.smsVendor, 'hasTempPackage': self.hasTempPackage, 'freeModeDisplayedIdle': self.freeModeDisplayedIdle, 'offlineNotifySwitch': self.offlineNotifySwitch, 'offlineNotifyTime': self.offlineNotifyTime, 'currencyMode': self.currency_mode, 'supportedConsumptionShow': self.supportedConsumptionShow, }) if hasattr(self, 'displayTempPackage'): rv.update({"displayTempPackage": self.displayTempPackage}) rv.update(self.switches) return rv @classmethod def all(cls): return [dealer.to_dict() for dealer in cls.objects.all()] @staticmethod def cache_key(ownerId): # type:(Optional[ObjectId, str])->str return Dealer.CACHE_KEY.format(id = str(ownerId)) @staticmethod def get_dealer(ownerId): # type:(ObjectId)->Optional[DealerDict, None] if not ownerId: return None dealer = cache.get(Dealer.cache_key(ownerId)) # type: dict if dealer: return DealerDict(dealer) else: dealer = Dealer.objects(id = ownerId).first() # type: Dealer if dealer: value = dealer.to_dict() cache.set(Dealer.cache_key(ownerId), value) return DealerDict(value) else: logger.error('can not find dealer from db,id=%s' % ownerId) return None @staticmethod def invalid_cache(ownerId): # type:(Optional[ObjectId, str])->None cache.delete(Dealer.cache_key(ownerId)) @staticmethod def update_dealer(ownerId, **valueDict): # type:(ObjectId, **Any)->bool try: if 'noRecharge' in valueDict and valueDict['noRecharge']: valueDict.update({'beforeCharge': False}) elif 'beforeCharge' in valueDict and valueDict['beforeCharge']: valueDict.update({'noRecharge': False}) Dealer.objects(id = ObjectId(ownerId)).update(**valueDict) except Exception as e: logger.exception('update dealer info error=%s' % e) return False Dealer.invalid_cache(ownerId) return True @property def isManagerialOpenIdBound(self): return self.managerialOpenId != '' 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 bound.openId def set_bound_pay_openid(self, key, **payload): # type: (str, Dict)->None self.payOpenIdMap[key] = BoundOpenInfo(**payload) def get_bound_pay_info(self, key): pay_openid_map = self.payOpenIdMap bound = pay_openid_map.get(key, BoundOpenInfo(openId = '')) return bound.to_detail_dict() def set_bound_pay_info(self, key, **payload): bound = BoundOpenInfo(**payload) self.payOpenIdMap[key] = bound return def isAutoWithdrawOpenIdBound(self, key): pay_openid_map = self.payOpenIdMap # type: dict bound = pay_openid_map.get(key, BoundOpenInfo(openId = '')) return bound.openId != "" and bound.nickname != "" @property def format_default_discount(self): tmp_rule_dict = {} for k, v in self.defaultDiscountConfig.items(): tmp_rule_dict[k.replace('-', '.')] = v return tmp_rule_dict @property def format_card_discount(self): card_rule_dict = dict() for k, v in self.defaultCardDiscountConfig.items(): card_rule_dict[k.replace("-", ".")] = v return card_rule_dict @staticmethod def get_dealers(agentId): dealers = Dealer.get_collection().find({'agentId': agentId}) return [str(dealer['_id']) for dealer in dealers] @staticmethod def get_dealer_from_groupId(groupId): group = Group.get_group(groupId) if group is not None: dealer = Dealer.objects(id = group['ownerId']).first() else: group = Group.objects(id = groupId).first() dealer = Dealer.objects(id = group.ownerId).first() return dealer def get_own_devices(self): groupIds = Group.get_group_ids_of_dealer(str(self.id)) devNoList = Device.get_devNos_by_group(groupIds) return Device.get_dev_by_nos(devNoList).values() @property def income_aggregate_source(self): # type: ()->Dict[str, AnyStr] """ e.g. {'ad': u'广告'} :return: """ supports = [item for item in self.supportedIncomeAggregate if item != 'ad'] g = itemgetter(*supports) return dict(zip(supports, g(DEALER_INCOME_SOURCE_TRANSLATION))) @property def consumption_aggregate_source(self): # type: ()->Dict[str, AnyStr] """ e.g. {'elect': u'电量'} :return: """ g = itemgetter(*self.supportedConsumptionAggregate) return dict(zip(self.supportedConsumptionAggregate, g(DEALER_CONSUMPTION_AGG_KIND_TRANSLATION))) def set_agent_profit_share(self, share): # type: (Percent)->int """ :param share: :return: """ updated = self.update(agentProfitShare = share.mongo_amount) return updated def set_agent_mer_profit_share(self, share): # type: (Percent)->int """ :param share: :return: """ updated = self.update(agentMerProfitShare = share.mongo_amount) return updated @classmethod def set_traffic_costs_multiply(cls, dealer_ids, cost): # type: (List[ObjectId], RMB)->int updated = cls.get_collection().update_many({'_id': {'$in': dealer_ids}}, {'$set': {'annualTrafficCost': cost.mongo_amount}}, upsert = False) return updated @property def login_url(self): # type:()->str return concat_dealer_access_entry_url(agentId = self.agentId) @property def withdraw_sms_provider(self): if self.monitorPhone: return dealerMonitorWithdrawSMSProvider else: return dealerWithdrawSMSProvider @property def withdraw_sms_phone_number(self): if self.monitorPhone: return str(self.monitorPhone) else: return str(self.username) @property def auto_withdraw_bound_open_id(self): from apps.web.helpers import get_wechat_auth_bridge auth_bridge = get_wechat_auth_bridge(source = self, app_type = APP_TYPE.WECHAT_WITHDRAW) return self.get_bound_pay_openid(auth_bridge.bound_openid_key) @property def auto_withdraw_bank_account(self): return self.withdrawOptions.get('bankAccount', None) def record_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 DEALER_INCOME_SOURCE.choices(), 'not support this source' assert source_key, 'source key must not be none' income_type = DealerConst.MAP_SOURCE_TO_TYPE[source] return self.incr_fund(income_type, source_key, money) def new_withdraw_handler(self, record): # type: (WithdrawRecord) -> WithdrawHandler from apps.web.dealer.withdraw import DealerWithdrawHandler return DealerWithdrawHandler(self, record) 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 == DEALER_INCOME_TYPE.AD_INCOME: minimum = RMB(settings.AD_WITHDRAW_MINIMUM) else: minimum = RMB(settings.WITHDRAW_MINIMUM) if amount < minimum: raise ServiceException( {'result': 0, 'description': u"提现实际到账金额不能少于%s元" % (settings.WITHDRAW_MINIMUM,), 'payload': {}}) def new_withdraw_record(self, withdraw_gateway, pay_entity, source_key, income_type, amount, pay_type, manual, recurrent): """ 创建自动提现的单子 :param withdraw_gateway: 提现网关 WechatPaymentGateway :param pay_entity: 银行卡 WithdrawBankCard :param income_type: 收益类型 str :param amount: 提现金额(非实际到账) RMB :param pay_type: 提现的方式 str :param manual: 手动提现 Bool :param recurrent: 是否自动提现 Bool :return: WithdrawRecord """ agent = Agent.objects(id = self.agentId).first() # type: Agent if not agent: logger.error('agent is None, agentId={}'.format(self.agentId)) raise ServiceException( { 'result': 0, 'description': u'系统繁忙,请稍后再试', 'payload': {} }) if income_type == DEALER_INCOME_TYPE.AD_INCOME: agentFeeRation = Permillage('0') managerFeeRatio = Permillage('0') dealerFeeRatio = Permillage('0') else: agentFeeRation = agent.withdrawFeeRatio managerFeeRatio = agent.withdrawFeeRatioCost dealerFeeRatio = self.withdrawFeeRatio # 微信或者支付宝平台服务费 serviceFee = amount * (dealerFeeRatio.as_ratio) # 计算微信转银行卡手续费 has_bank_fee = False if pay_type == WITHDRAW_PAY_TYPE.BANK: if recurrent: has_bank_fee = self.auto_withdraw_bank_fee else: withdraw_agent = withdraw_gateway.occupant # type: Agent has_bank_fee = withdraw_agent.dealerBankWithdrawFee and self.bankWithdrawFee 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') actualPay = amount - serviceFee - bank_trans_fee self.check_withdraw_min_fee(income_type, pay_type, actualPay) if dealerFeeRatio > managerFeeRatio: earned = amount * ((dealerFeeRatio - managerFeeRatio).as_ratio) if earned < RMB(0): earned = RMB(0) elif earned > serviceFee: earned = serviceFee # 分给代理商 agentEarned = RMB(0) if dealerFeeRatio > agentFeeRation: agentEarned = amount * ((dealerFeeRatio - agentFeeRation).as_ratio) if agentEarned < RMB(0): agent_earned = RMB(0) elif agentEarned > earned: agentEarned = earned # 代理商分完如果还有 继续分给厂商 managerEarned = (earned - agentEarned) if managerEarned < RMB(0): managerEarned = RMB(0) else: agentEarned = RMB(0) managerEarned = RMB(0) partition = [ { 'role': ROLE.agent, 'id': self.agentId, 'cost': agentFeeRation.mongo_amount, 'earned': agentEarned.mongo_amount }, { 'role': ROLE.manager, 'id': str(agent.primary_agent.id), 'cost': managerFeeRatio.mongo_amount, 'earned': managerEarned.mongo_amount } ] 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': serviceFee, 'bankTransFee': bank_trans_fee, 'actualPay': actualPay, 'withdrawFeeRatio': dealerFeeRatio, 'partition': partition }, manual = manual, recurrent = recurrent ) def get_addr_list(self): result = [] for obj in self.expressAddrList: if not obj.default: result.append({'tel':obj.tel,'name':obj.name,'addr':obj.addr,'id':str(obj.id),'default':obj.default}) else: result.insert(0, {'tel':obj.tel,'name':obj.name,'addr':obj.addr,'id':str(obj.id),'default':obj.default}) return result @classmethod def get_auto_withdraw_dealers(cls): dealers = list() for _dealer in cls.objects.filter(withdrawOptions__autoWithdrawSwitch = True): # type: Dealer if "autoWithdraw" in _dealer.features: dealers.append(_dealer) return dealers @classmethod def get_income_balance_list(cls, dealer, incomeType, minNum=0): """ 计算经销商在 每个收益来源下面的可提现余额 以及 手续费 :param dealer: :param incomeType: :param minNum: 过滤金额 :return: list --> (sourceKey, amount) 来源 当前账户余额 """ assert incomeType in DEALER_INCOME_TYPE.choices(), 'not support this income type' balanceDict = getattr(dealer, DealerConst.MAP_TYPE_TO_FIELD[incomeType]) withdrawInfoList = list() for sourceKey, item in balanceDict.items(): if not WithdrawGateway.is_ledger(sourceKey): continue balance = item.balance if balance <= RMB(0): continue if balance < RMB(minNum): continue withdrawInfoList.append((sourceKey, balance)) return withdrawInfoList @classmethod def factory(cls, **kwargs): factory_type = kwargs.pop('factory_type') if factory_type == 'app': app_type = kwargs.pop('app_type') return lambda dealer: cls.from_dealer(dealer = dealer, app_type = app_type, **kwargs) else: raise InvalidParameter(u'参数错误') @classmethod def from_dealer(cls, dealer, app_type, **kwargs): appKey = "{}_app".format(app_type) attr_or_func = getattr(dealer, appKey) if callable(attr_or_func): return attr_or_func(**kwargs) else: return attr_or_func @property def my_avatar(self): if self.avatar: return self.avatar if self.isManagerialOpenIdBound and self.wechatAuthUserInfo: return self.wechatAuthUserInfo.get('avatar', '') return '' def withdraw_source_key(self, pay_app = None): # type:(PayAppBase)->str """ 经销商提现资金池KEY是虚拟的, 仅仅对应账号的资金统计值 :param pay_app: :return: """ return APP_KEY_DELIMITER.join( [WithdrawGateway.NO_LEDGER_PREFIX, pay_app.pay_app_type, getattr(pay_app, '__gateway_key__')]) @classmethod def get_cooperative_dealer_ids(cls, dealerId): rcds = Group.get_collection().find({'partnerList.id': dealerId}) dealer_ids = set([dealerId]) for rcd in rcds: # type: Group dealer_ids.add(rcd['ownerId']) return list(dealer_ids) @classmethod def get_cooperative_group_ids(cls, dealerId): gs = Group.get_collection().find({'partnerList.id': dealerId}) groupIds = [] for _ in gs: groupIds.append(str(_['_id'])) gsMe = Group.get_collection().find({'ownerId': dealerId}) for _ in gsMe: groupIds.append(str(_['_id'])) return groupIds @property def is_inhouse_wallet(self): app = get_app(source = self, app_type = APP_TYPE.WECHAT_ENV_PAY, role = ROLE.myuser) return getattr(app, 'inhouse', False) @property def defaultMonthlyPackage(self): """ 默认的包月套餐的列表 :return: """ return MonthlyPackageTemp.default_one(ownerId=str(self.id)) @property def monthlyPackage(self): """ 所有的包月套餐的列表 :return: """ return MonthlyPackageTemp.get_template_by_dealer(str(self.id)) def isJosEnable(self, clientEnv="wechat"): """ 判断该经销商是否能够使用京东的拉新活动 # 由于分账的原因 目前只能够走 使用我们自身资金池的用户 :param clientEnv: 客户端的环境 :return: """ return False def limit_filter_date(self, startTime, endTime): # type:(str, str)->(str, str) if not startTime: startTime = datetime.datetime.now().strftime('%Y-%m-%d') if not endTime: endTime = datetime.datetime.now().strftime('%Y-%m-%d') if str(self.id) == '5d132407003048494763a51b': # 港德的隐藏数据需求 if startTime < '2021-01-01': startTime = '2021-01-01' if endTime < '2021-01-01': startTime = '2021-01-01' endTime = '2020-12-31' else: if startTime > endTime: endTime = startTime return startTime, endTime @classmethod def limit_filter_year(cls, dealerId, year): if dealerId == '5d132407003048494763a51b': # 港德的隐藏数据需求 if int(year) < 2021: return None return year def get_currency_group_ids(self, group, groups = None): # type: (Optional[Group, GroupDict], List[Optional[Group, GroupDict]])->list dealer_dict = DealerDict(self.to_dict()) # type: DealerDict return dealer_dict.get_currency_group_ids(group, groups) @property def ad_show(self): if self.adShow: return 'show' else: return self.noAdPolicy @property def minProfitShare(self): """ 经销商 所有的地址中 经销商的最低的分成比例 :return: """ profitShare = Percent('100') for _group in Group.objects.filter(ownerId=str(self.id)): if not _group.partnerList: continue for _partner in _group.partnerList: _profitShare = Percent(_partner["percent"]) if _profitShare < profitShare: profitShare = _profitShare return profitShare def rent_freeze_balance(self, income_type, money, source_key, transaction_id): # type:(str, RMB, str, str)->bool """ 出租设备的冻结订单 :param income_type: :param money: :param source_key: :param transaction_id: :return: """ assert source_key, 'gateway is null' assert transaction_id, 'transaction id is null' orderType = "inHandRentOrderList" field = self.income_field_name(income_type = income_type) fund_key = self.fund_key(income_type = income_type, source_key = source_key) query = {'_id': self.id, '{}.transaction_id'.format(orderType): {'$ne': transaction_id}} update = { '$inc': { '{fund_key}.balance'.format(fund_key = fund_key): (-money).mongo_amount }, '$addToSet': { '{}'.format(orderType): { 'transaction_id': transaction_id, 'field': field, 'key': source_key, 'value': money.mongo_amount } } } result = self.get_collection().update_one(query, update, upsert = False) return bool(result.modified_count == 1) def rent_clear_frozen_balance(self, transaction_id): # type:(str)->bool """ 订单扣款完毕 清除冻结信息 :param transaction_id: :return: """ assert transaction_id, 'transaction id is null' orderType = "inHandRentOrderList" query = {'_id': self.id, '{}.transaction_id'.format(orderType): transaction_id} update = { '$pull': { '{}'.format(orderType): { 'transaction_id': transaction_id } } } result = self.get_collection().update_one(query, update, upsert = False) return bool(result.modified_count == 1) def rent_recover_frozen_balance(self, income_type, money, source_key, transaction_id): # type:(str, RMB, str, str)->bool """ 回滚冻结的支付订单余额 :param source_key: :param income_type: :param money: :param transaction_id: :return: """ assert source_key, 'gateway is null' assert transaction_id, 'transaction id is null' orderType = "inHandRentOrderList" fund_key = self.fund_key(income_type = income_type, source_key = source_key) query = {'_id': self.id, '{}.transaction_id'.format(orderType): transaction_id} update = { '$inc': { '{fund_key}.balance'.format(fund_key = fund_key): money.mongo_amount }, '$pull': { '{}'.format(orderType): { 'transaction_id': transaction_id } } } result = self.get_collection().update_one(query, update, upsert = False) return bool(result.modified_count == 1) def get_linkage_switch(self): """ 获取经销商的两级联动开关设置 """ return { LinkageSwitchEnum.CHARGE_INSURANCE: self.linkageSwitch.chargeInsurance } def turn_on(self, category): """ 打开保险的开关 """ # 打开保险前的前置检查 if category == LinkageSwitchEnum.CHARGE_INSURANCE: if not self.is_inhouse_wallet: return False self.linkageSwitch.chargeInsurance = True return bool(self.update(linkageSwitch__chargeInsurance=self.linkageSwitch.chargeInsurance)) def turn_off(self, category): """ 关掉保险的开关 """ if category == LinkageSwitchEnum.CHARGE_INSURANCE: self.linkageSwitch.chargeInsurance = False return bool(self.update(linkageSwitch__chargeInsurance=self.linkageSwitch.chargeInsurance)) def get_all_api_devs(self): devList = Device.get_devs_by_ownerId(str(self.id)) return filter(lambda _: _.isApi == True, devList) @property def api_quota_info(self): totalQuota = self.api_app.apiDeviceMax devs = self.get_all_api_devs() usedQuota = len(devs) remainingQuota = totalQuota - usedQuota if totalQuota - usedQuota > 0 else 0 return { 'totalQuota': totalQuota, 'usedQuota': usedQuota, 'remainingQuota': remainingQuota } @property def api_app(self): if self.apiInfo: return self.apiInfo.fetch() else: return ApiAppInfo() def update_api_app(self, **payload): if not self.apiInfo: self.apiInfo = ApiAppInfo().save() self.save() apiInfo = self.apiInfo.fetch() # type: ApiAppInfo apiInfo.update(**payload) @property def disable_ad_plan(self): if self.disableAdPlan: # type: DisableAdPlan return self.disableAdPlan.fetch() else: return DisableAdPlan() def update_disable_ad_plan(self, **payload): if not self.disableAdPlan: self.disableAdPlan = DisableAdPlan().save() self.save() disable_ad_plan = self.disableAdPlan.fetch() # type: DisableAdPlan disable_ad_plan.update(**payload) def disable_ad_quota_info(self): all_devs = Device.get_devs_by_ownerId(str(self.id)) now = datetime.datetime.now() configured_filter = lambda _: _.disableADExpireDate and _.disableADExpireDate > now expiring_soon_filter = lambda _: _.disableADExpireDate and (_.disableADExpireDate - now).total_seconds() <= 60 * 60 * 24 * 15 configured = len(filter(configured_filter, all_devs)) expiringSoon= len(filter(expiring_soon_filter, all_devs)) notConfigured = len(Device.get_devs_by_ownerId(str(self.id))) - configured return { 'configured': configured, 'expiringSoon': expiringSoon, 'notConfigured': notConfigured } def query_home_page_layout(self): menuDict = {} menu_features = [] for menu, default_value in Const.MAIN_MENU_LIST.iteritems(): if default_value: menu_features.append('hide_{}'.format(menu)) else: menu_features.append(menu) menuDict[menu] = default_value homeDataDict = {} home_data_features = ['show_ad_income', 'show_offline_coins'] for menu, default_value in Const.HOME_PAGE_DATA_LIST.iteritems(): homeDataDict[menu] = default_value queryResult = self.query_feature_by_list(menu_features + home_data_features) for k, v in queryResult.items(): if k in home_data_features: if k == 'show_ad_income': homeDataDict['today_ad_income'] = v elif k == 'show_offline_coins': homeDataDict['offline_coins'] = v else: if 'hide_' in k: if v: menu_name = k.replace('hide_', '') menuDict[menu_name] = False else: menu_name = k menuDict[menu_name] = v return menuDict, homeDataDict @property def service_phone(self): # 如果代理商接管客服电话,就显示代理商的服务电话 if self.linkageSwitch.agentProxyServicePhone is True: agent = Agent.objects(id=self.agentId).first() # type: Optional[Agent] if agent and agent.service_phone: return agent.service_phone elif self.servicePhone: return self.servicePhone else: return self.username @property def my_agent(self): # type:()->Agent if not hasattr(self, '__my_agent__'): my_agent = Agent.objects(id=self.agentId).first() setattr(self, '__my_agent__', my_agent) return getattr(self, '__my_agent__') @property def force_follow_gzh(self): if self.forceFollowGzh == 'agent': isNeedFollow = self.my_agent.forceFollowGzh else: isNeedFollow = True if self.forceFollowGzh == 'yes' else False return isNeedFollow @property def show_auth_window(self): showAuth = 0 if self.supports('not_show_auth_window') else 1 if showAuth == 0: return False else: return True @property def productAgent(self): if not hasattr(self, '__product_agent__'): agent = get_user_manager_agent(self) setattr(self, '__product_agent__', agent) return getattr(self, '__product_agent__') def freeze_ledger_balance(self, money, source_key, transaction_id): """ 消费订单分账前的冻结 """ income_type = DEALER_INCOME_TYPE.DEVICE_INCOME self._freeze_balance(income_type, money, source_key, transaction_id, "inhandLedger") def clear_ledger_balance(self, transaction_id): """ 分账记录收益完成 回滚 """ self._clear_frozen_balance(transaction_id, "inhandLedger") class DealerDict(dict): """ 经销商的缓存表示 """ def __repr__(self): return '' % (self.get('groupId', 'unknown'),) @property def v(self): return dict(self) @property def id(self): return self.get('id') @property def agentId(self): return self.get('agentId') @property def entity(self): if hasattr(self, '__entity__'): return getattr(self, '__entity__') dealer = Dealer.objects(id = self.id).first() if not dealer: raise Exception('no such dealer'.format(self.id)) else: setattr(self, '__entity__', dealer) return dealer @property def currency_mode(self): if 'currencyMode' not in self: Dealer.invalid_cache(self.id) dealer = Dealer.get_dealer(self.id) # type: DealerDict self.update(dealer.v) return self.get('currencyMode') @property def supportedConsumptionShow(self): """ 客户端的 消费消息显示开关 """ if 'supportedConsumptionShow' not in self: Dealer.invalid_cache(self.id) dealer = Dealer.get_dealer(self.id) self.update(dealer.v) return self["supportedConsumptionShow"] def is_currency(self, left_group, right_group): # type: (GroupDict, GroupDict)->bool if left_group.ownerId != right_group.ownerId: return False if self.currency_mode == 'allNo': return False if self.currency_mode == 'allYes': return True if left_group.currencyGroup and right_group.currencyGroup and ( left_group.currencyGroup == right_group.currencyGroup): return True else: return False def get_currency_group_ids(self, group, groups = None): # type: (Optional[Group, GroupDict], List[Optional[Group, GroupDict]])->list if self.currency_mode == 'allNo': return [] else: if not groups: groups = Group.get_groups_of_dealer(str(self.id)) if self.currency_mode == 'allYes': rv = [] for item in groups: # type: GroupDict if item.groupId == group.groupId: continue rv.append(item.groupId) return rv else: if not group.currencyGroup: return [] else: rv = [] for item in groups: # type: GroupDict if item.groupId == group.groupId: continue if item.currencyGroup == group.currencyGroup: rv.append(item.groupId) return rv @property def servicePhone(self): return self['servicePhone'] @property def my_agent(self): if '__my_agent__' not in self: my_agent = Agent.objects(id=self.agentId).first() self['__my_agent__'] = my_agent return self.get('__my_agent__') @property def username(self): return self['username'] @property def nickname(self): return self['nickname'] class UpscoreRecord(Document): logicalCode = StringField(verbose_name = "设备逻辑编号", default = '') devNo = StringField(verbose_name = "设备编号") devType = StringField(verbose_name = "设备类型", default = '') ownerId = StringField(verbose_name = "所有者", default = "") time = DateTimeField(verbose_name = "时间", default = datetime.datetime.now) score = IntField(verbose_name = "上分数量", default = 0) groupName = StringField(verbose_name = "上分分组名称", default = "") address = StringField(verbose_name = "上分设备地址", default = "") remark = StringField(verbose_name = "备注", default = "") package = DictField(verbose_name = "套餐", default = {}) type = StringField(verbose_name = '上分类型(套餐上分,远程充卡),默认为空,表示上分', default = '') meta = {"collection": "UpscoreRecord", "db_alias": "logdata", # "shard_key":("ownerId",) } @classmethod def get_collection(cls): return cls._get_collection() class AdjustUserVirtualCardRecord(Document): cardId = StringField(verbose_name = "虚拟卡ID") cardNo = StringField(verbose_name = "虚拟卡卡号") adjustDays = IntField(verbose_name = "调整天数", default = 0) beforeAdjust = DateTimeField(verbose_name = "调整前的过期时间") adjustQuota = ListField(verbose_name = "调整的额度", default = []) oldQuota = ListField(verbose_name = "调整之前的额度", default = []) operator = StringField(verbose_name = "操作人") dealerId = StringField(verbose_name = "卡的管理者") adjustType = StringField(verbose_name = "调整方式", choices = TYPE_ADJUST_USER_VIRTUAL_CARD.choices()) dateTimeAdded = DateTimeField(verbose_name = "添加时间", default = datetime.datetime.now) meta = {'collection': 'adjust_user_virtual_card_record', 'db_alias': 'logdata'} @classmethod def get_collection(cls): return cls._get_collection() class DealerRechargeRecord(Searchable): """ 经销商充值记录 """ class PayState(IterConstant): UnPaid = 'UnPaid' Paid = 'Paid' Failure = 'Failure' Fake = 'Fake' Cancel = 'Cancel' Quit = 'Quit' Close = 'Close' class ProductType(IterConstant): SimCard = 'WF4801' # 流量卡充值 ApiCost = 'WF48A' # API接入 DisableAd = 'WF48D' # 禁用广告接入 AutoSimCard = 'WF4802' # 流量卡自动充值 ManualSimCard = 'WF4803' # 流量卡线下充值 SimOrderVerify = 'WF5801' # 流量卡充值对账后重新分账 ProductDesc = { ProductType.SimCard: cn(u'流量卡充值'), ProductType.AutoSimCard: cn(u'流量卡自动充值'), ProductType.ManualSimCard: cn(u'流量卡线下充值'), ProductType.ApiCost: cn(u'API配额充值'), ProductType.DisableAd: cn(u'纯净计划'), ProductType.SimOrderVerify: cn(u'流量卡充值对账'), } MapSubType = { DealerPaySubType.AUTO_SIM_CARD: ProductType.AutoSimCard, DealerPaySubType.MANUAL_SIM_CARD: ProductType.ManualSimCard, DealerPaySubType.SIM_CARD: ProductType.SimCard, DealerPaySubType.API_COST: ProductType.ApiCost, DealerPaySubType.DISABLE_AD: ProductType.DisableAd, DealerPaySubType.SIM_ORDER_VERIFY: ProductType.SimOrderVerify } MY_MAIN_TYPE = OrderMainType.PAY orderNo = StringField(verbose_name = u"订单号", unique = True) wxOrderNo = StringField(verbose_name = u"渠道订单号. 聚合商户,是聚合平台商户订单号;直连商户则是微信或支付宝订单号", default = "") transactionId = StringField(verbose_name = u"微信或者支付宝订单号", default = "") product = StringField(verbose_name = u'产品名称', default = ProductType.SimCard) items = ListField(verbose_name = u"充值商品", null = False) name = StringField(verbose_name = u'商品名称', default = '') dealerId = StringField(verbose_name = u"经销商ID", null = False) nickname = StringField(verbose_name = u"昵称", default = '') totalFee = IntField(verbose_name = u"订单金额(以分为单位)", min_value = 0, null = False) settleInfo = DictField(verbose_name = u'结算信息', default = {}) status = StringField(verbose_name = u"充值结果", default = PayState.UnPaid) description = StringField(verbose_name = u"订单错误结果描述,一般为第三方错误码", default = "") createdTime = DateTimeField(default = datetime.datetime.now, verbose_name = u"生成时间") finishedTime = DateTimeField(default = None, verbose_name = u"支付完成时间") attachParas = DictField(verbose_name = u"消费模型信息", default = {}) gateway = StringField(verbose_name = u'支付网关类型', default = '') payAppType = StringField(verbose_name = u'支付应用类型', default = '') payGatewayKey = StringField(verbose_name = u'支付网关key', default = '') withdrawSourceKey = StringField(verbose_name = u'提现网关source key', default = '') subject = StringField(verbose_name = u'商品标题', default = '') extraInfo = DictField(verbose_name = u"订单本身模型信息", default = None) meta = {'collection': 'dealer_recharge_record', 'db_alias': 'default', 'indexes': [ { 'fields': ['dealerId', 'product'] }, 'orderNo', 'wxOrderNo', 'transactionId', # 'name' ]} search_fields = ('wxOrderNo', 'name') def __repr__(self): return '' % (str(self.id)) @classmethod def get_product(cls, sub_type): return { 'type': cls.MapSubType[sub_type], 'desc': cls.ProductDesc[cls.MapSubType[sub_type]] } @classmethod def issue(cls, sub_type, payment_gateway, user, identifier=None, **payload): # type: (str, PaymentGateway, CapitalUser, basestring, Dict)->DealerRechargeRecord payload.update({ 'product': cls.get_product(sub_type)['type'], 'gateway': payment_gateway.gateway_type, 'payAppType': payment_gateway.pay_app_type, 'payGatewayKey': payment_gateway.gateway_key, 'subject': cls.get_product(sub_type)['desc'], 'createdTime': datetime.datetime.now() }) payload.update({ 'withdrawSourceKey': payment_gateway.withdraw_source_key() }) if 'extraInfo' not in payload: payload['extraInfo'] = {} payload['status'] = cls.PayState.UnPaid identifier = identifier if identifier else str(user.id) if 'orderNo' not in payload: payload['orderNo'] = OrderNoMaker.make_order_no_32( identifier = identifier, main_type = cls.MY_MAIN_TYPE, sub_type = sub_type) record = cls(**payload) record.save() return record @classmethod def get_record(cls, order_no, dealer_id = None): # type: (str, str)->Optional[DealerRechargeRecord] # TODO: 分片需要带上片键 record = cls.objects(orderNo = order_no).first() return record def succeed(self, **kwargs): payload = { 'status': self.PayState.Paid } if kwargs: payload.update(kwargs) if 'finishedTime' not in payload: payload.update({'finishedTime': datetime.datetime.now()}) result = DealerRechargeRecord.get_collection().update_one( filter = {'_id': ObjectId(self.id), 'status': {'$nin': [self.PayState.Paid, self.PayState.Close, self.PayState.Cancel]}}, update = {'$set': payload}, upsert = False ) return result.matched_count == 1 def fail(self, **kwargs): self.update(status = self.PayState.Failure, finishedTime = datetime.datetime.now(), **kwargs) return self.reload() def cancel(self): result = DealerRechargeRecord.get_collection().update_one( filter = {'_id': ObjectId(self.id), 'status': self.PayState.UnPaid}, update = {'$set': {'status': self.PayState.Cancel, 'finishedTime': datetime.datetime.now()}}, upsert = False ) return result.matched_count == 1 def quit(self): self.status = self.PayState.Quit self.save() return self def close(self, **kwargs): payload = { 'status': self.PayState.Close } if kwargs: payload.update(kwargs) result = self.get_collection().update_one( filter = {'_id': ObjectId(self.id), 'status': {'$nin': [self.PayState.Paid, self.PayState.Close, self.PayState.Cancel]}}, update = {'$set': kwargs}, upsert = False ) return result.matched_count == 1 @property def is_success(self): return self.status == self.PayState.Paid @property def is_fail(self): return self.status == self.PayState.Failure @property def is_cancel(self): return self.status == self.PayState.Cancel @property def is_unpay(self): return self.status == self.PayState.UnPaid @property def is_close(self): return self.status == self.PayState.Close @property def sum_of_price(self): # type: ()->RMB return sum_rmb([RMB(_['price']) for _ in self.items]) def get_body(self): return self.ProductDesc.get(self.product) @property def fen_total_fee(self): return self.totalFee @property def my_gateway(self): return self.gateway @property def pay_app_type(self): return self.payAppType @property def pay_gateway_key(self): return self.payGatewayKey @property def withdraw_source_key(self): return self.withdrawSourceKey class RefundDealerRechargeRecord(RefundOrderBase): meta = { 'collection': 'refund_dealer_recharge_record', 'db_alias': 'default' } @classmethod def issue(cls, order, refundCash): # type:(DealerRechargeRecord, RMB)->RefundDealerRechargeRecord refund_order_no = OrderNoMaker.make_order_no_32( identifier = order.orderNo, main_type = OrderMainType.REFUND, sub_type = RefundSubType.REFUND) return cls( rechargeObjId = order.id, # refundSeq=next_seq, orderNo = refund_order_no, money = refundCash, status = cls.Status.CREATED, payAppType = order.pay_app_type).save() @property def pay_app_type(self): return self.payAppType @property def pay_sub_order(self): # type: ()->DealerRechargeRecord if not hasattr(self, '__pay_sub_order__'): pay_order = DealerRechargeRecord.objects(id = str(self.rechargeObjId)).first() setattr(self, '__pay_sub_order__', pay_order) return getattr(self, '__pay_sub_order__') @pay_sub_order.setter def pay_sub_order(self, order): setattr(self, '__pay_sub_order__', order) @classmethod def get_record(cls, **kwargs): return cls.objects(**kwargs).first() @property def notify_url(self): if self.pay_app_type in [PayAppType.WECHAT, PayAppType.WECHAT_MINI]: return REFUND_NOTIFY_URL.WECHAT_REFUND_BACK elif self.pay_app_type == PayAppType.JD_AGGR: return REFUND_NOTIFY_URL.JD_AGGRE_REFUND_BACK elif self.pay_app_type == PayAppType.JD_OPEN: return REFUND_NOTIFY_URL.JDOPEN_REFUND_BACK else: return None class OnSale(DynamicDocument): """ 优惠活动管理 """ dealerId = StringField(verbose_name = "经销商ID", null = False) name = StringField(verbose_name = '活动名称', default = '') showSite = IntField(verbose_name = "展现的位置", default = 0) # 展现的位置0,表示扫码后的套餐页面。 logicalCodeList = ListField(verbose_name = "需要推送的设备", null = False) onsaleType = StringField(verbose_name = '活动类型', default = '') detailDict = DictField(verbose_name = '活动详细数据', default = {}) desc = StringField(verbose_name = '描述', default = '') img = StringField(verbose_name = '创意图片', default = '') onClickUrl = StringField(verbose_name = '回调地址', default = '') startTime = DateTimeField(verbose_name = '起始时间', default = datetime.datetime.now) endTime = DateTimeField(verbose_name = '结束时间', default = datetime.datetime.now) status = StringField(verbose_name = '状态', default = 'start') #: 展现类型,onlyOne,点击后就不弹出。forever,表示即使点击过,还是一直弹出,web:表示是页面,不是图片 showType = StringField(verbose_name = '状态', default = 'onlyOne') onsaleTypeDict = { u'首次使用送金币': { 'onClickUrl': '/user/promotion/getCoins', 'desc': u'适用所有设备类型,用户首次扫码的时候,允许客户领金币进行体验,本次活动领过金币的用户,下次无法领取金币。', 'expression': "", 'img': '/app/img/marketing/first_give_coins.jpg', 'showType': 'onlyOne' }, u'首次免费按摩': { 'onClickUrl': '/user/promotion/getDuration', 'desc': u'适用按摩坐垫以及足疗机,用户首次扫码的时候,允许客户直接启动设备体验,本次活动体验过的用户,下次无法再体验。', 'expression': "dev['devType']['code'] in ['100110','100111','100112','100113','100119','100900']", 'img': '/app/img/marketing/first_free.jpg', 'showType': 'onlyOne' }, u'优惠充值大放送': { 'onClickUrl': '/user/onsaleRecharge', 'desc': u'适用所有设备类型,用户优惠充值,活动期间始终弹出此推广创意,即使用户已经充值优惠过了。(用户点击推广活动后,会跳转到对应设备的充值页面。需要提前在优惠设置中,把对应的地址下的充值套餐配置好)', 'expression': "", 'img': '/app/img/marketing/recharegeOnsale.jpg', 'showType': 'forever' }, u'手机实名免费用': { 'onClickUrl': '/user/inputMobile', 'desc': u'首次验证手机号后,可以免费使用设备', 'expression': "", 'img': '/app/img/marketing/inputMobile.jpg', 'showType': 'onlyOne-web' }, u'包年包月优惠多多': { 'onClickUrl': '/user/onsaleTicketList', 'desc': u'适用所有设备类型,用户优惠充值,活动期间始终弹出此推广创意,即使用户已经充值优惠过了。(用户点击推广活动后,会跳转到对应设备的充值页面。需要提前在优惠设置中,把对应的地址下的充值套餐配置好)', 'expression': "", 'img': '/app/img/marketing/recharegeOnsale.jpg', 'showType': 'forever' }, } def to_dict(self): return { 'id': str(self.id), 'name': self.name, 'showSite': self.showSite, 'logicalCodeList': self.logicalCodeList, 'onsaleType': self.onsaleType, 'onClickUrl': self.onClickUrl, 'desc': self.desc, 'detailDict': self.detailDict, 'img': self.img, 'startTime': self.startTime.strftime("%Y-%m-%d"), 'endTime': self.endTime.strftime("%Y-%m-%d"), 'status': self.status } # 优惠活动访问记录 class OnSaleRecord(DynamicDocument): onsaleId = StringField(verbose_name = "经销商ID", null = False) userId = StringField(verbose_name = "用户ID", null = False) nickName = StringField(verbose_name = "用户昵称", null = False) addedTime = DateTimeField(verbose_name = "访问时间", default = datetime.datetime.now) onsaleDetail = DictField(verbose_name = "活动详情数据", default = {}) meta = { "collection": "on_sale_record", "db_alias": "logdata", } #: 经销商创建卡卷 class VirtualCard(Searchable): cardName = StringField(verbose_name = "卡名称", default = "") ownerId = StringField(verbose_name = "卡的发布老板", default = "") groupIds = ListField(verbose_name = "可用使用卡的地址", default = []) # 空表示没有地址,*表示所有地址下可用 devTypeList = ListField(verbose_name = "支持的设备类型清单", default = []) # 存放设备类型的ID price = MonetaryField(verbose_name = "卡的售价", default = RMB('0.00')) createTime = DateTimeField(verbose_name = "创建时间", default = datetime.datetime.now) periodDays = FloatField(verbose_name = "卡的可用天数", default = 30.0) expiredTime = DateTimeField(verbose_name="卡卷售卖下架时间", default=lambda : datetime.datetime.now() + datetime.timedelta(days=365)) dayQuota = ListField(verbose_name = "日限额度", default = []) # {'unit':u'次','count':2} quota = ListField(verbose_name = "总的额度", default = []) userLimit = IntField(verbose_name = "用户数量限制", default = 10) userDesc = StringField(verbose_name = "用户侧的描述", default = "") dealerDesc = StringField(verbose_name = "经销商侧描述", default = "") status = IntField(verbose_name = "卡状态", default = 1) # -1:删除;0:停止发售;1:开始发售 # 作为内置参数,接口不暴露出来 使用的时候后台脚本修改, 修改脚本名称为 adjust_virtual_card_need_renew needRenewMax = IntField(verbose_name = "过期后可续卡天数", default=10) needRenewMin = IntField(verbose_name = "过期前可续卡天数", default=10) # 暂停发售之后是否可以继续续卡 renewIgnoreStatus = BooleanField(verbose_name = "暂停发售之后是否可以继续续卡", default = False) # 坤元虚拟卡特有:功率 power = IntField(verbose_name = "包月卡功率限制", default = 0) meta = { "collection": "VirtualCard", "db_alias": "default", } @property def is_expired(self): return self.expiredTime <= datetime.datetime.now() @property def description(self): """ 用于描述虚拟卡的信息 :return: """ try: qStr = u"额度:" for item in self.quota: qStr += "{} {}".format(item.get("count"), item.get("unit")) tStr = u"时间:{}天".format(self.periodDays) return "{}\t{}".format(qStr, tStr) except Exception: return None @classmethod def get_last(cls, ownerId): return cls.objects.filter(ownerId=ownerId).order_by("-id").first() @property def groups(self): if '*' in self.groupIds: groups = '*' else: groups = [{'groupName': grp['groupName'], 'address': grp['address'], 'groupId': grp['groupId']} for grp in Group.get_groups_by_group_ids(self.groupIds).values()] return groups @property def devTypes(self): devs = DeviceType.objects.filter(id__in=self.devTypeList).only("majorDeviceType", "name") devTypes = list() for _dev in devs: # type: DeviceType devTypes.append({"devTypeName": _dev.name, "majorDeviceType": _dev.majorDeviceType}) return devTypes def to_dict(self): return { "id": str(self.id), "cardId": str(self.id), "cardName": self.cardName, "price": self.price, "periodDays": self.periodDays, "createTime": self.createTime, "expiredTime": self.expiredTime, "userDesc": self.userDesc, "dealerDesc": self.dealerDesc, "status": self.status, 'isExpired': self.is_expired, 'power': self.power, "quota": self.quota, "dayQuota": self.dayQuota, "groups": self.groups, "devTypes": self.devTypes, 'userLimit': self.userLimit } class ElecPriceTemplate(Searchable): ownerId = StringField(verbose_name = "卡的发布老板", default = "") name = StringField(verbose_name = "卡的发布老板", default = "") priceList = ListField(verbose_name = "48分时价格", default = []) class ItemType(Searchable): ownerId = StringField(verbose_name = "ownerId", default = "") title = StringField(verbose_name = "标题", default = "") desc = StringField(verbose_name = "描述", default = "") picUrl = StringField(verbose_name = "图片", default = "") price = IntField(verbose_name = "价格,分", default = 1) class SubAccount(UserSearchable): """ 子账号表 """ agentId = StringField(verbose_name = "agentId", min_length = 1) phone = StringField(verbose_name = "电话", default = "") bossId = ObjectIdField(verbose_name = "主账号ID") permissionList = ListField(verbose_name = "菜单清单", default = []) meta = { 'collection': 'sub_account', 'db_alias': 'default', 'indexes': [ { 'fields': ['username', 'bossId'], 'unique': True }, { 'fields': ['username', 'agentId'], 'unique': True } ], } def __str__(self): return '{}'.format( self.__class__.__name__, str(self.id), self.username, self.nickname, self.agentId) @property def myBoss(self): # type:()->Dealer if hasattr(self, '_myBoss'): return getattr(self, '_myBoss') else: myBoss = Dealer.objects.get(id = self.bossId) setattr(self, '_myBoss', myBoss) return myBoss def query_feature_by_list(self, queryList): return self.myBoss.query_feature_by_list(queryList) def get_feature(self, feature_name): return self.myBoss.get_feature(feature_name) def query_home_page_layout(self): return self.myBoss.query_home_page_layout() def to_dict(self): value = self.myBoss.to_dict() value.update( { 'username': self.username, 'nickname': self.nickname, 'role': ROLE.subaccount } ) return value @property def switches(self): return self.myBoss.switches @property def isManagerialOpenIdBound(self): return self.myBoss.isManagerialOpenIdBound def get_bound_pay_openid(self, key): return self.myBoss.get_bound_pay_openid(key) @property def format_default_discount(self): return self.myBoss.format_default_discount def get_own_devices(self): groupIds = Group.get_group_ids_of_dealer(str(self.bossId)) devNoList = Device.get_devNos_by_group(groupIds) return Device.get_dev_by_nos(devNoList).values() @property def income_aggregate_source(self): # type: ()->Dict[str, AnyStr] """ e.g. {'ad': u'广告'} :return: """ g = itemgetter(*self.myBoss.supportedIncomeAggregate) return dict(zip(self.myBoss.supportedIncomeAggregate, g(DEALER_INCOME_SOURCE_TRANSLATION))) @property def consumption_aggregate_source(self): # type: ()->Dict[str, AnyStr] """ e.g. {'elect': u'电量'} :return: """ g = itemgetter(*self.myBoss.supportedConsumptionAggregate) return dict(zip(self.myBoss.supportedConsumptionAggregate, g(DEALER_CONSUMPTION_AGG_KIND_TRANSLATION))) def set_agent_profit_share(self, share): # type: (Percent)->int """ :param share: :return: """ updated = self.myBoss.update(agentProfitShare = share.mongo_amount) return updated @property def adShow(self): return self.myBoss.adShow @property def ad_show(self): return self.myBoss.ad_show @property def pushBrokerUrl(self): return self.myBoss.pushBrokerUrl @property def isPurePartner(self): return self.myBoss.isPurePartner @property def wechat(self): return self.myBoss.wechat @property def description(self): return self.myBoss.description @property def cardNo(self): return self.myBoss.cardNo @property def bankName(self): return self.myBoss.bankName @property def cardHolder(self): return self.myBoss.cardHolder @property def isRead(self): return self.myBoss.isRead @property def noPassReason(self): return self.myBoss.noPassReason @property def cibMerchant(self): return self.myBoss.cibMerchant @property def managerialAppId(self): return self.myBoss.managerialAppId @property def managerialOpenId(self): return self.myBoss.managerialOpenId @property def wechatAuthUserInfo(self): return self.myBoss.wechatAuthUserInfo @property def managerialWechatBoundTime(self): return self.myBoss.managerialWechatBoundTime @property def agentProfitShare(self): return self.myBoss.agentProfitShare @property def beforeCharge(self): return self.myBoss.beforeCharge @property def noRecharge(self): return self.myBoss.noRecharge @property def defaultWashConfig(self): return self.myBoss.defaultWashConfig @property def withdrawlNotify(self): return self.myBoss.withdrawlNotify @property def offlineNotify(self): return self.myBoss.offlineNotifySwitch @property def dailyIncomeReportPushSwitch(self): return self.myBoss.dailyIncomeReportPushSwitch @property def newUserPaymentOrderPushSwitch(self): return self.myBoss.newUserPaymentOrderPushSwitch @property def devFaultPushDealerSwitch(self): return self.myBoss.devFaultPushDealerSwitch @property def devFaultPushUserSwitch(self): return self.myBoss.devFaultPushUserSwitch @property def currency_mode(self): return self.myBoss.currency_mode @property def serviceName(self): return self.myBoss.serviceName @property def servicePhone(self): return self.myBoss.servicePhone @property def qrcodeUrl(self): return self.myBoss.qrcodeUrl @property def withdrawFeeRatio(self): return self.myBoss.withdrawFeeRatio @property def annualTrafficCost(self): return self.myBoss.annualTrafficCost @property def limitAdLocation(self): return self.myBoss.limitAdLocation @property def defaultDiscountConfig(self): return self.myBoss.defaultDiscountConfig @property def supportedIncomeAggregate(self): return self.myBoss.supportedIncomeAggregate @property def supportedConsumptionAggregate(self): return self.myBoss.supportedConsumptionAggregate @property def bottomAd(self): return self.myBoss.bottomAd @property def limitDevNum(self): return self.myBoss.limitDevNum @property def feature_boolean_map(self): return self.myBoss.feature_boolean_map @property def features(self): return self.myBoss.features @property def normal(self): my_boss = self.myBoss # type: Dealer if not my_boss.normal: return my_boss.normal else: return self.status == self.Status.NORMAL @property def forbidden(self): my_boss = self.myBoss # type: Dealer if my_boss.forbidden: return my_boss.forbidden else: return self.status == self.Status.FORBIDDEN @property def abnormal(self): my_boss = self.myBoss # type: Dealer if my_boss.abnormal: return my_boss.abnormal else: return self.status == self.Status.ABNORMAL @property def no_withdraw(self): return False @property def is_inhouse_wallet(self): return False def limit_filter_date(self, startTime, endTime): my_boss = self.myBoss # type: Dealer return my_boss.limit_filter_date(startTime, endTime) @property def service_phone(self): my_boss = self.myBoss # type: Dealer return my_boss.service_phone class Complaint(Searchable): openId = StringField(verbose_name = 'openId', default = '') logicalCode = StringField(verbose_name = 'logicalCode', default = '') groupName = StringField(verbose_name = 'groupName', default = '') orderNo = StringField(verbose_name = 'orderNo', default = '') handledStatus = StringField(verbose_name = 'status', default = 'init') # init/notice/warning/forbidden reason = StringField(verbose_name = 'reason', default = '') dateTimeAdded = DateTimeField(verbose_name = u'添加进来的时间', default = datetime.datetime.now) handledTime = DateTimeField(verbose_name = u'添加进来的时间', default = datetime.datetime.now) meta = {'collection': 'complaint', 'db_alias': 'logdata'} # 发送给经销商的消息 class DealerMessage(Searchable): ownerId = StringField(verbose_name = 'ownerId', default = '') fromUser = StringField(verbose_name = 'fromUser', default = '') messageType = StringField(verbose_name = 'messageType', default = '') title = StringField(verbose_name = 'title', default = '') desc = StringField(verbose_name = 'desc', default = '') read = BooleanField(verbose_name = 'status', default = False) dateTimeAdded = DateTimeField(verbose_name = u'添加时间', default = datetime.datetime.now) readTime = DateTimeField(verbose_name = u'添加时间', default = datetime.datetime.now) relatedInfo = DictField(verbose_name = u'关联信息,便于查找,比如投诉信息', default = {}) meta = {'collection': 'dealer_message', 'db_alias': 'logdata'} # 经销商的换货单 class ExchangeOrder(Searchable): factoryId = ObjectIdField(verbose_name = 'factoryId', default = '') agentId = ObjectIdField(verbose_name = 'agentId', default = '') ownerId = ObjectIdField(verbose_name = 'ownerId', default = '') dateTimeAdded = DateTimeField(verbose_name = 'dateTimeAdded', default = datetime.datetime.now()) dealerStatus = StringField(verbose_name = u'经销商侧状态', default = '') # 比如已提单、已发货等 partIds = ListField(verbose_name = u'换货单', default = []) pics = ListField(verbose_name = u'用户上传的图片', default = []) dealerWords = StringField(verbose_name = u'经销商留言', default = '') factoryAddr = DictField(verbose_name = u'厂家维修地址', default = {}) dealerOrderNo = StringField(verbose_name = u'经销商的快递单号', default = '') factoryStatus = StringField(verbose_name = u'厂家侧的状态', default = '') # 比如已发货、已同意换货等 factoryOrderNo = StringField(verbose_name = u'厂家的快递单号', default = '') dealerAddr = DictField(verbose_name = u'经销商地址', default = {}) factoryWords = StringField(verbose_name = u'厂家留言', default = '') more = StringField(verbose_name = u'备注', default = '') meta = {'collection': 'exchange_order', 'db_alias': 'default'} search_fields = ('dealerWords', 'factoryWords', 'factoryOrderNo', 'dealerOrderNo') def make_status_info(self): dealerInfo = '' factoryInfo = '' if self.dealerStatus == 'created': dealerInfo = u'经销商申请换板' elif self.dealerStatus == 'sended': dealerInfo = u'经销商已发货' if self.factoryStatus == 'agreed': factoryInfo = u'厂家同意换货' elif self.factoryStatus == 'disagreed': factoryInfo = u'厂家不同意' elif self.factoryStatus == 'sended': factoryInfo = u'售后中心已发货' elif self.factoryStatus == 'closed': factoryInfo = u'成功关闭' elif self.factoryStatus == '': factoryInfo = u'待确认' if dealerInfo and factoryInfo: return '%s,%s' % (dealerInfo, factoryInfo) elif dealerInfo: return dealerInfo else: return factoryInfo # 卡的上分记录表 class UpCardScoreRecord(Searchable): cardId = StringField(verbose_name = "cardId", default = '') cardNo = StringField(verbose_name = "cardNo", default = '') ownerId = StringField(verbose_name = "所有者", default = "") dateTimeAdded = DateTimeField(verbose_name = "时间", default = datetime.datetime.now) score = FloatField(verbose_name = "上分数量", default = 0.0) address = StringField(verbose_name = "绑定地址", default = "") remark = StringField(verbose_name = "备注", default = "") meta = {"collection": "UpCardScoreRecord", "db_alias": "logdata"} @classmethod def get_collection(cls): return cls._get_collection() class PermissionInfo(Searchable): # 权限模版 pid = StringField(verbose_name = 'pid', null = True, auto_incre = True) key = StringField(verbose_name = 'key', unique = True, null = False) value = BooleanField(verbose_name = 'value', default = False) name = StringField(verbose_name = '权限名称', default = '') desc = StringField(verbose_name = '权限详情', default = '') dateTimeAdded = DateTimeField(verbose_name = '添加时间', default = datetime.datetime.now) meta = { 'collection': 'permission_info', 'db_alias': 'default', } @classmethod def get_permissions(cls): def run(head): if head.count() == 0: return res = {} for item in head: value = run(all_Permission.filter(pid = str(item.id))) # 结果为None or dict res[item.key] = value or item.value return res all_Permission = cls.objects.all() head = all_Permission.filter(pid = None) return run(head) class PermissionRole(Searchable): # 成员表 AUTHORIZE_TYPE = ['dealer_to_dealer', 'dealer_to_subAccount'] roleName = StringField(verbose_name='角色名称', null=True, default='') operId = StringField(verbose_name='操作人ID') dealerId = StringField(verbose_name='经销商ID') subAccountId = StringField(verbose_name='子账号ID') permissionRuleId = StringField(verbose_name='权限配置') dateTimeAdded = DateTimeField(verbose_name='添加时间', default=datetime.datetime.now) lastModifiedTime = DateTimeField(verbose_name='最后一次操作时间', default=datetime.datetime.now) authorizeType = StringField(verbose_name='授权关系',choices = AUTHORIZE_TYPE) isActive = BooleanField(verbose_name='激活', default=False) meta = { 'collection': 'permission_role', 'db_alias': 'default', } @staticmethod def add_dealer_role(oper_id, dealerId): rule = PermissionRule.objects.create(ruleName=str(uuid.uuid4()), dealerId=dealerId, permissionDict=PermissionInfo.get_permissions()) models = { 'roleName': None, 'operId': oper_id, 'dealerId': dealerId, 'permissionRuleId': str(rule.id), 'authorizeType': 'dealer_to_dealer', } return PermissionRole.objects.create(**models) @staticmethod def add_subAccount_role(oper_id, dealerId): rule = PermissionRule.objects.create(ruleName=str(uuid.uuid4()), dealerId=dealerId, permissionDict=PermissionInfo.get_permissions()) models = { 'roleName': None, 'operId': oper_id, 'dealerId': dealerId, 'permissionRuleId': str(rule.id), 'authorizeType': 'dealer_to_subAccount', } return PermissionRole.objects.create(**models) def save(self, **kw): self.lastModifiedTime = datetime.datetime.now() return super(PermissionRole, self).save(**kw) @classmethod def get_role_permission(cls, dealerId, operId): cacheKey = '{}-{}'.format(dealerId, operId) permissionRule = cache.get(cacheKey) if not permissionRule: role = cls.objects.filter(dealerId=dealerId, operId=operId, isActive=True).first() permissionRule = {} if role: permissionRule = PermissionRule.objects.get(id=role.permissionRuleId).permissionDict cache.set(cacheKey, permissionRule) return permissionRule @classmethod def delete_role_permission(cls, operIds, dealerId): cls.objects.filter(operId__in=operIds, dealerId=dealerId).update(isActive=False) for operId in operIds: cacheKey = '{}-{}'.format(dealerId, operId) cache.delete(cacheKey) @classmethod def get_auth_to_dealer(cls, dealerId): """ 获取 本账号授权的经销商 :param dealerId: :return: """ return cls.objects.filter(dealerId=dealerId, authorizeType='dealer_to_dealer', isActive=True).values_list( 'operId') @classmethod def get_auth_to_sub(cls, dealerId): """ 获取 本账号授权的子账号 :param dealerId: :return: """ return cls.objects.filter(dealerId=dealerId, authorizeType='dealer_to_subAccount', isActive=True).values_list( 'operId') @classmethod def get_is_auth_list(cls, operId, authorizeType): """ :param operId: 操作人ID :param authorizeType: 授权类型 :return: """ return cls.objects.filter(operId=str(operId), authorizeType=authorizeType, isActive=True).values_list('dealerId') class PermissionRule(Searchable): # 权限配置表 ruleName = StringField(verbose_name = '配置名称') dealerId = StringField(verbose_name = '经销商') permissionDict = DictField(verbose_name = '权限内容') dateTimeAdded = DateTimeField(verbose_name = '添加时间', default = datetime.datetime.now) lastModifiedTime = DateTimeField(verbose_name = '最后一次操作时间', default = datetime.datetime.now) meta = { 'collection': 'permission_rule', 'db_alias': 'default', } def get_permissionDict(self): def update_permission(base_dict, update_dict): result = {} for key, value in base_dict.items(): if isinstance(value, dict): res = update_permission(value, update_dict[key]) result[key] = res else: # print key,value,update_dict if not isinstance(update_dict, dict): result[key] = update_dict else: if key in update_dict: result[key] = update_dict[key] else: result[key] = value return result return update_permission(PermissionInfo.get_permissions(), self.permissionDict) def save(self, **kw): self.lastModifiedTime = datetime.datetime.now() return super(PermissionRule, self).save(**kw) class TodoMessage(Searchable): title = StringField(verbose_name=u"标题") content = StringField(verbose_name=u"内容") type = IntField(verbose_name=u"待办的种类", choices=TodoTypeEnum.choices()) link = StringField(verbose_name=u"跳转地址") done = IntField(verbose_name=u"待办状态", choices=TodoDone.choices(), default=TodoDone.INIT) ownerId = StringField(verbose_name=u"经销商") expiredTime = DateTimeField(verbose_name=u"信息过期时间") dateTimeAdded = DateTimeField(verbose_name=u"信息添加的时间", default=datetime.datetime.now()) popOnlyOnce = BooleanField(verbose_name=u"登录后台只显示一次", default=True) def to_dict(self): return { "id": self.id, "title": self.title, 'content': self.content, "type": self.type, "link": self.link, "status": self.done, "ownerId": self.ownerId, 'popOnlyOnce': self.popOnlyOnce } def has_done(self): self.update(done=TodoDone.DONE) @property def checkModel(self): for _ in TodoTypeEnum: if _ == self.type: return _._load_todoCls() @classmethod def get_todo_message(cls, user, typeList = TodoTypeEnum.choices()): """ 获取所有未完成的任务 """ items = cls.objects.filter( ownerId = str(user.id), done__in = [TodoDone.INIT, TodoDone.ING], type__in = typeList, expiredTime__gt = datetime.datetime.now()) dataList = list() for _m in items: # type: TodoMessage checkModel = _m.checkModel # 进行一次任务检查 查看任务是否完成以及是否需要强制执行 hasDone, force = checkModel.check_has_done(_m) if hasDone: _m.has_done() continue data = _m.to_dict() data["force"] = force dataList.append(data) return dataList @classmethod def sim_expire_message(cls, ownerId, expireCount): params = { 'type': 'simCard', 'redirect': concat_front_end_url(uri = '/app/deviceCard.html') } link = add_query(concat_server_end_url(uri = '/dealer/wechat/entry'), params) msg = cls( id = "1", title = u"流量卡续费提醒", content = u"您当前有{}台设备流量卡需要续费,为了避免影响设备的正常运行,请您尽快续费(已经充值请忽略)".format(expireCount), type = TodoTypeEnum.SMS_TODO.code, link = link, ownerId = ownerId, done = int(TodoDone.ING), expiredTime = datetime.datetime.now() + datetime.timedelta(days = 365), popOnlyOnce = False ).to_dict() return msg