123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538 |
- # -*- 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
|