# -*- coding: utf-8 -*- # !/usr/bin/env python import logging import time from mongoengine.errors import DoesNotExist from django.utils.functional import cached_property from apilib.monetary import RMB, VirtualCoin from apilib.utils_datetime import to_datetime from apps.web.eventer.base import WorkEvent, FaultEvent from apps.web.eventer import EventBuilder from apps.web.south_intf.platform import notify_event_to_north from apps.web.south_intf.zhejiang_fire import send_event_to_zhejiang from apps.web.south_intf.zhongtian import report_zhongtian_service_complete, report_zhongtian_refund from apps.web.user.models import UserVirtualCard, VCardConsumeRecord, ServiceProgress, MyUser, Card from apps.web.api.models import APIStartDeviceRecord from apps.web.constant import Const from apilib.systypes import StrEnum from apps.web.core.accounting import Accounting from apps.web.device.models import Device, Group from apps.web.eventer.errors import NoCommandHandlerAvailable 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 event_data['cmdCode'] in ['05', '03']: return ChargingSiJiangWorkEvent(self.deviceAdapter, event_data) if event_data['cmdCode'] == '0D': return FaultEvent(self.deviceAdapter, event_data) class SI_JIANG_EVENT_CODE(StrEnum): OFFLINE_COIN_REPORT = '03' SWIPE_CARD_REPORT = '04' CHARGE_FINISHED = '05' ERROR = '0D' class ChargingSiJiangWorkEvent(WorkEvent): @cached_property def handler_map(self): return { '03': self.handle_OFFLINE_COIN_REPORT, '05': self.handle_CHARGE_FINISHED } def support_playback(self): return self.event_data['cmdCode'] in ['05'] def do(self, **args): devNo = self.device['devNo'] logger.info('si-jiang charging event detected, devNo=%s, curInfo=%s' % (devNo, self.event_data)) cmdCode = self.event_data['cmdCode'] try: self.handler_map[cmdCode]() except KeyError: raise NoCommandHandlerAvailable('device %s got no recognizable cmd %s ' % (devNo, cmdCode)) def handle_OFFLINE_COIN_REPORT(self): """ 投币事件上报 :return: """ Accounting.recordOfflineCoin( self.device, int(time.time()), int(self.event_data['coin']) ) self.deviceAdapter.get_port_status_from_dev() def handle_CHARGE_FINISHED(self): """ :return: """ devNo = self.device['devNo'] port = str(self.event_data['port']) recvTime = to_datetime(self.recvTime) try: lineInfo = Device.clear_port_control_cache(self.device.devNo, port) logger.debug( 'device({}) receive charge finished message. lineInfo={}'.format(self.device['devNo'], lineInfo, )) if 'startTime' not in lineInfo: logger.debug('startTime not found') return startTime = to_datetime(lineInfo['startTime']) usedTime = self.event_data.get('duration', int(round(((recvTime - startTime).total_seconds() / 60.0)))) leftTime = self.event_data['leftTime'] actualNeedTime = lineInfo['needTime'] group = Group.get_group(self.device['groupId']) consumeDict = { 'reason': self.event_data['reason'], 'leftTime': leftTime, 'chargeIndex': port, 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime, 'duration': usedTime } # 如果是刷卡的,直接更新消费记录,然后发送通知消息,不支持退费 if 'cardNo' in lineInfo: card = Card.objects(cardNo=lineInfo['cardNo'], agentId=self.dealer.agentId).get() consumeDict.update({'balance': lineInfo['balance']}) ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], { 'open_id': lineInfo['openId'], 'port': int(port), 'device_imei': self.device['devNo'], 'isFinished': False }, consumeDict ) #: 通知服务结束 title = u'%s 卡号:%s,按动态功率计算总时间为:%s分钟,剩余时间:%s分钟' % ( self.event_data['reason'], lineInfo['cardNo'], actualNeedTime, leftTime ) service = u'充电服务(设备编号:%s, 端口:%s,地址:%s)' % (self.device['logicalCode'], port, group['address']) finishTime = recvTime.strftime('%Y-%m-%d %H:%M:%S') self.notify_service_complete(card.managerialOpenId, title=title, service=service, finishTime=finishTime) elif 'consumeRcdId' in lineInfo: coins = RMB(lineInfo['coins']) backCoins = self.get_backCoins(coins=coins, leftTime=leftTime, actualNeedTime=actualNeedTime) vCardId = lineInfo.get('vCardId') try: vCard = UserVirtualCard.objects.get(id=vCardId) except DoesNotExist: logger.info('can not find the vCard id = %s' % vCardId) return managerialOpenId = self.get_managerialOpenId_by_openId(lineInfo['openId']) title = u'%s 按动态功率计算总时间为:%s分钟,剩余时间:%s分钟' % ( self.event_data['reason'], actualNeedTime, leftTime) service = u'充电服务(设备编号:%s, 端口:%s,地址:%s)' % ( self.device['logicalCode'], port, group['address']) self.notify_service_complete(managerialOpenId=managerialOpenId, title=title, service=service) ServiceProgress.update_progress_and_consume_rcd(self.device['ownerId'], {'open_id': lineInfo['openId'], 'port': int(port), 'device_imei': self.device['devNo'], 'isFinished': False}, consumeDict) consumeRcdId = lineInfo.get('consumeRcdId', None) if consumeRcdId is None: logger.error('can not find consume rcd id') return try: vCardConsumeRcd = VCardConsumeRecord.objects.get(id=consumeRcdId) except DoesNotExist: logger.error('can not find the consume rcd id = %s' % consumeRcdId) return vCard.refund_quota(vCardConsumeRcd, usedTime, 0.0, backCoins.mongo_amount) else: #: 这里需要考虑API调用的和普通使用场景 if 'extOrderNo' in lineInfo: record = APIStartDeviceRecord.get_api_record(self.device['logicalCode'], lineInfo['extOrderNo']) if not record: logger.debug('cannot find api start device record') return if record.postActionTriggered: logger.debug('api({}) post action has done.'.format(lineInfo['extOrderNo'])) return report_zhongtian_service_complete( event_code = '16', record = record, orderNo = lineInfo['extOrderNo'], deviceCode = self.device['logicalCode'], groupName = group['groupName'], address = group['address'], actualNeedTime = actualNeedTime, leftTime = leftTime, finishedState = self.event_data['finishedState']) if self.device.is_auto_refund: coins = VirtualCoin(lineInfo['coins']) money = RMB(lineInfo['money']) backCoins = self.get_backCoins(coins = coins, leftTime = leftTime, actualNeedTime = actualNeedTime) backMoney = self.get_backMoney(money = money, leftTime = leftTime, actualNeedTime = actualNeedTime) report_zhongtian_refund( eventCode = '16', record = record, orderNo = lineInfo['extOrderNo'], deviceCode = self.device['logicalCode'], groupName = group['groupName'], address = group['address'], backMoney = str(backMoney), backCoins = str(backCoins), actualNeedTime = actualNeedTime, leftTime = leftTime, finishedState = self.event_data['finishedState'] ) else: user = MyUser.objects(openId=lineInfo['openId'], groupId=self.device['groupId']).first() # 通知服务结束 if user: title = u'%s 按动态功率计算总时间为:%s分钟,剩余时间:%s分钟' % ( self.event_data['reason'], actualNeedTime, leftTime ) service = u'充电服务(设备编号:%s, 端口:%s,地址:%s)' % ( self.device['logicalCode'], port, group['address'] ) self.notify_service_complete(managerialOpenId=user.managerialOpenId, title=title, service=service) # 如果需要退款,计算退款数据. if self.device.is_auto_refund: coins = VirtualCoin(lineInfo['coins']) money = RMB(lineInfo.get('money', 0)) backCoins = self.get_backCoins(coins, leftTime, actualNeedTime) refundRMB = self.get_backMoney(money, leftTime, actualNeedTime) if backCoins > coins or refundRMB > money: logger.error( 'refund amount exceeds expectations dev=<{}>, coins=<{}> backCoins=<{}> price=<{}> refundRMB=<{}>'.format( self.device.devNo, coins, backCoins, money, refundRMB)) return # refund_net_pay会做保护. 如果不支持, 就不会往缓存塞money,payInfo refundCash = 'refundRMB_device_event' in self.device.owner.features if (backCoins <= VirtualCoin(0)) and (refundRMB <= RMB(0)): pass else: if refundCash: title = u'%s 按动态功率计算总时间为:%s分钟,还有:%s分钟的时间未使用完,给您退回剩余金额:%s元' % ( service, actualNeedTime, leftTime, refundRMB) backCount = u'金额:%s 元' % refundRMB else: title = u'%s 按动态功率计算总时间为:%s分钟,还有:%s分钟的时间未使用完,给您退还成相应的金币:%s元,您下次可以接着使用哦' % (service, actualNeedTime, leftTime, backCoins) backCount = u'金币:%s' % backCoins self.refund_net_pay(user, lineInfo, refundRMB, backCoins, consumeDict, refundCash) self.notify_refund_coins(managerialOpenId=user.managerialOpenId, title=title, backCount=backCount) ServiceProgress.update_progress_and_consume_rcd(ownerId=self.device['ownerId'], queryDict={ 'open_id': lineInfo['openId'], 'port': int(port), 'device_imei': self.device['devNo'], 'isFinished': False }, consumeDict=consumeDict) else: ServiceProgress.update_progress_and_consume_rcd(self.device['ownerId'], {'open_id': lineInfo['openId'], 'port': int(port), 'device_imei': self.device['devNo'], 'isFinished': False}, consumeDict) return except Exception as e: logger.exception(e) finally: notify_event_to_north(self.dealer, self.device, level = Const.EVENT_NORMAL, desc = self.event_data['reason']) send_event_to_zhejiang(self.dealer, self.device, self.event_data)