# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import json import logging import re from mongoengine import Q from typing import TYPE_CHECKING from apilib.monetary import RMB from apilib.utils_datetime import to_datetime from apps.web.common.proxy import ClientConsumeModelProxy from apps.web.constant import Const, DeviceCmdCode, MQTT_TIMEOUT, CONSUMETYPE from apps.web.core.adapter.base import SmartBox from apps.web.core.exceptions import ServiceException, InvalidParameter from apps.web.core.networking import MessageSender from apps.web.dealer.models import Dealer from apps.web.device.models import Device from apps.web.user.constant2 import StartDeviceType from apps.web.user.models import ConsumeRecord, MyUser logger = logging.getLogger(__name__) if TYPE_CHECKING: pass class PolicyPackageInit(SmartBox): INIT_POLICY = { 'forIdcard': { 'policyType': 'time', 'billingMethod': 'prepaid', 'autoRefund': True, 'money': 1, "minAfterStartCoins": 0, "rule": { 'prices': [ { "price": 0.25, "power": 150 }, { "price": 0.36, "power": 250 }, { "price": 0.54, "power": 350 }, { "price": 0.72, "power": 500 }, { "price": 0.90, "power": 700 }, { "price": 1.08, "power": 990 }, ], 'price': 1 } }, 'forApps': { "policyType": "time", "rule": { "prices": [ { "price": 0.25, "power": 150 }, { "price": 0.36, "power": 250 }, { "price": 0.54, "power": 350 }, { "price": 0.72, "power": 500 }, { "price": 0.90, "power": 700 }, { "price": 1.08, "power": 990 }, ], "price": 1 } } } def _generate_id(self, ids): i = 1 while True: if str(i) in ids: i += 1 else: return str(i) def _sort_by_sn(self): # 调整sn顺序 for item in self.ruleList: item["sn"] = self.ruleList.index(item) def _formart_ruleList(self, isTemp=False): if not isTemp and not filter(lambda _: _.get('switch') == True, self.ruleList): raise ServiceException( {'result': 0, 'description': '没有可供用户选择的套餐, 请新增或启用 至少一个用户套餐', 'payload': {}}) ids = [str(rule['id']) for rule in self.ruleList if 'id' in rule] for i, rule in enumerate(self.ruleList): ruleId = str(rule.get('id', '')) if not ruleId: ruleId = self._generate_id(ids) ids.append(ruleId) # 充满自停套餐 if ruleId == 'autoPackage': rule['time'] = float(rule.get('time') or 0) # 自定义输入套餐 if ruleId == 'customPackage': rule['time'] = float(rule.get('time') or 0) self.washConfig[ruleId] = { 'name': rule.get('name') or '套餐{}'.format(i + 1), 'price': round(RMB(rule.get('price') or 0), 2), 'coins': round(RMB(rule.get('price') or 0), 2) } if 'switch' in rule: self.washConfig[ruleId].update({'switch': rule.get('switch', True)}) if 'time' in rule: self.washConfig[ruleId].update({'time': round(float(rule.get('time') or 0), 2)}) if 'unit' in rule: self.washConfig[ruleId].update({'unit': rule.get('unit')}) if 'sn' in rule: self.washConfig[ruleId].update({'sn': rule['sn']}) if 'autoStop' in rule: self.washConfig[ruleId]['autoStop'] = rule['autoStop'] if 'autoRefund' in rule: self.washConfig[ruleId]['autoRefund'] = rule.get('autoRefund', False) # 此处有可能传入空字符串 加一个or判断为0 if 'minAfterStartCoins' in rule: minAfterStartCoins = round(RMB(rule['minAfterStartCoins'] or 0), 2) if minAfterStartCoins > 0 and minAfterStartCoins < self.washConfig[ruleId]['coins']: ServiceException( {'result': 0, 'description': '套餐({})最小启动金额需要大于套餐金额{}'.format(self.washConfig[ruleId]['name'], minAfterStartCoins), 'payload': {}}) self.washConfig[ruleId]['minAfterStartCoins'] = minAfterStartCoins # 此处有可能传入空字符串 加一个or判断为0 if 'minFee' in rule: self.washConfig[ruleId]['minFee'] = round(RMB(rule['minFee'] or 0), 2) if isTemp: self.washConfig[ruleId]['billingMethod'] = 'prepaid' else: self.washConfig[ruleId]['billingMethod'] = 'postpaid' # 检验部分 if RMB(rule.get('price') or 0) > self.dealer.maxPackagePrice: raise ServiceException( {'result': 0, 'description': '套餐支付金额( {}元 )超过最大限制'.format(rule['name']), 'payload': {}}) def _formart_policy_rule(self, policy): # 时间计费参数 prices = policy.get('rule', {}).get('prices', []) # 电量计费参数 price = policy.get('rule', {}).get('price') # 检验传过来的参数是否完整 policyType = policy.get('policyType') if policyType == 'time': if prices: pass else: raise ServiceException( {'result': 0, 'description': '按时间计费规则不可为空, 请填写计费规则', 'payload': {}}) if price: price = round(float(price), 2) else: price = round(self.INIT_POLICY['forApps']['rule']['price'], 2) elif policyType == 'elec': if prices: pass else: prices = self.INIT_POLICY['forApps']['rule']['prices'] if price: price = round(float(price), 2) else: raise ServiceException( {'result': 0, 'description': '按电量计费参数不可为空, 请填写计费参数', 'payload': {}}) else: raise ServiceException( {'result': 0, 'description': '计费方式不存在', 'payload': {}}) # 时间计费部分 if not prices: raise ServiceException( {'result': 0, 'description': '按时间计费规则不可为空, 请填写计费规则', 'payload': {}}) for item in prices: item['power'] = int(float(item.get('power', 0))) item['price'] = round(float(item.get('price', 0)), 2) if RMB(item['price']) > self.dealer.maxPackagePrice: raise ServiceException( {'result': 0, 'description': '计费规则(功率: {}, 价格: {})金额超限'.format(item['power'], item['price']), 'payload': {}}) prices = sorted(prices, key=lambda _: _['power']) # 电量计费部分 if price > 10: raise ServiceException( {'result': 0, 'description': '电量模式: 电费单价金额超限(电费单价不得大于10元/度)'.format(price), 'payload': {}}) return {'prices': prices, 'price': price} def _formart_apps(self): forApps = self.policyTemp.get('forApps', {}) forApps['rule'] = self._formart_policy_rule(forApps) forApps['policyType'] = forApps.get('policyType') return forApps def _formart_Idcard(self): forIdcard = self.policyTemp.get('forIdcard', {}) forIdcard['policyType'] = forIdcard.get('policyType', 'time') forIdcard['rule'] = self._formart_policy_rule(forIdcard) forIdcard['billingMethod'] = forIdcard.get('billingMethod', 'prepaid') if forIdcard['billingMethod'] == 'prepaid': forIdcard['money'] = round(float(forIdcard.get('money', 1)), 2) if RMB(forIdcard['money']) > self.dealer.maxPackagePrice: raise ServiceException( {'result': 0, 'description': '刷一次卡扣费金额( {} )超限'.format(forIdcard['money']), 'payload': {}}) forIdcard['autoRefund'] = forIdcard.get('autoRefund', True) elif forIdcard['billingMethod'] == 'postpaid': forIdcard['minAfterStartCoins'] = round(float(forIdcard.get('minAfterStartCoins', 1)), 2) return forIdcard def _formart_policy(self): self.policyTemp = {'forApps': self._formart_apps(), 'forIdcard': self._formart_Idcard()} def _get_ruleList(self, isTemp=False): if isTemp: config = self.device.get('tempWashConfig', {}) else: config = self.device['washConfig'] ruleList = [] for packageId, rule in config.items(): item = { 'id': packageId, 'name': rule['name'], 'coins': rule.get('price'), 'price': rule.get('price'), 'time': rule.get('time', 20), 'description': rule.get('description', ''), 'imgList': rule.get('imgList', []), 'unit': rule.get('unit', u'分钟'), 'switch': rule.get('switch', True), } if 'sn' in rule: item['sn'] = rule['sn'] if 'autoStop' in rule: item['autoStop'] = rule['autoStop'] if 'autoRefund' in rule: item['autoRefund'] = rule['autoRefund'] if 'minAfterStartCoins' in rule: item['minAfterStartCoins'] = rule['minAfterStartCoins'] if 'minFee' in rule: item['minFee'] = rule['minFee'] if 'billingMethod' in rule: item['billingMethod'] = rule['billingMethod'] ruleList.append(item) return sorted(ruleList, key=lambda x: (x.get('sn'), x.get('id'))) def _get_displaySwitch(self, isTemp=False): displaySwitchs = { "displayCoinsSwitch": False, "displayPriceSwitch": isTemp, "displayTimeSwitch": True, "setBasePriceAble": False, "setPulseAble": False } return displaySwitchs def _get_policy(self): return self.device.policyTemp or self.INIT_POLICY def get_reg_model(self, dealer, devTypeId, isTemp=False, **kw): if isTemp: ruleList = [] else: ruleList = [ { 'id': 'autoPackage', 'autoRefund': False, 'autoStop': True, 'billingMethod': 'postpaid', 'coins': 0.0, 'minAfterStartCoins': 3.0, 'minFee': 0.1, "name": "充满自停", 'policyType': 'time', 'price': 0.0, 'sn': 0, 'switch': True, 'time': 720.0, "unit": "分钟", }, { 'id': 'customPackage', 'autoRefund': False, 'autoStop': True, 'billingMethod': 'postpaid', 'coins': 0.0, 'minAfterStartCoins': 3.0, 'minFee': 0.1, "name": "自定义", 'policyType': 'time', 'price': 0.0, 'sn': 1, 'switch': True, 'time': 720.0, "unit": "分钟", }, { 'autoRefund': False, 'autoStop': True, 'billingMethod': 'postpaid', 'coins': 0.0, 'minAfterStartCoins': 1.0, 'minFee': 0.0, "name": "60分钟", 'policyType': 'time', 'price': 0.0, 'sn': 2, 'switch': True, 'time': 60.0, "unit": "分钟", }, ] payload = {'ruleList': ruleList, 'displaySwitchs': self._get_displaySwitch(isTemp=isTemp), 'policyTemp': self.INIT_POLICY} if devTypeId in dealer.defaultWashConfig: dealerDefaultWashConfig = dealer.defaultWashConfig[devTypeId] if dealerDefaultWashConfig.get('policyTemp'): payload.update({'policyTemp': dealerDefaultWashConfig['policyTemp']}) if dealerDefaultWashConfig.get('displaySwitchs'): payload.update({'displaySwitchs': dealerDefaultWashConfig['displaySwitchs']}) if dealerDefaultWashConfig.get('ruleList') and not isTemp: payload.update({'ruleList': dealerDefaultWashConfig['ruleList']}) return payload def reg_model(self, owner, ruleList, policyTemp, devType): self.dealer = owner # type: Dealer self.ruleList = ruleList self.devType = devType # self.displaySwitchs = self._get_displaySwitch self.policyTemp = policyTemp self.washConfig = {} # 1.根据sn排序 self._sort_by_sn() # 2 计费规则 policy 格式化 self._formart_policy() # 3.套餐 ruleList 格式话 self._formart_ruleList(False) payload = {'ruleList': ruleList, 'policyTemp': policyTemp} owner.defaultWashConfig[str(devType['id'])] = payload owner.save() return self.washConfig, self.policyTemp def get_show_package(self, dev, isTemp=False): self.device = dev # type: DeviceDict group = self.device.group # type: GroupDict # 探测是否地址为免费活动组,默认为否 is_free_service = group.is_free if isTemp: config = self.device.get('tempWashConfig', {}) else: config = self.device['washConfig'] packages = [] for packageId, rule in config.items(): # 没有启用的套餐 直接掠过 if not rule.get("switch", True): continue price = rule['price'] item = { 'id': packageId, 'coins': rule['coins'], 'name': rule['name'], 'time': rule['time'], 'price': price, 'unit': rule.get('unit', u'分钟'), 'sn': rule.get('sn') } if 'minFee' in rule and rule['minFee'] and float(rule['minFee']) > 0: item.update({'minFee': rule.get('minFee')}) if 'minAfterStartCoins' in rule and rule['minAfterStartCoins'] and float(rule['minAfterStartCoins']) > 0: item.update({'minAfterStartCoins': rule.get('minAfterStartCoins')}) if is_free_service: item.update({'description': '当前处于免费时段'}) else: item.update({'description': self._get_policy_text()}) if 'policyType' in rule: item.update({'policyType': rule.get('policyType')}) packages.append(item) return sorted(packages, key=lambda x: (x.get('sn'), x.get('id'))) def format_device_package(self, isTemp=False, **kw): self.dealer = self.device.owner # type: Dealer self.ruleList = kw['serviceData'] self.displaySwitchs = kw['displaySwitchs'] self.policyTemp = kw['policyTemp'] self.washConfig = {} if kw['logicalCode'].__class__.__name__ == 'list': devNoList = [Device.get_devNo_by_logicalCode(lc) for lc in kw['logicalCode']] else: devNoList = [Device.get_devNo_by_logicalCode(kw['logicalCode'])] devDict = Device.get_dev_by_nos(devNoList) if not devDict: raise ServiceException({'result': 2, 'description': u'找不到设备'}) devList = devDict.keys() if isTemp: # 1.根据sn排序 self._sort_by_sn() # 2.套餐 ruleList 格式话 self._formart_ruleList(isTemp) # 3. 更新数据 Device.objects.filter(devNo__in=devList, ownerId=str(self.dealer.id)).update(tempWashConfig=self.washConfig) else: # 1.根据sn排序 self._sort_by_sn() # 2 计费规则 policy 格式化 self._formart_policy() # 3.套餐 ruleList 格式话 self._formart_ruleList(isTemp) # 4 更新数据 Device.objects.filter(devNo__in=devList, ownerId=str(self.dealer.id)).update(washConfig=self.washConfig, policyTemp=self.policyTemp) # 清理缓存 Device.invalid_many_device_cache(devList) Device.get_many_dev_control_cache(devList) def get(self, dev, isTemp=False): self.device = dev # type: DeviceDict # 1. 获取ruleList 套餐 ruleList = self._get_ruleList(isTemp) # 2. 获取displacySwitch 显示开关 # displaySwitchs = dev.otherConf.get('displaySwitchs') or self._get_displaySwitch(isTemp) # 3. 获取policy 计费规则 policyTemp = self._get_policy() return {'ruleList': ruleList, 'policyTemp': policyTemp} def dealer_show_package(self, isTemp=False, **kw): displaySwitchs = self._get_displaySwitch(isTemp) try: # 1. 获取ruleList 套餐 ruleList = self._get_ruleList(isTemp) # 2. 获取policy 计费规则 policyTemp = self._get_policy() except: return {'displaySwitchs': displaySwitchs, 'policyTemp': self.INIT_POLICY, 'ruleList': []} return { 'displaySwitchs': displaySwitchs, 'policyTemp': policyTemp, 'ruleList': ruleList} def user_show_package(self, isTemp=False): group = self.device.group # type: GroupDict # 探测是否地址为免费活动组,默认为否 is_free_service = group.is_free if isTemp: config = self.device.get('tempWashConfig', {}) else: config = self.device['washConfig'] if "displaySwitchs" in self.device.otherConf: displaySwitchs = self.device.otherConf.get('displaySwitchs') displaySwitchs = dict(filter(lambda x: "display" in x[0], displaySwitchs.items())) else: displaySwitchs = {'displayCoinsSwitch': True, 'displayTimeSwitch': True, 'displayPriceSwitch': True, } packages = [] for packageId, rule in config.items(): # 没有启用的套餐 直接掠过 if not rule.get("switch", True): continue item = { 'id': packageId } if 'name' in rule: item['name'] = rule['name'] if 'coins' in rule: item['coins'] = rule['coins'] if 'price' in rule: item['price'] = rule['price'] if 'time' in rule: item['time'] = rule['time'] if 'description' in rule: item['description'] = rule['description'] if 'unit' in rule: item['unit'] = rule['unit'] if 'sn' in rule: item['sn'] = rule['sn'] if 'minFee' in rule and rule['minFee'] and float(rule['minFee']) > 0: item.update({'minFee': rule.get('minFee')}) if 'minAfterStartCoins' in rule and rule['minAfterStartCoins'] and float(rule['minAfterStartCoins']) > 0: item.update({'minAfterStartCoins': rule.get('minAfterStartCoins')}) if 'autoRefund' in rule: item.update({'autoRefund': rule.get('autoRefund')}) if is_free_service: item.update({'description': '当前处于免费时段'}) item.update({'policyType': self._get_policy_type()}) item.update(displaySwitchs) packages.append(item) return sorted(packages, key=lambda x: (x.get('sn'), x.get('id'))) def _get_policy_type(self): policy = self._get_policy() forApps = policy.get('forApps') return forApps.get('policyType', 'time') def _get_policy_text(self): policy = self._get_policy() forApps = policy.get('forApps') policyType = forApps.get('policyType') text = '' if policyType == 'time': text += '根据功率挡位的时长计费' elif policyType == 'elec': price = forApps.get('rule', {}).get('price') text += '根据电量({}元/度)计费'.format(price) else: pass return text @property def support_device_package(self): return True class PolicyCommon(PolicyPackageInit, SmartBox): """ 子类型区别 1. 默认类型(put_people_first): 常规套餐: 1.后付费,启动前需要充值 2.账户余额不足支付, 挂起一单, 直到下次支付, 才能再次启动, 3.只能启动一单. 临时套餐: 1.预付费. 2.下发金额,费用到了会停止. 3.可以启动多单. 2. 特殊类型(look_at_money): 常规套餐: 1.后付费,启动前需要充值. 2.启动下发账户全部余额, 余额用完会停止充电. 3.只能启动一单. 临时套餐: 1.预付费. 2.下发金额,费用到了会停止. 3.可以启动多单. """ def reverse_hex(self, data): # type:(str) -> str if not isinstance(data, (str, unicode)): raise TypeError return ''.join(list(reversed(re.findall(r'.{2}', data)))) def send_mqtt(self, payload, cmd=DeviceCmdCode.OPERATE_DEV_SYNC, timeout=10): """ 发送mqtt 指令默认210 返回data """ result = MessageSender.send(self.device, cmd, payload=payload, timeout=timeout) if cmd in [DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, DeviceCmdCode.PASSTHROUGH_OPERATE_DEV_NO_RESPONSE]: return if 'rst' in result and result['rst'] != 0: if result['rst'] == -1: raise ServiceException( {'result': 2, 'description': u'该设备正在玩命找网络,请您稍候再试', 'rst': -1}) elif result['rst'] == 1: raise ServiceException( {'result': 2, 'description': u'该设备忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能', 'rst': 1}) elif result['rst'] == 2: raise ServiceException( {'result': 2, 'description': u'设备启动失败, 检测未在工作状态', 'rst': 2}) elif result['rst'] == 3: raise ServiceException( {'result': 2, 'description': u'无启动端口信息', 'rst': 3}) elif result['rst'] == 4: raise ServiceException( {'result': 2, 'description': u'设备未启动', 'rst': 4}) elif result['rst'] == 5: raise ServiceException( {'result': 2, 'description': u'续充失败, 计费规则不一致', 'rst': 5}) elif result['rst'] == 6: raise ServiceException( {'result': 2, 'description': u'端口正在使用中, 请选择其他端口启动充电', 'rst': 6}) elif result['rst'] == 7: raise ServiceException( {'result': 2, 'description': u'启动参数有误, 请联系经销商重新配置(rule_type)', 'rst': 7}) elif result['rst'] == 8: raise ServiceException( {'result': 2, 'description': u'启动参数有误, 请联系经销商重新配置(billing_method)', 'rst': 8}) elif result['rst'] == 9: raise ServiceException( {'result': 2, 'description': u'启动参数有误, 请联系经销商重新配置(order_id)', 'rst': 9}) elif result['rst'] == 10: raise ServiceException( {'result': 2, 'description': u'启动参数有误, 请联系经销商重新配置(open_id)', 'rst': 10}) elif result['rst'] == 11: raise ServiceException( {'result': 2, 'description': u'启动参数有误, 请联系经销商重新配置(rule)', 'rst': 11}) elif result['rst'] == 12: raise ServiceException( {'result': 2, 'description': u'启动参数有误, 启动时间小于1分钟, 请联系经销商调整套餐', 'rst': 12}) elif result['rst'] == 13: raise ServiceException( {'result': 2, 'description': u'启动参数有误, 启动电量小于0.01kW·h, 请联系经销商调整套餐', 'rst': 13}) elif result['rst'] == 14: raise ServiceException( {'result': 2, 'description': u'没有找到此订单', 'rst': 14}) if result.get('data'): return result['data'] else: return result def _check_package(self, package): """ 获取设备启动的发送数据 根据设备的当前模式以及套餐获取 :param package: :return: """ unit = package.get('unit', u'分钟') _time = float(package.get('time', 0)) coins = float(package.get('coins', 0)) price = float(package.get('price', 0)) policyType = package.get('policyType') if not policyType: raise ServiceException({'result': 2, 'description': u'套餐计费方式错误, 请重新选择后配置'}) billingMethod = package.get('billingMethod', CONSUMETYPE.POSTPAID) result = {'policyType': policyType, 'coins': coins, 'price': price, 'billingMethod': billingMethod} if billingMethod == CONSUMETYPE.POSTPAID: if policyType == 'time': # 按时间计费 if unit == u'小时': result.update({'needTime': _time * 60, }) elif unit == u'天': result.update({'needTime': _time * 24 * 60}) elif unit == u'秒': result.update({'needTime': _time / 60, }) elif unit == u'分钟': result.update({'needTime': _time, }) else: raise ServiceException({'result': 2, 'description': u'套餐单位错误,请联系经销商'}) elif policyType == 'elec': if unit == u'度': result.update({'needElec': _time, }) else: raise ServiceException({'result': 2, 'description': u'套餐单位错误,请联系经销商'}) else: if coins <= 0: raise ServiceException({'result': 2, 'description': u'套餐单位错误,套餐金币不能为0'}) if price <= 0: raise ServiceException({'result': 2, 'description': u'套餐单位错误,套餐金额不能为0'}) if policyType == 'time': pass elif policyType == 'elec': pass else: raise ServiceException({'result': 2, 'description': u'套餐单位错误,请联系经销商'}) return result def update_device_configs(self, updateDict): dev = Device.objects.get(devNo=self.device.devNo) deviceConfigs = dev.otherConf.get('deviceConfigs', {}) deviceConfigs.update(updateDict) dev.otherConf['deviceConfigs'] = deviceConfigs dev.save() Device.invalid_device_cache(self.device.devNo) @property def look_at_money(self): return self.device.devTypeFeatures.get('look_at_money', False) @property def accumulate_max_power(self): return self.device.devTypeFeatures.get('accumulate_max_power', False) @property def device_configs(self): dev = Device.get_dev(self.device.devNo) deviceConfigs = dev.get('otherConf', {}).get('deviceConfigs', {}) return deviceConfigs @property def server_configs(self): dev = Device.get_dev(self.device.devNo) serverConfigs = dev.get('otherConf', {}).get('serverConfigs', {}) return serverConfigs def update_server_configs(self, updateDict): dev = Device.objects.get(devNo=self.device.devNo) serverConfigs = dev.otherConf.get('serverConfigs', {}) serverConfigs.update(updateDict) dev.otherConf['serverConfigs'] = serverConfigs dev.save() Device.invalid_device_cache(self.device.devNo) def stop(self, port=None): return self.send_mqtt({'funCode': 'stop', 'port': int(port), 'role': 'user'}) def stop_charging_port(self, port): return self.send_mqtt({'funCode': 'stop', 'port': int(port)}) @property def isHaveStopEvent(self): return True def check_dev_status(self, attachParas=None): if attachParas is None: raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路、电池类型信息'}) if attachParas.get('isTempPackage') == True: washConfig = self.device.get('tempWashConfig', {}) else: washConfig = self.device.get('washConfig', {}) packageId = attachParas.get('packageId') if packageId: # 此时为快捷启动充电之前已经校验过一次 package = washConfig.get(packageId) billingMethod = package.get('billingMethod') port = attachParas['chargeIndex'] result = self.send_mqtt({'funCode': 'port_info', 'port': int(port), }) if 'order_id' in result: if billingMethod == CONSUMETYPE.POSTPAID: raise ServiceException({'result': 2, 'description': u'当前端口繁忙'}) else: openId = attachParas.get('openId') if result['open_id'] == openId and result['order_type'] == 'com_start' and result[ 'billing_method'] == billingMethod: return else: raise ServiceException({'result': 2, 'description': u'当前端口繁忙'}) def prepare_package(self, packageId, attachParas, startType=StartDeviceType.ON_LIEN): is_temp_package = 'isTempPackage' in attachParas and attachParas['isTempPackage'] is True if is_temp_package: washConfig = self._device.get('tempWashConfig', None) else: washConfig = self._device.get('washConfig', None) # 校验业务配置 if not washConfig: raise ServiceException({'result': 2, 'description': u'未配置业务,请联系经销商'}) # 校验套餐ID的有效性 package = washConfig.get(packageId) if package is None: raise ServiceException({'result': 2, 'description': u'该套餐不存在,请联系经销商'}) package['packageId'] = packageId policyType = self.device.policyTemp.get('forApps', {}).get('policyType', 'time') self.billingMethod = package.get('billingMethod', 'prepaid') if self.billingMethod == CONSUMETYPE.POSTPAID: # 后付费 package = { "packageId": packageId, "name": package.get('name'), "switch": package.get('switch'), "policyType": policyType, "billingMethod": package.get('billingMethod'), "price": round(float(package.get('price', 0.0)), 2), "coins": round(float(package.get('coins', 0.0)), 2), "minAfterStartCoins": round(float(package.get('minAfterStartCoins', 0.0)), 2), "minFee": round(float(package.get('minFee', 0.0)), 2), "time": round(float(package.get('time')), 2), "unit": package.get('unit'), "sn": package.get('sn'), } if packageId == 'customPackage': customValue = float(attachParas.get('customValue') or 0) policyType = package['policyType'] if policyType == 'time': unit = package['unit'] if unit == '小时': customValue = customValue * 60 if customValue <= 10: raise ServiceException({'result': 2, 'description': '自定义套餐充电时长不能小于10分钟'}) limitTime = float(package['time']) or 720 package['limitTime'] = limitTime if customValue > limitTime: raise ServiceException( {'result': 2, 'description': '自定义套餐充最大充电时长为{}分钟,请规范操作'.format(limitTime)}) elif policyType == 'elec': if customValue <= 0: raise ServiceException({'result': 2, 'description': '自定义套餐充电电量不能小于0度'}) limitTime = float(package['time']) or 10 package['limitTime'] = limitTime if customValue > limitTime: raise ServiceException({'result': 2, 'description': '自定义套餐充最大充电量为{}度,请规范操作'.format(limitTime)}) else: raise ServiceException({'result': 2, 'description': '非法自定义套餐, 请联系经销商重新配置计费规则'}) package['time'] = customValue elif packageId == 'autoPackage': policyType = package['policyType'] fullValue = float(package['time']) if policyType == 'time': unit = package['unit'] if unit == '小时': fullValue = fullValue * 60 # 最大720分钟 超过就 package['fullValue'] = fullValue or 720 elif policyType == 'elec': package['fullValue'] = fullValue or 10 else: raise ServiceException({'result': 2, 'description': '套餐计费规则配置错误, 请联系经销商重新配置计费规则'}) else: # 预付费 # 1. 检验套餐设置的投币数 if 'coins' not in package: raise ServiceException({'result': 2, 'description': u'套餐未设置投币数,请联系经销商'}) package = { "packageId": packageId, "name": package.get('name'), "switch": package.get('switch'), "policyType": policyType, "autoRefund": package.get('autoRefund', False), "billingMethod": package.get('billingMethod'), "price": round(float(package.get('price', 0.0)), 2), "coins": round(float(package.get('coins', 0.0)), 2), "minAfterStartCoins": round(float(package.get('minAfterStartCoins', 0.0)), 2), "minFee": round(float(package.get('minFee', 0.0)), 2), "time": round(float(package.get('time')), 2), "unit": package.get('unit'), "sn": package.get('sn'), } return package def start_device_realiable(self, order): attachParas = order.attachParas package = order.package port = attachParas.get('chargeIndex') if not port: raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路'}) payload = { 'funCode': 'start', 'port': int(port), 'order_id': order.orderNo, 'open_id': order.openId, 'attach_paras': {}, } if attachParas.get('isFree'): payload['attach_paras']['isFree'] = True if attachParas.get('onPoints'): payload['attach_paras']['onPoints'] = True self.generate_rule(package, payload) uart_source = [] uart_source.append( {'write_start': json.dumps(payload, indent=4), 'time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}) result = self.send_mqtt(payload=payload, timeout=MQTT_TIMEOUT.START_DEVICE) uart_source.append( {'rece_start': json.dumps(result, indent=4), 'time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}) # 兼容远程上分 远程上分没有订单 try: order.update(uart_source=uart_source) except: pass return result def stop_by_order(self, port, orderNo): """ 用户扫码后, 如果订单没有结算, 点击停止按钮, 会去设备上查询, 如果真的没有查询到模块, 此单直接作废掉, 不计费, 让用户重新扫码使用 """ order = ClientConsumeModelProxy.get_one(shard_filter = {'ownerId': self.device.ownerId}, devNo=self.device.devNo, orderNo=orderNo, status__ne='finished') # type: ConsumeRecord if order: order_id = order.orderNo if order.remarks == '刷卡消费' or (order.startKey and order.startKey.startswith(self.device.devNo)): # 刷卡 order_id = order.startKey try: self.send_mqtt({'funCode': 'stop', 'role': 'user', 'order_id': order_id, 'port': int(port)}) except ServiceException as e: if e.result['rst'] == 14: # 没有找到订单, 服务器有订单 设备上没有订单, 设备上订单丢失 order.update(isNormal=False, errorDesc='设备上订单已丢失') else: raise e def check_power_price_rules(self, packages): save_configs = [] lastMaxPower = 0 for package in packages: try: minPower = float(package.get('min')) maxPower = float(package.get('max')) price = float(package.get('price')) except Exception: raise InvalidParameter(u'价格规则填写错误,请正确的输入每个功率段') self.check_params_range(str(price), minData=0.0, maxData=10.0, desc='功率段价格') if maxPower > 9999: raise InvalidParameter(u'最大功率不能超过9999W') if minPower != lastMaxPower: raise InvalidParameter(u'功率段之间有未覆盖到的区间,请正确设置功率段') else: lastMaxPower = maxPower save_configs.append({'min': minPower, 'max': maxPower, 'price': price}) return save_configs def get_power_price_rules(self): serverConfigs = self.get_server_configs() packages = serverConfigs.get('power_price_rules', []) if not packages: packages = Const.POWER_PRICE_RULES return packages def get_uart_step(self, account_type=None, is_card=False): if account_type == 'RATIO_TIME': if is_card: powerLevel = self.device.policyTemp.get('forIdcard', {}).get('rule', {}).get('ratios', []) else: powerLevel = self.device.policyTemp.get('forApps', {}).get('rule', {}).get('ratios', []) else: if is_card: powerLevel = self.device.policyTemp.get('forIdcard', {}).get('rule', {}).get('prices', []) else: powerLevel = self.device.policyTemp.get('forApps', {}).get('rule', {}).get('prices', []) account_rule = [] for pp in powerLevel: if 'power' in pp and 'price' in pp: account_rule.append( '{:0>4d}-{:0>5d}'.format(int(pp['power']), int(pp['price'] * 100))) return account_rule def generate_rule(self, package, payload): data = self._check_package(package) billingMethod = data['billingMethod'] policyType = data['policyType'] # rule_type: 'MONEY | TIME | ELEC | TIME_MONEY | ELEC_MONEY | TIME_ELEC | TIME_ELEC_MONEY | RATIO_TIME'#8种规则 account_type = 'real' # account_type(max, real, min) 计费的规则是最大功率计费,还是实时功率计费 if self.accumulate_max_power: account_type = 'max' policy = {'billing_method': billingMethod} if billingMethod == CONSUMETYPE.POSTPAID: # 后付费 if policyType == 'time': if self.look_at_money: policy.update({ 'rule_type': 'MONEY', 'rule': { 'account_type': account_type, 'need_time': int(data['needTime'] * 60), # 精确到秒 'step': self.get_uart_step() }}) open_id = payload.get('open_id') user = MyUser.objects.filter(openId=open_id).first() if user: balance = user.calc_currency_balance(self.device.owner, self.device.group) policy['rule']['amount'] = int(balance * 100) else: policy.update({ 'rule_type': 'TIME_MONEY', 'rule': { 'account_type': account_type, 'need_time': int(data['needTime'] * 60), # 精确到秒 'step': self.get_uart_step() }}) elif policyType == 'elec': elecFee = float(self.device.policyTemp.get('forApps', {}).get('rule', {}).get('price', 1)) # 电费单价, 用于显示金额 policy.update({ 'rule_type': 'ELEC', 'rule': { 'account_type': account_type, 'need_elec': int(data['needElec'] * 3600 * 1000), # 精确到焦耳 'elec_fee': int(elecFee * 100) }}) else: raise ServiceException({'result': 2, 'description': u'套餐单位错误,请联系经销商'}) else: # 预付费 if policyType == 'time': policy.update({ 'rule_type': 'MONEY', 'rule': { 'account_type': account_type, 'amount': int(data['coins'] * 100), 'step': self.get_uart_step() }, 'fee': int(data['coins'] * 100), # 本单实际支付金额 }, ) elif policyType == 'elec': elecFee = float(self.device.policyTemp.get('forApps', {}).get('rule', {}).get('price', 1)) # 电费单价, 用于显示金额 policy.update({ 'rule_type': 'MONEY_BY_ELEC', 'rule': { 'account_type': account_type, 'amount': int(data['coins'] * 100), # 精确到焦耳 'elec_fee': int(elecFee * 100) }, 'fee': int(data['coins'] * 100), # 本单实际支付金额 }) else: raise ServiceException({'result': 2, 'description': u'套餐单位错误,请联系经销商'}) payload.update(policy) def custom_push_url(self, order, user, **kw): """ # 如果是启动, 就进入个人服务中心, 如果是结束就进入订单详细 """ from apps.web.utils import concat_user_center_entry_url from apps.web.utils import concat_front_end_url if order.status in [ConsumeRecord.Status.END, ConsumeRecord.Status.FINISHED]: return super(PolicyCommon, self).custom_push_url(order, user, **kw) else: return concat_user_center_entry_url(agentId=user.productAgentId, redirect=concat_front_end_url( uri='/user/index.html#/user/deviceStatus?orderId={}'.format(str(order.id)))) def format_port_using_detail(self, detailDict): detailDict = super(PolicyCommon, self).format_port_using_detail(detailDict) if 'actualNeedTime' in detailDict: detailDict.pop('actualNeedTime') if 'coins' in detailDict: detailDict['coins'] = '{}元'.format(detailDict['coins']) if 'leftTime' in detailDict: detailDict.pop('leftTime') return detailDict def get_port_info(self, line): result = self.send_mqtt({'funCode': 'port_info', 'port': int(line)}) port_info = {} # 主板串口传回数据 if 'current_power' in result: port_info['power'] = str(result['current_power']) if 'status' in result: port_info['status'] = result['status'] if 'duration' in result: port_info['usedTime'] = round(result['duration'] / 60.0, 1) if 'elec' in result: port_info['usedElec'] = round(result['elec'] / 3600000.0, 3) if 'rule' in result: rule = result['rule'] if 'rule_type' in result: rule_type = result['rule_type'] if rule_type == 'TIME_MONEY': if 'need_time' in rule: port_info['needTime'] = round(rule['need_time'] / 60.0, 1) port_info['actualNeedTime'] = round(rule['need_time'] / 60.0, 1) elif rule_type == 'ELEC': if 'need_elec' in rule: port_info['needElec'] = round(rule['need_elec'] / 3600000.0, 3) elif rule_type == 'MONEY': if 'amount' in rule: port_info['coins'] = round(rule['amount'] / 100.0, 2) elif rule_type == 'MONEY_BY_ELEC': if 'amount' in rule: port_info['coins'] = round(rule['amount'] / 100.0, 2) if 'need_pay' in result: if result['need_pay'] == -1: pass else: port_info['consumeMoney'] = format(result['need_pay'] / 3600.0 / 100.0, '.2f') + '元' if 'billing_method' in result: if result['billing_method'] == CONSUMETYPE.POSTPAID: port_info['consumeType'] = CONSUMETYPE.POSTPAID else: if result['order_type'] == 'com_start': port_info['consumeType'] = CONSUMETYPE.MOBILE elif result['order_type'] == 'id_start': port_info['consumeType'] = CONSUMETYPE.CARD if 'card_id' in result: port_info['cardNo'] = format(int(result['card_id'], 16), 'd') if 'sts' in result: port_info['startTime'] = to_datetime(result['sts']).strftime('%m-%d %H:%M:%S') attach_paras = result.get('attach_paras', {}) if 'onPoints' in attach_paras: port_info['nickName'] = '经销商上分' elif 'isFree' in attach_paras: port_info.pop('coins', None) port_info.pop('consumeMoney', None) port_info['consumeType'] = 'isFree' else: if 'order_id' in result: consumeRcd = ConsumeRecord.objects.filter( Q(orderNo=result['order_id']) | Q(startKey=result['order_id'])).first() user = consumeRcd.user port_info['nickName'] = user.nickname port_info['openId'] = user.openId if consumeRcd.package.get('packageId', '') == 'autoPackage': if consumeRcd.package.get('policyType') == 'time': port_info['needTime'] = 999 port_info.pop('needElec', None) port_info.pop('usedElec', None) elif consumeRcd.package.get('policyType') == 'elec': port_info['needTime'] = 999 port_info.pop('needTime', None) else: if consumeRcd.package.get('policyType') == 'time': port_info.pop('needElec', None) port_info.pop('usedElec', None) elif consumeRcd.package.get('policyType') == 'elec': port_info.pop('needTime', None) port_info.pop('leftTime', None) return port_info def get_ports_info(self, **kw): result = self.get_port_status_from_dev() port_list = [] ctrInfo = Device.get_dev_control_cache(self.device.devNo) statsMap = {Const.DEV_WORK_STATUS_IDLE: 'idle', Const.DEV_WORK_STATUS_WORKING: 'busy', Const.DEV_WORK_STATUS_FAULT: 'fault', Const.DEV_WORK_STATUS_FORBIDDEN: 'ban', Const.DEV_WORK_STATUS_CONNECTED: 'connected', Const.DEV_WORK_STATUS_FINISHED: 'finished'} for port, _info in result.items(): _info['index'] = port if _info.get('status') == Const.DEV_WORK_STATUS_WORKING: _info.update(self.get_port_using_detail(port, ctrInfo, isLazy=True)) for k, v in _info.items(): if k.lower().endswith('money'): _info.pop(k) _info['status'] = statsMap.get(_info['status'], 'busy') port_list.append(_info) return port_list def check_order_state(self, openId): """ 通过 openId 以及设备来鉴别 订单 :param openId: :return: """ dealerId = self.device.ownerId return ClientConsumeModelProxy.get_not_finished_record(ownerId=dealerId, openId=openId, devTypeCode=self._device["devType"]["code"], package__billingMethod=CONSUMETYPE.POSTPAID) def force_stop_order(self, order, **kwargs): order.update(isNormal=False, errorDesc='经销商强制结束订单')