123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 |
- # -*- coding: utf-8 -*-
- # !/usr/bin/env python
- import datetime
- import logging
- import time
- from mongoengine import DoesNotExist
- from apilib.monetary import RMB, VirtualCoin, Ratio
- from apilib.utils_datetime import to_datetime
- from apilib.utils_string import make_title_from_dict
- from apilib.utils_sys import memcache_lock
- from apps.web.constant import Const, FAULT_LEVEL, DeviceCmdCode, DEALER_CONSUMPTION_AGG_KIND
- from apps.web.device.models import Group, Device
- from apps.web.eventer import EventBuilder
- from apps.web.eventer.base import FaultEvent, WorkEvent
- from apps.web.user.models import ServiceProgress, CardRechargeOrder, MyUser, UserVirtualCard
- logger = logging.getLogger(__name__)
- class builder(EventBuilder):
- def __getEvent__(self, device_event):
- event_data = self.deviceAdapter.analyze_event_data(device_event['data'])
- if event_data is None or 'cmdCode' not in event_data:
- return None
- if 'level' in device_event:
- event_data.update({'level': device_event['level']})
- if event_data["cmdCode"] in ["0A"]:
- return ChargingJNCarFaultEventer(self.deviceAdapter, event_data)
- else:
- return ChargingJNCarWorkEventer(self.deviceAdapter, event_data)
- class ChargingJNCarWorkEventer(WorkEvent):
- def support_playback(self):
- return self.event_data.get("cmdCode") == "2C"
- def do(self, **args):
- """
- 主驱动函数
- :param **args:
- :return:
- """
- cmdCode = self.event_data.get("cmdCode")
- # 充电状态上报
- if cmdCode == "26":
- self._do_port_report()
- # 结束事件上报
- elif cmdCode == "2C":
- self._do_finished()
- # 在线卡启动后上报
- elif cmdCode == "2D":
- self._do_card_consume()
- # 查询在线卡余额是否足够
- elif cmdCode == "10":
- self._do_card_balance()
- else:
- logger.error("<{}> receive an invalid cmdCode <{}>".format(self.device.devNo, cmdCode))
- def _do_port_report(self):
- """
- 处理端口状态上报的事件, 端口状态一律不再这里面更新,防止主板误报造成订单异常
- :return:
- """
- self.event_data.pop("cmdCode", None)
- portNum = self.event_data.get("portNum", 0)
- devCache = Device.get_dev_control_cache(self.device.devNo)
- for i in xrange(portNum):
- portStr = str(i + 1)
- portCache = devCache.get(portStr, dict())
- tempInfo = self.event_data.get(portStr)
- usedTime = tempInfo.get("usedTime", 0)
- power = tempInfo.get("power", 0)
- usedElec = tempInfo.get("usedElec", 0)
- voltage = tempInfo.get("voltage", 0)
- portCache.update({
- "usedTime": usedTime,
- "power": power,
- "usedElec": usedElec,
- "voltage": voltage
- })
- # devCache 中会有不是 dict 的项目
- Device.update_dev_control_cache(self.device.devNo, {portStr:portCache})
- # TODO zjl 端口上报记录 功率曲线绘制
- def _do_finished(self):
- """
- 处理结束事件 拿到sessionId 作为去重标准
- :return:
- """
- # TODO zjl 区别到底是 刷卡的结束还是扫码的结束 依赖缓存或者依赖主板上报?
- cardNo = self.event_data.get("cardNo")
- sessionId = self.event_data.get("sessionId")
- portStr = self.event_data.get("portStr")
- lock_id = "{}-{}-{}".format(self.device.devNo, portStr, sessionId)
- try:
- with memcache_lock(lock_id, value = '1', expire = 60) as acquired:
- if not acquired:
- return
- portCache = Device.clear_port_control_cache(self.device.devNo, portStr)
- if not portCache:
- return
- try:
- if int(cardNo):
- self._do_card_finished(portCache=portCache)
- else:
- self._do_net_pay_finished(portCache=portCache)
- except Exception as e:
- logger.exception(e)
- finally:
- openId = portCache.get("openId")
- user = MyUser.objects.filter(openId=openId, groupId=self.device.groupId).first()
- reason = self.event_data.get("desc", "")
- group = Group.get_group(self.device.groupId)
- # 通知用户充电结束
- self.notify_user(
- managerialOpenId=user.managerialOpenId if user else "",
- templateName="service_complete",
- title=u"\\n\\n结束原因:\\t\\t{reason}\\n\\n设备编号:\\t\\t{logicalCode}-{port}\\n\\n服务地址:\\t\\t{group}".format(
- reason=reason,
- logicalCode=self.device["logicalCode"],
- port=portStr,
- group=group.get("address", "") if group else "",
- ),
- service=u"充电服务",
- finishTime=to_datetime(self.recvTime).strftime('%Y-%m-%d %H:%M:%S'),
- remark = u'谢谢您的支持'
- )
- finally:
- self.deviceAdapter._response_finished(portStr, sessionId)
- def _do_card_balance(self):
- """
- ID 卡刷卡信号上报 用于检测是否是有效卡还是无效卡 卡消费金额默认大一些 防止出错
- :return:
- """
- cardNo = self.event_data.get("cardNo", "")
- cardCst = self.event_data.get("cardCst", 256)
- card = self.update_card_dealer_and_type(cardNo)
- if not card or not card.openId:
- return self.deviceAdapter._response_card_balance(0, "02")
- # 没有到账的卡给它充值, 应该用不上
- card_recharge_order = CardRechargeOrder.get_last_to_do_one(str(card.id))
- self.recharge_id_card(
- card=card,
- rechargeType='append',
- order=card_recharge_order
- )
- card.reload()
- # 检查卡的余额是否足够
- if float(card.balance) >= float(cardCst):
- res = "00"
- else:
- res = "01"
- return self.deviceAdapter._response_card_balance(float(card.balance), res)
- def _do_card_consume(self):
- """
- 刷卡投币成功启动设备后上报 目前只有ID卡 到了此命令之后 设备已经启动 此处需要处理的就是扣费以及记录
- :return:
- """
- self.deviceAdapter._send_data("2D", "{:0>10}".format(self.event_data.get("sessionId", "")), cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
- portStr = self.event_data.get("portStr")
- needElec = self.event_data.get("needElec", 0)
- cardNo = self.event_data.get("cardNo")
- cardCst = self.event_data.get("cardCst")
- card = self.update_card_dealer_and_type(cardNo)
- if not card or not card.openId:
- logger.warning("error card, cardNo is {}".format(cardNo))
- return
- res, cardBalance = self.consume_money_for_card(card, RMB(cardCst))
- if res != 1:
- logger.warning("consume error!!!, cardNo is {}".format(cardNo))
- return
- consumeDict = {
- "chargeIndex": portStr,
- "needElec": needElec
- }
- orderNo, cardOrderNo = self.record_consume_for_card(card, RMB(cardCst), servicedInfo=consumeDict)
- portCache = {
- "isStart": True,
- "status": Const.DEV_WORK_STATUS_WORKING,
- "openId": card.openId,
- "price": cardCst,
- "coins": cardCst,
- "needElec": needElec,
- "startTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
- "startTimeStamp": int(time.time()),
- "consumeType": "card",
- }
- Device.update_dev_control_cache(self.device.devNo, {portStr: portCache})
- ServiceProgress.register_card_service(
- self.device,
- int(portStr),
- card,
- consumeOrder={
- "orderNo": orderNo,
- "cardOrderNo": cardOrderNo,
- }
- )
- def _do_card_finished(self, portCache):
- """
- 处理刷卡结束的上报事件
- 卡的结束退费金额会在事件里面上报 这个地方不再需要去计算
- :return:
- """
- portStr = self.event_data.get("portStr")
- cardNo = self.event_data.get("cardNo")
- cardLeftBalance = self.event_data.get("cardLeftBalance")
- desc = self.event_data.get("desc")
- price = portCache.get("price", 0)
- if not price:
- return
- card = self.update_card_dealer_and_type(cardNo)
- consumeDict = {
- "chargeIndex": portStr,
- "cardId": str(card.id),
- "reason": desc
- }
- leftBalance = RMB(cardLeftBalance)
- # 处理退额超限问题
- if leftBalance > RMB(price):
- leftBalance = RMB(0)
- if leftBalance > RMB(0):
- self.refund_money_for_card(leftBalance, str(card.id))
- consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.REFUND_CARD: leftBalance.mongo_amount})
- self.notify_user(
- managerialOpenId=card.managerialOpenId,
- templateName="refund_coins",
- title=u"卡退款",
- backCount=u"{}".format(leftBalance.mongo_amount),
- finishTime=to_datetime(self.recvTime).strftime('%Y-%m-%d %H:%M:%S')
- )
- ServiceProgress.update_progress_and_consume_rcd(
- self.device.ownerId,
- {
- "open_id": card.openId,
- "device_imei": self.device.devNo,
- "port": int(portStr),
- "cardId": str(card.id),
- "isFinished": False
- },
- consumeDict
- )
- def _do_net_pay_finished(self, portCache):
- """
- 处理网络支付的结束事件
- :return:
- """
- portStr = self.event_data.get("portStr")
- openId = portCache.get("openId")
- # 没有 openId 的说明已经被处理过了
- if not openId:
- logger.error("portCache has no openId , dev is <{}>, port is <{}>".format(self.device.devNo, portStr))
- user = MyUser.objects.filter(openId=openId, groupId=self.device.groupId).first()
- # 消费相关信息的记录
- consumeDict = {
- "chargeIndex": portStr,
- "reason": self.event_data.get("desc"),
- }
- billingType = portCache.get("billingType", "elec").capitalize()
- leftKey = "left{}".format(billingType)
- needKey = "need{}".format(billingType)
- consumeDict.update({
- leftKey: self.event_data.get(leftKey)
- })
- # 退费的相应处理
- refundInfo = self._get_refund_money(
- self.event_data.get(leftKey, 0),
- portCache.get(needKey, 0),
- portCache
- )
- refundMoney = refundInfo.get("money")
- logger.info("refund Money is <{}>".format(refundMoney.mongo_amount))
- refundType = refundInfo.get("type")
- if refundType == 'vCard':
- vCardId = portCache.get('vCardId')
- logger.info("finished with vCard!")
- try:
- vCard = UserVirtualCard.objects.get(id=vCardId)
- except DoesNotExist:
- logger.info('can not find the vCard id = %s' % vCardId)
- return
- extra = []
- extra.append({u'虚拟卡券': u'{}--{}'.format(vCard.cardName, vCard.cardNo)})
- if billingType == 'time':
- extra.append({u'消费明细': u'消费{}分钟'.format(portCache.get(needKey, 0))})
- else:
- extra.append({u'消耗明细': u'消费{}度'.format(portCache.get(needKey, 0))})
- group = Group.get_group(self.device['groupId'])
- self.notify_user_service_complete(
- service_name='充电',
- openid=openId,
- port=self.event_data["port"],
- address=group["address"],
- reason=self.event_data.get('reason'),
- finished_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
- extra=extra
- )
- else:
- if refundMoney and refundMoney > VirtualCoin(0):
- self.refund_net_pay(user, portCache, RMB(refundMoney.amount), refundMoney, consumeDict, (refundType == "cash"))
- if DEALER_CONSUMPTION_AGG_KIND.REFUNDED_CASH in consumeDict:
- refundTitle = u"退款"
- backCount = "{} 元".format(refundMoney.mongo_amount)
- elif DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS in consumeDict:
- refundTitle = u"金币退款"
- backCount =u"金币: {}".format(refundMoney.mongo_amount)
- else:
- backCount = None
- if backCount:
- self.notify_user(
- managerialOpenId=user.managerialOpenId or "",
- templateName="refund_coins",
- title=refundTitle,
- backCount=backCount,
- finishTime=to_datetime(self.recvTime).strftime('%Y-%m-%d %H:%M:%S'))
- ServiceProgress.update_progress_and_consume_rcd(
- self.device.ownerId,
- {
- "open_id": openId,
- "device_imei": self.device.devNo,
- "port": int(portStr),
- "isFinished": False
- },
- consumeDict
- )
- def _get_refund_money(self, left, need, portCache, **kwargs):
- """
- 获取结束时候需要退还的 金币 或者 现金
- :param left: 剩余的
- :param need: 订购的
- :param portCache: 端口的缓存数据
- :param kwargs: 预留的扩充字段
- :return:
- """
- if not self.device.is_auto_refund:
- return {
- "type": "coin",
- "money": VirtualCoin(0)
- }
- if 'vCardId' in portCache and portCache["vCardId"]:
- return {
- "type": 'vCard',
- "money": VirtualCoin(0)
- }
- refund_recharge_ids = []
- if 'rechargeRcdId' in portCache:
- refund_recharge_ids.append(portCache['rechargeRcdId'])
- else:
- pay_info = portCache.get('payInfo', list())
- for item in pay_info:
- if 'rechargeRcdId' in item:
- refund_recharge_ids.append(item['rechargeRcdId'])
- price = portCache.get("price", 0)
- coins = portCache.get("coins", 0)
- if len(refund_recharge_ids) > 0:
- _type = "cash"
- payMoney = VirtualCoin(price)
- else:
- _type = "coin"
- payMoney = VirtualCoin(coins)
- try:
- refundMoney = Ratio(float(left) / float(need)) * VirtualCoin(payMoney)
- except ZeroDivisionError:
- refundMoney = VirtualCoin(0)
- # 防止退大额度
- if refundMoney > payMoney:
- refundMoney = VirtualCoin(0)
- return {
- "type": _type,
- "money": refundMoney
- }
- class ChargingJNCarFaultEventer(FaultEvent):
- def do(self, **args):
- group = Group.get_group(self.device['groupId'])
- if self.is_notify_dealer():
- titleDictList = [
- {u'告警名称': u'设备告警'},
- {u'设备编号': self.device["logicalCode"]},
- {u'地址名称': group["groupName"]}
- ]
- self.notify_dealer('device_fault', **{
- 'title': make_title_from_dict(titleDictList),
- 'device': u'%s号设备\\n' % self.device['groupNumber'],
- 'faultType': u'设备告警\\n',
- 'notifyTime': u'%s\\n' % datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
- 'fault': u'%s(详细情况,建议您到管理后台的告警模块进行查看)\\n' % self.event_data.get("errorDesc"),
- })
- self.record(
- faultCode=self.event_data.get("errorCode"),
- description=self.event_data.get("errorDesc"),
- title=u"设备告警",
- detail=self.event_data.get("errorDesc"),
- level=self.event_data.get('level', FAULT_LEVEL.NORMAL),
- portNo=self.event_data.get("portStr")
- )
|