# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import logging import warnings from bson import ObjectId from django.conf import settings from django.contrib.auth.hashers import make_password, check_password from mongoengine import StringField, DateTimeField, DynamicDocument, ListField, IntField, BooleanField, DictField, \ ObjectIdField, EmbeddedDocument, EmbeddedDocumentListField from typing import Optional, List, Dict, TYPE_CHECKING from apilib.monetary import RMB, sum_rmb, AccuracyRMB from apilib.systypes import IterConstant from apilib.utils_datetime import to_datetime, get_zero_time from apilib.utils_json import json_dumps, json_loads from apilib.utils_sys import memcache_lock, ThreadLock from apps.web import district from apps.web.agent.define import AGENT_INCOME_TYPE from apps.web.common.transaction import WithdrawStatus, WITHDRAW_PAY_TYPE, WithdrawHandler, OrderNoMaker, OrderMainType from apps.web.constant import Const from apps.web.core import APP_KEY_DELIMITER, PayAppType, ROLE from apps.web.core.db import Searchable, StrictDictField, MonetaryField, RoleBaseDocument, PermillageField, \ AccuracyMoneyField, BooleanIntField, BaseDocument from apps.web.core.exceptions import ImproperlyConfigured from apps.web.core.models import WechatPayApp from apps.web.core.payment import WithdrawGateway from apps.web.dealer.define import DEALER_INCOME_TYPE from apps.web.utils import LimitAttemptsManager from library.misc import BankAPI if TYPE_CHECKING: from apps.web.core.messages.sms import WithdrawSMSProvider from pymongo.results import UpdateResult from apps.web.device.models import DeviceDict, GroupDict from apps.web.core import PayAppBase logger = logging.getLogger(__name__) class District(object): memDict = {} @staticmethod def load_in_mem(dict_, parentId = None): for value in dict_: if value.has_key('children'): District.memDict[value['value']] = {'text': value['text'], 'parentId': parentId} District.load_in_mem(value['children'], value['value']) else: District.memDict[value['value']] = {'text': value['text'], 'parentId': parentId} @staticmethod def get_district(districtId): if not District.memDict: District.load_in_mem(district.DISTRICT) textList = [] area = District.memDict.get(districtId, {}) while area.get('parentId'): textList.append(area.get('text', '')) area = District.memDict.get(area.get('parentId', {})) textList.append(area.get('text', '')) return ' '.join(textList[::-1]) @staticmethod def get_area(districtId): if not District.memDict: District.load_in_mem(district.DISTRICT) area = District.memDict.get(districtId, {}) return area.get('text') @staticmethod def Is_include(bigId, smallId): if not District.memDict: District.load_in_mem(district.DISTRICT) if bigId == smallId: return True parentId = District.memDict.get(smallId).get('parentId') while parentId is not None: if parentId == bigId: return True parentId = District.memDict.get(parentId).get('parentId') return False @staticmethod def get_subIds(districtId): if not District.memDict: District.load_in_mem(district.DISTRICT) result = [] for k, v in District.memDict.items(): if v['parentId'] == districtId: result.append(k) return result # DeviceWarningField = namedtuple("DeviceWarningField", "warningPart warningStatus warningTime warningDesc warningUart") # # # class DeviceWarning(DeviceWarningField): # # def to_cache_dict(self): # _key = self.warningPart # _value = { # "warningStatus": self.warningStatus, # "warningTime": self.warningTime, # "warningDesc": self.warningDesc, # "warningUart": self.warningUart # } # # return {_key: _value} # # @classmethod # def create(cls, **kwargs): # # return cls( # warningPart=kwargs["warningPart"], # warningStatus=kwargs["warningStatus"], # warningTime=kwargs["warningTime"], # warningDesc=kwargs.get("warningDesc", ""), # warningUart=kwargs.get("warningUart", "") # ) # # def to_dict(self): # return { # "index": self.warningPart if self.warningPart.isdigit() else 0, # "warningStatus": self.warningStatus, # "warningTime": self.warningTime, # "warningDesc": self.warningDesc # } # # @property # def isPart(self): # """ # 是否是部件告警信息 # :return: # """ # return self.warningPart.isdigit() # class FrontendLog(Searchable): """ 从前端收集到的日志 """ context = StringField(verbose_name = "上下文", default = "") cookies = StringField(verbose_name = "cookie", default = "") level = StringField(verbose_name = "日志级别") content = StringField(verbose_name = "日志内容", min_length = 1) path = StringField(verbose_name = "哪个页面") dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = '生成时间') search_fields = ('content', 'level', 'context', 'path') meta = {'collection': 'frontend_logs', 'db_alias': 'logdata'} class CardBank(DynamicDocument): warnings.warn("CardBank is deprecated", DeprecationWarning) CardType = StringField(verbose_name = '银行卡类名', unique = True, null = False) CardName = StringField(verbose_name = '银行卡名称', null = False) meta = {'collection': 'card_bank', 'db_alias': 'default'} buffer = {} buffer_lock = ThreadLock() @classmethod def get_collection(cls): return cls._get_collection() @staticmethod def acquire_lock(): CardBank.buffer_lock.acquire_lock() @staticmethod def release_lock(): CardBank.buffer_lock.release_lock() @staticmethod def try_load_and_acquire_lock(): CardBank.buffer_lock.acquire_lock() CardBank.load_in_mem() @staticmethod def reset_buffer(): try: CardBank.acquire_lock() CardBank.buffer = {} finally: CardBank.release_lock() @staticmethod def load_in_mem(): try: CardBank.acquire_lock() if CardBank.buffer: return banks = CardBank.objects.all() for bank in banks: CardBank.buffer[bank.CardType] = bank.CardName finally: CardBank.release_lock() @staticmethod def get(cardType): try: CardBank.try_load_and_acquire_lock() if cardType in CardBank.buffer: return CardBank.buffer[cardType] else: return None finally: CardBank.release_lock() @staticmethod def has(cardType): try: CardBank.try_load_and_acquire_lock() if cardType in CardBank.buffer: return True else: return False finally: CardBank.release_lock() # : 微信支持的银行列表 #: REF https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=24_4 class Banks(DynamicDocument): CARD_TYPE_MAP = { 'DC': "储蓄卡", 'CC': "信用卡", 'SCC': "准贷记卡", 'PC': "预付费卡" } bankCode = StringField(verbose_name = u'银行code', unique = True, null = False) bankName = StringField(verbose_name = u'银行名称', unique = True, null = False) sn = IntField(verbose_name = u'排序序号', default = 0) wechatBankCode = StringField(verbose_name = u'微信银行ID', default = '') patterns = ListField(verbose_name = u'匹配正则', default = []) meta = {'collection': 'banks', 'db_alias': 'default'} buffer = {} buffer_lock = ThreadLock() def to_dict(self): return { 'bankCode': self.bankCode, 'bankName': self.bankName, 'wechatBankCode': self.wechatBankCode, 'patterns': self.patterns } @classmethod def get_bank_info(cls, cardNo): cls.__load_in_mem() bank_info = BankAPI().get_bank_card_info(card_no = cardNo) if bank_info: bank_code = bank_info['bankCode'] card_type = bank_info['cardType'] if bank_info['bankCode'] in cls.buffer['code']: bank_name = cls.buffer['code'][bank_code]['bankName'] bank_name2 = cls.buffer['code'][bank_code]['bankName2'] card_type = cls.CARD_TYPE_MAP.get(card_type, card_type) return { 'cardNo': cardNo, 'bankCode': bank_code, 'cardType': card_type, 'bankName': bank_name, 'bankName2': bank_name2 } else: return { 'cardNo': cardNo, 'bankCode': bank_code, 'cardType': card_type, 'bankName': '', 'bankName2': '' } return None @classmethod def get_public_banks(cls): cls.__load_in_mem() return cls.buffer['public'] @classmethod def get_personal_banks(cls): cls.__load_in_mem() return cls.buffer['personal'] @classmethod def support_personal(cls, bankName): cls.__load_in_mem() return bankName in cls.buffer['personal'] @classmethod def support_public(cls, bankName): cls.__load_in_mem() return bankName in cls.buffer['public'] @classmethod def get_wechat_bank_code(cls, bankName): cls.__load_in_mem() if bankName in cls.buffer['personal']: return cls.buffer['name'][bankName]['wechatBankCode'] else: return '' @classmethod def get_collection(cls): return cls._get_collection() @classmethod def __acquire_lock(cls): cls.buffer_lock.acquire_lock() @classmethod def __release_lock(cls): cls.buffer_lock.release_lock() def reset_buffer(self): try: self.__acquire_lock() self.buffer = {} finally: self.__release_lock() @classmethod def __load_in_mem(cls): if cls.buffer: return try: cls.__acquire_lock() if cls.buffer: return cls.buffer = { 'public': [], # type: list 'personal': [], # type: list 'name': {}, 'code': {} } banks = cls.objects().order_by('sn') for bank in banks: cls.buffer['name'][bank.bankName] = bank.to_dict() cls.buffer['name'][bank.bankName2] = bank.to_dict() cls.buffer['code'][bank.bankCode] = bank.to_dict() cls.buffer['public'].append(bank.bankName) if bank.bankName2: cls.buffer['public'].append(bank.bankName2) if bank.wechatBankCode: cls.buffer['personal'].append(bank.bankName) if bank.bankName2: cls.buffer['personal'].append(bank.bankName2) finally: cls.__release_lock() class FAQ(Searchable): """ 配置FAQ,首先让经销商|终端用户看到常用问题,找不到问题再联系客服 """ question = StringField(verbose_name = '问题') answer = StringField(verbose_name = '答案') target = StringField(verbose_name = '目标 := (Dealer|Agent|EndUser)', default = '*') devTypeId = StringField(verbose_name = '设备类型ID,默认为普适性问题,为空', default = '') images = ListField(verbose_name = '辅助说明图像') videos = ListField(verbose_name = '辅助说明视频') upvotes = IntField(verbose_name = '该FAQ有帮助') downvotes = IntField(verbose_name = '该FAQ没有帮助') managerId = StringField(verbose_name = '管理员ID', null = False) meta = {'collection': 'faqs', 'db_alias': 'default'} def to_dict(self): return { 'id': str(self.id), 'question': self.question, 'answer': self.answer, 'target': self.target, 'images': self.images, 'videos': self.videos, 'devTypeId': self.devTypeId, 'upvotes': self.upvotes, 'downvotes': self.downvotes } @classmethod def get_by_role(cls, role): #: 目前无用户体系,暂时将匿名用户作为终端用户 role = ROLE.myuser if role == ROLE.anonymoususer else role return cls.objects(target__in = ['*', role]) class Feature(Searchable): """ 特性列表的所有特性,由超级管理员配置 (角色,特性名称) 唯一 """ name = StringField(verbose_name = u'特性名称', min_length = 1) key = StringField(verbose_name = u'具体的特性代号,为英文') role = StringField(verbose_name = u'特性分配的角色', choices = ROLE.choices()) # managerId = StringField(verbose_name = u'厂商ID') default = BooleanField(verbose_name = u'默认值') # by = StringField(verbose_name = u'创建的超级管理员的ID') desc = StringField(verbose_name = u'描述') dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = u'生成时间') search_fields = ('key', 'name') meta = { 'indexes': [ {'fields': ['name', 'role'], 'unique': True}, ], "collection": "features", "db_alias": "default" } @classmethod def for_dealer(cls, **kwargs): return cls.objects(role = ROLE.dealer, **kwargs) @classmethod def for_agent(cls, **kwargs): return cls.objects(role = ROLE.agent, **kwargs) @classmethod def for_manager(cls, **kwargs): return cls.objects(role = ROLE.manager, **kwargs) def to_dict(self): return { 'id': str(self.id), 'role': self.role, 'name': self.name, # 'managerId': self.managerId, # 'by': self.by, 'key': self.key, 'default': self.default, 'desc': self.desc, 'dateTimeAdded': self.dateTimeAdded } def __repr__(self): return '' % (self.key, self.role) DEFAULT_DEALER_FEATURES = ['show_offline_coins'] class AddressType(Searchable): value = StringField(verbose_name = "地址值", unique = True) #: e.g. school label = StringField(verbose_name = "地址名称", default = "") #: e.g. 学校 showWeight = IntField(verbose_name = '显示权重', default = 0) createdAt = DateTimeField(default = datetime.datetime.now) meta = {"collection": "AddressType", "db_alias": "default"} class UserSearchable(RoleBaseDocument): class Status(IterConstant): NORMAL = 'normal' FORBIDDEN = 'forbidden' ABNORMAL = 'abnormal' NOWITHDRAW = 'noWithdraw' username = StringField(verbose_name = "用户名称:=手机号", max_length = 100) password = StringField(verbose_name = "密码", min_length = 1) nickname = StringField(verbose_name = '名字,用来标识', default = '') remarks = StringField(verbose_name = "备注", default = "") avatar = StringField(verbose_name = u"用户头像", default = "") activited = BooleanField(verbose_name = "是否激活", default = True) status = StringField(verbose_name = "账号状态", default = Status.NORMAL) dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = '添加进来的时间') last_login = DateTimeField(default = datetime.datetime.now, verbose_name = '最近登录时间') phoneOS = StringField(verbose_name = u"终端操作系统", default = "") lastLoginUserAgent = StringField(verbose_name = u"最后一次登录的userAgent", default = "") smsVendor = StringField(verbose_name = u'sms提供商', default = '') meta = { 'abstract': True, 'indexes': [ { 'fields': ['$username', '$nickname'], 'weights': {'username': 5, 'nickname': 5} } ] } def __str__(self): return '{}'.format( self.__class__.__name__, str(self.id), self.username, self.nickname) @property def human_id(self): return '{}({})'.format(self.username, self.role) @property def identity_id(self): return '{}_{}'.format(self.role, str(self.id)) def to_dict(self, shadow = False): return { 'username': self.username if not shadow else '******', 'nickname': self.nickname, 'remarks': self.remarks, 'activited': self.activited, 'status': self.status, 'dateTimeAdded': self.dateTimeAdded, 'last_login': self.last_login, 'role': self.role, 'avatar': self.my_avatar } @classmethod def create_user(cls, username, password, **attrs): user = cls(username = username, **attrs) user.password = make_password(password) user.save() return user def is_authenticated(self): return True def set_password(self, raw_password): self.password = make_password(raw_password) self.save() return self def check_password(self, raw_password): return check_password(raw_password, self.password) def unlock_login(self): lm = LimitAttemptsManager(identifier = self.username, category = "%sLogin" % self.role) lm.clear() @classmethod def get_last_login1(cls, **kwargs): return cls.objects(**kwargs).order_by('-last_login').first() def __check_feature_setup(self): # type:()->(str, set) self_features = getattr(self, 'features', None) if self_features is None: raise ImproperlyConfigured('no features attr provided') return self.role, set(self_features) ## Feature Module @property def feature_list(self): # type:()->List[dict] role, self_features = self.__check_feature_setup() features = [_.to_dict() for _ in Feature.objects(role = role)] # type: List[dict] rv = [ { 'name': feature['name'], 'key': feature['key'], 'value': feature['key'] in self_features } for feature in features ] #: unique to user, special features # set difference unique_features = self_features - {_['key'] for _ in features} rv += [{'name': _, 'key': _, 'value': True} for _ in unique_features] return rv def edit_feature_list(self, feature_list, special = None): # type:(list, Optional[str])->int self.__check_feature_setup() features = [_['key'] for _ in feature_list if _['value']] if special: features.append(special) return self.update(set__features = features) @property def feature_boolean_map(self): # type: ()->Dict[str, bool] features = {} for _ in self.feature_list: if _['key']: features.update({_['key']: _['value']}) return features def supports(self, feature): # type: (str)->bool _, self_features = self.__check_feature_setup() return feature in self_features @property def normal(self): return self.status in [self.Status.NORMAL, self.Status.NOWITHDRAW] @property def forbidden(self): return self.status == self.Status.FORBIDDEN @property def abnormal(self): return self.status == self.Status.ABNORMAL @property def no_withdraw(self): return self.status == self.Status.NOWITHDRAW @property def my_avatar(self): if self.avatar: return self.avatar else: return '' @property def universal_password_login(self): return getattr(self, '__universal_password_login__', False) @universal_password_login.setter def universal_password_login(self, upl): setattr(self, '__universal_password_login__', upl) class CapitalUser(UserSearchable): """ 带资金以及提现用户 """ meta = { 'abstract': True, } INCOME_SOURCE_LIST = list() INCOME_SOURCE_TO_TYPE = dict() INCOME_TYPE_LIST = list() INCOME_TYPE_TO_FIELD = dict() DEFAULT_WITHDRAW_OPTIONS = { 'autoWithdrawSwitch': False, 'autoWithdrawType': "wechat", 'autoWithdrawMin': RMB(settings.WITHDRAW_MINIMUM).mongo_amount, 'autoWithdrawStrategy': {'type': 'asWeek', 'value': 3}, 'autoWithdrawBankFee': True, 'alipay': { 'realName': '', 'loginId': '' }, 'wechat': { 'realName': '' } } ongoingWithdrawList = ListField(field = StringField()) inhandWithdrawList = ListField(field = DictField()) withdrawOptions = DictField(verbose_name = u"提现相关选项", default = DEFAULT_WITHDRAW_OPTIONS) bankWithdrawFee = BooleanField(verbose_name = u"银行卡提现手续开关(资金池代理商开关和这个开关为双开关)", default = True) @classmethod def income_field_name(cls, income_type): # type: (str)->str assert income_type in cls.INCOME_TYPE_LIST, 'not support this income type({})'.format(income_type) return cls.INCOME_TYPE_TO_FIELD[income_type] @classmethod def fund_key(cls, income_type, source_key): return '{field}.{key}'.format(field = cls.income_field_name(income_type), key = source_key) def balance_dict(self, income_type): return dict(getattr(self, self.income_field_name(income_type = income_type))) def set_balance(self, income_type, source_key, money): # type: (str, str, RMB)->bool """ 单元测试使用.初始化值 :param income_type: :param source_key: :param money: :return: """ assert isinstance(money, RMB), 'money has to be a RMB instance' assert income_type in self.INCOME_TYPE_LIST, 'not support this source{}'.format(income_type) assert source_key, 'source key must not be none' query = {'_id': ObjectId(self.id)} update = { '$set': { '{fund_key}.balance'.format(fund_key = self.fund_key(income_type, source_key)): money.mongo_amount, '{fund_key}.frozenBalance'.format(fund_key = self.fund_key(income_type, source_key)): RMB( 0).mongo_amount }, } result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult return bool(result.modified_count == 1) def incr_fund(self, income_type, source_key, money): # type: (str, str, RMB)->bool assert isinstance(money, RMB), 'money has to be a RMB instance' assert income_type in self.INCOME_TYPE_LIST, 'not support this source' assert source_key, 'gateway key must not be none' query = {'_id': ObjectId(self.id)} update = { '$inc': { '{fund_key}.balance'.format(fund_key = self.fund_key(income_type, 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_fund(self, income_type, source_key, money): # type: (str, str, RMB)->int """ 正常流程不使用该接口,会让账对不上 :param income_type: :param source_key: :param money: :return: """ assert isinstance(money, RMB), 'money has to be a RMB instance' assert income_type in self.INCOME_TYPE_LIST, 'not support this source' assert source_key, 'gateway key must not be none' query = {'_id': ObjectId(self.id)} update = { '$inc': { '{fund_key}.balance'.format(fund_key = self.fund_key(income_type, source_key)): ( -money).mongo_amount } } result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult return bool(result.modified_count == 1) @classmethod def prepare_sellet_income(cls, object_id, transaction_id, source, source_key, money, selfAgg=False): assert isinstance(money, RMB), 'money has to be a RMB instance' assert source in cls.INCOME_SOURCE_LIST, 'not support this source' assert source_key, 'source key must not be none' if money == RMB(0): logger.debug('{} sellet zero.'.format(cls.__name__, object_id)) return income_type = cls.INCOME_SOURCE_TO_TYPE[source] query = { '_id': ObjectId(object_id), 'ongoingWithdrawList.transaction_id': { '$ne': transaction_id } } update = { '$inc': { '{filed}.{key}.balance'.format(filed=cls.INCOME_TYPE_TO_FIELD[income_type], key=source_key): money.mongo_amount }, '$addToSet': { 'ongoingWithdrawList': {'transaction_id': transaction_id, 'money': money.mongo_amount} } } if selfAgg: update['$inc'].update({ 'aggregatedIncome.{source}'.format(source=source): money.mongo_amount, 'incomeMap.{source}'.format(source=source): money.mongo_amount }) result = cls.get_collection().update_one(query, update, upsert=False) # type: UpdateResult return bool(result.modified_count == 1) @classmethod def commit_sellet_income(cls, object_id, transaction_id): query = { '_id': ObjectId(object_id), 'ongoingWithdrawList': { '$elemMatch': { 'transaction_id': transaction_id } } } update = { '$pull': { 'ongoingWithdrawList': {'transaction_id': transaction_id} } } result = cls.get_collection().update_one(query, update, upsert=False) # type: UpdateResult return bool(result.modified_count == 1) def sub_balance(self, income_type, source_key = None, only_ledger = True): # type: (str, str, bool)->RMB balance_dict = self.balance_dict(income_type = income_type) # type: dict if not source_key: balance_list = [] for key, value in balance_dict.iteritems(): if only_ledger: if WithdrawGateway.is_ledger(key): balance_list.append(value.balance) else: balance_list.append(value.balance) return sum_rmb(balance_list) else: return balance_dict.get(source_key, Balance()).balance @property def total_balance(self): # type: ()->RMB total = RMB(0) for income_type in self.INCOME_TYPE_LIST: total += self.sub_balance(income_type) return total def sub_frozen_balance(self, income_type, source_key = None): # type: (str, str)->RMB field = self.income_field_name(income_type = income_type) balance_list = [] for item in self.inhandWithdrawList: if item['field'] != field: continue if source_key and item['key'] != source_key: continue balance_list.append(item['value']) balance_dict = self.balance_dict(income_type = income_type) # type: dict if not source_key: for key, value in balance_dict.iteritems(): if WithdrawGateway.is_ledger(key): balance_list.append(value.frozenBalance) return sum_rmb(balance_list) else: balance_list.append(balance_dict.get(source_key, Balance()).frozenBalance) return sum_rmb(balance_list) @property def total_frozen_balance(self): # type: ()->RMB total = RMB(0) for income_type in self.INCOME_TYPE_LIST: total += self.sub_frozen_balance(income_type) return total def _freeze_balance(self, income_type, money, source_key, transaction_id, freeze_type): assert source_key, 'gateway is null' assert transaction_id, 'transaction id is null' 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(freeze_type): {'$ne': transaction_id}} update = { '$inc': { '{fund_key}.balance'.format(fund_key=fund_key): (-money).mongo_amount }, '$addToSet': { freeze_type: { 'transaction_id': transaction_id, 'field': field, 'key': source_key, 'value': money.mongo_amount } } } result = self.get_collection().update_one(query, update) # type: UpdateResult return bool(result.modified_count == 1) def _clear_frozen_balance(self, transaction_id, freeze_type): query = {'_id': self.id, '{}.transaction_id'.format(freeze_type): transaction_id} update = { '$pull': { freeze_type: { 'transaction_id': transaction_id } } } result = self.get_collection().update_one(query, update) # type: UpdateResult return bool(result.modified_count == 1) def _recover_frozen_balance(self, income_type, money, source_key, transaction_id, freeze_type): assert source_key, 'gateway is null' assert transaction_id, 'transaction id is null' fund_key = self.fund_key(income_type=income_type, source_key=source_key) query = {'_id': self.id, '{}.transaction_id'.format(freeze_type): transaction_id} update = { '$inc': { '{fund_key}.balance'.format(fund_key=fund_key): money.mongo_amount }, '$pull': { freeze_type: { 'transaction_id': transaction_id } } } result = self.get_collection().update_one(query, update) # type: UpdateResult return bool(result.modified_count == 1) def freeze_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 self._freeze_balance(income_type, money, source_key, transaction_id, "inhandWithdrawList") def clear_frozen_balance(self, transaction_id): # type:(str)->bool """ 提现完成 清理冻结 :param transaction_id: :return: """ assert transaction_id, 'transaction id is null' return self._clear_frozen_balance(transaction_id, "inhandWithdrawList") def 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' return self._recover_frozen_balance(income_type, money, source_key, transaction_id, "inhandWithdrawList") def withdraw_source_key(self, pay_app = None): # type:(PayAppBase)->str """ 供PaymentGateway调用获取提现网关 :param pay_app: :return: """ raise NotImplementedError() @property def withdraw_sms_provider(self): # type: () -> WithdrawSMSProvider raise NotImplementedError() @property def withdraw_sms_phone_number(self): # type: () -> basestring raise NotImplementedError() 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 raise NotImplementedError() def new_withdraw_handler(self, record): # type: (WithdrawRecord) -> WithdrawHandler raise NotImplementedError() def get_bound_pay_openid(self, key): raise NotImplementedError() def withdraw_bank_card(self, accountCode): # type: (str)->WithdrawBankCard return WithdrawBankCard.objects(ownerId = str(self.id), role = self.role, accountCode = accountCode).first() @property def withdraw_bank_cards(self): banks = WithdrawBankCard.objects(ownerId = str(self.id), role = self.role).all() return [{ 'accountCode': bank.accountCode, 'bankName': bank.bankName } for bank in banks] @property def auto_withdraw_min(self): return self.withdrawOptions.get('autoWithdrawMin') @property def auto_withdraw_type(self): return self.withdrawOptions.get('autoWithdrawType') @property def auto_withdraw_switch(self): return self.withdrawOptions.get('autoWithdrawSwitch') @property def auto_withdraw_strategy(self): return self.withdrawOptions.get('autoWithdrawStrategy', {}) @property def auto_withdraw_bank_fee(self): return self.withdrawOptions.get('autoWithdrawBankFee', True) @property def withdraw_open_id(self): return getattr(self, '__withdraw_openid__') @withdraw_open_id.setter def withdraw_open_id(self, open_id): setattr(self, '__withdraw_openid__', open_id) @property def withdraw_alipay_config(self): return self.withdrawOptions.get('alipay', {}) @property def withdraw_alipay_login_id(self): return self.withdraw_alipay_config.get('loginId', '') @property def withdraw_alipay_real_name(self): return self.withdraw_alipay_config.get('realName', '') @property def withdraw_wechat_config(self): return self.withdrawOptions.get('wechat', {}) @property def withdraw_wechat_real_name(self): return self.withdraw_wechat_config.get('realName', '') or self.nickname @property def auto_withdraw_bound_open_id(self): raise NotImplementedError() def can_withdraw_today(self): return True @property def current_wallet_withdraw_source_key(self): logger.debug('get withdraw source key. source = {}'.format(repr(self))) from apps.web.helpers import AgentCustomizedBrandVisitor return AgentCustomizedBrandVisitor(factory_type = 'withdraw_source_key', pay_app = None).visit(self) def withdraw_support(self, source_key): from apps.web.agent.models import Agent is_ledger, withdraw_gateway_list = Agent.withdraw_gateway_list(source_key) if not is_ledger: return { 'wechat': { 'support': False, }, 'alipay': { 'support': False, }, 'bank': { 'support': False, } } dealerBankWithdrawFee = withdraw_gateway_list['wechat'].occupant.dealerBankWithdrawFee rv = { 'wechat': { 'support': True if withdraw_gateway_list['wechat'] else False, 'realName': self.withdraw_wechat_real_name }, 'alipay': { 'support': True if withdraw_gateway_list['alipay'] else False, 'realName': self.withdraw_alipay_real_name, 'loginId': self.withdraw_alipay_login_id }, 'bank': { 'support': True, 'cards': self.withdraw_bank_cards, 'transFee': dealerBankWithdrawFee and self.bankWithdrawFee } } return rv def set_withdraw_alipay(self, loginId, realName): self.update(withdrawOptions__alipay = {'loginId': loginId, 'realName': realName}) def set_withdraw_wechat(self, realName): self.update(withdrawOptions__wechat = {'realName': realName}) class WithdrawRefundRecord(Searchable): withdraw_record_id = ObjectIdField(unique = True) createdTime = DateTimeField(default = datetime.datetime.now) role = StringField(choices = ROLE.choices()) meta = { "collection": "withdraw_refund_record", "db_alias": "logdata" } class WithdrawRecord(Searchable): class WithdrawPayType(IterConstant): BANK = 'bank' WECHAT = 'wechat' order = StringField(verbose_name = u"订单号", unique = True) ownerId = StringField(verbose_name = u"提现者对应对象ID") role = StringField(verbose_name = u'提现者账号类型', choices = ROLE.choices()) name = StringField(verbose_name = u"提现人姓名", default = "") # type: str phone = StringField(verbose_name = u"持卡人电话", default = "") # type: str balance = MonetaryField(verbose_name = u"提现人提现时刻余额", default = RMB('0.00')) # type: RMB incomeType = StringField(verbose_name = u'提现收入类型', choices = AGENT_INCOME_TYPE.choices() + DEALER_INCOME_TYPE.choices()) withdrawSourceKey = StringField(verbose_name = u'提现来源KEY', default = None) payType = StringField(verbose_name = u"支付方式(微信,银行卡)", default = "") # type: str manual = BooleanField(verbose_name = u'是否手动处理', default = False) # type: bool recurrent = BooleanField(verbose_name = u"是否为自动循环提现", default = False) status = IntField(verbose_name = u"状态", default = WithdrawStatus.SUBMITTED) remarks = StringField(verbose_name = u"备注系统内部状态", default = "") description = StringField(verbose_name = u'给用户看的描述', default = '') postTime = DateTimeField(verbose_name = u"请求时间", default = datetime.datetime.now) finishedTime = DateTimeField(verbose_name = u"提现订单完成时间") amount = MonetaryField(verbose_name = u"提现金额", default = RMB('0.00')) # type: RMB serviceFee = MonetaryField(verbose_name = u"手续费", default = RMB('0.00')) # type: RMB actualPay = MonetaryField(verbose_name = u"实际金额", default = RMB('0.00')) # type: RMB bankTransFee = MonetaryField(verbose_name = u"转账银行费用", default = RMB('0.00')) # type: RMB refunded = BooleanField(verbose_name = u'是否已经退款了', default = False) cardUserName = StringField(verbose_name = u"持卡人姓名", default = "") accountCode = StringField(verbose_name = u"提现银行卡号", default = "") parentBankName = StringField(verbose_name = u"银行名称", default = "") subBankName = StringField(verbose_name = u"银行支行名称", default = "") payAgentId = StringField(verbose_name = u'提现处理平台代理商ID') withdrawFeeRatio = PermillageField(verbose_name = u'提现费率', default = Const.PLATFORM_DEFAULT_WITHDRAW_FEE_RATIO) partition = ListField(verbose_name = u'提现费率收入划分', default = []) withdrawGatewayKey = StringField(verbose_name = u"提现资金池gateway key", required = True) extras = DictField(verbose_name = u"记录提现额外信息", default = {}) search_fields = ('order', 'phone', 'name') meta = { 'indexes': [ { 'fields': ['ownerId', 'role'], }, 'status', '-postTime', 'payAgentId' ], 'collection': 'WithdrawRecord', 'db_alias': 'default' } @classmethod def get_record(cls, order_no): return cls.objects(order = order_no).first() @classmethod def get_collection(cls): return cls._get_collection() @classmethod def get_succeeded_records(cls, **kwargs): return cls.objects(status = WithdrawStatus.SUCCEEDED, **kwargs) @classmethod def get_processing_via_bank(cls): return cls.objects(status__in = [WithdrawStatus.PROCESSING, WithdrawStatus.BANK_PROCESSION], payType = WITHDRAW_PAY_TYPE.BANK, manual = False) @classmethod def get_failed_records(cls): return cls.objects(status = WithdrawStatus.FAILED, refunded = False, manual = False) @classmethod def create(cls, withdraw_user, withdraw_gateway, pay_entity, source_key, income_type, pay_type, fund_map, manual, recurrent, **kwargs): # type: (CapitalUser, WithdrawGateway, WithdrawBankCard, str, str, str, dict, bool, bool, dict) -> WithdrawRecord assert pay_type in ( WITHDRAW_PAY_TYPE.WECHAT, WITHDRAW_PAY_TYPE.BANK, WITHDRAW_PAY_TYPE.ALIPAY), 'not support this pay type' if manual: assert pay_type == WITHDRAW_PAY_TYPE.BANK, 'pay type must be bank in manual' withdraw_record_payload = { 'order': WithdrawRecord.make_no(withdraw_user.role, str(withdraw_user.id)), 'ownerId': str(withdraw_user.id), 'role': withdraw_user.role, 'name': withdraw_user.nickname, 'phone': withdraw_user.username, 'balance': withdraw_user.sub_balance(income_type = income_type, source_key = source_key), 'incomeType': income_type, 'withdrawSourceKey': source_key, 'payType': pay_type, 'manual': manual, 'amount': fund_map.get('amount'), 'serviceFee': fund_map.get('serviceFee'), 'actualPay': fund_map.get('actualPay'), 'bankTransFee': fund_map.get('bankTransFee'), 'accountCode': pay_entity.accountCode, 'cardUserName': pay_entity.accountName, 'parentBankName': pay_entity.bankName, 'subBankName': pay_entity.branchBankName, 'partition': fund_map.get('partition'), 'payAgentId': withdraw_gateway.occupantId, 'withdrawGatewayKey': withdraw_gateway.gateway_key, 'withdrawFeeRatio': fund_map.get('withdrawFeeRatio'), 'extras': {'v': 2} } if pay_type == WITHDRAW_PAY_TYPE.BANK: withdraw_record_payload['extras'].update({ 'withdrawBankCard': pay_entity.id }) if kwargs: withdraw_record_payload.update(**kwargs) if hasattr(withdraw_gateway, 'version'): withdraw_record_payload['extras'].update({ 'gateway_version': getattr(withdraw_gateway, 'version') }) record = cls(**withdraw_record_payload) record.save() return record @property def finished(self): return self.status in WithdrawStatus.finished_status() def succeed(self, **kwargs): payload = { 'status': WithdrawStatus.SUCCEEDED } if kwargs and 'extra' in kwargs: extra = kwargs.pop('extra') for key, value in extra.iteritems(): payload.update({ 'extra.{}'.format(key): value }) if kwargs: payload.update(kwargs) result = self.get_collection().update_one( filter = {'_id': ObjectId(self.id), 'status': {'$nin': WithdrawStatus.finished_status()}}, update = {'$set': payload}, upsert = False) return result.matched_count == 1 def fail(self, remarks = '', description = u'支付网关返回失败'): result = self.get_collection().update_one( filter = {'_id': ObjectId(self.id), 'status': {'$nin': WithdrawStatus.finished_status()}}, update = {'$set': { 'status': WithdrawStatus.FAILED, 'remarks': remarks, 'description': description, 'finishedTime': datetime.datetime.now() }}, upsert = False) return result.matched_count == 1 def revoke(self, remarks = '', description = u'提现失败'): result = self.get_collection().update_one( filter = {'_id': ObjectId(self.id), 'status': {'$nin': WithdrawStatus.finished_status()}}, update = {'$set': { 'status': WithdrawStatus.CLOSED, 'refunded': True, 'remarks': remarks, 'description': description, 'finishedTime': datetime.datetime.now() }}, upsert = False) return result.matched_count == 1 def set_processing(self, remarks = u'提现申请已经受理', description = u'提现申请已经受理', **kwargs): extras = self.extras extras.update(**kwargs) set_payload = { 'status': WithdrawStatus.PROCESSING, 'remarks': remarks, 'description': description, 'extras': extras } result = self.get_collection().update_one( filter = {'_id': ObjectId(self.id), 'status': {'$nin': WithdrawStatus.finished_status()}}, update = {'$set': set_payload}, upsert = False) return result.matched_count == 1 def set_bank_processing(self, **kwargs): extras = self.extras extras.update(**kwargs) set_payload = { 'status': WithdrawStatus.BANK_PROCESSION, 'description': u'银行正在处理中', 'remarks': '', 'extras': extras } result = self.get_collection().update_one( filter = { '_id': ObjectId(self.id), 'status': { '$nin': WithdrawStatus.finished_status() } }, update = {'$set': set_payload}, upsert = False) return result.matched_count == 1 @staticmethod def make_no(role, user_id): # type: (str, str)->str assert role in ROLE.choices(), 'no this role' return OrderNoMaker.make_order_no_32(identifier = str(user_id), main_type = OrderMainType.WITHDRAW, sub_type = ROLE.sub_type(role)) @staticmethod def is_my(order_no): try: return order_no[0] == OrderMainType.WITHDRAW and order_no[1] in ROLE.sub_type_list() except Exception as e: return False @staticmethod def process_memcache_lock(order): # type: (str)->None return memcache_lock('WithdrawRecord_%s' % order, 'processing') @property def source_key(self): if not self.withdrawSourceKey: pay_app_type, occupant_id, tokens = WithdrawGateway.parse_gateway_key(self.withdrawGatewayKey) appid, mchid = tokens[0], tokens[1] app = WechatPayApp(appid = appid, mchid = mchid) # type: WechatPayApp app.occupantId = occupant_id return APP_KEY_DELIMITER.join( [WithdrawGateway.LEDGER_PREFIX, PayAppType.WECHAT, getattr(app, '__source_key__')]) else: return self.withdrawSourceKey @property def is_new_version(self): return self.extras.get('v', 1) == 2 @classmethod def count_today(cls, ownerId, role = ROLE.dealer): return cls.objects( ownerId = ownerId, role = role, status__nin = [WithdrawStatus.CLOSED, WithdrawStatus.SUCCEEDED, WithdrawStatus.REFUND], postTime__gte = get_zero_time(datetime.datetime.now())).count() class PushRecord(Searchable): createdTime = DateTimeField(default = datetime.datetime.now) func_name = StringField() result = DictField() meta = { 'collection': 'push_records', 'db_alias': 'logdata', 'max_documents': 100, 'max_size': 2000000 } def __repr__(self): return '' % (self.func_name,) class DealerDoneReportRecord(DynamicDocument): reportName = StringField() reportDay = StringField() dealerIds = ListField() expireAt = DateTimeField(default = None) meta = { 'collection': 'dealer_done_report_record', 'db_alias': 'logdata' } @classmethod def update_done_list(cls, report_name, report_day, done_list): return cls.objects(reportName = report_name, reportDay = report_day).upsert_one( push_all__dealerIds = done_list, expireAt = to_datetime(report_day, time_format = "%Y-%m-%d") + datetime.timedelta(days = 7)) class RefundWithdrawRecord(Searchable): """ 退款提现单,需要记录给经销商/代理商退款的情况 """ withdraw_record_id = ObjectIdField(unique = True) createdTime = DateTimeField(default = datetime.datetime.now) meta = { 'abstract': True, } class ChangeLog(Searchable): """ 在一些敏感的,涉及到需要确保后续可回溯的操作的时候,需要进行记录. TODO 这里更加合理的实现,是运用MongoDB的changeStream特性 脚本监听,然后过滤处理存储。 """ content = StringField() event = StringField() sender = StringField() detail = StrictDictField() pre = StrictDictField() post = StrictDictField() createdTime = DateTimeField(default = datetime.datetime.now) meta = {"collection": "change_logs", "db_alias": "logdata"} search_fields = ('content', 'event', 'sender') def to_dict(self): return { 'content': self.content, 'event': self.event, 'sender': self.sender, 'detail': self.detail } class Balance(EmbeddedDocument): balance = MonetaryField(verbose_name = '提现收入', default = RMB('0.00')) frozenBalance = MonetaryField(verbose_name = '冻结提现收入', default = RMB('0.00')) def __repr__(self): return "balance = {}, frozenBalance = {}".format(self.balance, self.frozenBalance) class AccuracyBalance(EmbeddedDocument): balance = AccuracyMoneyField(verbose_name = '提现收入', default = AccuracyRMB('0.00')) frozenBalance = AccuracyMoneyField(verbose_name = '冻结提现收入', default = AccuracyRMB('0.00')) class TempValues(Searchable): """ 用于存放一些重要信息的临时数据库表,放memcached中不安全,就用这个 """ key = StringField(verbose_name = 'key', default = '', unique = True) value = StringField(verbose_name = 'value', default = '') expireAt = DateTimeField(default = None, verbose_name = u'过期时间') meta = {"collection": "temp_values", "db_alias": "logdata"} @staticmethod def get(key): obj = TempValues.objects.filter(__raw__ = {'key': key, '$or': [{'expireAt': {'$exists': False}}, { 'expireAt': {'$gte': datetime.datetime.now()}}]}).first() if obj: return json_loads(obj.value) return None @staticmethod def set(key, value, expire = 60 * 60): if isinstance(value, dict): _value = json_dumps(value, serialize_type = True) else: _value = value TempValues.objects(key = key).update_one(upsert = True, key = key, value = _value, expireAt = datetime.datetime.now() + datetime.timedelta(seconds = expire)) @staticmethod def remove(key): try: TempValues.objects(key = key).update( expireAt = datetime.datetime.now() - datetime.timedelta(seconds = 8 * 60 * 60)) except Exception, e: pass class OperatorLog(Searchable): """ 关键用户行为变更日志 着重记录用户的并不高频但重要的操作,更改配置项 """ class LogLevel(IterConstant): INFO = 'INFO' NOTICE = 'NOTICE' WARNING = 'WARNING' CRITICAL = 'CRITICAL' EMERGENCY = 'EMERGENCY' username = StringField(verbose_name = u'操作员账号') operatorId = ObjectIdField(verbose_name=u'操作员关联ID') role = StringField(verbose_name = u'操作员角色') level = StringField(verbose_name = u'日志级别') operatorName = StringField(verbose_name = u'操作名称') content = DictField(verbose_name = u'日志内容') dateTimeAdded = DateTimeField(verbose_name = u'添加时间', default = datetime.datetime.now) meta = {'collection': 'operator_logs', 'db_alias': 'logdata'} @classmethod def log(cls, user, level, operator_name, content): # type: (UserSearchable, basestring, basestring, dict)->None try: cls(username = user.username, role = user.role, operatorId = user.id, level = level, content = content, operatorName = operator_name).save() except Exception, e: logger.exception(e) @classmethod def log_dev_operation(cls, operator, device, operator_name, content, level=LogLevel.WARNING): try: content.update({ 'logicalCode': device.logicalCode, 'devNo': device.devNo, 'devTypeCode': device.devTypeCode }) return cls(username=operator.username, role=operator.role, operatorId=operator.id, level=level, content=content, operatorName=operator_name).save() except Exception, e: logger.exception(e) @classmethod def record_dev_setting_changes_log(cls, user, operator_name, logicalCode, devTypeCode, content): # type: (UserSearchable, basestring, str, str, dict)->Searchable try: content.update({ 'logicalCode': logicalCode, 'devTypeCode': devTypeCode }) return cls(username = user.username, role = user.role, operatorId = user.id, level = 'INFO', content = content, operatorName = operator_name).save() except Exception, e: logger.exception(e) class ExceptionLog(Searchable): """ 异常记录 """ user = StringField() dateTimeAdded = DateTimeField(verbose_name = u'添加时间', default = datetime.datetime.now) exception = StringField() extra = DictField() meta = {'collection': 'exception_log', 'db_alias': 'logdata'} @classmethod def log(cls, user, exception, extra): # type: (str, basestring, dict)->None try: cls( user = user, exception = exception, extra = extra ).save() except Exception, e: logger.exception(e) class MonthlySubTemp(EmbeddedDocument): displayName = StringField(verbose_name = u"展示名称", default = "包月套餐") price = MonetaryField(verbose_name = u"价格", default = RMB(30)) numOfMonth = IntField(verbose_name = u"包月的时间 几个月", default = 1) def to_dict(self): return { "displayName": self.displayName, "price": str(self.price), "numOfMonth": self.numOfMonth } @classmethod def default(cls): return cls() @classmethod def create(cls, **kwargs): displayName = kwargs.get("displayName") price = kwargs.get("price") numOfMonth = kwargs.get("numOfMonth") obj = cls.default() if displayName is not None: obj.displayName = displayName if price is not None: obj.price = RMB(price) if numOfMonth is not None: obj.numOfMonth = numOfMonth return obj # 包月计费模板 class MonthlyPackageTemp(Searchable): """ 设备类型 和包月计费 拆开 不管什么样的设备类型 次是使用的单位 而每次使用多少 则可以再具体有电量和时间两个基本的计量 """ ownerId = StringField(verbose_name = u"包月规则制定的经销商", default = "") name = StringField(verbose_name = u"模板名称", default = u"默认模板") isDefault = BooleanIntField(verbose_name = u"是否是默认规则", default = 0) saleable = BooleanIntField(verbose_name = u"是否开启此模板", default = 0) isDelete = BooleanIntField(verbose_name = u"是否被经销商删除", default = 0) bothCardAndMobile = BooleanIntField(verbose_name = u"是否通用", default = 0) maxCountOfDay = IntField(verbose_name = u"每日的最大的次数", default = 1, min_value = 0) maxCountOfMonth = IntField(verbose_name = u"每月的最大次数", default = 30, min_value = 0) maxTimeOfCount = IntField(verbose_name = u"每次最大充电量 单位度", default = 240, min_value = 0) maxElecOfCount = IntField(verbose_name = u"每次最大的充电时间 单位分钟", default = 1, min_value = 0) dateTimeAdded = DateTimeField(verbose_name = u"设置模板的时间", default = datetime.datetime.now) dateTimeUpdated = DateTimeField(verbose_name = u"计费模板更新的时间", default = datetime.datetime.now) subTemplate = EmbeddedDocumentListField(verbose_name = u"子模版", document_type = MonthlySubTemp, default = [MonthlySubTemp.default()]) meta = { "collection": "MonthlyPackageTemp", "db_alias": "default" } __unchargeable_fields = ("id", "ownerId", "dateTimeAdded") @classmethod def get_package_by_id(cls, _id): return cls.objects.filter(id = _id).first() @classmethod def default_one(cls, ownerId): """ :param ownerId: :return: """ obj = cls.objects.filter(ownerId = ownerId, isDefault = 1).first() if not obj: obj = cls(ownerId = ownerId, isDefault = 1).save() return obj @classmethod def get_template_by_dealer(cls, ownerId): """ 获取经销商的 所有包月模板 :param ownerId: :return: """ objs = cls.objects.filter(ownerId = ownerId, isDelete = 0) if not objs: return [cls.default_one(ownerId)] return objs @classmethod def get_dealer_package(cls, ownerId, **kwargs): return cls.objects.filter(ownerId = ownerId, **kwargs).first() def set_default(self): """ 设置为默认 :return: """ self.__class__.objects.filter(ownerId = self.ownerId, isDefault = 1).update(isDefault = 0) self.update(isDefault = 1) def to_dict(self): result = super(MonthlyPackageTemp, self).to_dict() subList = list() for _index, _sub in enumerate(self.subTemplate): _subResult = _sub.to_dict() _subResult["index"] = _index subList.append(_subResult) result["subTemplate"] = subList return result def update_by_fields(self, **kwargs): """ 更新 包月套餐的自带属性 某些字段不可更改 直接跳过 :param kwargs: :return: """ fields = self.__class__._fields_ordered for _field in fields: if kwargs.get(_field) is None: continue if _field in self.__unchargeable_fields: continue if _field == "subTemplate": # 子模版的保存 self.subTemplate = [MonthlySubTemp.create(**_sub) for _sub in kwargs.get(_field)] continue setattr(self, _field, kwargs.get(_field)) return self.save() @classmethod def create_by_fields(cls, ownerId, **kwargs): obj = cls(ownerId = ownerId) return obj.update_by_fields(**kwargs) class OrderRecordBase(Searchable): meta = { 'abstract': True, } openId = StringField(verbose_name = u"微信ID", default = '') nickname = StringField(verbose_name = u'用户昵称', default = '') devNo = StringField(verbose_name = u"设备ID", default = None) devType = StringField(verbose_name = u"设备类型", default = None) devTypeName = StringField(verbose_name = u"设备类型名称", default = None) devTypeCode = StringField(verbose_name = u"设备类型编码", default = None) logicalCode = StringField(verbose_name = u"设备逻辑编码", default = None) groupId = StringField(verbose_name = u"设备地址编号", default = None) address = StringField(verbose_name = u"设备地址", default = None) groupNumber = StringField(verbose_name = u"设备", default = None) groupName = StringField(verbose_name = u"交易场地", default = None) dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = u'生成时间') @property def dev_type_name(self): if self.devTypeName: return self.devTypeName if self.devType: return self.devType return u'未知' @property def device(self): # type:()->DeviceDict from apps.web.device.models import Device _attr_name = '__device__' if hasattr(self, _attr_name): return getattr(self, _attr_name) else: device = Device.get_dev(self.devNo) setattr(self, _attr_name, device) return device @property def dev_type_code(self): if self.devTypeCode: return self.devTypeCode device = self.device if device: return device.devTypeCode else: return '' @property def group(self): # type:()->GroupDict from apps.web.device.models import Group _attr_name = '__group__' if hasattr(self, _attr_name): return getattr(self, _attr_name) else: group = Group.get_group(self.groupId) setattr(self, _attr_name, group) return group @group.setter def group(self, group): _attr_name = '__group__' setattr(self, _attr_name, group) @property def owner(self): from apps.web.dealer.models import Dealer _attr_name = '__my_owner__' if hasattr(self, _attr_name): return getattr(self, _attr_name) else: ownerId = getattr(self, 'ownerId', None) if not ownerId: return None else: dealer = Dealer.objects(id = ownerId).first() setattr(self, _attr_name, dealer) return dealer @property def device_identity_info(self): return { 'logicalCode': self.logicalCode, 'devTypeCode': self.devTypeCode, 'devTypeName': self.dev_type_name, 'groupName': self.groupName, 'groupNumber': self.groupNumber, 'address': self.address, 'groupId': self.groupId } class WithdrawBanks(BaseDocument): CARD_TYPE_MAP = { 'DC': "借记卡", 'CC': "信用卡", 'SCC': "准贷记卡", 'PC': "预付费卡" } code = StringField(verbose_name = u'编号', unique = True, null = False) name = StringField(verbose_name = u'名称', null = False) bankAbbrevCode = StringField(verbose_name = u'银行编码') wechatBankCode = StringField(verbose_name = u'微信银行ID', default = '') meta = {'collection': 'withdraw_banks', 'db_alias': 'default'} buffer = None buffer_lock = ThreadLock() def to_dict(self): return { 'code': self.code, 'name': self.name, 'wechatBankCode': self.wechatBankCode, 'bankAbbrevCode': self.bankAbbrevCode } @classmethod def get_wechat_bank_code(cls, bankName): cls.__load_in_mem() if bankName in cls.buffer['wechat']: return cls.buffer['wechat'][bankName].get('wechatBankCode', '') else: return '' @classmethod def get_wechat_support_banks(cls, keyWord): cls.__load_in_mem() rv = [] for item in cls.buffer['wechat'].values(): if keyWord: if keyWord in item['name']: rv.append({ 'code': item['code'], 'name': item['name'] }) else: rv.append({ 'code': item['code'], 'name': item['name'] }) return rv @classmethod def get_by_bank_abbrev_code(cls, bankAbbrevCode): cls.__load_in_mem() if bankAbbrevCode in cls.buffer['bankAbbrevCode']: bank_info = cls.buffer['bankAbbrevCode'][bankAbbrevCode] return { 'bankName': bank_info['name'] } else: return None @classmethod def get_all_banks(cls, keyWord): cls.__load_in_mem() rv = [] for item in cls.buffer['all'].values(): if keyWord: if keyWord in item['name']: rv.append({ 'code': item['code'], 'name': item['name'] }) else: rv.append({ 'code': item['code'], 'name': item['name'] }) return rv @classmethod def support(cls, bankName): cls.__load_in_mem() return bankName in cls.buffer['all'] @classmethod def get_collection(cls): return cls._get_collection() @classmethod def __acquire_lock(cls): cls.buffer_lock.acquire_lock() @classmethod def __release_lock(cls): cls.buffer_lock.release_lock() def reset_buffer(self): try: self.__acquire_lock() self.buffer = None finally: self.__release_lock() @classmethod def __load_in_mem(cls): if cls.buffer: return try: cls.__acquire_lock() if cls.buffer: return cls.buffer = { 'all': {}, 'wechat': {}, 'bankAbbrevCode': {} } banks = cls.objects().all() for bank in banks: payload = bank.to_dict() cls.buffer['all'][bank.name] = payload if bank.wechatBankCode: cls.buffer['wechat'][bank.name] = payload if bank.bankAbbrevCode: cls.buffer['bankAbbrevCode'][bank.bankAbbrevCode] = payload finally: cls.__release_lock() class WithdrawDistrict(BaseDocument): code = StringField(verbose_name = u'编号', unique = True, null = False) name = StringField(verbose_name = u'名称', null = False) parent = StringField(verbose_name = u'父级编号', null = False) meta = { "collection": "withdraw_district", "db_alias": "default" } @classmethod def get_area(cls, province = None): parent = '0000' if not province else province return [ { 'code': item.code, 'name': item.name } for item in cls.objects(parent = parent).order_by('-code') ] class WithdrawBranchBanks(Searchable): code = StringField(verbose_name = u'编号', unique = True, null = False) name = StringField(verbose_name = u'名称', null = False) provinceCode = StringField(verbose_name = u'省编号', null = False) cityCode = StringField(verbose_name = u'城市编号', null = False) bankCode = StringField(verbose_name = u'银行编号', null = False) cnapsCode = StringField(verbose_name = u'联行号') search_fields = ('name',) meta = { "collection": "withdraw_branch_banks", "db_alias": "default" } class WithdrawBankCard(DynamicDocument): """ 提现银行卡信息 """ class AccountType(object): PERSONAL = 'personal' PUBLIC = 'public' ownerId = StringField(verbose_name = u"提现者ID", null = False) role = StringField(verbose_name = u'提现角色', null = False) accountCode = StringField(verbose_name = u"银行卡账号", null = False) accountName = StringField(verbose_name = u'持卡人姓名') accountType = StringField(verbose_name = u'卡类型(个人账号,对公账号)', default = AccountType.PERSONAL) bankCode = StringField(verbose_name = u'银行ID', default = '') bankName = StringField(verbose_name = u"银行名称", default = "") phone = StringField(verbose_name = u'银行预留电话', default = "") # 对公提现需要提供省市支行名称或者联行号 province = StringField(verbose_name = u"省名称", default = None) provinceCode = StringField(verbose_name = u"省编码", default = None) city = StringField(verbose_name = u"市名称", default = None) cityCode = StringField(verbose_name = u"市编码", default = None) branchBankName = StringField(verbose_name = u"银行支行名称", default = None) branchBankCode = StringField(verbose_name = u"银行支行编码", default = None) cnapsCode = StringField(verbose_name = u"联行号", default = None) dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = u'添加进来的时间') manual = BooleanField(verbose_name = u"该卡是否仅手动提现", default = False) meta = {"collection": "withdraw_bank_card", "db_alias": "default"} @classmethod def get_collection(cls): return cls._get_collection() def to_dict(self): if self.accountType == self.AccountType.PERSONAL: return { 'id': str(self.id), 'accountCode': self.accountCode, 'accountName': self.accountName, 'accountType': self.accountType, 'bankName': self.bankName, 'bankCode': self.bankCode, 'phone': self.phone, 'isPublic': False } else: return { 'id': str(self.id), 'accountCode': self.accountCode, 'accountName': self.accountName, 'accountType': self.accountType, 'bankName': self.bankName, 'bankCode': self.bankCode, 'phone': self.phone, 'province': self.province, 'provinceCode': self.provinceCode, 'city': self.city, 'cityCode': self.cityCode, 'branchBankName': self.branchBankName, 'branchBankCode': self.branchBankCode, 'cnapsCode': self.cnapsCode, 'isPublic': True } @classmethod def new_personal_withdraw_bank_card( cls, ownerId, role, accountName, accountCode, bankName, phone, id = None): if id: return cls.objects(id = id).upsert_one( ownerId = ownerId, role = role, bankName = bankName, accountType = cls.AccountType.PERSONAL, accountName = accountName, accountCode = accountCode, phone = phone) else: return cls.objects(ownerId = ownerId, role = role, accountCode = accountCode).upsert_one( ownerId = ownerId, role = role, bankName = bankName, accountType = cls.AccountType.PERSONAL, accountName = accountName, accountCode = accountCode, phone = phone) @classmethod def new_public_withdraw_bank_card(cls, ownerId, role, accountName, accountCode, bankCode, bankName, provinceCode, province, cityCode, city, branchBankCode, branchBankName, cnapsCode, phone, id = None): if id: return cls.objects(id = id).upsert_one( ownerId = ownerId, role = role, bankCode = bankCode, bankName = bankName, accountType = cls.AccountType.PUBLIC, accountName = accountName, accountCode = accountCode, phone = phone, provinceCode = provinceCode, province = province, cityCode = cityCode, city = city, branchBankCode = branchBankCode, branchBankName = branchBankName, cnapsCode = cnapsCode) else: return cls.objects(ownerId = ownerId, role = role, accountCode = accountCode).upsert_one( ownerId = ownerId, role = role, bankCode = bankCode, bankName = bankName, accountType = cls.AccountType.PUBLIC, accountName = accountName, accountCode = accountCode, phone = phone, provinceCode = provinceCode, province = province, cityCode = cityCode, city = city, branchBankCode = branchBankCode, branchBankName = branchBankName, cnapsCode = cnapsCode)