# -*- coding: utf-8 -*- # !/usr/bin/env python """ web.device.models ~~~~~~~~~ """ import copy import re import time import uuid import logging import datetime import warnings import hashlib import math import arrow from django.utils.functional import cached_property from parse import parse from pymongo.errors import DuplicateKeyError from pymongo.results import UpdateResult from typing import List, Tuple, Dict, Set, Optional, Any, Union, TYPE_CHECKING from apilib.systypes import IterConstant import simplejson as json from bson.objectid import ObjectId from django.conf import settings from django.core.cache import cache, caches from django.utils.module_loading import import_string from apilib.utils_json import json_dumps, json_loads from apilib.utils_mongo import format_dot_key from apps.web.common.models import OperatorLog, MonthlyPackageTemp from apps.web.core import ROLE from apps.web.core.exceptions import RentDeviceError, NoGroupFound from apps.web.core.helpers import ActionDeviceBuilder from apps.web.core.sysparas import SysParas from apps.web.dealer.constant import LinkageSwitchEnum from apps.web.exceptions import UserServerException from apps.web.helpers import DeviceTypeVisitor from apps.web.management.models import DeviceReplacement from library.memcache import Client as memcacheClient from mongoengine import StringField, DictField, ListField, BooleanField, FloatField, DateTimeField, ValidationError, \ IntField, PointField, EmbeddedDocumentListField, EmbeddedDocument, DoesNotExist, ObjectIdField, QuerySet, \ NotUniqueError, LazyReferenceField, DynamicDocument, DateField, EmbeddedDocumentField, DynamicEmbeddedDocument, \ LongField from mongoengine.queryset.visitor import Q from apilib.monetary import VirtualCoin, AccuracyRMB, Percent from apilib.utils import rec_update_dict, flatten from apps.web.constant import Const, FAULT_RECORD_STATUS, FAULT_LEVEL, SimStatus, DeviceCmdCode, DeviceOnlineStatus, \ MQTT_TIMEOUT, skip_package_unit_verify_list, skip_package_range_verify_list, skip_package_params_verify_list, \ support_policy_weifule, support_policy_device from apps.web.device.define import DeviceChannelType, SOFT_VER_NO_PULSE_RE_LIST, PULSE_DEV_TYPE_RE, BT_DEV_TYPE_RE, \ SOFT_VER_ONLY_PULSE_RE_LIST, TCP_DEV_TYPE_RE from apps.web.common.errors import NotInChoices from apps.web.core.db import Searchable, MonetaryField, AccuracyMoneyField, VirtualCoinField from apps.web.core.networking import MessageSender from apps.web.device.utils import device_online_cache_key, device_control_cache_key, device_warning_cache_key from apilib.monetary import RMB if TYPE_CHECKING: from apps.web.dealer.models import Dealer, DealerDict from apps.web.eventer import EventBuilder from apps.web.core.adapter.base import SmartBox from apps.web.common.models import UserSearchable from apps.web.user.models import ConsumeRecord logger = logging.getLogger(__name__) class GroupCacheMgr(object): """ 地址组 """ @staticmethod def __group_ids_of_dealer_cache_key(dealer_id): # type:(str)->str """ 经销商拥有的地址缓存 :type dealer_id: """ return 'dealer_group_ids_{ownerId}'.format(ownerId = dealer_id) @staticmethod def __group_ids_of_partner_cache_key(partner_id): # type:(str)->str """ 合伙人拥有的地址缓存 :type partner_id: object """ return 'partner_group_ids_{partnerId}'.format(partnerId = partner_id) @classmethod def __group_cache_key(cls, group_id): return 'group_{id}'.format(id = group_id) @classmethod def __many_group_cache_key(cls, group_id_list): return [cls.__group_cache_key(group_id) for group_id in group_id_list] @classmethod def __group_id_from_group_cache_key(cls, group_cache_key): # type: (str)->str return group_cache_key.split('_')[1] @staticmethod def __default_group_id_key(dealer_id): return '{}_default_group_id'.format(dealer_id) @classmethod def invalid_group_cache(cls, groupIds): # type: (list)->None cache.delete_many(cls.__many_group_cache_key(groupIds)) @classmethod def set_group_cache(cls, group_id, value): # type: (str, GroupDict)->None cache.set(cls.__group_cache_key(group_id), json_dumps(value, serialize_type = True)) @classmethod def get_group_cache(cls, group_id): # type: (str)->Union[GroupDict, None] cache_value = cache.get(cls.__group_cache_key(group_id)) if cache_value: return GroupDict(json_loads(cache_value)) else: return None @classmethod def get_many_group_cache(cls, group_id_list): # type: (list)->dict group_id_key_list = cls.__many_group_cache_key(group_id_list) rv = {} for group_cache_key, value in cache.get_many(group_id_key_list).iteritems(): group_id = cls.__group_id_from_group_cache_key(group_cache_key) rv[group_id] = GroupDict(json.loads(value)) return rv @classmethod def invalid_group_ids_of_dealer_cache(cls, dealer_id): # type: (str)->None cache.delete(cls.__group_ids_of_dealer_cache_key(dealer_id)) @classmethod def set_group_ids_of_dealer_cache(cls, dealer_id, value): # type: (str, list)->None cache.set(cls.__group_ids_of_dealer_cache_key(dealer_id), value) @classmethod def get_group_ids_of_dealer_cache(cls, dealer_id): # type: (str)->Union[list, None] return cache.get(cls.__group_ids_of_dealer_cache_key(dealer_id)) @classmethod def invalid_group_ids_of_partner_cache(cls, partner_id): # type: (str)->None cache.delete(cls.__group_ids_of_partner_cache_key(partner_id)) @classmethod def set_group_ids_of_partner_cache(cls, partner_id, value): # type: (str, list)->None cache.set(cls.__group_ids_of_partner_cache_key(partner_id), value) @classmethod def get_group_ids_of_partner_cache(cls, partner_id): # type: (str)->Union[list, None] return cache.get(cls.__group_ids_of_partner_cache_key(partner_id)) @classmethod def invalid_group_ids_of_partner_and_dealer_cache(cls, owner_id): # type: (str)->None cache.delete_many([cls.__group_ids_of_dealer_cache_key(owner_id), cls.__group_ids_of_partner_cache_key(owner_id)]) @classmethod def get_default_group_id(cls, dealer_id): # type: (str)->str return cache.get(cls.__default_group_id_key(dealer_id)) @classmethod def set_default_group_id(cls, dealer_id, group_id): return cache.set(cls.__default_group_id_key(dealer_id), group_id) class DeviceControlInfo(Searchable): devNo = StringField(verbose_name = u"设备编号", min_length = 1, unique = True) value = StringField(verbose_name = u'设备控制信息', default = '') meta = { 'collection': 'device_control_info', 'db_alias': 'logdata' } @staticmethod def get(devNo): try: obj = DeviceControlInfo.objects.get(devNo = devNo) result = json.loads(obj.value) return result except DoesNotExist, e: pass return {} @staticmethod def set(devNo, valueDict): try: value = json_dumps(valueDict) DeviceControlInfo.get_collection().update({'devNo': devNo}, {'$set': {'devNo': devNo, 'value': value}}, upsert = True) except Exception, e: logger.info('set device=%s json e=%s' % (devNo, e)) @staticmethod def delete(devNo): try: DeviceControlInfo.get_collection().remove({'devNo': devNo}) except Exception, e: logger.info('remove device=%s control info e=%s' % (devNo, e)) class GroupLinkageSwitch(DynamicEmbeddedDocument): """ 设备组开关项 """ chargeInsurance = BooleanField(default=False, verbose_name=u"充电险开关") def to_dict(self): return {"chargeInsurance": self.chargeInsurance} class Group(Searchable): ownerId = StringField(verbose_name = "所有者", default = "") groupName = StringField(verbose_name = "名称", default = "") address = StringField(verbose_name = "地址", default = "") districtId = StringField(verbose_name = "地域", default = "") addressType = StringField(verbose_name = "类型", default = "") ruleDict = DictField(verbose_name = "充值规则", default = Const.DEFAULT_DISCOUNT_RULE) cardRuleDict = DictField(verbose_name = "卡充值规则", default = {}) partnerList = ListField(verbose_name = "合伙人", default = []) isDefault = BooleanField(verbose_name = "是否默认地址", default = True) isFree = BooleanField(verbose_name = "是否供免费使用", default = False) # 功能形按钮 popPriceDescriptionButton = BooleanField(verbose_name = "扫码后是否弹出计费规则", default = False) beforeCharge = BooleanField(verbose_name = "强制充值", default = False) dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = '添加进来的时间') dateTimeUpdated = DateTimeField(default = datetime.datetime.now, verbose_name = '更新时间') country = StringField(verbose_name = '镇村', default = '') town = StringField(verbose_name = '镇区', default = '') village = StringField(verbose_name = '村居委会', default = '') monthlyPackage = LazyReferenceField(verbose_name = "包月套餐", document_type = MonthlyPackageTemp) currencyGroup = StringField(verbose_name = u"地址标签", default = '') maxVCard = IntField(verbose_name = u"可发售的虚拟卡的数量", default = 9999) otherConf = DictField(verbose_name = u'用于扩充记录', default = {'elecFee': 1.0, 'devElec': 0.2}) linkageSwitch = EmbeddedDocumentField(document_type=GroupLinkageSwitch, verbose_name="开关", default=GroupLinkageSwitch) swapFlag = BooleanField(verbose_name = "互联互通标记", default = False) meta = { "collection": "Group", "db_alias": "default", 'indexes': [ { 'fields': ['ownerId', 'groupName'], 'unique': True } ], } CacheMgr = GroupCacheMgr def __repr__(self): return '' % (self.id,) @property def groupId(self): """ 兼容GroupDict接口 :return: """ return str(self.id) @property def monthlyRule(self): if self.monthlyPackage: package = self.monthlyPackage.fetch() # type: MonthlyPackageTemp else: from apps.web.dealer.models import Dealer dealer = Dealer.objects.get(id = self.ownerId) package = dealer.defaultMonthlyPackage # type: MonthlyPackageTemp if package.saleable: return package return None @monthlyRule.setter def monthlyRule(self, package): self.update(monthlyPackage = package) # 覆写. 需要对ruleDict参数进行一次转换 def update(self, **kwargs): if 'ruleDict' in kwargs: rule_dict = kwargs.pop('ruleDict') kwargs.update({'ruleDict': format_dot_key(rule_dict)}) if 'cardRuleDict' in kwargs: card_rule_dict = kwargs.pop('cardRuleDict') kwargs.update({'cardRuleDict': format_dot_key(card_rule_dict)}) update_result = super(Group, self).update(full_result = True, **kwargs) # type: UpdateResult created = bool(update_result.upserted_id) updated = bool((not update_result.upserted_id) and update_result.matched_count and update_result.modified_count) if updated: self.CacheMgr.invalid_group_cache([str(self.id)]) return {'created': created, 'updated': updated, 'groupId': update_result.upserted_id} if created else { 'created': created, 'updated': updated, 'groupId': str(self.id) } def delete(self, signal_kwargs = None, **write_concern): super(Group, self).delete(signal_kwargs, **write_concern) self.CacheMgr.invalid_group_cache([str(self.id)]) @property def cached(self): return self.get_group(str(self.id)) @property def format_rule_dict(self): return format_dot_key(self.ruleDict, to_dot = True) @property def format_card_dict(self): return format_dot_key(self.cardRuleDict, to_dot = True) @classmethod def __update_group(cls, group, **update): # type: (Group, Dict)->str """ 实现比较别扭,利用以下几个事实: 如果是添加一个地址, old_if_default == new_if_default, old_partner_list == new_partner_list 如果更新一个地址,但是isDefault不更新, old_if_default == new_if_default 如果更新一个地址,但是partnerList不更新,old_partner_list == new_partner_list 注意利用update等接口,mongoengine不用更新model。这里也不想reload,直接利用传入参数来判断 :param group: :param update: :return: """ old_if_default = group.isDefault old_partner_list = group.partnerList new_if_default = update.get(Group.isDefault.name, old_if_default) new_partner_list = update.get(Group.partnerList.name, old_partner_list) update_result = group.update(upsert = True, **update) # type: dict try: group_id = update_result['groupId'] if (update_result['created'] and new_if_default) or ( update_result['updated'] and new_if_default and new_if_default != old_if_default): objs = Group.objects(ownerId = group.ownerId, id__ne = group_id, isDefault = True).filter() for obj in objs: try: obj.update(isDefault = False) except Exception as e: # 当添加第一个组的时候,会取不到旧的组 logger.debug('adding first group %s' % e) return group_id finally: if update_result['created']: cls.CacheMgr.invalid_group_ids_of_dealer_cache(group.ownerId) for partner in new_partner_list: cls.CacheMgr.invalid_group_ids_of_partner_cache(partner['id']) else: if new_if_default and new_if_default != old_if_default: cls.CacheMgr.set_default_group_id(group.ownerId, str(group.id)) old_partner_ids = set([partner['id'] for partner in old_partner_list]) new_partner_ids = set([partner['id'] for partner in new_partner_list]) need_invalid_partner_ids = old_partner_ids ^ new_partner_ids for partner_id in need_invalid_partner_ids: cls.CacheMgr.invalid_group_ids_of_partner_cache(partner_id) @classmethod def update_group(cls, group_id, **payload): group = Group.objects(id=str(group_id)).first() if not group: logger.debug('failed to get group %s' % (group_id,)) return False, u'地址不存在,请更新后重试', group_id if 'ownerId' in payload: ownerId = payload.pop('ownerId') if ownerId != group.ownerId: logger.error('set ownerId<{}> != groupOwnerId<{}>'.format(ownerId, group.ownerId)) return False, u'非经销商所属地址,请联系客服', group_id try: group_id = Group.__update_group(group, **payload) return True, 'success', group_id except NoGroupFound, e: logger.exception('group is not exist. groupId = %s, dealerId = %s' % (group_id, group.ownerId)) return False, u'组不存在,请更新后再试', group_id except Exception as e: logger.exception( 'save group error. exception = %s, groupId = %s, dealerId=%s' % (e, group_id, group.ownerId)) return False, u'更新失败,请重试', group_id @classmethod def add_group(cls, ownerId, name, districtId, address, addressType, isDefault, discountRuleDict, discountCardRuleDict, country, tag, beforeCharge): objs = cls.objects(ownerId=ownerId) # type: QuerySet if objs.count() >= 2000: return False, u'最多只能保存2000个地址', None group = objs.filter(groupName=name).first() if group: return False, u'相同名称的地址已经存在', None try: update = { 'districtId': districtId, 'address': address, 'addressType': addressType, 'isDefault': isDefault, 'ruleDict': discountRuleDict, 'cardRuleDict': discountCardRuleDict, 'country': country, 'tag': tag, 'beforeCharge': beforeCharge } group = cls( ownerId=ownerId, groupName=name ) group_id = cls.__update_group(group, **update) return True, 'success', group_id except Exception as e: logger.exception('add address error=%s' % e) return False, u'保存地址错误', None @classmethod def delete_Group(cls, ownerId, groupId): try: group = cls.objects(id = str(groupId)).first() # type: Group if not group: return False else: group.delete() cls.CacheMgr.invalid_group_ids_of_dealer_cache(ownerId) for partner in group.partnerList: cls.CacheMgr.invalid_group_ids_of_partner_cache(partner['id']) return True except Exception as e: logger.exception(e) return False @classmethod def __cache_and_get_group(cls, groupId): # type: (str)->Union[GroupDict, None] try: group = cls.objects(id = ObjectId(groupId)).first() # type: Group if not group: return None partnerDict = {} for partner in group.partnerList: partnerDict[partner['id']] = partner newRuleDict = format_dot_key(group['ruleDict'], to_dot = True) newCardRuleDict = format_dot_key(group["cardRuleDict"], to_dot = True) value = GroupDict({ 'groupId': str(group.id), 'address': group.address, 'districtId': group.districtId, 'addressType': group.addressType, 'isDefault': group.isDefault, 'groupName': group.groupName, 'ruleDict': newRuleDict, 'cardRuleDict': newCardRuleDict, 'partnerDict': partnerDict, 'ownerId': group.ownerId, 'isFree': group.isFree, 'beforeCharge': group.beforeCharge, 'country': group.country, 'town': group.town, 'village': group.village, 'dateTimeAdded': group.dateTimeAdded, 'currencyGroup': group.currencyGroup, 'maxVCard': group.maxVCard, 'linkageSwitch': group.linkageSwitch.to_dict(), 'swapFlag': group.swapFlag, 'popPriceDescriptionButton': group.popPriceDescriptionButton, "otherConf": group.otherConf }) cls.CacheMgr.set_group_cache(group_id = groupId, value = value) return value except Exception as e: logger.exception('get group = %s, error = %s' % (groupId, e)) return None @classmethod def get_group_ids_of_partner(cls, partner_id): partner_group_list = cls.CacheMgr.get_group_ids_of_partner_cache(partner_id = partner_id) if partner_group_list: return partner_group_list partner_group_list = [] try: rcds = Group.get_collection().find({'partnerList.id': partner_id}) for rcd in rcds: partner_group_list.append(str(rcd['_id'])) cls.CacheMgr.set_group_ids_of_partner_cache(partner_id, partner_group_list) except Exception, e: logger.exception('get partner group list error=%s' % e) return partner_group_list # 添加搜索(先筛选缓存,再找数据库) @classmethod def search_group_ids_of_partner(cls, partner_id, searchKey = None): partner_group_list = cls.CacheMgr.get_group_ids_of_partner_cache(partner_id = partner_id) if partner_group_list: if searchKey: for item in cls.get_groups_by_group_ids(partner_group_list).values(): if searchKey.upper() not in item[u'groupName'].upper(): partner_group_list.remove(item[u'groupId']) return partner_group_list partner_group_list = [] try: rcds = Group.get_collection().find({u'partnerList.id': partner_id}) for rcd in rcds: if searchKey.upper() in rcd[u'groupName'].upper(): partner_group_list.append(str(rcd[u'_id'])) except Exception, e: logger.exception('get partner group list error=%s' % e) return partner_group_list @classmethod def get_group_ids_of_dealer(cls, dealer_id): dealer_group_list = cls.CacheMgr.get_group_ids_of_dealer_cache(dealer_id) if dealer_group_list: assert isinstance(dealer_group_list, list), 'adrList has to be a list, %s was given, ownerId=%s' % ( dealer_group_list, dealer_id) return dealer_group_list dealer_group_list = [] try: objs = Group.objects.filter(ownerId = dealer_id) for obj in objs: dealer_group_list.append(str(obj.id)) if dealer_group_list: cls.CacheMgr.set_group_ids_of_dealer_cache(dealer_id = dealer_id, value = dealer_group_list) else: logger.debug('groups of dealer is zero.'.format(dealer_id)) except Exception as e: logger.exception('get group list error=%s' % e) return dealer_group_list # 添加搜索(先筛选缓存,再找数据库) @classmethod def search_group_ids_of_dealer(cls, dealer_id, searchKey = None): dealer_group_list = cls.CacheMgr.get_group_ids_of_dealer_cache(dealer_id) if dealer_group_list is not None: if searchKey: for item in cls.get_groups_by_group_ids(dealer_group_list).values(): if searchKey.upper() not in item['groupName'].upper(): dealer_group_list.remove(item['groupId']) assert isinstance(dealer_group_list, list), 'adrList has to be a list, %s was given, ownerId=%s' % ( dealer_group_list, dealer_id) return dealer_group_list dealer_group_list = [] try: objs = Group.objects.filter(ownerId = dealer_id, groupName__contains = searchKey) for obj in objs: dealer_group_list.append(str(obj.id)) except Exception, e: logger.exception('get group list error=%s' % e) return dealer_group_list @classmethod def get_group_ids_of_dealer_and_partner(cls, ownerId): return cls.get_group_ids_of_dealer(ownerId) + cls.get_group_ids_of_partner(ownerId) @classmethod def get_groups_by_group_ids(cls, groupIds): # type: (list)->dict value_dict = cls.CacheMgr.get_many_group_cache(group_id_list = groupIds) left_group_ids = list(set(groupIds) - set(value_dict.keys())) for group_id in left_group_ids: group_dict = cls.__cache_and_get_group(group_id) if group_dict: value_dict[group_id] = group_dict return value_dict @classmethod def get_groups_of_dealer(cls, dealerId): return cls.get_groups_by_group_ids(cls.get_group_ids_of_dealer(dealerId)).values() @classmethod def get_dealer_group(cls, dealerId, **kwargs): group = cls.objects.filter(ownerId = dealerId, **kwargs).first() return group @classmethod def get_group(cls, groupId): # type: (str)->GroupDict """ 没有找到组返回None """ if not groupId: return None group_dict = cls.CacheMgr.get_group_cache(groupId) if group_dict: return group_dict else: return cls.__cache_and_get_group(groupId) @classmethod def get_default_group_id(cls, dealer_id): group_id = cls.CacheMgr.get_default_group_id(dealer_id) if not group_id: default_group = Group.objects(ownerId = dealer_id, isDefault = True).first() if default_group: group_id = str(default_group.id) cls.CacheMgr.set_default_group_id(dealer_id, group_id) return group_id @classmethod def get_default_group(cls, dealer_id): default_group_id = cls.get_default_group_id(dealer_id) if not default_group_id: return None return cls.get_group(default_group_id) @classmethod def get_groupName_by_logicalCode(cls, logicalCode): groupId = Device.objects(logicalCode = logicalCode).first().groupId if groupId != '': return Group.get_group(groupId)['groupName'] else: return None @classmethod def get_group_ids_without_partner(cls, ownerId): gs = cls.objects(ownerId = ownerId) return [str(_.id) for _ in gs if _.partnerList is None or _.partnerList == []] @classmethod def get_all_partner_ids(cls, dealerId): groups = cls.objects.filter(ownerId = dealerId) partners = set() for _group in groups: if _group.partnerList: for _partner in _group.partnerList: partners.add(_partner["id"]) return partners @classmethod def get_lawful_partner_ids(cls, dealerId): """获取经销商的所有 有效的合伙人Id""" groups = cls.objects.filter(ownerId = dealerId) partners = list() for _group in groups: if not _group.partnerList: continue for _partner in _group.partnerList: if float(_partner["percent"]) <= 0: continue partners.append(_partner["id"]) return set(partners) @classmethod def check_virtual_card_number(cls, groupId): from apps.web.user.models import UserVirtualCard vCard = UserVirtualCard.objects.filter( groupIds__in = [groupId], expiredTime__gt = datetime.datetime.now() ) group = cls.objects.get(id = groupId) return group.maxVCard > vCard.count() @classmethod def invalid_group_cache(cls, groupIds): cls.CacheMgr.invalid_group_cache(groupIds) @classmethod def is_currency_mode_group(cls, groupId1, groupId2): # type: (str,str) -> bool if groupId1 == groupId2: return True group1 = cls.get_group(groupId1) group2 = cls.get_group(groupId2) if not all([group1, group2]): # 没有组 return False dealerId1 = group1.get('ownerId') dealerId2 = group2.get('ownerId') if dealerId1 != dealerId2: # 不是一个经销商 return False from apps.web.dealer.models import Dealer dealer = Dealer.objects.filter(id = dealerId1).first() if not dealer: return False currency_mode = dealer.currency_mode if currency_mode == 'allYes': return True elif currency_mode == 'allNo': return False elif currency_mode == 'asGroup': currencyGroup1 = group1.get('currencyGroup') # 此处默认为"" currencyGroup2 = group2.get('currencyGroup') if currencyGroup1 == currencyGroup2: # 在同一个通用规则下 return True else: return False elif currency_mode == '': # 默认不通用 return False else: pass def get_linkage_switch(self): return { LinkageSwitchEnum.CHARGE_INSURANCE: self.linkageSwitch.chargeInsurance } def turn_on(self, category): """ 打开保险的开关 """ if category == LinkageSwitchEnum.CHARGE_INSURANCE: self.linkageSwitch.chargeInsurance = True self.update(linkageSwitch__chargeInsurance=self.linkageSwitch.chargeInsurance) self.invalid_group_cache([str(self.id)]) def turn_off(self, category): """ 关闭保险的开关 """ if category == LinkageSwitchEnum.CHARGE_INSURANCE: self.linkageSwitch.chargeInsurance = False self.update(linkageSwitch__chargeInsurance=self.linkageSwitch.chargeInsurance) self.invalid_group_cache([str(self.id)]) class DeviceDefaultSetting(Searchable): """默认值修改,如默认清洗套餐等, 方便配置管理""" devType = StringField(verbose_name = "设备类型", unique = True) packages = ListField(DictField(), verbose_name = "套餐", default = []) meta = {"collection": "DeviceDefaultSetting", "db_alias": "default"} class MajorDeviceType(Searchable): name = StringField(verbose_name = u'主类型名称', unique = True) createdTime = DateTimeField(default = datetime.datetime.now, verbose_name = u'添加进来的时间') search_fields = ('name',) meta = {'collection': 'major_device_type', 'db_alias': 'default'} def __repr__(self): return '' % (self.name,) def to_dict(self): return { 'name': self.name, } class BillAsServiceFeature(dict): @property def support(self): return self.get('support', False) @property def on(self): if not self.support: return False return self.get('on', False) @property def elec_charge(self): return RMB(self.get('elecCharge', 0)) @property def service_charge(self): return RMB(self.get('serviceCharge', 0)) @property def charge_unit(self): if self.support: return { 'elecCharge': self.elec_charge, 'serviceCharge': self.service_charge } else: return None def current_elec_fee(self, consumeMoney): return RMB(float(consumeMoney) / float(self.elec_charge + self.service_charge) * float(self.elec_charge)) def current_service_fee(self, consumeMoney): return RMB(float(consumeMoney) / float(self.elec_charge + self.service_charge) * float(self.service_charge)) class DeviceType(Searchable): name = StringField(verbose_name = u'设备类型名称(经销商,代理商,厂商看到的名称)') alias = StringField(verbose_name = u'设备类型别名(扫码用户看到的类型)') code = StringField(verbose_name = '设备编码') online = BooleanField(verbose_name = '是否为在线设备') package = ListField(DictField(), verbose_name = '套餐', default = []) # : 有部分设备,如售货机,套餐的时间属性,并无意义,通过此字段可以跳过对时间的处理 timeBased = BooleanField(verbose_name = '计量是否以时间为基准', default = True) stock = BooleanField(verbose_name = '是否库存', default = False) instructions = StringField(default = '', verbose_name = '使用说明') dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = '添加进来的时间') unit = StringField(verbose_name = '消费单位', default = u'分钟') # 消费单位,多少钱**包/分钟/升/度 payableWhileBusy = BooleanField(verbose_name = u'是否支持端口正在使用时追加充值', default = False) displayCardCharge = BooleanField(verbose_name = u'是否支持套餐页面显示充卡入口', default = False) #: 每次注册累加,通过权重来排序 popularity = IntField(verbose_name = '展示权重', default = 0) agentId = StringField(verbose_name = '所属代理商') managerId = StringField(verbose_name = '所属厂商') supportDiagnostics = BooleanField(verbose_name = u'是否支持自主调测', default = False) supportVirtualCard = BooleanField(verbose_name = u'是否支持虚拟卡的使用', default = True) supportIcCardcharge = BooleanField(verbose_name = u'是否支持IC卡的充值', default = True) majorDeviceType = StringField(verbose_name = u'主类型名称', null = False) autoRefundEnable = BooleanField(verbose_name = u'是否支持自动退款', default = True) finishedMessageTemplateDict = DictField(verbose_name = u'结束推送信息模板字典', default = {}) finishedReasonDict = DictField(verbose_name = u'结束原因类型字典', default = {}) servicesButtonDict = DictField(verbose_name = u'正在服务显示按钮字典', default = {}) features = DictField(verbose_name = u'设备类型特性', default = {}) dailyRent = DictField(verbose_name=u"设备类型的日租特性", default={}) extraInfo = DictField(verbose_name=u"特定设备类型的特殊自定义信息", default={}) meta = {'collection': 'DeviceType', 'db_alias': 'default', 'unique_together': {'name', 'agentId'}} def __repr__(self): return '' % (self.code,) @classmethod def all(cls, agentId): return [_.to_dict() for _ in cls.objects(agentId = str(agentId)).all()] @classmethod def most_popular(cls): return cls.objects().order_by('-popularity').first() def to_dict(self): result = { 'id': str(self.id), 'name': self.name, 'alias': self.alias or self.name, 'code': self.code, 'online': self.online, 'package': self.package, 'timeBased': self.timeBased, 'stock': self.stock, 'unit': self.unit, 'instructions': self.instructions, 'payableWhileBusy': self.payableWhileBusy, 'majorDeviceType': self.majorDeviceType, 'supportDiagnostics': self.supportDiagnostics, 'supportVirtualCard': self.supportVirtualCard, 'features': self.features, 'autoRefundEnable': self.autoRefundEnable, 'displayCardCharge': self.displayCardCharge, 'extraInfo': dict() } for _k, _v in self.extraInfo.items(): result['extraInfo'].update({_k: _v}) return result @property def dict_for_register_device(self): return { 'id': str(self.id), 'name': self.name, 'code': self.code, 'online': self.online, 'timeBased': self.timeBased, 'stock': self.stock, 'unit': self.unit, 'instructions': self.instructions, 'payableWhileBusy': self.payableWhileBusy, 'majorDeviceType': self.majorDeviceType, 'supportDiagnostics': self.supportDiagnostics, 'supportVirtualCard': self.supportVirtualCard, 'features': self.features, 'autoRefundEnable': self.autoRefundEnable, 'displayCardCharge': self.displayCardCharge } @classmethod def get_by_ids(cls, ids): return cls.objects.filter(id__in = ids) @classmethod def get_by_id(cls, id_): try: return cls.objects.filter(id = id_).first() except ValidationError: logger.exception('') return None @classmethod def get_services_button(cls, id): return cls.objects(id = id).first().servicesButtonDict @property def supportRent(self): """ 设备类型是否支持租售 :return: """ return bool(self.dailyRent) @property def rentTerm(self): """ 设备类型的租期 :return: """ if not self.supportRent: raise RentDeviceError(u"该设备类型不支持出租") return self.dailyRent["rentTerm"] @property def rentMoney(self): """ 该设备类型的单日租金 :return: """ if not self.supportRent: raise RentDeviceError(u"该设备类型不支持出租") return self.dailyRent["rentMoney"] @property def maxWaitForActive(self): if not self.supportRent: raise RentDeviceError(u"该设备类型不支持出租") return self.dailyRent["maxWaitForActive"] @property def bill_as_service_feature(self): # type: ()->BillAsServiceFeature return BillAsServiceFeature(self.features.get('billAsService', {})) @classmethod def is_pulse_device_type(cls, device_type_code): if not device_type_code: return False if device_type_code in [Const.DEVICE_TYPE_CODE_HP_GATE, Const.DEVICE_TYPE_CODE_QUICK_CHARGE]: return False if PULSE_DEV_TYPE_RE.match(device_type_code): return True else: return False @classmethod def is_bt_device_type(cls, device_type_code): if not device_type_code: return False if BT_DEV_TYPE_RE.match(device_type_code): return True else: return False @classmethod def find_candidate(cls, devObj, dealer): # type: (Device, Dealer)->Tuple[List, List] def is_only_pulse_soft_version(softVer): for item in SOFT_VER_ONLY_PULSE_RE_LIST: if item.match(softVer): return True else: return False def is_hw_support_pulse(device): support = True if device.softVer.startswith('v6.'): if device.hwVer in ['2gsck', 'pureuart', 'flb', '4gcat1dual']: support = False elif device.mf == 'Quectel': pass else: for item in SOFT_VER_NO_PULSE_RE_LIST: if item.match(device.softVer): support = False break return support def handle_bt(devObj, dealer): # type: (Device, Dealer)->Tuple[List, List] recommend, all = [], [] lastObj = Device.objects(__raw__ = { 'ownerId': str(dealer.id), 'logicalCode': {'$regex': '^[^B]'} }).order_by('-dateTimeUpdated').first() if lastObj and lastObj.devType and lastObj.devType.get('id') and lastObj.devType.get('code'): if DeviceType.is_bt_device_type(lastObj.devType.get('code')): lastDevType = lastObj.devType else: lastDevType = None else: lastDevType = None last_id_equal_list = [] last_type_equal_list = [] bt_list = [] try: devTypes = DeviceTypeVisitor().visit(dealer) except Exception as e: logger.warning('dealer exception in device type visitor.'.format(str(dealer.id))) devTypes = [] for devType in devTypes: if not DeviceType.is_bt_device_type(devType.code): continue if lastDevType: if str(devType.id) == lastDevType.get('id') and str(devType.code) == lastDevType.get('code'): last_id_equal_list.append(devType.to_dict()) continue elif str(devType.code) == lastDevType.get('code'): last_type_equal_list.append(devType.to_dict()) continue bt_list.append(devType.to_dict()) if last_id_equal_list: recommend = last_id_equal_list all.extend(recommend) all.extend(last_type_equal_list) else: recommend = last_type_equal_list all.extend(recommend) all.extend(bt_list) return recommend, all def handle_pulse_driver_code(devObj, dealer): # type: (Device, Dealer)->Tuple[List, List] hw_support_pulse = is_hw_support_pulse(device = devObj) recommend, all = [], [] if not hw_support_pulse: return recommend, all lastObj = Device.objects(ownerId = str(dealer.id), driverCode = devObj.driverCode).order_by( '-dateTimeUpdated').first() if lastObj and lastObj.devType and lastObj.devType.get('id') and lastObj.devType.get('code'): if DeviceType.is_pulse_device_type(lastObj.devType.get('code')): lastDevType = lastObj.devType else: lastDevType = None else: lastDevType = None last_id_equal_list = [] last_type_equal_list = [] pulse_list = [] try: devTypes = DeviceTypeVisitor().visit(dealer) except Exception as e: logger.warning('dealer exception in device type visitor.'.format(str(dealer.id))) devTypes = [] for devType in devTypes: if not DeviceType.is_pulse_device_type(devType.code): continue if lastDevType: if str(devType.id) == lastDevType.get('id') and str(devType.code) == lastDevType.get('code'): last_id_equal_list.append(devType.to_dict()) continue elif str(devType.code) == lastDevType.get('code'): last_type_equal_list.append(devType.to_dict()) continue pulse_list.append(devType.to_dict()) if last_id_equal_list: recommend = last_id_equal_list all.extend(recommend) all.extend(last_type_equal_list) else: recommend = last_type_equal_list all.extend(recommend) all.extend(pulse_list) return recommend, all def handle_not_pulse_driver_code(devObj, dealer): hw_support_pulse = is_hw_support_pulse(device = devObj) supportDevTypes = DeviceTypeDriverRelation.support_device_type_list(devObj.driverCode) if (not supportDevTypes) and (not hw_support_pulse): logger.warning('driver code {} is not registered.'.format(devObj.driverCode)) return [], [] lastObj = Device.objects(ownerId = str(dealer.id), driverCode = devObj.driverCode).order_by( '-dateTimeUpdated').first() if lastObj and lastObj.devType and lastObj.devType.get('id') and lastObj.devType.get('code'): lastDevType = lastObj.devType lastDevTypeCode = lastDevType.get('code') if not lastDevTypeCode: lastDevType = None else: if DeviceType.is_pulse_device_type(lastDevTypeCode): if not hw_support_pulse: lastDevType = None else: if lastDevTypeCode not in supportDevTypes: lastDevType = None else: lastDevType = None recommend, all = [], [] pulse_list = [] equal_list = [] no_pulse_list = [] last_id_equal_list = [] last_type_equal_list = [] try: devTypes = DeviceTypeVisitor().visit(dealer) except Exception as e: logger.warning('dealer exception in device type visitor.'.format(str(dealer.id))) devTypes = [] for devType in devTypes: if lastDevType: if str(devType.id) == lastDevType.get('id') and devType.code == lastDevType.get('code'): last_id_equal_list.append(devType.to_dict()) continue elif str(devType.code) == lastDevType.get('code'): last_type_equal_list.append(devType.to_dict()) continue if str(devType.code) not in supportDevTypes: if DeviceType.is_pulse_device_type(str(devType.code)): if hw_support_pulse: pulse_list.append(devType.to_dict()) continue else: continue else: if devType.code == devObj.driverCode: equal_list.append(devType.to_dict()) else: no_pulse_list.append(devType.to_dict()) if last_id_equal_list: recommend = last_id_equal_list all.extend(recommend) all.extend(last_type_equal_list) else: recommend = last_type_equal_list all.extend(recommend) all.extend(equal_list) all.extend(no_pulse_list) all.extend(pulse_list) return recommend, all def handle_no_driver_code(devObj, dealer): # type: (Device, Dealer)->Tuple[List, List] recommend, all = [], [] if devObj.softVer and is_only_pulse_soft_version(devObj.softVer): only_pulse = True else: only_pulse = False try: devTypes = DeviceTypeVisitor().visit(dealer) except Exception as e: logger.warning('dealer exception in device type visitor.'.format(str(dealer.id))) devTypes = [] for devType in devTypes: if only_pulse: if DeviceType.is_pulse_device_type(devType.code): all.append(devType.to_dict()) else: all.append(devType.to_dict()) return recommend, all def handle_tcp_device(devObj, dealer): lastObj = Device.objects(ownerId = str(dealer.id), driverCode = devObj.driverCode).order_by( '-dateTimeUpdated').first() if lastObj and lastObj.devType and lastObj.devType.get('id') and lastObj.devType.get('code'): lastDevType = lastObj.devType else: lastDevType = None recommend, all = [], [] last_id_equal_list = [] last_type_equal_list = [] match_list = [] match_re = re.compile('^{}[0-9]{}$'.format(devObj.driverCode[0:2], devObj.driverCode[3:])) try: devTypes = DeviceTypeVisitor().visit(dealer) except Exception as e: logger.warning('dealer exception in device type visitor.'.format(str(dealer.id))) devTypes = [] for devType in devTypes: if not match_re.match(devType.code): continue if lastDevType: if str(devType.id) == lastDevType.get('id') and match_re.match(devType.code): last_id_equal_list.append(devType.to_dict()) continue elif str(devType.code) == lastDevType.get('code') and match_re.match(devType.code): last_type_equal_list.append(devType.to_dict()) continue match_list.append(devType.to_dict()) if last_id_equal_list: recommend = last_id_equal_list all.extend(recommend) all.extend(last_type_equal_list) else: recommend = last_type_equal_list all.extend(recommend) all.extend(match_list) return recommend, all if Device.utils_is_bluetooth(devObj.logicalCode): return handle_bt(devObj, dealer) elif devObj.driverCode: if TCP_DEV_TYPE_RE.match(devObj.driverCode): return handle_tcp_device(devObj, dealer) elif devObj.driverCode in ['000000', '999999']: # 没有驱动版本, 这个无法注册, 必须先处理问题 return [], [] elif devObj.driverCode in ['100000', '100120']: # 仅支持脉冲的驱动版本 return handle_pulse_driver_code(devObj, dealer) else: if devObj.softVer and is_only_pulse_soft_version(devObj.softVer): # 只支持脉冲的软件版本 return handle_pulse_driver_code(devObj, dealer) else: return handle_not_pulse_driver_code(devObj, dealer) else: if devObj.softVer and is_only_pulse_soft_version(devObj.softVer): # 只支持脉冲的软件版本 return handle_pulse_driver_code(devObj, dealer) else: return handle_no_driver_code(devObj, dealer) class DeviceTypeDriverRelation(DynamicDocument): driverCode = StringField(required = True, null = False) devTypeCode = StringField(required = True, null = False) meta = { 'collection': 'device_type_driver_relation', 'db_alias': 'default', "indexes": [ 'driverCode', {'fields': ['driverCode', 'devTypeCode'], 'unique': True} ], } @classmethod def support_device_type_list(cls, driverCode): return [str(obj.devTypeCode) for obj in cls.objects(driverCode = driverCode).all()] class DeviceCacheMgr(object): LOGICAL_CODE_KEY = 'logicalCode{}' DEVNO_UNDER_GROUP_KEY = '{groupId}_devList' @classmethod def __l_cache_key(cls, logicalCode): return cls.LOGICAL_CODE_KEY.format(logicalCode) @classmethod def delete_l_cache(cls, logicalCode): cache.delete(cls.__l_cache_key(logicalCode)) @classmethod def update_l_cache(cls, devNo, logicalCode): cache.set(cls.__l_cache_key(logicalCode), str(devNo)) @classmethod def get_dev_no_from_l_cache(cls, logicalCode): return cache.get(cls.__l_cache_key(logicalCode)) @classmethod def __dev_no_list_of_group_cache_key(cls, group_id): # type:(str)->str return cls.DEVNO_UNDER_GROUP_KEY.format(groupId = group_id) @classmethod def set_dev_no_list_of_group_cache(cls, group_id, dev_no_list): # type: (str, list)->None cache.set(cls.__dev_no_list_of_group_cache_key(group_id = group_id), dev_no_list) @classmethod def get_dev_no_list_of_group_cache(cls, group_id): return cache.get(cls.__dev_no_list_of_group_cache_key(group_id)) @classmethod def delete_dev_no_list_of_group_cache(cls, group_id): cache.delete(cls.__dev_no_list_of_group_cache_key(group_id = group_id)) @classmethod def get_many_dev_no_list_of_group_cache(cls, group_id_list): return { parse(cls.DEVNO_UNDER_GROUP_KEY, key).named['groupId']: items for key, items in cache.get_many([cls.__dev_no_list_of_group_cache_key(_) for _ in group_id_list]).iteritems() } @classmethod def delete_many_dev_no_list_of_group_cache(cls, group_id_list): # type: (List[str])->None cache.delete_many([cls.__dev_no_list_of_group_cache_key(group_id = groupId) for groupId in group_id_list]) @classmethod def delete_device_cache(cls, dev_no): cache.delete(dev_no) @classmethod def delete_many_device_cache(cls, dev_no_list): cache.delete_many(dev_no_list) @staticmethod def update_device_cache(devNo, devValue): cache.set(devNo, devValue) @classmethod def get_many_device_cache(cls, dev_no_list): return cache.get_many(dev_no_list) @classmethod def get_device_cache(cls, dev_no): return cache.get(dev_no) class DeviceRegisterLog(EmbeddedDocument): operation = StringField(verbose_name = u'注册行为(register,unregister)') ownerId = ObjectIdField(verbose_name = u'经销商ID') dateTimeAdded = DateTimeField(verbose_name = u'设备注册时间', default = lambda: datetime.datetime.now()) operator = StringField(verbose_name = u'操作员', default = '') extra = DictField(verbose_name = u'额外信息', default = {}) class DailyRent(EmbeddedDocument): """ 设备日租相关的特性 """ # 与设备相关的 active = BooleanField(verbose_name=u"是否激活", default=False) # 租金与租期与设备类型相互绑定 租金的单位是元 租期的单位是天 rentMoney = MonetaryField(verbose_name=u"单日租金") rentTerm = IntField(verbose_name=u"租期") # 激活的截至时间 超过这个时间系统会自动激活 # 设备被标记为出租设备的日期 rentDate = DateTimeField(verbose_name=u"出租日期") # 设备的截至激活日期 超过这个时间 设备将会自动激活 lastActiveDate = DateTimeField(verbose_name=u"激活截至日期") # 设备的实际激活时间 activeDate = DateTimeField(verbose_name=u"激活日期") # 结算相关 lastRentDate = DateField(verbose_name=u"最后结算的日期 结算到了哪一天") class Device(Searchable): devNo = StringField(verbose_name = u"设备编号", min_length = 1, unique = True) logicalCode = StringField(verbose_name = u"逻辑编码", default = None) devType = DictField(verbose_name = "设备类型", default = {}) maxCoins = IntField(verbose_name = "最大投币数", default = 4) groupId = StringField(verbose_name = "地址ID", default = "") districtId = StringField(verbose_name = "地域", default = "") ownerId = StringField(verbose_name = "所有者", default = "") groupNumber = StringField(verbose_name = "设备组内编号", default = "") server = StringField(verbose_name = "server", default = "120.27.251.159:1883") server1 = StringField(verbose_name = "IP1", default = "") mf = StringField(verbose_name = "厂商", default = "") softVer = StringField(verbose_name = "软件版本", default = "") hwVer = StringField(verbose_name = "固件版本", default = "") cycle = IntField(verbose_name = "心跳间隔", default = Const.DEV_CYCLE_DEFAULT) washConfig = DictField(verbose_name = "设备配置", default = {}) tempWashConfig = DictField(verbose_name = "临时设备配置", default = {}) trapSwtich = IntField(verbose_name = "trap开关", default = 1) heartSwitch = StringField(verbose_name = "心跳开关", default = "") remarks = StringField(verbose_name = "备注", default = "") signal = IntField(verbose_name = "信号", default = 0) pulseWidth1 = IntField(verbose_name = "脉冲宽度", default = 40) pulseInterval1 = IntField(verbose_name = "脉冲间隔", default = 500) battery = IntField(verbose_name = "待机电平", default = 0) isFault = BooleanField(verbose_name = "是否故障停用", default = False) isDND = BooleanField(verbose_name = u'是否勿扰模式', default = False) isDNDTimeInterval = ListField(verbose_name = u'勿扰模式时间区间', default = []) dateTimeAdded = StringField(default = lambda: datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), verbose_name = '设备添加进来的时间') # TODO: 这个名字不太合适 dateTimeUpdated = DateTimeField(default = lambda: datetime.datetime.now(), verbose_name = '经销商注册时间, 其他操作不能按照字段名来更新, 只能在注册的时候更新该时间') location = PointField(default = None, verbose_name = '设备经纬度') expireDate = DateTimeField(default = None, verbose_name = '流量卡费用截止时间') # 废弃掉 simExpireDate = DateTimeField(default = None, verbose_name = 'SIM卡费用过期时间(数据来自运营商)') simActiveDate = DateTimeField(default = None, verbose_name = 'SIM卡激活时间(数据来自运营商)') simActiveFirstTime = DateTimeField(default = None, verbose_name = 'SIM卡激活时间(数据来自设备上报事件)') simStatus = StringField(default = SimStatus.Updated, verbose_name = 'sim卡的状态') # updated:表示已经更新了 ;chargedUnupdated充值了,但是未更新时间;illegal 非法卡 simChargeAuto = BooleanField(default = False, verbose_name = u'sim卡自动充值与否') simSource = StringField(default = '', verbose_name = 'sim卡商') # jieyang/hezhou/qiben serviceState = StringField(default = Const.ServiceState.Normal.name, verbose_name = '服务状态Const.ServiceState') instructions = StringField(default = '', verbose_name = '设备自定义使用说明', max_length = 100) autoRefundLeftMoney = BooleanField(verbose_name = '是否支持设备自动退款', default = False) imsi = StringField(default = '', verbose_name = 'imsi') iccid = StringField(default = '', verbose_name = 'iccid') iccidHistory = ListField(field = StringField(), verbose_name = u'SIM卡换卡记录', default = []) otherConf = DictField(verbose_name = u'各种设备其他配置', default = {}) driverCode = StringField(default = '', verbose_name = 'driverCode') driverVersion = StringField(default = '', verbose_name = 'driverVersion') mcuVersion = StringField(default = '', verbose_name = 'mcuVersion') coreVer = StringField(default = '', verbose_name = u'核心版本') boardValid = IntField(verbose_name = u'主板检测电压有效值(0 - 低电平有效, 1 - 高电平有效, 2 - 不做检测)', default = 2) # boardVolt = IntField(verbose_name=u'主板检测电压(0 - 低电平, 1 - 高电平)', default=0) annualTrafficCost = MonetaryField(default = Const.PLATFORM_DEFAULT_TRAFFIC_COST, verbose_name = '该字段废弃') trafficCardCost = MonetaryField(verbose_name = "流量卡的真实成本", default = None) dateTimeBinded = DateTimeField(verbose_name = u'设备绑定时间', default = None) debug = StringField(verbose_name = u'调试标记', default = '') registerLog = EmbeddedDocumentListField(document_type = DeviceRegisterLog, verbose_name = u'设备(解)注册日志') stockDetailDict = DictField(verbose_name = u'库存详情', default = {}) nodeDict = DictField(verbose_name = u'节点字典', default = {}) # {'1':devNo} gatewayNode = StringField(verbose_name = u'网关节点 devNo', default = '') isRent = BooleanField(verbose_name=u"设备是否出租", default=False) dailyRent = EmbeddedDocumentField(document_type=DailyRent, verbose_name=u"设备出租的相关信息") binder = StringField(verbose_name = u'绑定者标识', default = '') isApi = BooleanField(verbose_name=u"是否未API设备", default=None) disableADExpireDate = DateTimeField(verbose_name=u'纯净版有效期', default=None) policyTemp = DictField(verbose_name=u'计费模板', default={}) meta = { 'collection': 'Device', 'db_alias': 'default', # 'shard_key':('devNo',) } search_fields = ('devNo', 'logicalCode') __mgr_cache = caches['devmgr'] __base_cache_mgr = DeviceCacheMgr def __str__(self): return '{}'.format(self.__class__.__name__, self.logicalCode, self.devNo) @property def devTypeCode(self): return self.devType.get('code', '') @property def devTypeName(self): return self.devType.get('name', '') @property def devTypeFeatures(self): return self.devType.get('features', {}) @property def bill_as_service_feature(self): # type: ()->BillAsServiceFeature return BillAsServiceFeature(self.devTypeFeatures.get('billAsService', {})) def rent(self): """ 将设备标记为出租类型 标记前必须是已经注册的设备 除了激活信息外 其余所有出租信息均已在此添加 :return: """ if self.isRent: raise RentDeviceError(u"设备已经出租,请勿重复操作!") # 出租日期 rentDate = datetime.datetime.now() devType = DeviceType.objects.get(id=self.devType["id"]) # type: DeviceType # 添加激活 的相关信息 dailyRent = DailyRent( active=False, rentMoney=RMB(devType.rentMoney), rentTerm=int(devType.rentTerm), rentDate=rentDate, lastActiveDate=rentDate + datetime.timedelta(days=devType.maxWaitForActive), ) # 兼容之前的没有isRent的设备 result = self.__class__.objects.filter( Q(id=self.id), Q(isRent=False) | Q(isRent=None) ).update( isRent=True, dailyRent=dailyRent ) if not result: raise RentDeviceError(u"操作失败") # TODO 禁用设别(如何禁用) def active_rent(self): """ 激活出租的设备 设备激活后 从激活的当天开始收取日租费用 激活的设备不能再次激活 """ if self.dailyRent.active: raise RentDeviceError(u"请勿重复激活设备") # 更正激活状态和日期 self.__class__.objects.filter( id=self.id, dailyRent__active=False ).update( dailyRent__activeDate=datetime.datetime.now(), dailyRent__active=True ) @property def rentMoney(self): """ 设备的租金 """ return RMB(self.dailyRent.rentMoney) def __hash__(self): return hash(self.devNo) @classmethod def __fill_up_online_info(cls, device, online_cache_info): # type: (dict, dict)->dict device.update({ 'online': DeviceOnlineStatus.DEV_STATUS_OFFLINE, 'offTime': 0, 'signal': 0, 'updateTime': 0 }) try: # 蓝牙或者特殊的调试标记的模块始终在线 if cls.utils_is_bluetooth(device[cls.logicalCode.name]) or device.get('debug', '') == 'fake_online': device.update({ 'online': DeviceOnlineStatus.DEV_STATUS_ONLINE, 'updateTime': long(time.time() * 1000) }) return device except Exception as e: pass if online_cache_info: online_info = json.loads(online_cache_info) if online_info['online'] == DeviceOnlineStatus.DEV_STATUS_ONLINE: cycle = device['cycle'] if device['cycle'] else Const.DEV_CYCLE_DEFAULT if (long(time.time() * 1000) - long( online_info['updateTime'])) > Const.DEVICE_ONLINE_CHECK_TIMES * cycle * 1000: device.update( { 'online': DeviceOnlineStatus.DEV_STATUS_OFFLINE, 'updateTime': long(online_info['updateTime']), 'offTime': Const.DEVICE_ONLINE_CHECK_TIMES * cycle * 1000 + long(online_info['updateTime']) } ) else: device.update( { 'online': DeviceOnlineStatus.DEV_STATUS_ONLINE, 'updateTime': long(online_info['updateTime']) } ) elif online_info['online'] == DeviceOnlineStatus.DEV_STATUS_OFFLINE: device.update( { 'online': DeviceOnlineStatus.DEV_STATUS_OFFLINE, 'offTime': long(online_info['offTime']) } ) device.update({'signal': online_info.get('signal', 0)}) return device @classmethod def __fill_up_control_info(cls, device, control_cache_info): # type: (dict, dict)->dict # TODO zjl 将设备的控制信息与故障信息拆开 device.update({ 'status': Const.DEV_WORK_STATUS_IDLE, 'statusInfo': '', }) if control_cache_info: if 'status' not in control_cache_info or 'usePorts' in control_cache_info: try: ports_cache_info = control_cache_info if 'ports' in control_cache_info: ports_cache_info = control_cache_info['ports'] for key, value in ports_cache_info.iteritems(): if str(key).isdigit(): # 如果有isStart并且为false, 说明该端口已经结束 if 'isStart' in value: if not value['isStart']: continue # 没有isStart或者isStart为True, 还需要使用status和finishedTime校正 # 在没有收到结束事件的时候, 端口也有一个预估的结束时间, 超过这个时间, 也认为结束 if 'status' in value: if 'finishedTime' in value: if value['finishedTime'] > int(time.time()): device.update({'status': Const.DEV_WORK_STATUS_WORKING}) break else: if value['status'] == Const.DEV_WORK_STATUS_WORKING: device.update({'status': Const.DEV_WORK_STATUS_WORKING}) break # 如果都没有, 则认为端口空闲 except Exception as e: logger.exception(e) device.update({'status': Const.DEV_WORK_STATUS_IDLE}) else: if control_cache_info['status'] == Const.DEV_WORK_STATUS_WORKING: if control_cache_info.get('finishedTime', 0) < int(time.time()): device.update({'status': Const.DEV_WORK_STATUS_IDLE}) else: device.update({'status': Const.DEV_WORK_STATUS_WORKING}) # elif control_cache_info['status'] == Const.DEV_WORK_STATUS_FAULT: # device.update( # {'status': Const.DEV_WORK_STATUS_FAULT, # 'statusInfo': control_cache_info.get('statusInfo', u'故障')}) else: device.update({'status': control_cache_info['status']}) else: device.update({'status': Const.DEV_WORK_STATUS_IDLE}) return device @classmethod def __fill_up_warning_info(cls, device, warning_cache_info): """标记设备是否处于告警状态""" warning_cache_info = warning_cache_info or dict() for _part, _warning in warning_cache_info.items(): if _warning.get("warningStatus", False): warning = True break else: warning = False device["deviceWarning"] = warning return device @classmethod def __fill_up_status_info(cls, device, online_cache_info, service_cache_info, warning_cache_info): # type: (dict, dict, dict, dict)->dict device = cls.__fill_up_online_info(device, online_cache_info) device = cls.__fill_up_control_info(device, service_cache_info) device = cls.__fill_up_warning_info(device, warning_cache_info) return device @classmethod def utils_is_expired(cls, device): # type:(Union[Device, DeviceDict])->bool sim_status = device.simStatus if sim_status == SimStatus.Charged: return False if sim_status == SimStatus.Illegal: return True # 没有拿到sim卡信息的,可能是才上线,运营商都没有数据。还有一种情况,是换卡(我们家的别人家的) sim_expire_date = device.fixedSimExpireDate if not sim_expire_date: return False # 10天内必须充值,不让用 return datetime.datetime.now() >= sim_expire_date @classmethod def utils_is_expired_in_this_month(cls, device): # type:(Union[Device, DeviceDict])->bool sim_status = device.simStatus if sim_status == SimStatus.Charged: return False if sim_status == SimStatus.Illegal: return True sim_expire_date = device.fixedSimExpireDate # type: datetime.datetime if not sim_expire_date: return False now = datetime.datetime.now() return now.year == sim_expire_date.year and now.month == sim_expire_date.month @classmethod def utils_is_registered(cls, device): # type: (Union[Device, DeviceDict])->bool owner_id = device.ownerId if owner_id and owner_id != '': return True else: return False @classmethod def utils_is_bluetooth(cls, logicalCode): # type: (str)->bool if logicalCode.startswith('B'): return True else: return False @classmethod def utils_channel_type(cls, device): # type: (Union[Device, DeviceDict])->str if cls.utils_is_bluetooth(device.logicalCode): return DeviceChannelType.Channel_BT core_ver = device.coreVer if not core_ver: return DeviceChannelType.Channel_UNKNOWN if 'ASR1802' in core_ver or 'RDA8910' in core_ver: if device.mf == 'vivestone_mesh_gate': return DeviceChannelType.Channel_4G_GATE else: return DeviceChannelType.Channel_4G if 'LuaRTOS' in core_ver: return DeviceChannelType.Channel_WIFI if '8955' in core_ver: return DeviceChannelType.Channel_2G return DeviceChannelType.Channel_UNKNOWN @classmethod def utils_major_type(cls, device): # type: (Union[Device, DeviceDict])->str if not cls.utils_is_registered(device): return '' dev_type = device.devType majorDeviceType = dev_type.get('majorDeviceType', None) if not majorDeviceType: majorDeviceType = device.devType.get('name', None) if not majorDeviceType: majorDeviceType = u'自助设备' return majorDeviceType @property def is_expire(self): return self.utils_is_expired(self) @property def is_expire_in_this_month(self): return self.utils_is_expired_in_this_month(self) @classmethod def get_sim_expire_date(cls, device): # type: (Union[Device, DeviceDict])->Optional[datetime] if cls.utils_is_bluetooth(device.logicalCode): return None if device.simExpireDate: expire_date = device.simExpireDate elif device.expireDate: expire_date = device.expireDate else: expire_date = None if not expire_date: return None else: if device.simStatus == SimStatus.Updated: sim_card = SIMCard.objects(iccid = device.iccid).first() # type: SIMCard if sim_card and sim_card.channel == '58': return expire_date.replace(day = 10) return expire_date.replace(day = Const.SIM_CARD_FORBIDDEN_DAY) @property def fixedSimExpireDate(self): return self.get_sim_expire_date(self) def is_authorized_to_dealer(self, dealer_id): return self.ownerId == dealer_id @property def channelType(self): return self.utils_channel_type(self) @property def is_registered(self): return self.utils_is_registered(self) @property def majorDeviceType(self): return self.utils_major_type(self) @classmethod def invalid_l_cache(cls, logicalCode): cls.__base_cache_mgr.delete_l_cache(logicalCode) @classmethod def update_l_cache(cls, devNo, logicalCode): cls.__base_cache_mgr.update_l_cache(devNo, logicalCode) @classmethod def invalid_device_cache(cls, devNo): cls.__base_cache_mgr.delete_device_cache(devNo) @classmethod def invalid_many_device_cache(cls, dev_no_list): if dev_no_list: cls.__base_cache_mgr.delete_many_device_cache(dev_no_list) @classmethod def __update_device_cache(cls, devNo, devValue): cls.__base_cache_mgr.update_device_cache(devNo, json_dumps(data = devValue, serialize_type = True)) @classmethod def __get_device_cache(cls, dev_no): cache_info = cls.__base_cache_mgr.get_device_cache(dev_no) if not cache_info: return None return json_loads(cache_info) @classmethod def get_and_update_device_cache(cls, dev_no, **kwargs): # type: (str, Dict)->None dev_cache = cls.__get_device_cache(dev_no) if not dev_cache: return None dev_cache.update(**kwargs) cls.__update_device_cache(dev_no, dev_cache) @classmethod def update_online_cache(cls, devNo, online, signal = None,updateTime = None): online_key = device_online_cache_key(devNo) if not signal: online_now_cache = cls.__mgr_cache.get(online_key) if online_now_cache: online_now_cache = json.loads(online_now_cache) if 'signal' in online_now_cache: signal = online_now_cache['signal'] else: signal = 0 else: signal = 0 online_set_cache = { 'online': online, 'signal': signal, 'updateTime': long(time.time() * 1000) if not updateTime else updateTime } if not online: online_set_cache.update({'offTime': time.time()}) cls.__mgr_cache.set(online_key, json.dumps(online_set_cache)) return online_set_cache @classmethod def _get_many_device_status_cache(cls, devNoList): cacheKeys = [] for devNo in devNoList: cacheKeys.append(device_online_cache_key(devNo)) cacheKeys.append(device_control_cache_key(devNo)) cacheKeys.append(device_warning_cache_key(devNo)) return cls.__mgr_cache.get_many(cacheKeys) @classmethod def get_many_device_status_cache(cls, deviceDict): # type: (Dict[str, Dict])->Dict[str, Dict] result = {} all_cache_info = Device._get_many_device_status_cache(deviceDict.keys()) for devNo, device_info in deviceDict.iteritems(): result[devNo] = cls.__fill_up_status_info( device_info, all_cache_info.get(device_online_cache_key(devNo), None), all_cache_info.get(device_control_cache_key(devNo), None), all_cache_info.get(device_warning_cache_key(devNo), None)) return result @classmethod def get_device_status_cache(cls, device): # type: (DeviceDict)->DeviceDict online_key = device_online_cache_key(device.devNo) control_key = device_control_cache_key(device.devNo) warning_key = device_warning_cache_key(device.devNo) cacheKeys = [online_key, control_key, warning_key] cache_info = cls.__mgr_cache.get_many(cacheKeys) control_cache_info = cache_info.get(control_key, None) if control_cache_info is None: logger.debug('oh,memcached missed,devNo=%s' % device.devNo) cache_info[control_key] = DeviceControlInfo.get(device.devNo) Device.__mgr_cache.set(control_key, cache_info[control_key]) cls.__fill_up_status_info( device, cache_info.get(device_online_cache_key(device.devNo), None), cache_info.get(device_control_cache_key(device.devNo), None), cache_info.get(device_warning_cache_key(device.devNo), None)) return device @staticmethod def error_start_times_cache_key(devNo, port): return "est_{devNo}_{port}".format(devNo = devNo, port = port) @classmethod def get_error_start_times(cls, devNo, port): cacheKey = Device.error_start_times_cache_key(devNo, port) return Device.__mgr_cache.get(cacheKey, 0) @classmethod def set_error_start_times(cls, devNo, port, value, timeout = None): cacheKey = Device.error_start_times_cache_key(devNo, port) Device.__mgr_cache.set(cacheKey, value, timeout) @classmethod def delete_error_start_times(cls, devNo, port): cacheKey = Device.error_start_times_cache_key(devNo, port) Device.__mgr_cache.delete(cacheKey) @classmethod def update_dev_control_cache(cls, devNo, newValue): """ 更新设备操作信息.分update和overwrite两种方式,overwrite方式是重写,比如某个端口开始充电,就需要把以前的数据清理掉 递归地更新端口信息。 如果要更新某个端口的某个字段,该端口其余的信息是保留的,如果要清空用下方的clear_port_control_cache :param devNo: :param newValue: :return: """ key = device_control_cache_key(devNo) oldValue = Device.__mgr_cache.get(key) if oldValue is None: logger.debug('oh,memcached missed,devNo=%s' % devNo) oldValue = DeviceControlInfo.get(devNo) if not oldValue: Device.__mgr_cache.set(key, newValue) DeviceControlInfo.set(devNo, newValue) return if isinstance(oldValue, dict): #: 老接口是直接替换 {'port': {}}, 此例不会在递归更新种更新该端口为{} if not newValue.values(): warnings.warn("deprecated, use clear_dev_control_cache instead!", DeprecationWarning) return Device.clear_port_control_cache(devNo, newValue.keys()[0]) else: oldValue = rec_update_dict(oldValue, newValue) Device.__mgr_cache.set(key, oldValue, 24 * 3600) DeviceControlInfo.set(devNo, oldValue) #: 获取设备操作信息 @classmethod def get_dev_control_cache(cls, devNo): key = device_control_cache_key(devNo) result = Device.__mgr_cache.get(key, {}) if not result: logger.debug('oh, memcached missed, devNo=%s' % devNo) result = DeviceControlInfo.get(devNo) if result: Device.__mgr_cache.set(key, result) logger.debug('device control info is: {}'.format(devNo, str(result))) return result @classmethod def get_dev_warning_cache(cls, devNo): """获取设备的全部告警信息""" key = device_warning_cache_key(devNo) result = cls.__mgr_cache.get(key, dict()) return result @classmethod def get_part_warning_cache(cls, devNo, part): """获取设备部件的告警信息""" key = device_warning_cache_key(devNo) result = cls.__mgr_cache.get(key, dict()) partCache = result.get(part, dict()) return partCache @classmethod def update_dev_warning_cache(cls, devNo, warningData): """更新或新增设备的部件告警信息""" key = device_warning_cache_key(devNo) oldValue = cls.__mgr_cache.get(key, dict()) # type: dict oldValue.update(warningData) cls.__mgr_cache.set(key, oldValue) @classmethod def clear_dev_warning_cache(cls, devNo): """清除设备的全部故障""" key = device_warning_cache_key(devNo) cls.__mgr_cache.set(key, {}) @classmethod def clear_part_warning_cache(cls, devNo, part): """清除设备部件告警信息""" key = device_warning_cache_key(devNo) oldValue = cls.__mgr_cache.get(key, dict()) # type: dict partCache = oldValue.get(part) if not partCache: return oldValue.update({part: {}}) cls.__mgr_cache.set(key, oldValue) @classmethod def get_port_control_cache(cls, devNo, port): _cache_info = cls.get_dev_control_cache(devNo) if not _cache_info: return {} else: return _cache_info.get(port, {}) @classmethod def get_many_dev_control_cache(cls, devNoList): keyList = [device_control_cache_key(devNo) for devNo in devNoList] return Device.__mgr_cache.get_many(keyList, {}) @classmethod def clear_port_control_cache(cls, devNo, port): key = device_control_cache_key(devNo) oldValue = cls.__mgr_cache.get(key) if not oldValue: logger.debug('oh,memcached missed,devNo=%s' % devNo) oldValue = DeviceControlInfo.get(devNo) if not oldValue: return {} port_cache = {} try: port_cache = oldValue.get(str(port), {}) oldValue[str(port)] = {} cls.__mgr_cache.set(key, oldValue) DeviceControlInfo.set(devNo, oldValue) except Exception as e: logger.exception(e) finally: return port_cache @classmethod def invalid_device_control_cache(cls, devNo): DeviceControlInfo.delete(devNo) return cls.__mgr_cache.delete(device_control_cache_key(devNo)) @classmethod def get_devNo_by_logicalCode(cls, logicalCode): # type: (str)->Optional[str] if not logicalCode: return None devNo = Device.__base_cache_mgr.get_dev_no_from_l_cache(logicalCode) if devNo is None: try: dev = Device.get_collection().find_one({'logicalCode': logicalCode}) if dev is None: return None devNo = dev['devNo'] Device.__base_cache_mgr.update_l_cache(devNo, logicalCode) return devNo except Exception as e: logger.exception('get dev by logicalCode error=%s' % e) return devNo @classmethod def get_logicalCode_by_devNo(cls, devNo): try: dev = Device.get_dev(devNo) # type: DeviceDict if not dev: return None else: return dev.logicalCode except Exception as e: logger.exception('get logical code failure. dev = %s; error = %s' % (devNo, e)) return None @classmethod def get_logicalCode_by_groupId(cls, groupId): # type:(str) -> list devMap = cls.get_devices_by_group([groupId]) return [_d.logicalCode for _d in devMap.values() if _d] @classmethod def __load_dev_no_list_of_group_cache(cls, groupId): try: objs = Device.objects.filter(groupId = groupId) tmpList = [obj.devNo for obj in objs] cls.__base_cache_mgr.set_dev_no_list_of_group_cache(groupId, tmpList) return tmpList except Exception as e: logger.exception('load to memcache error=%s,groupId=%s' % (e, groupId)) return [] @classmethod def __get_dev_no_list_by_group(cls, group_id_list): # type: (List[str])->Dict result = {} valueDict = cls.__base_cache_mgr.get_many_dev_no_list_of_group_cache(group_id_list) for group_id, dev_no_list in valueDict.items(): result[group_id] = dev_no_list leftIds = list(set(group_id_list) - set(valueDict.keys())) for group_id in leftIds: dev_no_list = Device.__load_dev_no_list_of_group_cache(group_id) result[group_id] = dev_no_list return result @classmethod def invalid_group_device_list_cache(cls, group_id_list): cls.__base_cache_mgr.delete_many_dev_no_list_of_group_cache(group_id_list) @classmethod def get_device_count_by_group(cls, group_id_list): # type: (List[str])->Dict value_dict = cls.__get_dev_no_list_by_group(group_id_list) return { group_id: len(dev_no_list) for group_id, dev_no_list in value_dict.items() } @classmethod def get_devNos_by_group(cls, group_id_list): # type: (List[str])->List assert isinstance(group_id_list, list), u'groupIds应为list' value_dict = cls.__get_dev_no_list_by_group(group_id_list) result = [] for dev_no_list in value_dict.values(): result.extend(dev_no_list) return result @staticmethod def protected_fields(): # type:()->list """ 返回不向外公开的字段 :return: """ return [ 'mf', 'iccid', 'server', 'imsi', 'hwVer', 'softVer', 'ownerId', 'driverCode', 'driverVersion', 'instructions', 'otherConf' ] @classmethod def get_dev_by_nos(cls, devNoList, verbose = False, base_only = False): # type:(List[str], Optional[bool], Optional[bool])->(Optional[Dict[str, DeviceDict]]) """ :param devNoList: 设备号列表 :param verbose: 是否显示更详细的信息,如设备所在的组名称 :return: """ assert isinstance(devNoList, list), u'devNoList应为list' def complete(device, verbose = False): if not verbose: return device else: groupInfo = Group.get_group(groupId = device['groupId']) device['groupInfo'] = groupInfo return device result = {} try: all_device_cache_info = Device.__base_cache_mgr.get_many_device_cache(devNoList) all_service_cache_info = None if not base_only: all_service_cache_info = Device._get_many_device_status_cache(devNoList) for devNo, device_cache_info in all_device_cache_info.items(): device_value = complete(json_loads(str(device_cache_info)), verbose = verbose) if not base_only: device_value = cls.__fill_up_status_info( device_value, all_service_cache_info.get(device_online_cache_key(devNo), None), all_service_cache_info.get(device_control_cache_key(devNo), None), all_service_cache_info.get(device_warning_cache_key(devNo), None)) result[devNo] = DeviceDict(device_value) leftDevNoList = set(devNoList) - set(all_device_cache_info.keys()) for devNo in leftDevNoList: logger.debug('device cache miss.'.format(devNo)) try: obj = Device.objects.get(devNo = devNo) # type: Device device_value = { 'id': str(obj.id), 'devNo': obj.devNo, 'server': str(obj.server), 'groupNumber': obj.groupNumber, 'ownerId': obj.ownerId, 'groupId': obj.groupId, 'remarks': obj.remarks, 'logicalCode': obj.logicalCode, 'washConfig': obj.washConfig, 'tempWashConfig': obj.tempWashConfig, 'devType': obj.devType, 'majorDeviceType': obj.majorDeviceType, 'isFault': obj.isFault, 'isDND': obj.isDND, 'isRent': obj.isRent, 'isDNDTimeInterval': obj.isDNDTimeInterval, 'serviceState': obj.serviceState, 'simExpireDate': obj.simExpireDate, 'expireDate': obj.expireDate, 'simStatus': obj.simStatus, 'lbs': obj.lbsFlag, 'lat': obj.lat, 'lng': obj.lng, 'instructions': obj.instructions, 'dateTimeAdded': obj.dateTimeAdded, 'mf': obj.mf, 'softVer': obj.softVer, 'hwVer': obj.hwVer, 'cycle': obj.cycle, 'trapSwtich': obj.trapSwtich, 'heartSwitch': obj.heartSwitch, 'pulseWidth1': obj.pulseWidth1, 'pulseInterval1': obj.pulseInterval1, 'battery': obj.battery, 'iccid': obj.iccid, 'imsi': obj.imsi, 'otherConf': obj.otherConf, 'driverCode': obj.driverCode, 'driverVersion': obj.driverVersion, 'coreVer': obj.coreVer, 'quantity': getattr(obj, 'quantity', 0), 'consumptionQuantity': getattr(obj, 'consumptionQuantity', 0), 'debug': obj.debug, 'boardValid': obj.boardValid, 'disableDevice': getattr(obj, 'otherConf', {}).get('disableDevice', False), 'isApi': obj.isApi, 'disableADExpireDate': obj.disableADExpireDate, 'policyTemp': obj.policyTemp } if hasattr(obj, 'priceDescription'): device_value.update({'priceDescription': obj.priceDescription}) if obj.logicalCode: cls.__update_device_cache(devNo, device_value) device_value = complete(device_value, verbose = verbose) if not base_only: device_value = cls.__fill_up_status_info( device_value, all_service_cache_info.get(device_online_cache_key(devNo), None), all_service_cache_info.get(device_control_cache_key(devNo), None), all_service_cache_info.get(device_warning_cache_key(devNo), None) ) result[devNo] = DeviceDict(device_value) except Exception as e: logger.info('get dev by nos(devNo=%s) error=%s' % (devNo, e)) continue except memcacheClient.MemcachedKeyCharacterError as e: logger.exception('invalid memcache key, input=%s error=%s' % (devNoList, e)) return None except Exception as e: logger.exception('get_dev_by_nos input=%s error=%s' % (devNoList, e)) return None return result @staticmethod def get_devices_by_group(groupIds, verbose = False): devNoList = Device.get_devNos_by_group(groupIds) devDict = Device.get_dev_by_nos(devNoList, verbose) return devDict @staticmethod def all(): """ 获取所有经销商的设备列表""" Dealer = import_string('apps.web.dealer.models.Dealer') dealerIds = [str(d.id) for d in Dealer.objects] groupIds = flatten([Group.get_group_ids_of_dealer(dealerId) for dealerId in dealerIds]) return [v for k, v in Device.get_devices_by_group(list(groupIds)).items()] @staticmethod def get_dev(devNo): # type: (str)->Optional[DeviceDict] """ :param devNo: :return: """ if (devNo is None) or not (devNo.strip()): return None devDict = Device.get_dev_by_nos([devNo], base_only = True) if (devDict is None) or (devNo not in devDict): return None return devDict[devNo] @staticmethod def get_dev_by_logicalCode(logicalCode): # type: (str)->DeviceDict return Device.get_dev(Device.get_devNo_by_logicalCode(logicalCode)) get_dev_by_l = get_dev_by_logicalCode @classmethod def get_devs_by_ownerId(cls, ownerId): return Device.get_devices_by_group(Group.get_group_ids_of_dealer(ownerId)).values() @classmethod def register_device(cls, dealer, devNo, logicalCode, groupId, districtId, groupNumber, remarks, rawWashConfigs, devType, agentId=None, policyTemp=None): """ ..20171218 #: 整改 0: 去掉更新devNo, logicalCode字段,该2个字段的对应关系在`预注册`阶段已经插入封闭后期不可更改保持数据的一致性。 1: 当缓存找不到该设备时报错,确保无垃圾信息入库 :param dealer: :param devNo: :param logicalCode: :param groupId: :param districtId: :param groupNumber: :param remarks: :param rawWashConfigs: :param devType: DeviceType :return: """ # 检测套餐单位是否一致 def check_package_unit(typeCode, package): if typeCode in skip_package_unit_verify_list: return True dic = map(lambda x: {'unit': x.get('unit')}, package) res = reduce(lambda x, y: x if x == y else None, dic) if res is None: return False else: return True # 检查套餐单位参数的范围 def check_params_range(typeCode, package): if typeCode in skip_package_range_verify_list: return None dic = map(lambda x: [x.get('time')], package) res = reduce(lambda x, y: x + y, dic) result = None for item in res: l = str(item).split(".") if len(l) > 1 and len(l[1]) > 2: result = (res.index(item) // 3) + 1 break return result def check_params(typeCode, package): if typeCode in skip_package_params_verify_list: return True lis = map(lambda x: [x.get('time'), x.get('price'), x.get('coins')], package) res = reduce(lambda x, y: x + y, lis) for i in res: if i is None or float(i) < 0: return False return True # 添加sn排序顺序 for item in rawWashConfigs: item["sn"] = rawWashConfigs.index(item) # 组装washConfig washConfigs = {} policyTemps = {} devType_dict = devType.to_dict() devType_dict.pop('package', None) if not check_params(devType_dict["code"], rawWashConfigs): return False, u'请完整的填写套餐中的所有参数' if not check_package_unit(devType_dict["code"], rawWashConfigs): return False, u'提交失败,设备套餐用户获得参数单位不一致' f = check_params_range(devType_dict["code"], rawWashConfigs) if f: return False, u'设备第%s个套餐 用户获得参数应为小数点后两位' % f if devType_dict["code"] in support_policy_weifule: try: from apps.web.core.models import DriverAdapter adapter = DriverAdapter.get_driver_adapter(devType['code'], None) if adapter.support_device_package: washConfigs, policyTemps = adapter.reg_model(dealer, rawWashConfigs, policyTemp, devType) except Exception as e: return False, e.result['description'] elif devType_dict["code"] in support_policy_device: try: from apps.web.core.models import DriverAdapter adapter = DriverAdapter.get_driver_adapter(devType['code'], None) if adapter.support_device_package: washConfigs, policyTemps = adapter.reg_model(dealer, rawWashConfigs, policyTemp, devType) except Exception as e: return False, e.result['description'] elif devType_dict["code"] in [Const.DEVICE_TYPE_CODE_WASHCAR_LANGUANG]: devType_dict.update({'displayCardCharge': True}) elif devType_dict["code"] in [Const.DEVICE_TYPE_CODE_CAR_CHANGING_JINQUE, Const.DEVICE_TYPE_CODE_CAR_WEIFILE_HOME_JFPG, Const.DEVICE_TYPE_CODE_CAR_WEIFILE_HOME_DOUB_JFPG, Const.DEVICE_TYPE_CODE_CHARGE_XIAOKEDOU]: ii = 0 for config in rawWashConfigs: ii += 1 # 注意,为了兼容设备类型里套餐的name字段,已统一更改description 为 name washConfigs[str(ii)] = {} if 'switch' in config: washConfigs[str(ii)].update({'switch': config['switch']}) if 'billingMethod' in config: washConfigs[str(ii)].update({'billingMethod': config['billingMethod']}) if 'price' in config: washConfigs[str(ii)].update({'price': round(float(config['price']), 2) or 0}) if 'coins' in config: washConfigs[str(ii)].update({'coins': round(float(config['coins']), 2) or 0}) if 'time' in config: washConfigs[str(ii)].update({'time': config['time'] or 0}) if 'name' in config: washConfigs[str(ii)].update({'name': config['name'] or '套餐{}'.format(ii)}) if 'unit' in config: washConfigs[str(ii)].update({'unit': config['unit']}) if 'sn' in config: washConfigs[str(ii)].update({'sn': config['sn']}) if 'autoStop' in config: washConfigs[str(ii)].update({'autoStop': config['autoStop']}) if 'autoRefund' in config: washConfigs[str(ii)].update({'autoRefund': config['autoRefund']}) if 'minAfterStartCoins' in config: washConfigs[str(ii)].update({'minAfterStartCoins': config['minAfterStartCoins']}) if 'minFee' in config: washConfigs[str(ii)].update({'minFee': config['minFee']}) else: if devType_dict["code"] == Const.DEVICE_TYPE_CODE_HP_GATE: # 霍珀的阶梯套餐适配 ii = 0 tempPackage = {} for step in rawWashConfigs: ii += 1 tempPackage[str(ii)] = { "maxHour": step.get("maxHour", 24), "price": step.get("price"), "unit": step.get("unit", u"小时"), "sn": step.get("sn") } washConfigs[str(1)] = tempPackage else: ii = 0 for config in rawWashConfigs: ii += 1 # 注意,为了兼容设备类型里套餐的name字段,已统一更改description 为 name washConfigs[str(ii)] = { 'coins': float(config['coins']), 'name': config['name'], 'time': float(config.get('time', 0)), 'price': float(config['price']), 'description': config.get('description', ''), 'imgList': config.get('imgList', []), 'unit': config.get('unit', u'分钟'), 'sn': config.get('sn') } if config.get('pulse'): washConfigs[str(ii)].update({ 'pulse': float(config.get('pulse')), }) if config.get('basePrice'): washConfigs[str(ii)].update({ 'basePrice': float(config.get('basePrice')), }) if config.get('billingMethod'): washConfigs[str(ii)].update({ 'billingMethod': config.get('billingMethod'), }) # 添加默认套餐到经销商下辖 dealer.defaultWashConfig[str(devType['id'])] = rawWashConfigs dealer.save() try: traffic_cost = Device.objects.get(devNo=devNo).trafficCardCost updateDict = { 'ownerId': str(dealer.id), 'groupId': groupId, 'districtId': districtId, 'groupNumber': str(groupNumber), 'washConfig': washConfigs, 'remarks': remarks, 'devType': devType_dict, 'isFault': False, 'isApi': False, 'dateTimeAdded': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 'dateTimeUpdated': datetime.datetime.now(), 'otherConf': {} } if policyTemp: updateDict.update({'policyTemp': policyTemps}) # 设备如果已经注册过, 就有流量卡成本, 并且始终以这个为准 if not traffic_cost: if dealer.trafficCardCost: updateDict.update({'trafficCardCost': dealer.trafficCardCost}) else: from apps.web.agent.models import Agent agent = Agent.objects(id=dealer.agentId).get() # type: Agent updateDict.update({'trafficCardCost': agent.trafficCardCost}) if devType_dict['code'] == Const.DEVICE_TYPE_CODE_TISSUE: # 适配的代码,如果是纸巾机,就需要初始化记录库存情况 updateDict.update({'consumptionQuantity': 0, 'quantity': 0}) cls.objects(devNo=devNo, logicalCode=logicalCode).update_one(upsert=False, **updateDict) content = { 'logicalCode': logicalCode, 'devNo': devNo, 'devTypeCode': devType_dict['code'] } if agentId: content.update({'agentId': agentId}) OperatorLog.log(user=dealer, level=OperatorLog.LogLevel.INFO, operator_name='register device', content=content) except Exception as e: logger.exception( 'set cache error=%s, when registering device (devNo=%s), ownerId=%s' % (e, devNo, str(dealer.id))) return False, u'注册设备失败,请重试' finally: # 失效缓存 cls.invalid_device_cache(devNo) cls.invalid_group_device_list_cache([groupId]) return True, 'success' @classmethod def get_elec_static_time(cls, devNo): device = cls.objects(devNo = devNo).first() deviceType = DeviceType.objects(id = device.devType['id']).first() return deviceType.features.get('elecStaticTime', 900) @property def lbsFlag(self): if self.location: return True else: return False @property def lbs(self): return self.lbsFlag @property def lat(self): if self.location: return self.location['coordinates'][1] else: return 360 @property def lng(self): if self.location: return self.location['coordinates'][0] else: return 360 @staticmethod def testSignal(logicalCode): devNo = Device.get_devNo_by_logicalCode(logicalCode) if devNo is None: return u'设备不存在', 0 device = Device.get_dev(devNo) # type: DeviceDict if not device: return u'设备不存在', 0 if not device.online: return u'设备掉线,无法获取网络信号情况', 0 result = MessageSender.send(device, DeviceCmdCode.GET_DEVINFO, {'IMEI': devNo}, timeout = MQTT_TIMEOUT.SHORT) if result['rst'] != 0: return 'OK', 0 else: return 'OK', int(result.get('signal', 0)) @classmethod def un_register(cls, dev, force=False, operator=None): # type: (DeviceDict, bool, str)->None if not dev.is_registered: logger.debug('dev is not registered.'.format(dev.devNo)) return # 出租设备只有系统管理员能够解绑 if dev.isRent and not force: logger.debug("dev <{}> is rent, not allow to unregiste!") raise RentDeviceError(u"租用设备无法解绑") try: emptyValue = { 'groupId': Device.groupId.default, 'districtId': Device.districtId.default, 'devType': Device.devType.default, 'groupNumber': Device.groupNumber.default, 'washConfig': Device.washConfig.default, 'tempWashConfig': Device.tempWashConfig.default, 'remarks': Device.remarks.default, 'instructions': Device.instructions.default, 'otherConf': Device.otherConf.default, 'ownerId': '', 'policyTemp': Device.policyTemp.default, } OperatorLog.log(user=dev.owner, level=OperatorLog.LogLevel.INFO, operator_name='unregister device' if not force else 'force unregister device', content=dict(dev.my_obj.to_mongo())) Device.objects(devNo=dev.devNo).update_one(**emptyValue) finally: Device.invalid_group_device_list_cache([dev.groupId]) Device.invalid_device_cache(dev.devNo) @staticmethod def filter(dealerId, searchKey, online = '', expireStatus = '', equipmentGroupId = ''): def cmp_group_devName(x, y): # type: (Dict, Dict)->int if x['groupId'] > y['groupId']: return 1 elif x['groupId'] == y['groupId']: if x['online'] < y['online']: return -1 elif x['online'] > y['online']: return 1 else: if x['id'] < y['id']: return -1 else: return 0 else: return -1 def match_expire_status(dev, expire_status): # type:(DeviceDict, str)->bool if expire_status not in ['normal', 'expire', 'toExpire']: return True if expire_status == 'normal': if (not dev.is_expired) and (not dev.is_to_expired): return True else: return False if expire_status == 'expire': if dev.is_expired: return True else: return False if expire_status == 'toExpire': if dev.is_to_expired: return True else: return False if online == '' or online == 'all': onlineList = [0, 1] elif online == '1': onlineList = [1] elif online == '0': onlineList = [0] else: onlineList = [] groupIds = [equipmentGroupId] partnerGroupList = Group.get_group_ids_of_partner(dealerId) if equipmentGroupId == 'all' or equipmentGroupId == '': groupIds = Group.get_group_ids_of_dealer(dealerId) groupIds.extend(partnerGroupList) groupDict = Group.get_groups_by_group_ids(groupIds) devNoList = Device.get_devNos_by_group(groupIds) devs = Device.get_dev_by_nos(devNoList).values() devList = [] for dev in devs: # type: DeviceDict try: if dev.online not in onlineList: continue if not match_expire_status(dev, expireStatus): continue group = groupDict.get(dev['groupId']) if any(map(lambda _: searchKey in _, (dev['devNo'], dev['remarks'], dev['logicalCode'], group['groupName'], group['address']))): item = { 'id': dev.devNo, 'logicalCode': dev.logicalCode, 'name': group['groupName'], 'groupId': group['groupId'], 'type': dev.devType['name'], 'typeId': dev.devType['id'], 'online': dev.online, 'timeBased': dev.devType.get('timeBased', True), 'isManager': False if dev['groupId'] in partnerGroupList else True, 'serviceState': dev.get('serviceState', Const.ServiceState.Normal.name), 'consumptionQuantity': dev.get('consumptionQuantity', 0), 'quantity': dev.get('quantity', 0), 'devType': {'code': dev.devType.get('code', '99999')}, 'expireDate': dev.formatSimExpireDate, 'simStatus': dev.simStatus } devList.append(item) except Exception, e: logger.exception( '[Device.filter] has some error=%s dealerId=%s, searchKey=%s' % (e, dealerId, searchKey)) continue devList.sort(cmp = cmp_group_devName) return groupIds, devList @staticmethod def tree_filter(dealerId = None, agentId = None, managerId = None, searchKey = None, pageIndex = 1, pageSize = 10, **kwargs): # type: (Optional[str], Optional[str], Optional[str], Optional[str], int, int, **Any)->Tuple[int, List[dict]] """ 分层获取设备列表 :param dealerId: :param agentId: :param managerId: :param searchKey: :param pageIndex: :param pageSize: :return: """ online = kwargs.get('online', None) registered = kwargs.get('registered', None) Agent = import_string('apps.web.agent.models.Agent') Dealer = import_string('apps.web.dealer.models.Dealer') Manager = import_string('apps.web.management.models.Manager') shadow = kwargs.get('shadow', False) dealerMap = {} agentMap = {} managerMap = {} #: 显示没有注册的设备 if not registered and registered is not None: devices = Device.objects(Q(ownerId__exists = 0) | Q(ownerId = "")).search(searchKey) else: #: 如果没有任何厂商代理商经销商,显示所有的设备 if dealerId is not None: dealerFilter = Q(id = ObjectId(dealerId)) else: dealerFilter = Q() if agentId is not None: agentIds = [agentId] dealerFilter &= Q(agentId__in = agentIds) else: if managerId is not None: agentQuery = Q(managerId = managerId) else: managerMap = { str(d['_id']): d for d in Manager.get_collection().find({}, {'_id': 1, 'nickname': 1, 'username': 1, 'domain': 1}) } agentQuery = Q(managerId__in = managerMap.keys()) agentMap = { str(d['_id']): d for d in Agent.get_collection().find(agentQuery.to_query(Agent), {'_id': 1, 'username': 1, 'nickname': 1, 'managerId': 1}) } dealerFilter &= Q(agentId__in = agentMap.keys()) dealerMap = { str(d['_id']): d for d in Dealer.get_collection().find(dealerFilter.to_query(Dealer), {'_id': 1, 'nickname': 1, 'username': 1, 'agentId': 1}) } dealerIds = dealerMap.keys() devices = Device.objects(ownerId__in = dealerIds).search(searchKey) total = devices.count() devNos = [] groupIds = [] for d in devices.paginate(pageIndex, pageSize): devNos.append(d['devNo']) if d['groupId']: groupIds.append(d['groupId']) devs = Device.get_dev_by_nos(devNos) groups = Group.get_groups_by_group_ids(groupIds) def device_filter(devices): if online is not None: for dev in devices: # type: DeviceDict if str(dev.online) == str(online): yield dev else: for dev in devices: yield dev items = [] for dev in device_filter(devs.values()): # type: DeviceDict group = groups.get(dev.groupId, {}) item = { 'logicalCode': dev.logicalCode, 'devNo': dev.devNo, 'createdTime': dev['dateTimeAdded'], 'groupId': dev.groupId, 'groupName': group.get('groupName'), 'address': group.get('address'), 'online': dev.online, 'signal': dev.signal, 'lastOffTime': dev.offTime, 'simExpireDate': dev.fixedSimExpireDate, 'simStatus': dev.simStatus, 'server': dev.server, 'devType': dev.devType, 'driverCode': dev.get('driverCode', ''), 'driverVersion': dev.get('driverVersion', ''), 'cycle': dev['cycle'], 'pulseInterval1': dev['pulseInterval1'], 'battery': dev['battery'], 'imsi': dev['imsi'], 'iccid': dev['iccid'], 'remarks': dev['remarks'], 'registered': dev.is_registered, 'softVer': dev['softVer'], 'coreVer': dev['coreVer'], "disableDevice": dev.get("otherConf", {}).get("disableDevice", False), 'hwVer': dev.hwVer, 'supportPG': dev.support_power_graph } if dev['ownerId']: if dev['ownerId'] in dealerMap: dealer = dealerMap[dev['ownerId']] else: dealer = Dealer.get_collection().find_one({'_id': ObjectId(dev['ownerId'])}, {'_id': 1, 'nickname': 1, 'username': 1, 'agentId': 1}) dealerMap[dev['ownerId']] = dealer if dealer['agentId'] not in agentMap: agent = Agent.get_collection().find_one({'_id': ObjectId(dealer['agentId'])}, {'_id': 1, 'username': 1, 'nickname': 1, 'managerId': 1}) if not agent: logger.error( 'no agent. dealerId = {}, agentId = {}'.format(dealer['_id'], str(dealer['agentId']))) continue agentMap[dealer['agentId']] = agent agent = agentMap[dealer['agentId']] if agent['managerId'] in managerMap: manager = managerMap[agent['managerId']] else: manager = Manager.get_collection().find_one({'_id': ObjectId(agent['managerId'])}, {'_id': 1, 'nickname': 1, 'username': 1, 'domain': 1}) managerMap[agent['managerId']] = manager if manager['domain'] != settings.MY_DOMAIN: logger.debug('manager not belong to me.'.format(manager['_id'], manager['domain'])) continue item['manager'] = { 'id': manager.get('_id'), 'username': '******' if shadow else manager.get('username', ''), 'nickname': manager.get('nickname', '') } item['agent'] = { 'id': agent.get('_id'), 'username': '******' if shadow else agent.get('username', ''), 'nickname': agent.get('nickname', '') } item['dealer'] = { 'id': dealer.get('_id'), 'username': '******' if shadow else dealer.get('username', ''), 'nickname': dealer.get('nickname', '') } else: item['manager'] = { 'id': '', 'username': '', 'nickname': '' } item['agent'] = { 'id': '', 'username': '', 'nickname': '' } item['dealer'] = { 'id': '', 'username': '', 'nickname': '' } # 添加续费记录的相关信息 from apps.web.dealer.models import DealerRechargeRecord DRRS = DealerRechargeRecord.objects.filter(dealerId = str(item['dealer']['id']), status = "Paid").order_by( '-finishedTime') item[u'chargeRecord'] = [] for DRR in DRRS: for res in DRR.items: if res.get(u'devNo') == dev.devNo: res[u'orderNo'] = DRR.orderNo res[u'nickname'] = DRR.nickname res[u'finishedTime'] = DRR.finishedTime item[u'chargeRecord'].append(res) items.append(item) return total, items @staticmethod def accurate_filter(logicalCode, shadow): Agent = import_string('apps.web.agent.models.Agent') Dealer = import_string('apps.web.dealer.models.Dealer') Manager = import_string('apps.web.management.models.Manager') dev = Device.get_dev_by_logicalCode(logicalCode) # type: DeviceDict if not dev: return if dev.get("groupId"): group = Group.get_group(dev.get("groupId")) else: group = dict() item = { 'logicalCode': dev.logicalCode, 'devNo': dev.devNo, 'createdTime': dev['dateTimeAdded'], 'groupId': dev.groupId, 'groupName': group.get('groupName'), 'address': group.get('address'), 'online': dev.online, 'signal': dev.signal, 'lastOffTime': dev.offTime, 'simExpireDate': dev.fixedSimExpireDate, 'server': dev.server, 'devType': dev.devType, 'driverCode': dev.get('driverCode', ''), 'driverVersion': dev.get('driverVersion', ''), 'cycle': dev['cycle'], 'pulseInterval1': dev['pulseInterval1'], 'battery': dev['battery'], 'imsi': dev['imsi'], 'iccid': dev['iccid'], 'remarks': dev['remarks'], 'registered': dev.is_registered, 'softVer': dev['softVer'], 'coreVer': dev['coreVer'], 'hwVer': dev.hwVer, 'supportPG': dev.support_power_graph } if dev.get("ownerId"): try: dealer = Dealer.get_collection().find_one({'_id': ObjectId(dev['ownerId'])}, {'_id': 1, 'nickname': 1, 'username': 1, "agentId": 1}) agent = Agent.get_collection().find_one({'_id': ObjectId(dealer['agentId'])}, {'_id': 1, 'username': 1, 'nickname': 1, "managerId": 1}) manager = Manager.get_collection().find_one({'_id': ObjectId(agent['managerId'])}, {'_id': 1, 'nickname': 1, 'username': 1, 'domain': 1}) item['manager'] = { 'id': manager.get('_id'), 'username': manager.get('username', '') if shadow is False else '******', 'nickname': manager.get('nickname', '') } item['agent'] = { 'id': agent.get('_id'), 'username': agent.get('username', '') if shadow is False else '******', 'nickname': agent.get('nickname', '') } item['dealer'] = { 'id': dealer.get('_id'), 'username': dealer.get('username', '') if shadow is False else '******', 'nickname': dealer.get('nickname', '') } except Exception as e: item['manager'] = { 'id': '', 'username': '', 'nickname': '' } item['agent'] = { 'id': '', 'username': '', 'nickname': '' } item['dealer'] = { 'id': '', 'username': '', 'nickname': '' } else: item['manager'] = { 'id': '', 'username': '', 'nickname': '' } item['agent'] = { 'id': '', 'username': '', 'nickname': '' } item['dealer'] = { 'id': '', 'username': '', 'nickname': '' } # 添加续费记录的相关信息 from apps.web.dealer.models import DealerRechargeRecord DRRS = DealerRechargeRecord.objects.filter(dealerId = str(item['dealer']['id']), status = "Paid").order_by( '-finishedTime') item[u'chargeRecord'] = [] for DRR in DRRS: for res in DRR.items: if res.get('devNo') == dev.devNo: res[u'orderNo'] = DRR.orderNo res[u'nickname'] = DRR.nickname res[u'finishedTime'] = DRR.finishedTime item[u'chargeRecord'].append(res) return item @classmethod def update_field(cls, dev_no, update = False, **valueDict): try: cls.get_collection().update_one({'devNo': dev_no}, {'$set': valueDict}) if update: cls.get_and_update_device_cache(dev_no, **valueDict) else: cls.invalid_device_cache(dev_no) except Exception as e: logger.exception('update dev error=%s' % e) return False return True def is_auto_refund(self): return self.autoRefundLeftMoney @staticmethod def get_dev_no_from_request(request): devNo = request.GET.get('devNo', None) if not devNo: logicalCode = request.GET.get('logicalCode', None) if logicalCode: devNo = Device.get_devNo_by_logicalCode(logicalCode) return devNo @staticmethod def update_port_control_cache(devNo, portInfo, updateType = 'update'): #: 两种方式update,以及overwrite oldValue = Device.get_dev_control_cache(devNo) port = str(portInfo['port']) if port in oldValue and updateType == 'update': oldValue[port].update(portInfo) else: oldValue[port] = portInfo Device.update_dev_control_cache(devNo, oldValue) return oldValue[port] @staticmethod def modify_port_control_cache(devNo, port, portInfo): key = device_control_cache_key(devNo) oldValue = Device.__mgr_cache.get(key) if oldValue is None: logger.debug('oh,memcached missed,devNo=%s' % devNo) oldValue = DeviceControlInfo.get(devNo) if not isinstance(oldValue, dict): logger.warning('control cache old value is not dict. value = {}'.format(oldValue)) oldValue = None if not oldValue or str(port) not in oldValue: new_value = {str(port): portInfo} Device.__mgr_cache.set(key, new_value) DeviceControlInfo.set(devNo, new_value) else: oldValue[str(port)].update(portInfo) Device.__mgr_cache.set(key, oldValue) DeviceControlInfo.set(devNo, oldValue) @staticmethod def overwrite_port_control_cache(devNo, port, portInfo): key = device_control_cache_key(devNo) oldValue = Device.__mgr_cache.get(key) if oldValue is None: logger.debug('oh,memcached missed,devNo=%s' % devNo) oldValue = DeviceControlInfo.get(devNo) if not isinstance(oldValue, dict): logger.warning('control cache old value is not dict. value = {}'.format(oldValue)) oldValue = None if not oldValue: new_value = {str(port): portInfo} Device.__mgr_cache.set(key, new_value) DeviceControlInfo.set(devNo, new_value) else: oldValue[str(port)] = portInfo Device.__mgr_cache.set(key, oldValue) DeviceControlInfo.set(devNo, oldValue) @classmethod def generate_sim_expire_query(cls, expire_start_date, expire_end_date, dealer_id = None, sim_source = None, sim_status = None): query = {'ownerId': dealer_id} if dealer_id else {'ownerId': {'$nin': [None, '']}} if expire_start_date and expire_end_date: query.update({ '$or': [ { 'simExpireDate': { '$lt': expire_end_date, '$gte': expire_start_date } }, { 'expireDate': { '$lt': expire_end_date, '$gte': expire_start_date }, 'simExpireDate': None }, ] }) elif expire_start_date: query.update({ '$or': [ { 'simExpireDate': { '$gte': expire_start_date } }, { 'expireDate': { '$gte': expire_start_date }, 'simExpireDate': None }, ] }) elif expire_end_date: query.update({ '$or': [ { 'simExpireDate': { '$lt': expire_end_date } }, { 'expireDate': { '$lt': expire_end_date }, 'simExpireDate': None }, ] }) query.update({ 'logicalCode': {'$regex': '^[^B]'}, }) if sim_source: query.update({'simSource': sim_source}) if sim_status: query.update({'simStatus': {'$in': [sim_status, None]}}) return query @classmethod def check_sim_expire_notify(cls, device): # type: (Optional[Device, DeviceDict])->bool if not device.fixedSimExpireDate: return False if device.simStatus != SimStatus.Updated: return False now = arrow.now() first_day = now.replace(day = 1, hour = 0, minute = 0, second = 0, microsecond = 0) # type: arrow last_day = first_day.shift(months = 1).shift(days = -1) # type: arrow if device.fixedSimExpireDate >= first_day.naive: return True else: return False @property def sim_expire_notify(self): return self.check_sim_expire_notify(self) def set_fault(self): updated = self.update(isFault = True) assert updated, 'set %r fault failed' % (self,) Device.invalid_device_cache(self.devNo) @property def cached(self): return Device.get_dev(self.devNo) @staticmethod # 计算设备的使用率 def calc_dev_usage(devNo): ctrInfo = Device.get_dev_control_cache(devNo) usage = 0 if ctrInfo is not None and ctrInfo: portCount, busyCount = 0, 0 for k, v in ctrInfo.items(): if str(k).isdigit(): portCount += 1 if v.has_key('status') and v['status'] == Const.DEV_WORK_STATUS_WORKING: busyCount += 1 else: continue if portCount: usage = float(busyCount) / portCount * 100.0 else: serviceInfo = Device.get_dev_control_cache(devNo) if serviceInfo is not None and serviceInfo.has_key('status') and serviceInfo[ 'status'] == Const.DEV_WORK_STATUS_WORKING: if not serviceInfo.has_key('finishedTime'): return 0 if time.time() > serviceInfo['finishedTime']: return 0 return 100.0 return float(usage) @classmethod def invalid_all_cache(cls, device): # type:(Optional[DeviceDict, Device])->None cls.__base_cache_mgr.delete_device_cache(device.devNo) cls.__base_cache_mgr.delete_l_cache(device.logicalCode) cls.invalid_group_device_list_cache([device.groupId]) cls.invalid_device_control_cache(device.devNo) @classmethod def replace(cls, old_lc, new_lc, operator): # type:(str, str, UserSearchable)->None old_device = Device.objects(logicalCode = old_lc).first() # type: Optional[Device] new_device = Device.objects(logicalCode = new_lc).first() # type: Optional[Device] if not old_device: raise UserServerException(u'旧设备({})不存在'.format(old_lc)) if not new_device: raise UserServerException(u'新设备({})不存在'.format(new_lc)) if (old_device.ownerId != new_device.ownerId) and new_device.ownerId: raise UserServerException(u'设备注册经销商不一致,不允许替换') record = DeviceReplacement( oldLogicalCode = old_lc, newLogicalCode = new_lc, oldDevice = dict(old_device.to_mongo()), newDevice = dict(new_device.to_mongo()), replaceType = 'replace' ).save() OperatorLog.log(user = operator, level = OperatorLog.LogLevel.CRITICAL, operator_name = u'换绑设备', content = { 'ref_id': record.id }) try: old_device.delete() update_dict = { 'logicalCode': old_device.logicalCode, 'ownerId': old_device.ownerId, 'groupId': old_device.groupId, 'districtId': old_device.districtId, 'groupNumber': old_device.groupNumber, 'washConfig': old_device.washConfig, 'tempWashConfig': old_device.tempWashConfig, 'remarks': old_device.remarks, 'instructions': old_device.instructions, 'isFault': old_device.isFault, 'autoRefundLeftMoney': old_device.autoRefundLeftMoney, 'devType': old_device.devType, 'dateTimeUpdated': datetime.datetime.now(), 'iccidHistory': old_device.iccidHistory, 'otherConf': old_device.otherConf, 'registerLog': old_device.registerLog, 'serviceState': old_device.serviceState, 'trafficCardCost': old_device.trafficCardCost, 'stockDetailDict': old_device.stockDetailDict } if old_device.location: update_dict['location'] = old_device.location updated = new_device.update(**update_dict) if not updated: raise UserServerException(u'未更新成功,请重试') finally: Device.invalid_all_cache(old_device) Device.invalid_all_cache(new_device) @classmethod def swap(cls, left_lc, right_lc, operator): # type:(str,str,UserSearchable)->None left_device = Device.objects(logicalCode = left_lc).first() # type: Optional[Device] right_device = Device.objects(logicalCode = right_lc).first() # type: Optional[Device] if not left_device: raise UserServerException(u'设备({})不存在'.format(left_lc)) if not right_device: raise UserServerException(u'设备({})不存在'.format(right_lc)) if left_device.ownerId != right_device.ownerId: raise UserServerException(u'不同经销商设备不能交换二维码') raw_left_device = dict(left_device.to_mongo()) # type: Dict raw_right_device = dict(right_device.to_mongo()) # type: Dict record = DeviceReplacement( oldLogicalCode = left_lc, newLogicalCode = right_lc, oldDevice = raw_left_device, newDevice = raw_right_device, replaceType = 'swap' ).save() OperatorLog.log(user = operator, level = OperatorLog.LogLevel.CRITICAL, operator_name = u'互换设备', content = { 'ref_id': record.id }) try: map(lambda _: _.delete(), [left_device, right_device]) raw_left_device['logicalCode'] = right_lc Device(**raw_left_device).save() raw_right_device['logicalCode'] = left_lc Device(**raw_right_device).save() finally: Device.invalid_all_cache(left_device) Device.invalid_all_cache(right_device) @classmethod def rollback_replacement(cls, replacement_id): replacement = DeviceReplacement.objects(id = replacement_id).first() # type: Optional[DeviceReplacement] if not replacement: raise Exception(u'无此调换记录') raw_old_device = replacement.oldDevice # type: dict raw_new_device = replacement.newDevice # type: dict devices = Device.objects(devNo__in = [raw_old_device['devNo'], raw_new_device['devNo']]) map(lambda _: _.delete(), devices) old_device = Device(**raw_old_device).save() new_device = Device(**raw_new_device).save() updated = replacement.update(revokedTime = datetime.datetime.now()) if not updated: logger.error('cannot update %r' % (replacement,)) Device.invalid_all_cache(old_device) Device.invalid_all_cache(new_device) @classmethod def bind(cls, devNo, logicalCode, operator = None): # type:(str, str, UserSearchable)->None document = { 'logicalCode': logicalCode, 'devNo': devNo, 'dateTimeBinded': datetime.datetime.now(), 'binder': operator.identity_id if operator else '' } try: Device.get_collection().insert_one(copy.deepcopy(document)) except DuplicateKeyError: result = Device.get_collection().update_one( filter = {'devNo': devNo, 'logicalCode': {'$in': [None, '']}}, update = {'$set': document}, upsert = False) if result.matched_count <= 0: raise UserServerException(u'IMEI({})已经绑定'.format(devNo)) elif result.modified_count <= 0: raise UserServerException(u'绑定失败,请重试') Device.__base_cache_mgr.update_l_cache(devNo, logicalCode) Device.get_dev(devNo) try: CheckDevice.get_collection().update_one({'imei': devNo}, {'$set': { 'imei': devNo, 'logicalCode': logicalCode, 'bindTime': datetime.datetime.now() } }, upsert = True) except Exception, e: logger.exception(e) @classmethod def unbind(cls, dev, operator = None): # type: (DeviceDict, UserSearchable)->None if not dev: raise UserServerException(u'该设备不存在,无须解绑') if dev.is_registered: raise UserServerException(u'设备已经注册,无法解绑') device = dev.my_obj # type: Device if not device: raise UserServerException(u'该设备不存在,无须解绑') if operator: if operator.identity_id != device.binder: raise UserServerException(u'不是您绑定的设备,您无法进行解绑定') device.delete() Device.invalid_all_cache(dev) try: CheckDevice.get_collection().delete_one({'imei': dev.devNo}) except: pass @classmethod def get_sim_expire_notify_devices(cls, dealer_id = None, extra_filter = None, fields = {'_id': 0, 'logicalCode': 1, 'ownerId': 1}): now = arrow.now() this_month_first_day = now.replace(day = 1, hour = 0, minute = 0, second = 0, microsecond = 0) # type: arrow next_month_first_day = this_month_first_day.shift(months = 1) # type: arrow query = cls.generate_sim_expire_query(expire_start_date = this_month_first_day.naive, expire_end_date = next_month_first_day.naive, dealer_id = dealer_id, sim_status = SimStatus.Updated) if extra_filter: query.update(extra_filter) devices = Device.get_collection().find(query, fields) return list(devices) @classmethod def set_debug_flag(cls, devNo, flag = ''): Device.get_collection().update({'devNo': devNo}, {'$set': {'debug': flag}}, upsert = False) Device.invalid_device_cache(devNo) @staticmethod def bolai_get_node_dev(cls, gatewayDevNo, nodeIndex): devObj = Device.objects.get(devNo = gatewayDevNo) nodeDevNo = devObj.nodeDict.get(nodeIndex, None) if not nodeDevNo: return None devObj = Device.objects.get(devNo = nodeDevNo) return devObj @classmethod def switch_api_mode(cls, logicalCode, isApi): # type:(Union[str, unicode, list], bool) -> bool if isinstance(logicalCode, list): logicalCodes = logicalCode elif isinstance(logicalCode, (str, unicode)): logicalCodes = [logicalCode] else: return if not logicalCodes: return devs = Device.objects.filter(logicalCode__in=logicalCodes) if devs: result = devs.update(isApi=isApi) Device.invalid_many_device_cache(map(lambda _: _.devNo, devs)) return result @classmethod def check_and_update_tcpip_info(cls,dev,devPort,serverIp,serverPort): if not dev.network_address: devObj = Device.objects.get(devNo = dev['devNo']) devObj.otherConf['devPort'] = devPort devObj.server = serverIp + ':' + str(serverPort) devObj.save() Device.invalid_device_cache(dev['devNo']) oldServer,oldPort,oldLabel = dev.network_address if 'devPort' not in dev.otherConf or dev.otherConf['devPort'] != devPort or oldLabel != 'car-tcp-ip' or oldServer != serverIp or oldPort != serverPort: devObj = Device.objects.get(devNo = dev['devNo']) devObj.otherConf['devPort'] = devPort devObj.server = serverIp + ':' + str(serverPort) devObj.save() Device.invalid_device_cache(dev['devNo']) @property def owner(self): # type:()->Dealer if hasattr(self, '__owner__'): return getattr(self, '__owner__') if not self.ownerId: return None Dealer = import_string('apps.web.dealer.models.Dealer') dealer = Dealer.objects(id = self.ownerId).first() if dealer: setattr(self, '__owner__', dealer) return dealer @owner.setter def owner(self, value): setattr(self, '__owner__', value) class DeviceDict(dict): """ 设备的缓存表示. 注意不能直接當作字典進行json編碼 """ @property def id(self): if "id" not in self.v: Device.invalid_device_cache(self.devNo) _id = Device.get_dev(self.devNo).get("id") else: _id = self["id"] return _id @property def cycle(self): return self['cycle'] @property def majorDeviceType(self): return self['majorDeviceType'] @property def status(self): return self['status'] @property def need_fetch_online(self): if self.online == DeviceOnlineStatus.DEV_STATUS_ONLINE: return True # 如果离线,但是在2个小时内有上线记录,也需要去检查下状态 if self.lastUpdateOnlineTime > 0: elapse_time = (long(time.time() * 1000) - long(self['updateTime'])) if elapse_time < 2 * 60 * 60 * 1000: return True return False @property def statusInfo(self): return self['statusInfo'] @property def online(self): return self['online'] @property def offTime(self): # type: ()->long return self['offTime'] @property def lastUpdateOnlineTime(self): # type: ()->long return self['updateTime'] @property def signal(self): # type: ()->int return self['signal'] @property def devNo(self): return self['devNo'] @property def logicalCode(self): return self['logicalCode'] @property def groupId(self): return self.get('groupId', Device.groupId.default) @property def group(self): # type:() -> GroupDict if hasattr(self, '__group__'): return getattr(self, '__group__') group = Group.get_group(self.groupId) if group: setattr(self, '__group__', group) return group @property def is_registered(self): # type:()->bool return Device.utils_is_registered(self) @property def is_expired(self): # type:()->bool return Device.utils_is_expired(self) @property def is_to_expired(self): expire_date = self.fixedSimExpireDate if not expire_date: return False now = arrow.now() start_date = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0).naive end_date = now.replace(day=(Const.SIM_CARD_FORBIDDEN_DAY + 1), hour=0, minute=0, second=0, microsecond=0).naive if (expire_date > start_date) and (expire_date < end_date): return True else: return False @property def channelType(self): return Device.utils_channel_type(self) @property def coreVer(self): return self.get('coreVer', '') @property def softVer(self): return self.get('softVer', '') @property def mf(self): return self.get('mf', '') @property def ownerId(self): return self.get('ownerId', Device.ownerId.default) @property def simExpireDate(self): sim_expire_date = self.get('simExpireDate', Device.simExpireDate.default) if not sim_expire_date: return sim_expire_date if isinstance(sim_expire_date, basestring): return datetime.datetime.strptime(sim_expire_date, Const.DATE_FMT) else: return sim_expire_date @property def formatSimExpireDate(self): # type: ()->basestring if Device.utils_is_bluetooth(self.logicalCode): return '-' expire_date = self.fixedSimExpireDate if expire_date: return expire_date.strftime(Const.DATE_FMT) else: return u'待更新' @property def expireDate(self): return self.get('expireDate', Device.expireDate.default) @property def sim_expire_notify(self): return Device.check_sim_expire_notify(self) @property def fixedSimExpireDate(self): return Device.get_sim_expire_date(self) @property def simStatus(self): return self.get('simStatus', SimStatus.Updated) @property def devType(self): # type: ()->Dict return self.get('devType', {}) @property def devTypeId(self): return self.devType.get('id') @property def devTypeName(self): return self.devType.get('name') @property def devTypeCode(self): # type: ()->str return self.devType.get('code') @property def devTypeFeatures(self): return self.devType.get('features', {}) @property def is_online_type(self): # type: ()->bool return self.devType.get('online', None) @property def hwVer(self): return self.get('hwVer', '') @property def iccid(self): return self.get('iccid', '') @property def imsi(self): return self.get('imsi', '') @property def server(self): return self.get('server', Device.server.default) @property def network_address(self): host, port = self.server.split(':') host = SysParas.get_private_ip(str(host)) if int(port) < 30000: return host, port, 'mqtt' elif int(port) < 50000: return host, port, 'tcpip' else: return host, port, 'car-tcp-ip' @property def lbs(self): return self['lbs'] @property def lat(self): return self['lat'] @property def lng(self): return self['lng'] @property def debug(self): return self.get('debug', '') @property def groupNumber(self): return self.get('groupNumber', '') @property def otherConf(self): return self.get('otherConf', {}) @property def support_reliable(self): tokens = self.driverVersion.split('.') if tokens[0] >= 'v5': if 'start_device_realiable' in self.deviceAdapter.__class__.__dict__: return True else: return False else: return False @property def support_sid_topic(self): try: tokens = self.softVer.split('.') if len(tokens) == 3: mainVersion = int(str(tokens[0]).replace("v", "")) if 7 <= mainVersion < 40: logger.debug('version is: {}; sidTopic = False'.format(self.softVer)) return False if int(tokens[2]) >= int(Const.TOPIC_WITH_SID_VERSION): logger.debug('version is: {}; sidTopic = True'.format(self.softVer)) return True logger.debug('version is: {}; sidTopic = False'.format(self.softVer)) return False except Exception as e: logger.debug('exception. version is: {}; sidTopic = False'.format(self.softVer)) logger.exception(e) return False @property def ban(self): return self.get('disableDevice', False) @property def driverCode(self): return self.get('driverCode', '000000') @property def driverVersion(self): return self.get('driverVersion', '0.0.0') @property def support_power_graph(self): return self.get('otherConf', dict()).get('supportPG', False) @property def is_auto_refund(self): try: return self.my_obj.is_auto_refund() except Exception as e: logger.exception(e) return False @property def is_DND_now(self): if self.get('isDND', False) is False: return False DNDRange = self.get('isDNDTimeInterval', []) nowTime = datetime.datetime.now().strftime('%H:%M:%S') for _ in DNDRange: if _.get('isDNDStartTime') <= nowTime <= _.get('isDNDEndTime'): return True return False @property def is_fault(self): return self["isFault"] @property def is_warning(self): return self["deviceWarning"] @property def isRent(self): return self.get("isRent", False) @property def isApi(self): return self.get("isApi") @property def disableADExpireDate(self): return self.get("disableADExpireDate") @property def bill_as_service_feature(self): # type:()-> BillAsServiceFeature return BillAsServiceFeature(self.devTypeFeatures.get('billAsService', {})) @property def policyTemp(self): return self.get("policyTemp", {}) @property def priceDescription(self): if self.get('priceDescription'): priceDescription = self['priceDescription'] priceDescription = priceDescription.replace('\n', '
') priceDescription = priceDescription.replace(' ', ' ') return priceDescription elif self.policyTemp and not self.get('priceDescription'): forApps = self.policyTemp.get('forApps', {}) policyType = forApps.get('policyType') if policyType == 'time': prices = forApps.get('rule', {}).get('prices', []) text = '计费模式: 按功率计费' + '
' lastStep = 0 for price in prices: text += '{}-{}瓦, {}元/小时
'.format(lastStep, price['power'], price['price']) lastStep = price['power'] return text elif policyType == 'elec': price = forApps.get('rule', {}).get('price') text = '计费模式: 按电量计费' + '
' if price: text += '单价: {}元/度
'.format(price) return text else: # 保留一个按次计费 pass else: pass @property def identity_info(self): return { 'devNo': self.devNo, 'logicalCode': self.logicalCode, 'devTypeName': self.devTypeName, 'devTypeCode': self.devTypeCode, 'groupId': self.groupId, 'groupName': self.group.groupName, 'groupNumber': self.groupNumber, 'address': self.group.address } @cached_property def deviceAdapter(self): # type:()->SmartBox return ActionDeviceBuilder.create_action_device(self) @cached_property def eventer(self): # type: ()->EventBuilder return ActionDeviceBuilder.create_eventer_by_adapter(self.deviceAdapter) @cached_property def parts(self): return Part.objects.filter(logicalCode=self.logicalCode) @cached_property def owner(self): # type:()->Dealer cls = import_string('apps.web.dealer.models.Dealer') dealer = cls.objects(id=self.ownerId).first() return dealer @cached_property def my_obj(self): # type:() -> Device return Device.objects(devNo=self.devNo).first() @cached_property def dealer(self): # type:() -> DealerDict cls = import_string('apps.web.dealer.models.Dealer') dealer = cls.get_dealer(self.ownerId) return dealer @property def v(self): return dict(self) def __repr__(self): return 'DeviceDict'.format(self.logicalCode, self.devNo) def __update_status(self): Device.get_device_status_cache(self) def __getitem__(self, k): if k in self: return super(DeviceDict, self).__getitem__(k) if k in ['online', 'status', 'signal', 'offTime', 'statusInfo', 'updateTime', 'offTime', 'deviceWarning']: assert 'logicalCode' in self, 'must has logicalCode field' self.__update_status() elif k == 'majorDeviceType': self['majorDeviceType'] = Device.utils_major_type(self) elif k in ['lbs', 'lat', 'lng']: location = self.get('location', None) lbs = True if not location: lat = 360 lng = 360 lbs = False else: try: lat = location['coordinates'][1] except (KeyError, IndexError): lat = 360 lbs = False try: lng = location['coordinates'][0] except (KeyError, IndexError): lng = 360 lbs = False self.update({'lbs': lbs, 'lat': lat, 'lng': lng}) return super(DeviceDict, self).__getitem__(k) def set_online(self, signal=None): """ 设置设备在线,填充updateTime 设置设备离线,填充offTime :param signal: :return: """ new_online_info = Device.update_online_cache(self.devNo, DeviceOnlineStatus.DEV_STATUS_ONLINE, signal) self.update(new_online_info) def set_offline(self): new_online_info = Device.update_online_cache(self.devNo, DeviceOnlineStatus.DEV_STATUS_OFFLINE) self.update(new_online_info) def is_authorized_to_dealer(self, dealer_id): return self.ownerId == dealer_id def package(self, packageId, isTemporary=False): if isTemporary: washConfig = self.get('tempWashConfig', {}) else: washConfig = self.get('washConfig', {}) package = washConfig.get(packageId) if package: package['packageId'] = packageId package['isTemporary'] = isTemporary return package def update_device_obj(self, **kwargs): if len(kwargs.keys()) <= 0: return Device.get_collection().update_one({ 'devNo': self.devNo }, { '$set': kwargs }) Device.invalid_device_cache(self.devNo) try: delattr(self, '__my_obj__') except Exception as e: pass def update_other_conf(self, **kwargs): _set = {} for key, value in kwargs.iteritems(): _set['otherConf.{}'.format(key)] = value self.update_device_obj(**_set) def get_other_conf_item(self, item_name, default=None): return self.my_obj.otherConf.get(item_name, default) def support_dev_type_features(self, feature_name): return self.devTypeFeatures.get(feature_name, False) def is_port_can_use(self, port): # TODO 追加是否能够续充的特性 canAdd = self.support_dev_type_features("canAdd") return self.deviceAdapter.is_port_can_use(port, canAdd) class FeedBack(Searchable): ownerId = StringField(verbose_name = u"所有者", default = "") openId = StringField(verbose_name = u"微信ID", default = "") nickname = StringField(verbose_name = u"名称", max_length = 255, default = "") description = StringField(verbose_name = u"投诉内容", max_length = 255, default = "") status = IntField(verbose_name = u"处理状态", default = Const.FeedBackResult.UNTREATED) # 为0:未处理,1:已处理,2:驳回 feedType = StringField(verbose_name = u"反馈类型(故障,订单投诉)", default = "refund") createTime = StringField(verbose_name = u"创建时间", default = lambda: datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')) dealTime = StringField(verbose_name = u"处理时间", default = "") phone = StringField(verbose_name = u"联系电话", default = "") devTypeName = StringField(verbose_name = u"设备类型", default = "") dealerRemark = StringField(verbose_name = u"经销商回复", default = "") imgList = ListField(verbose_name = u'图片列表(往往作用户说明作用)', default = []) dealerImgList = ListField(verbose_name = u'经销商回复图片', default = []) isRead = BooleanField(verbose_name = u'消息已读', default = False) detailInfo = DictField(verbose_name = u'各投诉类型详细信息', default = None) devNo = StringField(verbose_name = u"设备编号", default = None) logicalCode = StringField(verbose_name = u"逻辑编码", default = None) groupId = StringField(verbose_name = u"逻辑编码", default = None) groupNumber = StringField(verbose_name = u"设备组内编号", default = None) groupName = StringField(verbose_name = u"设备组内编号", default = None) address = StringField(verbose_name = u"设备组内编号", default = None) consumeRecordOrderNo = StringField(default = None) coins = VirtualCoinField(verbose_name = u"操作数目,比如退几个币", default = None) resultDesc = StringField(verbose_name = u"处理描述", default = None) meta = {"collection": "FeedBacks", "db_alias": "logdata"} search_fields = ( 'nickname', 'description', 'detailInfo.logicalCode', 'detailInfo.groupNumber', 'detailInfo.groupName') @property def detail(self): rv = { 'ownerId': self.ownerId, 'nickname': self.nickname, 'description': self.description, 'status': self.status, 'feedType': self.feed_type, 'createTime': self.createTime, 'dealTime': self.dealTime, 'phone': self.phone, 'dealerRemark': self.dealerRemark, 'imgList': self.imgList, 'dealerImgList': self.dealerImgList, 'isRead': self.isRead, 'detailInfo': {} } if self.openId: rv['openId'] = self.openId if self.detailInfo: rv['detailInfo'].update(self.detailInfo) else: if self.consumeRecordOrderNo: rv['detailInfo'].update({ 'orderNo': self.consumeRecordOrderNo }) if 'orderNo' in rv['detailInfo']: order_no = rv['detailInfo'].get('orderNo') if order_no: from apps.web.common.proxy import ClientConsumeModelProxy order = ClientConsumeModelProxy.get_one( shard_filter = {'ownerId': self.ownerId}, orderNo = order_no) # type: ConsumeRecord rv['detailInfo'].update(order.info_for_feedback) if 'coins' not in rv['detailInfo'] and self.coins: rv['detailInfo']['coins'] = self.coins if self.logicalCode: rv['detailInfo'].update({ 'logicalCode': self.logicalCode, 'devTypeName': self.devTypeName, 'groupNumber': self.groupNumber, 'groupName': self.groupName, 'address': self.address, }) rv['detailInfo'].pop('devNo', None) rv['detailInfo'].pop('devTypeCode', None) return rv @property def feed_type(self): if self.consumeRecordOrderNo: return 'netpay' if self.feedType in ['refund', 'upper']: return 'other' else: return self.feedType @property def summary(self): rv = { 'id': str(self.id), 'nickname': self.nickname, 'feedType': self.feed_type, 'status': self.status, 'description': self.description, 'dealerRemark': self.dealerRemark, 'createTime': self.createTime, 'detailInfo': {} } if self.logicalCode: rv['detailInfo'].update({ 'logicalCode': self.logicalCode, 'devTypeName': self.devTypeName, 'groupName': self.groupName }) elif self.detailInfo and 'logicalCode' in self.detailInfo: rv['detailInfo'].update({ 'logicalCode': self.detailInfo.get('logicalCode'), 'devTypeName': self.detailInfo.get('devTypeName'), 'groupName': self.detailInfo.get('groupName') }) return rv @classmethod def get_unhandled_count(cls, **kwargs): # type:(Dict[str, Any])->int return cls.objects(status = Const.FeedBackResult.UNTREATED, **kwargs).count() @property def message_type(self): # type: ()->basestring mapping = { 'fault': u'设备故障', 'refund': u'退币申请', 'upper': u'申请上分' } return mapping.get(self.feedType, u'报告老板') def set_handled(self, **kwargs): return self.update(status = Const.FeedBackResult.TREATED, isRead = False, **kwargs) def set_rejected(self, **kwargs): return self.update(status = Const.FeedBackResult.REJECTED, isRead = False, **kwargs) def handle(self, action, **kwargs): if action == 'reject': self.set_rejected(**kwargs) else: self.set_handled(**kwargs) @property def my_logicalCode(self): if self.detailInfo and 'logicalCode' in self.detailInfo: return self.detailInfo['logicalCode'] else: return self.logicalCode @property def my_group_id(self): if self.groupId: return self.groupId else: if self.detailInfo: return self.detailInfo.get('groupId', None) else: return None class GroupDict(dict): """ 组的缓存表示 """ def __repr__(self): return '' % (self.get('groupId', 'unknown'),) @property def v(self): return dict(self) @property def ownerId(self): return self['ownerId'] @property def owner(self): # type:()->Dealer if hasattr(self, '__owner__'): return getattr(self, '__owner__') Dealer = import_string('apps.web.dealer.models.Dealer') dealer = Dealer.objects(id=self.ownerId).first() if dealer: setattr(self, '__owner__', dealer) return dealer @property def object(self): # type() -> Group group = getattr(self, '__object__', None) if not group: group = Group.objects.get(id=self.id) setattr(self, '__object__', group) return group @property def id(self): return ObjectId(self['groupId']) @property def groupId(self): return self['groupId'] @property def groupName(self): return self.get('groupName') @property def address(self): return self.get('address', '') @property def partners(self): return self.partnersDict.values() @property def otherConf(self): return self.get("otherConf", dict()) @property def elecFee(self): return RMB(self.otherConf.get("elecFee", 0)) @property def partnersDict(self): return self.get("partnerDict", {}) @property def ruleDict(self): # type:() -> dict return self.get("ruleDict", dict()) def rule_is_accepted(self, ruleId): # type:(str) -> bool return ruleId in self.ruleDict def get_accepted_rule(self, ruleId): # type:(str) -> str return self.ruleDict.get("ruleId") @property def occupied_numbers(self): # type: ()->Set[str] return set( [ _['groupNumber'] for _ in Device.get_collection().find( {'groupId': self.get('groupId')}, {'groupNumber': 1, '_id': 0}) ] ) @property def is_free(self): return self.get('isFree', False) @property def card_rule_list(self): rule_list = [] if not self.get("cardRuleDict"): Dealer = import_string('apps.web.dealer.models.Dealer') dealer = Dealer.objects.get(id = self.ownerId) card_rule_dict = dealer.format_card_discount else: card_rule_dict = self.get("cardRuleDict") for k, v in card_rule_dict.items(): rule_list.append({'payAmount': k, 'coins': v}) return rule_list @property def recharge_rule_list(self): ruleDict = self.get('ruleDict', {}) res = [ { "id": ruleId, "payAmount": float(ruleId), "coins": float(coins), } for ruleId, coins in ruleDict.items() ] # 优惠充值界面套餐排序(根据金币)升序显示 res = sorted(res, key = lambda x: x['coins']) return res @property def currencyGroup(self): if 'currencyGroup' not in self: Group.CacheMgr.invalid_group_cache([self.groupId]) group = Group.get_group(self.groupId) # type: GroupDict self.update(group.v) return self.get('currencyGroup') @property def identity_info(self): return { 'groupId': self.groupId, 'groupName': self.groupName, 'address': self.address } @property def popPriceDescriptionButton(self): return self.get('popPriceDescriptionButton', False) @property def totalShare(self): # type:() -> Percent return sum([Percent(_['percent']) for _ in self.partners if _.get("isActive")], Percent(0)) @property def hasPayElecFee(self): return bool(filter(lambda x: x.get("payElecFee"), self.partners)) def remove_partner(self, partnerId): """ 组内移除合伙人 """ self.partnersDict.pop(partnerId, None) result = self.object.update(partnerList=self.partnersDict.values()) self.object.invalid_group_cache([str(self.id)]) self.object.__class__.CacheMgr.invalid_group_ids_of_partner_cache(partnerId) return result def update_partner(self, partnerId, ratio, payElecFee, isActive): """ 更新合伙人信息 """ totalShare = self.totalShare curShare = Percent(self.partnersDict.get(partnerId, dict()).get("percent", 0)) # 地址组的参数校验 if totalShare - curShare + Percent(ratio) > Percent(100): raise ValueError("分成比例设置错误") if payElecFee and self.hasPayElecFee: raise ValueError("该地址已有电费承担方,请勿重复设置") partnersDict = self.partnersDict if partnerId in partnersDict: partnersDict[partnerId].update({ 'percent': ratio, 'id': partnerId, 'payElecFee': payElecFee, 'isActive': isActive }) else: partnersDict[partnerId] = { 'percent': ratio, 'id': partnerId, 'payElecFee': payElecFee, 'isActive': isActive } result = self.object.update(partnerList=partnersDict.values()) self.object.invalid_group_cache([str(self.id)]) return result class RequestBodyDict(dict): def __init__(self, *args, **kwargs): dict.__init__(self, *args, **kwargs) self.__dict__ = self def __repr__(self): return '' class CheckDevice(Searchable): logicalCode = StringField(verbose_name = u'设备逻辑码') imei = StringField(verbose_name = u'设备电子标签', unique = True) testTime = DateTimeField(verbose_name = u'开始测试时间') testResult = IntField(verbose_name = u'测试结果', default = 0) testResultDesc = StringField(verbose_name = u'测试结果描述', default = '') bindTime = DateTimeField(verbose_name = u'绑定时间') label = StringField(verbose_name = u'设备标记', default = '') checkSuit = DictField(verbose_name = u'测试套件', default = {}) # 生产是先上线测试, 在挑一个模块测试, 到客户那里一些关键信息没有, 先记录下来 server = StringField(verbose_name = "server") imsi = StringField(default = '', verbose_name = 'imsi') iccid = StringField(default = '', verbose_name = 'iccid') softVer = StringField(verbose_name = "软件版本", default = "") mf = StringField(verbose_name = "固件版本", default = "") coreVer = StringField(default = '', verbose_name = u'核心版本') driverCode = StringField(default = '', verbose_name = 'driverCode') driverVersion = StringField(default = '', verbose_name = 'driverVersion') meta = {"collection": "check_devices", "db_alias": "logdata"} class StockRecord(Searchable): token = StringField(verbose_name = 'unique token', default = lambda: str(uuid.uuid4())) logicCode = StringField(verbose_name = '设备逻辑码') imei = StringField(verbose_name = '设备电子标签') stockType = StringField(verbose_name = '操作方式', default = '') # 新增、消耗 stockTime = StringField(verbose_name = '库存变动时间', default = '') number = IntField(verbose_name = '数目', default = 0) more = StringField(verbose_name = '备注', default = '') meta = { "collection": "StockRecord", "db_alias": "logdata", 'indexes': ['logicCode', 'imei', 'stockType', 'stockTime', 'number', 'more'], # 'shard_key':('logicalCode',) } def __repr__(self): return '' % (self.id,) class FaultRecord(Searchable): """ 告警记录 """ logicalCode = StringField(verbose_name = u'设备逻辑码') imei = StringField(verbose_name = u'设备电子标签') portNo = IntField(verbose_name = u'端口号', default = 0) groupName = StringField(verbose_name = u'地址名称', default = '') address = StringField(verbose_name = u'地址', default = '') faultCode = StringField(verbose_name = u'故障码', default = '') # 告警的恢复是通过故障码 +故障源匹配,然后进行恢复 title = StringField(verbose_name = u'标题', default = "") description = StringField(verbose_name = u'故障描述', default = '') dealerId = ObjectIdField(verbose_name = u'经销商ID') createdTime = DateTimeField(verbose_name = u'收到时间', default = datetime.datetime.now) status = StringField(verbose_name = u'状态', choices = FAULT_RECORD_STATUS.choices(), default = FAULT_RECORD_STATUS.INIT) count = IntField(default = 0) more = StringField(verbose_name = u'其他信息', default = '') detail = DictField(verbose_name = u'详情') level = StringField(verbose_name = u'告警状态', choices = FAULT_LEVEL.choices(), default = FAULT_LEVEL.NORMAL) alarmEventId = StringField(verbose_name = "告警ID", default = "") dealedTime = DateTimeField(verbose_name = u'处理时间', default = datetime.datetime.now) dealedDetail = StringField(verbose_name = u'处理信息', default = '') meta = { "collection": "FaultRecord", "db_alias": "logdata", 'ordering': ['-createdTime'] # "shard_key":("imei",) } def set_status(self, status, **kwargs): choices = FAULT_RECORD_STATUS.choices() if status not in choices: raise NotInChoices('%s not in %s' % (status, choices)) else: return self.update(status = status, **kwargs) def to_dict(self): return { 'id': str(self.id), 'logicalCode': self.logicalCode, # 'imei': self.imei, # 'faultCode': self.faultCode, 'description': self.description, 'title': self.title or self.logicalCode + u' 告警', 'createdTime': self.createdTime.strftime(Const.DATETIME_FMT), # 'dealerId': str(self.dealerId), 'status': self.status, # 'more': self.more, # 'detail': self.detail, # 'count': self.count, 'level': self.level, 'groupName': self.groupName, # 'address':self.address, 'dealedTime': self.dealedTime.strftime(Const.DATETIME_FMT), 'dealedDetail': self.dealedDetail, 'canCheck': True } @classmethod def unhandled_count_by_dealer(cls, dealerId): # type:(ObjectId)->int return cls.objects(dealerId = dealerId, status = FAULT_RECORD_STATUS.INIT).count() @classmethod def record_xf(cls, logicalCode, port, description, faultCode, createdTime, groupId, alarmEventId): """ 记录消防队相关信息 :return: """ # 过滤掉5分钟以内重复发送的信息 record = cls.objects.filter( logicalCode = logicalCode, port = port, faultCode = faultCode, description = description, createdTime__gt = createdTime - datetime.timedelta(minutes = 5) ) if record: raise Exception("重复数据") group = Group.get_group(groupId) devNo = Device.get_devNo_by_logicalCode(logicalCode) device = Device.get_dev(devNo) record = cls( logicalCode = logicalCode, imei = devNo, portNo = port, faultCode = faultCode, description = description, groupName = group.get("groupName", ""), address = group.get("address", ""), alarmEventId = alarmEventId, dealerId = device.get("ownerId", ""), createdTime = createdTime ) record.save() def __repr__(self): return '' % (self.id,) class DeviceEvent(Searchable): """ 对应设备上报的由设备管理存储的设备事件表,可用于统计和在一定程度上恢复线下投币等数据 命令码 200 握手 201 查询设备信息 202 参数设置 203 移动支付 204 移动支付上报事件 205 硬币投币事件上报 207 心跳包 该表不允许任何业务逻辑使用, 仅用来对账和恢复数据 """ cmd = IntField(verbose_name = '命令码') devNo = StringField(verbose_name = '设备号') money = IntField(verbose_name = '投币数额') time = IntField(verbose_name = '上报时间戳') meta = {'collection': 'events', 'db_alias': 'logdata'} class DeviceCommandParam(EmbeddedDocument): id = ObjectIdField(default = ObjectId) description = StringField() key = StringField() default = StringField() allow_change = BooleanField(default = False) type = StringField(default='string') def __repr__(self): return ' key=%s default=%s' % (self.key, self.default) def to_dict(self): return { "id": str(self.id), "description": self.description, "key": self.key, "default": self.default, "allow_change": self.allow_change } def to_payload(self, value): if not self.allow_change: value = self.default # 避免字符串的转义 将json 以及 bool 类型的数据统统处理掉 try: if self.type == 'string': value = str(value) else: value = json.loads(value) except Exception: value = value return {str(self.key): value} class DeviceCommand(Searchable): """ 直接对模块发送指令 一半适用于 管理平台 或者 厂商平台 增添身份 对于该指令的支持 """ # 用于区分设备的两个字段 common 优先级大于devTypeCode devTypeCode = StringField(verbose_name = u'设备类型编码', default = '') common = BooleanField(verbose_name = u'是否所有设备通用', default = False) description = StringField(verbose_name = "指令描述", default = u"指令") # 对于模板的一些内容 topic_pre = StringField(verbose_name = "topic前缀", default = "smartBox") cmd = IntField(verbose_name = u"指令的cmd", default = 201) params = EmbeddedDocumentListField(document_type = DeviceCommandParam, default = []) # 命令使用者的身份控制 active = BooleanField(verbose_name = u"是否启用该指令", default = True) clientId = StringField(verbose_name = u"调用方的Id", default = "") role = StringField(verbose_name = u"身份", choices = ROLE.choices(), default = "supermanager") dateTimeAdded = DateTimeField(verbose_name = u"指令添加时间", default = datetime.datetime.now) meta = {'collection': 'DeviceCommands', 'db_alias': 'default'} search_fields = ('cmd', 'devTypeCode') def __repr__(self): return '' % (self.description, self.cmd, self.devTypeCode) @classmethod def common_and_type_specific(cls, logicalCode, commander): """ :param logicalCode: :param commander: :return: """ def _is_legal(cmd, commanderId): if cmd.role == ROLE.supermanager: return True else: return str(commanderId) == cmd.clientId dev = Device.get_dev_by_l(logicalCode) if not dev: return list() devTypeCode = dev.devType.get("code") cmdQuery = cls.objects(Q(devTypeCode = devTypeCode) | Q(common = True), role = commander.role, active = True) dataList = list() for _item in cmdQuery: if not _is_legal(_item, str(commander.id)): continue data = _item.get_one() data.update({"IMEI": dev.devNo}) dataList.append(data) return dataList def to_dict(self): return { "id": str(self.id), "devTypeCode": self.devTypeCode, "common": self.common, "description": self.description, "topic_pre": self.topic_pre, "active": self.active, "cmd": self.cmd, "clientId": self.clientId, "role": self.role, "params": [_param.to_dict() for _param in self.params] } def get_one(self): """获取指令""" return { "id": str(self.id), "description": self.description, "topic_pre": self.topic_pre, "cmd": self.cmd, "params": [_param.to_dict() for _param in self.params] } def package_command(self, dev, params): """ 将指令经过一系列校验之后 打包成指定的格式 :param dev: :param params: :return: """ # 首先拼接载体部分 payload = {"IMEI": dev.devNo, "cmd": self.cmd} for _param in params: paramId = _param.get("id") value = _param.get("default") param = self.params.filter(id = paramId).first() if not param: continue payload.update(param.to_payload(value)) return payload def supports(self, device): return True class Comment(Searchable): devNo = StringField(verbose_name = "设备编号", min_length = 1) logicalCode = StringField(verbose_name = "逻辑编码", min_length = 1) groupId = StringField(verbose_name = "逻辑编码", min_length = 1) groupNumber = StringField(verbose_name = "设备组内编号", default = "") groupName = StringField(verbose_name = "设备组内编号", default = "") address = StringField(verbose_name = "设备组内编号", default = "") ownerId = StringField(verbose_name = "所有者", default = "") openId = StringField(verbose_name = "微信ID", default = "") nickname = StringField(verbose_name = "名称", max_length = 255, default = "") ratingList = ListField(verbose_name = "得分", default = []) description = StringField(verbose_name = "用户留言", max_length = 255, default = "") createTime = StringField(verbose_name = "创建时间", default = lambda: datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')) meta = {"collection": "Comment", "db_alias": "logdata"} class SIMCard(Searchable): iccid = StringField(verbose_name = "iccid", unique = True) imsi = StringField(verbose_name = "imsi") supplier = StringField(verbose_name = "供应商") provider = StringField(verbose_name = "运营商",default = u'移动') activeTime = DateTimeField(verbose_name = "激活时间") expireTime = DateTimeField(verbose_name = "过期时间") chargeTime = DateTimeField(verbose_name = "充值时间") channel = StringField(verbose_name = u'通道号', default = '') meta = {"collection": "SIMCard", "db_alias": "logdata"} class Cell(Searchable): logicalCode = StringField(verbose_name = "设备编号", min_length = 1) cellNo = StringField(verbose_name = "格子编号", min_length = 1) boardNo = IntField(verbose_name = "板地址", default = 0) lockNo = IntField(verbose_name = "锁地址", default = 0) itemTitle = StringField(verbose_name = "商品标题", default = '') itemDesc = StringField(verbose_name = "商品描述", default = '') itemPicUrl = StringField(verbose_name = "商品图片URL", default = '') itemPrice = IntField(verbose_name = "商品价格,单位分", default = 0) lockStatus = StringField(verbose_name = "门锁状态", default = 'close') itemStatus = StringField(verbose_name = "商品状态", default = 'empty') meta = {"collection": "Cell", "db_alias": "default"} @staticmethod def update_dev_quantity_from_cell(dev): quantity = Cell.objects.filter(logicalCode = dev['logicalCode'], itemStatus = 'full').count() consumptionQuantity = Cell.objects.filter(logicalCode = dev['logicalCode'], itemStatus = 'empty').count() Device.update_field(dev_no = dev['devNo'], update = True, quantity = quantity, consumptionQuantity = consumptionQuantity) class Part(Searchable): class Status(IterConstant): IDLE = Const.DEV_WORK_STATUS_IDLE WORKING = Const.DEV_WORK_STATUS_WORKING FORBIDDEN = Const.DEV_WORK_STATUS_FORBIDDEN # 结束运行的状态 FAULT = Const.DEV_WORK_STATUS_FAULT class OnlineStatus(IterConstant): ONLINE = 'online' OFFLINE = 'offline' logicalCode = StringField(verbose_name = "设备编号", min_length = 1) ownerId = StringField(verbose_name = "ownerId", min_length = 1) partNo = StringField(verbose_name = "部件编码", min_length = 1) partName = StringField(verbose_name = "部件名称", min_length = 1) partType = StringField(verbose_name = "部件类型编码", default = '5001', min_length = 1) # 部件类型,根据各个对接业务,自己定义 attachParas = DictField(verbose_name="其余参数", default={}) dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = '添加进来的时间') dateTimeUpdated = DateTimeField(default = datetime.datetime.now, verbose_name = '更新时间') expiredTime = DateTimeField(verbose_name = u'维保过期时间', default = datetime.datetime.now() + datetime.timedelta(days = Const.MAINTENANCE_DAYS)) status = IntField(verbose_name = u'状态',default = Const.DEV_WORK_STATUS_IDLE) onlineStatus = StringField(verbose_name = u'在线情况',default = 'online') meta = {"collection": "Part", "db_alias": "default"} @staticmethod def upsert_part(logicalCode, ownerId, partNo, partName, partType): # 先把原来一样的part 删除掉 Part.objects.filter(logicalCode = logicalCode, partName = partName, partNo__ne = partNo).delete() count = Part.objects.filter(logicalCode = logicalCode, partNo = partNo).count() if count == 0: # 如果没有,说明是首次上线 if partName == u'网络板': part = Part( logicalCode = logicalCode, ownerId = ownerId, partNo = partNo, partName = partName, partType = partType, expiredTime = datetime.datetime.now() + datetime.timedelta(days = 365) ) else: part = Part( logicalCode = logicalCode, ownerId = ownerId, partNo = partNo, partName = partName, partType = partType, ) part.save() else: return @staticmethod def insert_part_if_not_exist(logicalCode, ownerId, partNo, partName,partType='',partStatus = Const.DEV_WORK_STATUS_IDLE): part = Part.objects(logicalCode = logicalCode,ownerId = ownerId,partNo = str(partNo)).first() if part is None: part = Part(logicalCode = logicalCode,ownerId = ownerId,partNo = str(partNo),partName = partName,partType = partType,partStatus = partStatus) try: part.save() except Exception,e: return None return part @staticmethod def update_part_work_status(logicalCode,partNo,workStatus): part = Part.objects(logicalCode = logicalCode,partNo = str(partNo)).first() if part is None: return part.status = workStatus try: part.save() except Exception,e: return @staticmethod def update_part_network_status(logicalCode,partNo=None,onlineStatus='online'): if partNo: part = Part.objects(logicalCode = logicalCode,partNo = str(partNo)).first() if part is None: return part.onlineStatus = onlineStatus try: part.save() except Exception,e: return else: for part in Part.objects(logicalCode = logicalCode): part.onlineStatus = onlineStatus try: part.save() except Exception,e: continue @staticmethod def delete_dev(logicalCode): Part.objects(logicalCode= logicalCode).delete() class Town(Searchable): name = StringField(verbose_name = 'name', min_length = 1) level = StringField(verbose_name = 'level', min_length = 1) # 级别,1:镇级别,2:村级别 code = StringField(verbose_name = 'level', min_length = 1) # 编码 townDict = {} villageDict = {} @staticmethod def load_in_mem_if_not_exist(): if Town.townDict or Town.villageDict: return for town in Town.objects.filter(level = '1'): Town.townDict[town.name] = town.code for village in Town.objects.filter(level = '2'): Town.villageDict[village.name] = village.code @staticmethod def analyze_town_village(address): Town.load_in_mem_if_not_exist() townCode, villageCode = None, None for k, v in Town.townDict.items(): if k in address: townCode = v break for k, v in Town.villageDict.items(): if k in address: villageCode = v break return townCode, villageCode # 设备的状态记录表,用于汇总设备的状态报表 class DevStatusRecord(Searchable): devNo = StringField(verbose_name = "devNo", min_length = 1) status = StringField(verbose_name = "status", min_length = 1) # offline,offlineBusy,online startTime = DateTimeField(default = datetime.datetime.now, verbose_name = '状态开始时间') endTime = DateTimeField(default = datetime.datetime.now, verbose_name = '状态结束时间') duration = IntField(verbose_name = "持续时长", default = 0) # 单位秒 valueDict = DictField( verbose_name = "附带数据,比如信号量") # 'signalUsages':[{'time':'2010-01-01 00:00:00','signal':9,'usage':0-100(设备使用率)}] meta = {'collection': 'dev_status_record', 'db_alias': 'logdata', 'unique_together': {'devNo', 'startTime', 'endTime'}} class PortReport(Searchable): logicalCode = StringField(verbose_name = "设备编号", min_length = 1) temperature = IntField(verbose_name = "温度信息", min_length = 1) time = IntField(verbose_name = "时间信息", min_length = 1) portInfo = DictField(verbose_name = "端口信息", default = {}) meta = {"collection": "port_report", "db_alias": "default"} class ManagerInputDev(Searchable): logicalCode = StringField(verbose_name = "二维码编号", unique = True) devNo = StringField(verbose_name = "设备编号", unique = True) managerId = StringField(verbose_name = "厂商ID") inputTime = DateTimeField(default = lambda: datetime.datetime.now(), verbose_name = '录入时间') meta = { "collection": "ManagerInputDev", "db_alias": "default" } # 用于记录微付乐单板的事件1、用于全网分析,也不会被日志冲掉;2、不用memcach,用数据库记录关键数据 class WeifuleDeviceOrder(Searchable): orderNo = StringField(verbose_name = 'orderNo') funCode = StringField(verbose_name = 'funCode') dateTimeAdded = DateTimeField(verbose_name = 'dateTimeAdded', default = datetime.datetime.now()) event = DictField(verbose_name = u'消息体内容', default = {}) meta = { "collection": "weifule_device_order", "db_alias": "logdata" } class SerialTimeOut(Searchable): logicalCode = StringField(verbose_name = 'logicalCode', default = '') devNo = StringField(verbose_name = 'devNo', default = '') ownerId = StringField(verbose_name = 'ownerId', default = '') dateTimeAdded = DateTimeField(verbose_name = 'time', default = datetime.datetime.now) devTypeCode = StringField(verbose_name = 'devTypeCode', default = '') packet = StringField() meta = { "collection": "serial_time_out", "db_alias": "logdata" } @classmethod def new_one(cls, device, fun_code, data): # type:(DeviceDict,str,str)->None try: SerialTimeOut( logicalCode = device.logicalCode, devNo = device.devNo, ownerId = device.ownerId, devTypeCode = device.get('devType', '').get('code', ''), packet = u'{}_{}'.format(fun_code, data)).save() except Exception as e: logger.exception(e) class MqttErrorLog(DynamicDocument): logicalCode = StringField(verbose_name='logicalCode', default='') devNo = StringField(verbose_name='devNo', default='') ownerId = StringField(verbose_name='ownerId', default='') dateTimeAdded = DateTimeField(verbose_name='time', default=datetime.datetime.now) devTypeCode = StringField(verbose_name='devTypeCode', default='') payload = StringField() rst = StringField() meta = { "collection": "mqtt_error_log", "db_alias": "logdata" } @classmethod def new_one(cls, device, payload, rst): # type:(DeviceDict,dict,str)->None try: MqttErrorLog( logicalCode=device.logicalCode, devNo=device.devNo, ownerId=device.ownerId, devTypeCode=device.devTypeCode, payload=json_dumps(payload), rst=str(rst)).save() except Exception as e: logger.exception(e) class DeviceMqttStatics(DynamicDocument): devNo = StringField(verbose_name='IMEI', default='') total = LongField(verbose_name=u'总共执行次数', default=0) uartTimeout = LongField(verbose_name=u'串口错误', default=0) connFail = LongField(verbose_name=u'串口错误', default=0) otherError = LongField(verbose_name=u'串口错误', default=0) meta = { 'indexes': [ { 'fields': ['devNo'], 'unique': True }, ], "collection": "device_mqtt_statics", "db_alias": "logdata" } @classmethod def update(cls, device, total, uart_error, conn_error, other_error): cls.objects(devNo=device.devNo).upsert_one(devNo=device.devNo, inc__total=total, inc__uartTimeout=uart_error, inc__connFail=conn_error, inc__otherError=other_error) class DevicePortReport(Searchable): """ 粤万通的设备端口 10分钟 上报一次的数据 用于结算使用金额 """ devNo = StringField(verbose_name = "设备编号") port = StringField(verbose_name = "上报端口号") orderNo = StringField(verbose_name = "订单号") openId = StringField(verbose_name = "端口使用者") # 粤万通的充电柜 即时充电完成 但是订单没有结束之前 还是会上报电量 这个地方就放置多计费 isBilling = BooleanField(verbose_name = "是否参与电量计费", default = True) voltage = IntField(verbose_name = "电压") power = IntField(verbose_name = "功率") elec = IntField(verbose_name = "电量") chargeTime = IntField(verbose_name = "充电时间") stayTime = IntField(verbose_name = "占位时间", default = 0) stageTime = IntField(verbose_name = "该阶段的充电时间") dateTimeAdded = DateTimeField(verbose_name = "生成时间", default = datetime.datetime.now) meta = { "collection": "DevicePortReport", "db_alias": "logdata", "index": [ "orderNo" ] } @classmethod def last_one(cls, orderNo): return cls.objects.filter(orderNo = orderNo).order_by("-dateTimeAdded").first() @classmethod def last_billing_one(cls, orderNo): return cls.objects.filter(orderNo = orderNo, isBilling = True).order_by("-dateTimeAdded").first() @classmethod def create(cls, devNo, port, orderNo, openId, voltage, power, elec, chargeTime, stayTime = None, **kwargs): if not stayTime: stayTime = 0 lastOne = cls.last_one(orderNo) leftChargeTime = lastOne.chargeTime if lastOne else 0 report = cls( devNo = devNo, port = port, orderNo = orderNo, openId = openId, voltage = voltage, power = power, elec = elec, chargeTime = chargeTime, stageTime = chargeTime - leftChargeTime, stayTime = stayTime, **kwargs ) try: report.save() except Exception as e: logger.error(e) return return report @classmethod def billing_records(cls, orderNo): return cls.objects.filter(orderNo = orderNo, isBilling = True).all() @classmethod def stay_records(cls, orderNo): return cls.objects.filter(orderNo = orderNo, isBilling = False).all() @classmethod def calculate(cls, records, intervalMap): """ 计算记录的 :param records: :param intervalMap: :return: """ intervalMap = copy.deepcopy(intervalMap) for record in records: cls.add_power_time(intervalMap, record.power, record.stageTime) # 根据interval_map的累加值计算钱 consume = 0 for _, item in intervalMap.items(): unitPrice = item.get("unitPrice") # 单位是小时 元 _time = item.get("time") # 单位是分钟 需要转换 consume += unitPrice * _time / 60.0 return consume @classmethod def calculate_consume(cls, orderNo, allChargeTime, lastPower, interval_map, devNo, port, openId, voltage, elec): """ 计算消费金钱数据 并将最后一次的主动查询或上报数据保存 :param orderNo: :param allChargeTime: :param lastPower: :param interval_map: :param devNo: 用于记录数据 :param port: 用于记录数据 :param openId: 用于记录数据 :param voltage: 用于记录数据 :param elec: 用于记录数据 :return: """ interval_map = copy.deepcopy(interval_map) timeList = list() totalConsume = VirtualCoin(0) # 依次累加功率段的时间 for record in cls.objects.filter(orderNo = orderNo): timeList.append(record.chargeTime) cls.add_power_time(interval_map, record.power, record.stageTime) # 计算最后一次上报到结束消费这之间的消费金额 try: lastChargeTime = max(timeList) except ValueError as e: lastChargeTime = 0 chargeTime = allChargeTime - lastChargeTime cls.add_power_time(interval_map, lastPower, chargeTime) # 把最后一次的数据也记录上去 lastRecord = cls( devNo = devNo, port = port, orderNo = orderNo, openId = openId, voltage = voltage, power = lastPower, elec = elec, chargeTime = allChargeTime, stageTime = chargeTime ) try: lastRecord.save() except Exception as e: logger.error(e) # 根据interval_map的累加值计算钱 for _, item in interval_map.items(): unitPrice = item.get("unitPrice") _time = item.get("time") totalConsume += unitPrice * _time / 60.0 return totalConsume @staticmethod def add_power_time(interval_map, power, chargeTime): """ 获取功率区间的单价 :param interval_map: 功率区间 :param power: 该阶段功率 :param chargeTime: 该阶段充电时间 :return: Ratio """ for interval, item in interval_map.items(): if int(power) in interval: item.update({ "time": item["time"] + chargeTime }) class DevicePortLastReport(Searchable): """ 关键信息 端口号 + 设备号 唯一 """ orderNo = StringField(verbose_name = "订单编号") devNo = StringField(verbose_name = u"设备编号") port = StringField(verbose_name = u"端口号") power = IntField(verbose_name = "功率", default = 0) elec = FloatField(verbose_name = "已充入电量", default = 0.0) chargeTime = IntField(verbose_name = u"充电时间", default = 0) stayTime = IntField(verbose_name = u"占位时间", default = 0) chargeConsume = AccuracyMoneyField(verbose_name = u"目前为止的充电费用", default = AccuracyRMB(0)) stayConsume = AccuracyMoneyField(verbose_name = u"到目前为止的占位费用", default = AccuracyRMB(0)) totalConsume = AccuracyMoneyField(verbose_name = u"目前为止累计的总消费", default = AccuracyRMB(0)) datetimeUpdated = DateTimeField(verbose_name = u"更新时间", default = datetime.datetime.now) dateTimeAdded = DateTimeField(verbose_name = "生成时间", default = datetime.datetime.now) meta = {'collection': 'device_port_last_report', 'db_alias': 'logdata'} @classmethod def null(cls): return cls() @classmethod def save_last(cls, devNo, port, **kwargs): """ 将设备上报的信息 存储方式为复写 :param devNo: :param port: :param kwargs: :return: """ try: obj = cls.objects.get(devNo = devNo, port = port) except DoesNotExist: obj = cls(devNo = devNo, port = port) obj.power = kwargs.get("power") obj.elec = kwargs.get("elec") obj.chargeTime = kwargs.get("chargeTime") obj.stayTime = kwargs.get("stayTime") orderNo = kwargs.get("orderNo") # 订单号不一致 说明是覆盖之前的记录 更新一下记录的时间 nowTime = datetime.datetime.now() if obj.orderNo != orderNo: obj.chargeConsume = AccuracyRMB(0) obj.stayConsume = AccuracyRMB(0) obj.totalConsume = AccuracyRMB(0) obj.dateTimeAdded = nowTime obj.orderNo = orderNo obj.datetimeUpdated = nowTime return obj.save() @classmethod def get_last(cls, devNo, port, orderNo): """ 获取该 订单的上次上报的信息 :param devNo: :param port: :param orderNo: :return: """ try: obj = cls.objects.get(devNo = devNo, port = port) except DoesNotExist: return cls.null() if obj.orderNo != orderNo: return cls.null() return obj @classmethod def get_last_by_order(cls, orderNo): """ 获取消费的金额 :param orderNo: :return: """ try: obj = cls.objects.get(orderNo = orderNo) except DoesNotExist: obj = cls.null() return obj def update_consume(self, chargeConsume, stayConsume): """更新消费金额 精确到0.1厘""" chargeConsume = AccuracyRMB(chargeConsume) + self.chargeConsume stayConsume = AccuracyRMB(stayConsume) totalConsume = chargeConsume + stayConsume self.update(chargeConsume = chargeConsume, stayConsume = stayConsume, totalConsume = totalConsume) class OfflineCoinStatistics(Searchable): logicalCode = StringField(verbose_name = u'设备编号') devNo = StringField(verbose_name = u"IMEI") count = IntField(verbose_name = u'投币次数') groupId = StringField(verbose_name = u'地址ID') dateTimeAdded = DateTimeField(verbose_name = u"添加时间", default = datetime.datetime.now) meta = { "collection": "offline_coin_statistics", "db_alias": "logdata", "index": [ "logicalCode" ] } @classmethod def recordCoinEvent(cls, logicalCode, devNo, count, groupId): report = cls( logicalCode = logicalCode, devNo = devNo, count = count, groupId = groupId ) try: report.save() except Exception as e: logger.error(e) return class OfflineReportDealers(DynamicDocument): """ 投币的经销商统计 只要有投币的经销商 以ID+DATE为键存入 """ ownerId = StringField(verbose_name = "经销商id", required = True) reportDay = StringField(verbose_name = "report日期", required = True) datetimeAdded = DateTimeField(verbose_name = "记录时间", default = datetime.datetime.now) meta = { "collection": "offline_report_dealers", "db_alias": "logdata", 'indexes': [ { 'fields': ['ownerId', 'reportDay'], 'unique': True }, { 'fields': ['datetimeAdded'], # 保留 7 天的数据 方便经销商的追溯 'expireAfterSeconds': 24 * 3600 * 7 } ], } @classmethod def record_dealer(cls, dealerId, dateStr = None): """ 有投币的经销商统计一下 :param dealerId: :param dateStr: 时间 :return: """ if dateStr is None: dateStr = datetime.datetime.today().strftime("%Y-%m-%d") if not dealerId: logger.error('dealer id is null.') return try: cls(ownerId = dealerId, reportDay = dateStr).save() except NotUniqueError: return except Exception as e: logger.exception(e) @classmethod def get_rpt_dealIds(cls, dateStr = None): if dateStr is None: dateStr = datetime.datetime.today().strftime("%Y-%m-%d") return [_rpt.ownerId for _rpt in cls.objects.filter(reportDay = dateStr).all()] class Battery(Searchable): batterySn = StringField(verbose_name = "电池的SN编号", regex = r"\d{15}") dealerId = StringField(verbose_name = "经销商的ID") devNo = StringField(verbose_name = "电池存放的设备编号", default = "") portNo = StringField(verbose_name = "电池存放的设备端口号", default = "") openId = StringField(verbose_name = "取走电池的用户", default = "") normal = BooleanField(verbose_name = "电池是否正常", default = True) dateTimeAdded = DateTimeField(verbose_name = "电池加入系统的时间", default = datetime.datetime.now) dateTimeUpdated = DateTimeField(verbose_name = "电池信息更新的时间", default = datetime.datetime.now) meta = { "collection": "Battery" } search_fields = ("devNo", "batterySn") def save(self, **kwargs): self.dateTimeUpdated = datetime.datetime.now() return super(Battery, self).save(**kwargs) @staticmethod def is_battery_sn_format(batterySn): """ 是否是正确的电池编号格式 :param batterySn: :return: """ try: result = batterySn.isdigit() and len(batterySn) == 15 except (AttributeError, TypeError): result = False return result @classmethod def add_from_device(cls, batterySn, devNo, port, dealerId): """ 从设备侧读取的电池编号信息入库 :param batterySn: :param devNo: :param port: :param dealerId: :return: """ battery = cls(batterySn = batterySn, devNo = devNo, portNo = port, dealerId = dealerId) try: record = battery.save() except NotUniqueError: return None return record @classmethod def add_from_enter(cls, batterySn, dealerId): """ 手动录入电池信息 :param batterySn: :param dealerId: :return: """ battery = cls(batterySn = batterySn, dealerId = dealerId) try: record = battery.save() except NotUniqueError: return None return record @classmethod def delete_one(cls, dealerId, batterySn): logger.info("delete battery dealer is <{}>, batterySn is <{}>".format(dealerId, batterySn)) battery = cls.objects.filter(dealerId = dealerId, batterySn = batterySn) if not battery: return False try: battery.delete() except Exception as e: logger.exception("dealerId is <{}>, batterySn is <{}>, error is {}".format(dealerId, batterySn, e)) return False return True @classmethod def get_one(cls, dealerId, batterySn): return cls.objects.filter(dealerId = dealerId, batterySn = batterySn).first() def is_owner(self, dealerId): return self.dealerId == dealerId def to_dict(self): # TODO zjl 持有信息需要修改 if self.openId: from apps.web.user.models import MyUser user = MyUser.objects.filter(openId = self.openId).first() lastOwner = "{}".format(user.nickname) elif self.devNo: logicalCode = Device.get_dev(self.devNo).logicalCode lastOwner = "{}-{}".format(logicalCode, self.portNo) else: # 刚刚添加的设备 或者是被清除掉openId的电池 lastOwner = u"" data = { "batterySn": self.batterySn, "dealerId": self.dealerId, "dateTimeUpdated": self.dateTimeUpdated.strftime("%Y-%m-%d %H:%M:%S"), "lastOwner": lastOwner, "disable": self.disable } return data def update_dev_info(self, devNo, port): self.devNo = devNo self.portNo = port self.openId = "" try: self.save() except Exception as e: logger.exception( "batterySn is <{}>, devNo is <{}>, port is <{}>, error is <{}>".format(self.batterySn, devNo, port, e)) return False return True def update_user_info(self, openId): """ 同一个用户同义时间只能拥有一块电池 在更新用户的时候 需要将用户之前拥有过的电池都清除掉 :param openId: :return: """ self.__class__.objects.filter(openId = openId).update(openId = "") self.openId = openId self.devNo = "" self.portNo = "" try: self.save() except Exception as e: logger.exception("batterySn is <{}>, openId is <{}>, error is <{}>".format(self.batterySn, openId, e)) return False return True @classmethod def get_user_last_battery_sn(cls, openId, dealerId): battery = cls.objects.filter(openId = openId, dealerId = dealerId).first() return battery.batterySn if battery else None @property def disable(self): return not self.normal @disable.setter def disable(self, value): if not isinstance(value, bool): raise TypeError(u"type of value must be bool, value is <{}>, battery is <{}>".format(value, self)) if not self.dealerId: raise ValueError(u"no dealer battery can not set disable, battery is <{}>".format(self)) self.update(normal = not value) # TODO capped collection 固有集合 默认 不可删除,不可修改(但非StringField可以修改),只能迭代,目前保留三万条 class DeviceUploadInfo(Searchable): device_imei = StringField(verbose_name = "设备IMEI号") order_id = StringField(verbose_name = "事件ID", default = None) order_type = StringField(verbose_name = "事件类型", default = None) money = FloatField(verbose_name = "使用的金额(分)", default = None) left_money = FloatField(verbose_name = "当前剩余的金额(分)", default = None) amount = FloatField(verbose_name = "本单的总金额", default = None) create_time = FloatField(verbose_name = "创建订单时间", default = None) exec_time = FloatField(verbose_name = "开始充电时间", default = None) time = FloatField(verbose_name = "已使用时间", default = None) elec = FloatField(verbose_name = "已使用电量(毫度)", default = None) status = StringField(verbose_name = "当前设备状态码", default = None) last_clock = FloatField(verbose_name = "最近的时钟信息", default = None) last_ecnt = FloatField(verbose_name = "最近的计量值", default = None) last_update_time = DateTimeField(verbose_name = "最近一次更新数据时间", default = datetime.datetime.now()) meta = { "collection": "device_upload_info", "db_alias": "logdata" } class DeviceCmdStatics(DynamicDocument): devNo = StringField(verbose_name = u"设备IMEI号") cmd = StringField(verbose_name = u"事件ID") count = IntField(verbose_name = u'计数', default = 0) meta = { "collection": "device_cmd_statics", "db_alias": "logdata" } @classmethod def get_collection(cls): return cls._get_collection() @classmethod def record(cls, devNo, cmd): cls.get_collection().update_one( filter = {'devNo': devNo, 'cmd': cmd}, update = {'$inc': {'count': 1}}, upsert = True) class DeviceRentOrderDetail(EmbeddedDocument): status = BooleanField(verbose_name=u"结算是否成功", require=True) reason = StringField(verbose_name=u"结算失败的原因", default="") dateTime = DateTimeField(verbose_name=u"结算时间", require=True) sourceKey = StringField(verbose_name=u"扣款的source") def to_dict(self): return { "dateTime": self.dateTime.strftime("%Y-%m-%d %H:%M:%S"), "status": self.status, "reason": self.reason } class DeviceRentOrder(Searchable): """ 设备的日出租的订单 """ orderNo = StringField(verbose_name=u"订单编号", required=True) devNo = StringField(verbose_name=u"账单设备", require=True) # 以账单产生时候的经销商ID为准 dealerId = StringField(verbose_name=u"账单所有者", require=True) billDate = DateField(verbose_name=u"账单日期", required=True) billAmount = MonetaryField(verbose_name=u"账单金额", require=True) status = BooleanField(verbose_name=u"是否已经结算", default=False) dateTimeAdded = DateTimeField(verbose_name=u"订单添加时间", default=datetime.datetime.now) detail = EmbeddedDocumentListField(document_type=DeviceRentOrderDetail, verbose_name=u"详情详情") meta = { "collection": "DeviceRentOrder", "db_alias": "logdata" } @staticmethod def make_no(date, devNo): return "{}{}".format(devNo, date.strftime("%Y%m%d%H%M%S")) @classmethod def create_by_device(cls, device, date=None): # type:(Device, datetime.date) -> DeviceRentOrder """ 创建设备的日订单 默认是当日的订单 :param device: :param date: :return: """ date = date or datetime.date.today() try: order = cls.objects.get(devNo=device.devNo, billDate=date) except DoesNotExist: order = cls( orderNo=cls.make_no(date, device.devNo), devNo=device.devNo, dealerId=device.ownerId, billDate=date, billAmount=device.rentMoney ).save() return order @classmethod def get_by_device(cls, device): # type:(Device) -> QuerySet """ 获取所有经销商的日租订单 :param device: :return: """ return cls.objects.filter( dealerId=device.ownerId, devNo=device.devNo, ).order_by("-dateTimeAdded") @classmethod def get_not_paid_by_dealer(cls, dealer): # type:(Dealer) -> QuerySet """ 获取经销商所有没有结算的订单 :param dealer: :return: """ return cls.objects.filter( dealerId=str(dealer.id), status=False, ).order_by("dateTimeAdded") @classmethod def get_not_paid_by_device(cls, device): # type:(Device) -> QuerySet """ 获取设备没有完成的订单 :param device: :return: """ return cls.objects.filter( devNo=device.devNo, status=False, ).order_by("dateTimeAdded") @property def dealer(self): # type:() -> Dealer return Dealer.objects.get(id=self.dealerId) def to_dict(self): return { "orderNo": self.orderNo, "billDate": self.billDate.strftime("%Y-%m-%d"), "billAmount": self.billAmount, "status": self.status, "detail": [_.to_dict() for _ in self.detail] } def update_for_success(self, dateTime, source): """ 结算成功的更新 添加支付信息 添加扣款信息 更换状态 :param dateTime: :param source: :return: """ assert not self.status, u"状态更新失败" detail = DeviceRentOrderDetail( status=True, sourceKey=source, dateTime=dateTime ) detailList = self.detail or list() detailList.append(detail) # 只能更新状态为False的 self.__class__.objects.filter( id=self.id, status=False ).update( status=True, detail=detailList ) def update_for_fail(self, dateTime, reason=u""): """ 结算失败的状态更新 添加支付信息 添加失败原因 :param dateTime: :param reason: :return: """ # 订单状态只能从单项 即失败 ---> 成功 assert not self.status, u"状态更新失败" detail = DeviceRentOrderDetail( status=False, reason=reason, dateTime=dateTime ) detailList = self.detail or list() detailList.append(detail) # 只能更新状态为False的 self.__class__.objects.filter( id=self.id, status=False ).update( status=False, detail=detailList ) class EventTimes(Searchable): """ 统计 MQTT 100事件上报的频率 """ devNo = StringField(verbose_name=u"设备编号") driverCode = StringField(verbose_name=u"驱动编码") driverVersion = StringField(verbose_name="驱动版本") devTypeCode = StringField(verbose_name=u"设备类型") server = StringField(verbose_name=u"服务器") times = IntField(verbose_name=u"事件次数") date = StringField(verbose_name="统计时间") meta = { "collection": "EventTimes", "db_alias": "logdata" } class Picture(EmbeddedDocument): IsCover = IntField(verbose_name = u'是否为封面 ',default=0) # 是否为封面,0:否,1:是 PicID = IntField(verbose_name = u'PicID') Url = StringField(verbose_name = u'图片网址 ') Title = StringField(verbose_name = u'图片标题', default = '') TitleForShow = IntField(verbose_name = u'前台界面用于展现的枚举值,不作为业务用') SrcType = StringField(verbose_name = u'图片来源',default = '') class PriceCharging(EmbeddedDocument): FeeTime = StringField(verbose_name = u'收费时间段',default = '00:00-24:00') ElectricityFee = FloatField(verbose_name = u'站点充电费',default = 0.0000) ServiceFee = FloatField(verbose_name = u'站点服务费',default = 0.0000) class DiscountPriceCharging(EmbeddedDocument): #站点充电优惠价格 和高德的字段定义统一,这里再重新定义一个 DiscountTime = StringField(verbose_name = u'时间段描述',default = '00:00-24:00') DiscountElectricityFee = FloatField(verbose_name = u'站点充电费优惠价',default = 0.0000) DiscountServiceFee = FloatField(verbose_name = u'站点服务费优惠价',default = 0.0000) class ChargeTag(EmbeddedDocument): tagId = IntField(verbose_name = u'标签Id') tagType = IntField(verbose_name = u'标签类型(0:普通标签,1:停车标签)',default = 0) tagDesc = StringField(verbose_name = u'标签描述',default = '') tagOrder = IntField(verbose_name = u'排序',default = 0) tagName = StringField(verbose_name = u'标签名称') color = StringField(verbose_name = u'标签颜色') class SwapGroup(Searchable): """ 互联互通数据 """ groupId = StringField(verbose_name="groupId", unique = True) StationID = StringField(verbose_name = u"充电站ID", unique = True) ownerId = StringField(verbose_name="ownerId") swapFlag = BooleanField(verbose_name = u'是否打开互联互通',default = False) location = PointField(verbose_name = '经纬度',default = None ) gcjLng = FloatField(verbose_name = u'gcj版本lng坐标',default = None) gcjLat = FloatField(verbose_name = u'gcj版本lat坐标',default = None) BusineHours = StringField(verbose_name=u"营业时间",default='00:00-24:00')#eg 10:00-15:00 SiteGuide = StringField(verbose_name = u'站点引导',default='') Construction = IntField(verbose_name = u'建设场所',default = 1) SupportOrder = IntField(verbose_name = u'是否支持预约',default = 0) Pictures = EmbeddedDocumentListField(document_type = Picture, verbose_name = u'站点照片 ',default=[]) MatchCars = StringField(verbose_name = u'使用车型描述',default = '') ParkInfo = StringField(verbose_name = u'车位楼层及数量描述',default = '') StationStatus = IntField(verbose_name = u'站点状态 ',default = 50)# 0:未知 1:建设中 5:关闭下线 6:维护中 50:正常使用 ParkNums = IntField(verbose_name = u'车位数量 ',default = 0)# originalAlternateFeeRemark = StringField(verbose_name = u'慢充原价描述',default = '') alternateFeeRemark = StringField(verbose_name = u'慢充价格描述',default = '') originalDirectFeeRemark = StringField(verbose_name = u'快充原价描述',default = '') directFeeRemark = StringField(verbose_name = u'快充价格描述',default = '') PriceChargingInfo = EmbeddedDocumentListField(document_type = PriceCharging, verbose_name = u'站点收费价格明细 ') DiscountPriceChargingInfo = EmbeddedDocumentListField(document_type = DiscountPriceCharging, verbose_name = u'站点收费价格明细_优惠价') ParkFee = StringField(verbose_name = u'停车费率描述',default=u'') RightTag = StringField(verbose_name = u'车主权益',default=u'') ChargeTagList = EmbeddedDocumentListField(document_type = ChargeTag, verbose_name = u'站标签列表') StationTel = StringField(verbose_name = u'站点电话',default = '') ServiceTel = StringField(verbose_name = u'服务电话',default = '') StationType = IntField(verbose_name = u'站点类型 ',default = 1)# Remark = StringField(verbose_name = u'备注',default = '') joinedTime = DateTimeField(default = None, verbose_name = u'加入互联互通的时间') deviceChangedTime = DateTimeField(default = datetime.datetime.now, verbose_name = u'站下面设备变化的最新时间') # 实时统计的部分信息,这里放数据库里,根据设备的事件进行状态更新 chargeType = IntField(verbose_name = u'充电类型 ',default = 0) # 0:快慢充都有,1:快充、2:慢充 dcPortsSum = DictField(verbose_name = u'直流快充端口数统计 ',default = {'allPorts':0,'usedPorts':0,'usePorts':0}) acPortsSum = DictField(verbose_name = u'交流快充端口数统计 ',default = {'allPorts':0,'usedPorts':0,'usePorts':0}) exceptSum = DictField(verbose_name = u'异常设备统计 ',default = {'offline':0,'fault':0}) deviceNum = IntField(verbose_name = u'设备数量',default = 0) @staticmethod def make_stationID(groupId): h = hashlib.md5() h.update(groupId.encode('utf-8')) return h.hexdigest()[8:24] @staticmethod def find_one_device_cur_fee(groupId): devs = Device.get_devices_by_group([groupId]) devNo = None device = None for dev in devs.values(): device = dev if dev.majorDeviceType and (u'交流' in dev.majorDeviceType or u'直流' in dev.majorDeviceType): devNo = dev['devNo'] break if devNo is None : return None # 各个业务记录的电费和服务费的形式可能不一样,所以放到设备类型中 box = ActionDeviceBuilder.create_action_device(device) return box.get_cur_fee() def get_tag_desc_list(self): self.ChargeTagList.sort(key=lambda x:x.tagOrder) return [ tag.tagDesc for tag in self.ChargeTagList if tag] @staticmethod def get_tag_desc_list_for_dict(swapInfo): swapInfo['ChargeTagList'].sort(key=lambda x:x['tagOrder']) result = [] for tag in swapInfo['ChargeTagList']: if not tag: continue if tag['tagDesc'] and tag['tagName']: result.append('%s:%s' %(tag['tagName'],tag['tagDesc'])) elif tag['tagName']: result.append(tag['tagName']) return result @staticmethod def recount_devnum(groupId): result = 0 devs = Device.get_devices_by_group([groupId]) for dev in devs.values(): if dev.majorDeviceType and (u'交流' in dev.majorDeviceType or u'直流' in dev.majorDeviceType): result += 1 continue swap = SwapGroup.objects(groupId = groupId).first() if swap: swap.deviceChangedTime = datetime.datetime.now() swap.deviceNum = result try: swap.save() except Exception,e: return @staticmethod def update_swap_time_and_num(groupId,devNum=0): swap = SwapGroup.objects(groupId = groupId).first() if swap: swap.deviceChangedTime = datetime.datetime.now() swap.deviceNum += devNum if swap.deviceNum <0: swap.deviceNum = 0 try: swap.save() except Exception,e: return @staticmethod def delete_group(groupId): return SwapGroup.objects(groupId = groupId).delete() @property def lng(self): return self.location['coordinates'][0] @property def lat(self): return self.location['coordinates'][1] @property def pictures(self): return [{'IsCover':pic.IsCover,'Url':pic.Url,'Title':pic.Title,'SrcType':pic.SrcType} for pic in self.Pictures] @staticmethod def bd09_to_gcj02(lng, lat): """ 百度坐标系到google坐标系的经纬度转换 :param lng: 百度经度 :param lat: 百度维度 :return: """ if lng == 0.0 or lat == 0.0: return lng, lat x_pi = 3.14159265358979324 * 3000.0 / 180.0 x = lng - 0.0065 y = lat - 0.006 z = math.sqrt(x * x + y * y) - 0.00002 * math.sin(y * x_pi) theta = math.atan2(y, x) - 0.000003 * math.cos(x * x_pi) gg_lng = z * math.cos(theta) gg_lat = z * math.sin(theta) return [gg_lng, gg_lat] @staticmethod def get_cur_fee(priceInfoList): nowTime = datetime.datetime.now().strftime('%H:%M') for priceInfo in priceInfoList: tempList = priceInfo['FeeTime'].split('-') startTime = tempList[0] endTime = tempList[1] if nowTime >= startTime and nowTime <= endTime: return {'curElecFee':priceInfo['ElectricityFee'],'curServiceFee':priceInfo['ServiceFee']} return {'curElecFee':'-','curServiceFee':'-'} @staticmethod def get_stats(groupId): lcs = Device.get_logicalCode_by_groupId(groupId) parts = Part.objects(logicalCode__in = lcs) dcAllPorts,dcUsedPorts,acAllPorts,acUsedPorts=0,0,0,0 for part in parts: if part.status not in [Part.Status.IDLE,Part.Status.WORKING]: continue if part.partType == 'dc': dcAllPorts += 1 if part.status == Part.Status.WORKING: dcUsedPorts +=1 continue if part.partType == 'ac': acAllPorts += 1 if part.status == Part.Status.WORKING: acUsedPorts +=1 offlineNum,faultNum = 0,0 for lc in lcs: dev = Device.get_dev_by_l(lc) if dev is None: continue if not dev.devType or not dev.devType['majorDeviceType']: continue if not (u'直流' in dev.devType['majorDeviceType'] or u'交流' in dev.devType['majorDeviceType']): continue if not dev.online: offlineNum += 0 if dev.status == Const.DEV_WORK_STATUS_FAULT: faultNum += 1 return { 'dcAllPorts':dcAllPorts,'dcUsedPorts':dcUsedPorts,'dcUsePorts':dcAllPorts-dcUsedPorts, 'acAllPorts':acAllPorts,'acUsedPorts':acUsedPorts,'acUsePorts':acAllPorts-acUsedPorts, 'offline':offlineNum,'fault':faultNum }