# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import time from apilib.monetary import RMB from apps.web.common.proxy import ClientConsumeModelProxy from apps.web.constant import Const, MQTT_TIMEOUT, DeviceCmdCode, CONSUMETYPE from apps.web.core.adapter.base import SmartBox from apps.web.core.adapter.policy_common import PolicyPackageInit from apps.web.core.exceptions import ServiceException from apps.web.core.networking import MessageSender from apps.web.device.models import Device from apps.web.user.constant2 import StartDeviceType from apps.web.user.models import ConsumeRecord, MyUser class PolicyBase(PolicyPackageInit, SmartBox): @property def look_at_money(self): return self.device.devTypeFeatures.get('look_at_money', False) def send_mqtt(self, data, cmd=DeviceCmdCode.OPERATE_DEV_SYNC, timeout=10): """ 发送mqtt 指令默认210 返回data """ payload = {'IMEI': self.device.devNo, 'data': data} result = MessageSender.send( self.device, cmd, payload=payload, timeout=timeout ) self.check_feedback_result(result) if cmd in [DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, DeviceCmdCode.PASSTHROUGH_OPERATE_DEV_NO_RESPONSE]: return if result.get('data') == '00': raise ServiceException({'result': 2, 'description': u'设备操作失败.请重试'}) else: return result def check_dev_status(self, attachParas=None): port = int(attachParas['chargeIndex']) openId = attachParas['openId'] port_stat = self.get_port_info(port) status = port_stat.get('status') last_openId = port_stat.get('openId') if status == 'busy': if openId and self.device.devType.get('payableWhileBusy'): if openId != last_openId: raise ServiceException({'result': 2, 'description': u'当前充电的设备不是您的设备哦!无法进行续充'}) else: if 'cardNo' in port_stat: raise ServiceException({'result': 2, 'description': u'刷卡启动的订单,无法扫码续充哦'}) else: raise ServiceException({'result': 2, 'description': u'当前端口处于充电状态, 请确认是否为您选择的充电端口哦!'}) def get_port_info(self, port): data = {'fun_code': 2, 'port': int(port)} devInfo = self.send_mqtt(data) portInfo = devInfo.get('data', {}) result = {'status': portInfo['status']} exec_orders = portInfo.get('orders', []) or portInfo.get('exec_orders', []) _wait = [] for exec_order in exec_orders: if exec_order['status'] == 'running': policy = exec_order.get('policy', {}) result['openId'] = policy['open_id'] result['billingMethod'] = policy['billingMethod'] result['policyType'] = policy['type'] result['voltage'] = round(portInfo.get('volt', 0), 2) result['power'] = round(portInfo.get('watt', 0), 2) result['ampere'] = round((portInfo.get('ampr', 0) * 0.001), 2) result['usedTime'] = round(exec_order.get('time', 0) / 60.0, 1) result['usedElec'] = round(exec_order.get('elec', 0) * 0.000001, 4) if 'exec_time' in exec_order: result['startTime'] = datetime.datetime.fromtimestamp(int(exec_order['exec_time'])).strftime( '%m-%d %H:%M:%S') if 'execute_time' in exec_order: result['startTime'] = datetime.datetime.fromtimestamp(int(exec_order['execute_time'])).strftime( '%m-%d %H:%M:%S') if 'card_no' in result: result['cardNo'] = result['card_no'] if exec_order['status'] == 'waiting': _one = {} _wait.append(_one) if _wait: result['waittingOrder'] = _wait return result def _check_package(self, package, isTemp=False): """ 获取设备启动的发送数据 根据设备的当前模式以及套餐获取 :param package: :return: """ # 针对套餐特性的修改 套餐的特性在校验通过后 不应该再到设备处理一次 有可能的话 check_package 应该放到verify来处理 先对package加工 然后verify # TODO 一旦verify通过之后 package 任何特性都不应该被中途改变 直至订单结束 policyType = package['policyType'] billingMethod = package['billingMethod'] if billingMethod == 'postpaid': money = 0.0 over_money_stop = False auto_refund = False if self.look_at_money: open_id = package.pop('open_id', None) user = MyUser.objects.filter(openId=open_id).first() if user: over_money_stop = True balance = user.calc_currency_balance(self.device.owner, self.device.group) money = round(float(balance), 2) else: money = package['price'] or 0.0 over_money_stop = True auto_refund = package.get('auto_refund', False) rv = { 'policyType': policyType, 'billingMethod': billingMethod, 'money': money, 'over_money_stop': over_money_stop, 'auto_refund': auto_refund, } auto_stop = package.get('autoStop', True) # 时间模式自动停止取决于套餐里面的自动停止开关, 电量模式一定是自动停止(充满后无法再冲入) if isTemp: if policyType == 'time': rv.update({ 'needTime': '临时套餐', 'auto_stop': auto_stop, 'over_money_stop': True }) else: rv.update({ 'needElec': '临时套餐', 'auto_stop': auto_stop, 'over_money_stop': True }) else: if policyType == 'time': # 自定义套餐 if 'customValue' in package: needTime = '自定义{}分钟'.format(package['customValue']) _write_value = float(package['customValue']) # 充满自停套餐 elif 'fullValue' in package: needTime = '充满自停' _write_value = float(package['fullValue']) auto_stop = True else: unit = package.get('unit') if unit == '分钟': needTime = '{:.1f}分钟'.format(float(package.get('time') or 0)) _write_value = float(package.get('time') or 0) elif unit == '小时': needTime = '{:.1f}分钟'.format(float(package.get('time') or 0) * 60) _write_value = float(package.get('time') or 0) * 60 else: raise ServiceException('当前模式为时间计费模式, 套餐单位配置错误') rv.update({ 'needTime': needTime, 'auto_stop': auto_stop, '_write_value': _write_value, }) elif policyType == 'elec': rv.update({'auto_stop': True}) if 'customValue' in package: needElec = '自定义{}'.format(package['customValue']) _write_value = float(package['customValue']) elif 'fullValue' in package: needElec = '充满自停' _write_value = float(package['fullValue']) else: needElec = '{:.3f}'.format(float(package.get('time') or 0)) _write_value = float(package.get('time', 0)) rv.update({ 'needElec': needElec, '_write_value': _write_value, }) return rv def generate_apps_rules(self, package, isTemp=False): _package = self._check_package(package, isTemp) _policyTemp = self.device.policyTemp.get('forApps', self.INIT_POLICY['forApps']) policyType = _package.get('policyType') if policyType == 'time': # 格式化规则 prices = sorted(_policyTemp.get('rule', {}).get('prices', []), key=lambda _: _['power']) # 单位元转换为分 for item in prices: item['value'] = int(float(item.pop('price')) * 100) item['power'] = int(item['power']) rule = { 'prices': prices } # 临时套餐取最大12个小时填进去 if isTemp: rule.update({'time': 12 * 60 * 60}) else: rule.update({'time': _package['_write_value'] * 60}) elif policyType == 'elec': price = RMB.yuan_to_fen(_policyTemp.get('rule', {}).get('price', 0)) rule = { 'elec': _package['needElec'] * 1000000, 'price': price } # 临时套餐取最大取10度填进去 if isTemp: rule.update({'elec': 5 * 1000000}) else: rule.update({'elec': _package['_write_value'] * 1000000}) else: raise ServiceException({'result': 2, 'description': u'设备尚未配置该套餐,请联系经销商配置该计费套餐'}) # 适配100298 mode 固定为price policy = { 'type': policyType, 'mode': 'price', 'auto_stop': _package['auto_stop'], 'auto_refund': _package['auto_refund'], 'money': int(float(_package['money']) * 100), 'over_money_stop': _package['over_money_stop'], 'rule': rule, 'billingMethod': _package['billingMethod'] } l_power = _package["l_power"] if "l_power" in _package else None l_power and policy.update({"l_power": l_power}) return policy def _prepare_package_for_card(self, attachParas): is_free = self.device.group.is_free package = { "packageId": attachParas['packageId'], "name": attachParas["name"], "policyType": attachParas['policyType'], "isPostpaid": attachParas["billingMethod"] == "postpaid", "price": attachParas["price"].amount, "autoRefund": attachParas["autoRefund"], "autoStop": attachParas["autoStop"], "rules": attachParas["rules"], "isFree": is_free, "refundProtectTime": self.device.otherConf.get('refundProtectionTime', 5) } return package def prepare_package(self, packageId, attachParas, startType=StartDeviceType.ON_LIEN): """ 准备套餐模型 """ # 卡启动的套餐 if startType == StartDeviceType.CARD: return self._prepare_package_for_card(attachParas) # 获取基础的套餐模型 名称等等 package = super(PolicyBase, self).prepare_package(packageId, attachParas) # 获取套餐的计量方式 时间/电量 policyType = self.device.policyTemp.get('forApps', {}).get('policyType', 'time') # 获取套餐的计费规则 _policyTemp = self.device.policyTemp.get('forApps', self.INIT_POLICY['forApps']) if policyType == "time": rules = sorted(_policyTemp.get('rule', {}).get('prices', []), key=lambda _: _['power']) else: rules = _policyTemp.get('rule', {}).get('price', 0) # 是否套餐免费 is_free = self.device.group.is_free # 自定义套餐的时间 是客户自行设置 if packageId == "customPackage": if policyType == "time": if package["unit"] == u"分钟": need = int(attachParas["consumeValue"]) else: need = int(float(attachParas["consumeValue"]) * 60) else: need = float(attachParas["consumeValue"]) # 其余的 直接是package 里面的time数值 else: if policyType == "time": if package["unit"] == u"分钟": need = int(package["time"]) or 720 else: need = int(float(package["time"]) * 60) or 720 else: need = float(package["time"]) or 10 # 获取两种套餐的基本最大值 if policyType == "time": if need > 720: raise ServiceException({"result": 2, "description": u"套餐时间最大为720分钟,请重新输入套餐值或联系经销商处理"}) if need < 10: raise ServiceException({"result": 2, "description": u"套餐时间最小为10分钟,请重新输入套餐值或联系经销商处理"}) else: if need > 10: raise ServiceException({"result": 2, "description": u"套餐电量最大为10度,请重新输入套餐值或联系经销商处理"}) if need <= 0: raise ServiceException({"result": 2, "description": u"套餐电量不得设置小于0度,请重新输入套餐值或联系经销商处理"}) package = { "packageId": packageId, "name": package["name"], "policyType": policyType, "isPostpaid": package.get("billingMethod") == "postpaid", "price": RMB(package["price"]).amount, "minAfterStartCoins": RMB(package["minAfterStartCoins"]).amount, "minFee": RMB(package["minFee"]).amount, "autoRefund": package["autoRefund"], "autoStop": package["autoStop"], "time": need, "unit": u"分钟" if policyType == "time" else u"度", "sn": package["sn"], "rules": rules, "isFree": is_free, "refundProtectTime": self.device['otherConf'].get('refundProtectionTime', 5) } return package def prepare_serviced_info(self, package, attach_paras): # type:(dict, dict)->dict isTemp = attach_paras.get('isTempPackage', False) _package = self._check_package(package, isTemp) rv = { 'chargeIndex': attach_paras['chargeIndex'], 'policyTemp': self.device.policyTemp.get('forApps', {}).get('rule'), 'billingMethod': _package['billingMethod'], } if _package['policyType'] == 'time': rv.update({ 'policyType': _package['policyType'], 'needTime': _package['needTime'], }) elif _package['policyType'] == 'elec': rv.update({ 'policyType': _package['policyType'], 'needElec': _package['needElec'], }) return rv def start_device(self, package, openId, attachParas): if attachParas is None: raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路、电池类型信息'}) if 'chargeIndex' not in attachParas: raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路'}) port = int(attachParas['chargeIndex']) if attachParas.get('onPoints'): orderNo = ConsumeRecord.make_no() + self.device.owner.username else: orderNo = attachParas.get('orderNo') isTemp = attachParas.get('isTempPackage', False) # 兼容远程上分, lua不能有null package.update({'open_id': openId or self.device.ownerId}) policy = self.generate_apps_rules(package, isTemp) policy.update({'open_id': openId or self.device.ownerId, }) data = { 'fun_code': 7, 'port': port, 'order_id': orderNo, 'policy': policy, } devInfo = self.send_mqtt(data, timeout=MQTT_TIMEOUT.START_DEVICE) ctrInfo = Device.get_dev_control_cache(self.device.devNo) lineInfo = ctrInfo.get(str(port), {}) if not lineInfo or lineInfo.get('status') != Const.DEV_WORK_STATUS_WORKING: lineInfo = { 'port': str(port), 'status': Const.DEV_WORK_STATUS_WORKING, 'order_type': 'apps_start', 'startTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'orderNo': orderNo, } Device.update_port_control_cache(self._device['devNo'], lineInfo) devInfo['finished_time'] = int(time.time()) + 3600 * 12 return devInfo def check_feedback_result(self, result): if 'rst' not in result: raise ServiceException({'result': 2, 'description': u'报文异常'}) if result['rst'] == -1: raise ServiceException({'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'}) if result['rst'] == 1: raise ServiceException({'result': 2, 'description': u'串口通讯失败,您稍候再试,或者联系客服'}) def start(self, packageId, openId=None, attachParas={}): washConfig = self.device.get("washConfig", dict()) package = washConfig.get(packageId) if not package: raise ServiceException({"result": 2, "description": u"未找到套餐,请联系经销商!"}) # new if attachParas.get('onPoints'): package = self.prepare_package(packageId, attachParas) attachParas.update({'openId': openId}) self.check_dev_status(attachParas) if self.device.bill_as_service_feature.support and self.device.bill_as_service_feature.on: return self.bill_as_service_start_device(package, openId, attachParas) else: return self.start_device(package, openId, attachParas) 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='经销商强制结束订单') @staticmethod def _get_order_policy(order): """ TODO 具体是否契合原版本 需要验证 """ package = order.package policy = { "type": package.category, "money": int(float(order.price) * 100), "auto_stop": package.autoStop, "over_money_stop": True if not package.isPostpaid else False } # 时间模式 if package.category == "time": prices = [ { "price": int(float(_["price"]) * 100), "power": int(_["power"]) } for _ in package.rules ] rules = { "prices": prices, "time": package.time * 60 or 12 * 60 * 60 } # 电量模式 else: price = int(package.rules * 100) rules = { "price": price, "elec": package.elec * 1000000 or 5 * 1000000 } policy.update({ "rule": rules, }) return policy def start_device_realiable(self, order): # type:(ConsumeRecord)->dict port = order.port orderNo = order.orderNo policy = self._get_order_policy(order) policy.update({ 'open_id': order.openId or self.device.ownerId, "billingMethod": "postpaid" if order.isPostPaid else "prepaid" }) data = { 'fun_code': 7, 'port': port, 'order_id': orderNo, 'policy': policy, } devInfo = self.send_mqtt(data, timeout=MQTT_TIMEOUT.START_DEVICE) devInfo['deviceStartTime'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") return devInfo