# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import logging import time from apilib.monetary import RMB, VirtualCoin from apilib.utils_sys import memcache_lock from apps.web.agent.models import Agent from apps.web.constant import DEALER_CONSUMPTION_AGG_KIND, Const from apps.web.core.accounting import Accounting from apps.web.core.adapter.base import fill_2_hexByte from apps.web.dealer.models import Dealer from apps.web.device.models import Group, Device, OfflineCoinStatistics from apps.web.eventer import EventBuilder from apps.web.eventer.base import WorkEvent from apps.web.eventer.errors import RequestInvalid from apps.web.eventer.hedong import is_server_refund from apps.web.report.utils import record_consumption_stats from apps.web.user.models import ServiceProgress, MyUser, ConsumeRecord, CardRechargeOrder, Card from apilib.utils_datetime import to_datetime 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 ['A4', 'A5', 'A6', 'A7', 'A8', 'AB', 'B8']: return ChargingBeiSiYunWorkEvent(self.deviceAdapter, event_data) return None #TODO 现网重新配置 class ChargingBeiSiYunWorkEvent(WorkEvent): @staticmethod def cache_key(devNo, cmd): return "event-{}-{}".format(devNo, cmd) def do(self, **args): devNo = self.device['devNo'] logger.info('dian chuan car charging event detected, devNo=%s,curInfo=%s' % (devNo, self.event_data)) if self.event_data['cmdCode'] == 'A4': port = self.event_data['port'] orderNo = self.event_data['orderNo'] cmdCode = self.event_data['cmdCode'] cacheKey = self.cache_key(devNo, cmdCode) with memcache_lock(key=cacheKey, value=orderNo, expire=360) as acquired: if not acquired: return self.do_finished() if self.event_data['cmdCode'] == 'A5': self.report_power_data() if self.event_data['cmdCode'] == 'A6': self.device_status() if self.event_data['cmdCode'] == 'A7': self.do_card_start() if self.event_data['cmdCode'] == 'A8': self.do_card_balance_check() if self.event_data['cmdCode'] == 'AB': self.do_coins_start() if self.event_data['cmdCode'] == 'B8 ': self.device_disconnected() def do_finished(self): devNo = self.device.devNo port = self.event_data.get("port") portStr = str(port+1) recvTime = to_datetime(self.recvTime) group = Group.get_group(self.device['groupId']) lineInfo = Device.get_dev_control_cache(self.device.devNo) lineInfo = lineInfo[str(port + 1)] dealer = Dealer.objects(id=group['ownerId']).first() if not dealer: logger.error('dealer is not found, dealerId=%s' % group['ownerId']) self.deviceAdapter._ack(portStr) return agent = Agent.objects(id=dealer.agentId).first() if not agent: logger.error('agent is not found, agentId=%s' % dealer.agentId) self.deviceAdapter._ack(portStr) return coins = lineInfo.get("coins") if 'coins' not in lineInfo: logger.debug('port cache has no coins. no order in port {}'.format(portStr)) self.deviceAdapter._ack(portStr) return if 'startTime' not in lineInfo: logger.debug('startTime is not in lineInfo') self.deviceAdapter._ack(portStr) return startTime = to_datetime(lineInfo['startTime']) if startTime > recvTime: # 如果web服务器时间和事件监控服务器时间不一致,导致开始时间比事件时间还大 usedTime = 0 else: usedTime = int(round(((recvTime - startTime).total_seconds() / 60.0))) cardId = lineInfo.get('cardId', '') vCardId = lineInfo.get('vCardId', None) money = VirtualCoin(lineInfo['coins']) backCoins = VirtualCoin(0.0) price = RMB(lineInfo.get('price', 0.0)) refundRMB = RMB('0.0') billingType = lineInfo.get('billingType', 'time') consumeType = lineInfo.get('consumeType', '') refundProtectionTime = int(self.device.get_other_conf_item('refundProtectionTime', 5)) minUsedTime = self.device.get_other_conf_item('minUsedTime', 0) isTempPackage = lineInfo.get('isTempPackage', False) spendElec = self.event_data["usedElec"] isRefundProtection = False if usedTime <= refundProtectionTime: backCoins = money isRefundProtection = True logger.info( 'exec protection refund devNo=<{}>, port=<{}>, backCoins=<{}>'.format(devNo, portStr, backCoins)) if backCoins > money: backCoins = money refundRMB = price * (float(backCoins) / float(money)) if money != VirtualCoin(0) else RMB(0) logger.debug( 'refund money is: {}; refund rmb is: {}'.format(str(backCoins.amount), str(refundRMB.amount))) try: if cardId == '' and vCardId is None and consumeType != 'coin': openId = lineInfo['openId'] user = MyUser.objects(openId=openId, groupId=self.device['groupId']).first() #type:MyUser extra = [] consumeDict = { 'chargeIndex': portStr, 'reason': self.event_data['reason'], 'duration': usedTime, 'uartData': self.event_data.get('uartData', ''), 'elec': spendElec } need_refund = False if not group.get('isFree', False): if isTempPackage is True: if isRefundProtection is True: need_refund = True else: pass elif self.device.is_auto_refund: need_refund = True elif isRefundProtection is True: need_refund = True else: pass else: pass logger.info("netPay need refund is {}".format(need_refund)) if not need_refund: ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], { 'open_id': openId, 'device_imei': self.device['devNo'], 'port': int(portStr), 'isFinished': False }, consumeDict ) extra.append({u'消费明细': u'消费{}(金币)'.format(money)}) else: if isTempPackage: backCoins = VirtualCoin(0) self.refund_net_pay(user, lineInfo, refundRMB, backCoins, consumeDict, True) else: self.refund_net_pay(user, lineInfo, refundRMB, backCoins, consumeDict, ('refundRMB_device_event' in dealer.features)) ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], { 'open_id': openId, 'device_imei': self.device['devNo'], 'port': int(portStr), 'isFinished': False }, consumeDict) if DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS in consumeDict: extra.append({u'消费明细': u'消费{}金币,退款{}金币'.format(money - backCoins, backCoins)}) extra.append({U'充电量':u'充电{}度'.format(spendElec)}) elif DEALER_CONSUMPTION_AGG_KIND.REFUNDED_CASH in consumeDict: extra.append({u'消费金额': u'消费{}元,退款{}元'.format(price - refundRMB, refundRMB)}) extra.append({U'充电量': u'充电{}度'.format(spendElec)}) self.notify_user_service_complete( service_name='充电', openid=user.managerialOpenId, port=portStr, address=group["address"], reason=self.event_data.get('reason'), finished_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), extra=extra ) elif cardId != '': # 刷的实体卡 logger.info("finished with card") try: cardRefundProtectionTime = self.device.get('otherConf', {}).get('cardRefundProtectionTime', 0) if cardRefundProtectionTime > 0: if usedTime < cardRefundProtectionTime: backCoins = money logger.info( 'exec protection refund devNo=<{}>, port=<{}>, backCoins=<{}>'.format(devNo, portStr, backCoins)) if backCoins > money: backCoins = money except Exception as e: logger.exception(e) card = Card.objects.get(id=cardId) openId = card.openId virtual_card = card.bound_virtual_card # type: UserVirtualCard extra = [] extra.append({u'实体卡': u'{}--{}'.format(card.cardName, card.cardNo)}) consumeDict = { 'chargeIndex': portStr, 'reason': self.event_data['reason'], 'duration': usedTime, 'elec': spendElec, 'elecFee': self.calc_elec_fee(spendElec) } try: if virtual_card: self.refund_virtual_card(backCoins, cardId, consumeDict, lineInfo, spendElec, usedTime, virtual_card) extra.append({u'虚拟卡券': u'{}--{}'.format(virtual_card.cardName, virtual_card.cardNo)}) extra.append({u'消费明细': u'消费{}分钟'.format(usedTime)}) else: if is_server_refund(billingType, self.device, dealer, agent): logger.info( 'ready to server refund money <{}> for user card <{}> in device<{}>'.format( backCoins, str(card.id), self.device.devNo)) consumeDict.update( {DEALER_CONSUMPTION_AGG_KIND.REFUND_CARD: backCoins.mongo_amount}) self.refund_money_for_card(backCoins, str(card.id)) extra.append({u'消费明细': u'消费{}金币,退款{}金币'.format(money - backCoins, backCoins)}) else: extra.append({u'消费明细': u'消费{}金币'.format(money)}) finally: ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], { 'cardId': cardId, 'device_imei': self.device['devNo'], 'port': int(portStr), 'isFinished': False }, consumeDict) self.notify_user_service_complete( service_name='充电', openid=openId, port=portStr, address=group["address"], reason=self.event_data.get('reason'), finished_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), extra=extra ) elif consumeType == 'coin': logger.info("finished with coin") CoinConsumeRcd = ConsumeRecord.objects.get(orderNo=lineInfo['consumeOrderNo']) # type: ConsumeRecord CoinConsumeRcd.servicedInfo['elec'] = spendElec CoinConsumeRcd.servicedInfo['actualNeedTime'] = u'动态功率计算为%s分钟' % usedTime CoinConsumeRcd.servicedInfo['coin'] = u'投币金额为' % coins CoinConsumeRcd.servicedInfo['reason'] = self.event_data['reason'] CoinConsumeRcd.servicedInfo['chargeIndex'] = portStr CoinConsumeRcd.finishedTime = recvTime.strftime('%Y-%m-%d %H:%M:%S') CoinConsumeRcd.save() valueDict = { DEALER_CONSUMPTION_AGG_KIND.DURATION: usedTime, DEALER_CONSUMPTION_AGG_KIND.ELEC: spendElec, DEALER_CONSUMPTION_AGG_KIND.ELECFEE: self.calc_elec_fee(spendElec) } status = CoinConsumeRcd.update_agg_info(valueDict) if status: record_consumption_stats(CoinConsumeRcd) else: logger.error( '[update_progress_and_consume_rcd] failed to update_agg_info record=%r' % ( CoinConsumeRcd,)) except Exception as e: logger.exception('deal with jingneng devNo=%s event e=%s' % (devNo, e)) finally: self.deviceAdapter._ack(portStr) dataDict = {'backMoney': str(refundRMB.mongo_amount), 'backCoins': str(backCoins.mongo_amount)} if 'orderNo' in lineInfo: dataDict.update({'orderNo': lineInfo['orderNo']}) self.event_data.update({'deviceCode': self.device["logicalCode"]}) self.event_data.update({'spendElec': spendElec}) Device.clear_port_control_cache(self.device.devNo, portStr) def report_power_data(self): ctrInfo = Device.get_dev_control_cache(self.device.devNo) port = self.event_data["port"] portStr = str(port + 1) lineInfo = ctrInfo.get(portStr, {}) portStatus = lineInfo.get("portStatusDict") if portStatus == Const.DEV_WORK_STATUS_WORKING: self.deviceAdapter._response_data(self.event_data['cmdCode'], portStr) def device_status(self): portStatusDict = self.event_data["portStatusDict"] group = Group.get_group(self.device["groupId"]) fault_list = [] for port, status in portStatusDict.items(): if status["status"] in [Const.DEV_WORK_STATUS_FAULT_RELAY_CONNECT, Const.DEV_WORK_STATUS_FAULT_OVERLOAD, \ Const.DEV_WORK_STATUS_FAULT_OVERLOAD]: fault_list.append(port) if not fault_list: self.deviceAdapter._response_data(self.event_data['cmdCode'], "00") else: self.deviceAdapter._response_data(self.event_data['cmdCode'], "00") for i in fault_list: faultCode = portStatusDict[i]["statusCode"] if faultCode == 1: faultContent = "当前设备第%s号端口警告:插座空闲,功率正常,继电器故障" % (port) self.notify_dealer( templateName="device_fault", title="注意!继电器故障!", device=u"{groupNumber}组-{logicalCode}".format(groupNumber=self.device["groupNumber"], logicalCode=self.device["logicalCode"]), location=u"{address}-{groupName}".format(address=group["address"], groupName=group["groupName"]), fault=faultContent, notifyTime=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") ) self.record( faultCode=faultCode, description=faultContent, title="注意!继电器故障!", ) elif faultCode == 2: faultContent = "当前设备第%s号端口警告:插座空闲,功率故障,继电器正常" % (port) self.notify_dealer( templateName="device_fault", title="注意!功率故障!", device=u"{groupNumber}组-{logicalCode}".format(groupNumber=self.device["groupNumber"], logicalCode=self.device["logicalCode"]), location=u"{address}-{groupName}".format(address=group["address"], groupName=group["groupName"]), fault=faultContent, notifyTime=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") ) self.record( faultCode=faultCode, description=faultContent, title="注意!功率故障!", ) elif faultCode == 3: faultContent = "当前设备第%s号端口警告:插座空闲,功率故障,继电器故障" % (port) self.notify_dealer( templateName="device_fault", title="注意!功率故障,继电器故障!", device=u"{groupNumber}组-{logicalCode}".format(groupNumber=self.device["groupNumber"], logicalCode=self.device["logicalCode"]), location=u"{address}-{groupName}".format(address=group["address"], groupName=group["groupName"]), fault=faultContent, notifyTime=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") ) self.record( faultCode=faultCode, description=faultContent, title="注意!功率故障,继电器故障!", ) elif faultCode == 5: faultContent = "当前设备第%s号端口警告:插座工作,功率正常,继电器故障" % (port) self.notify_dealer( templateName="device_fault", title="注意!继电器故障!", device=u"{groupNumber}组-{logicalCode}".format(groupNumber=self.device["groupNumber"], logicalCode=self.device["logicalCode"]), location=u"{address}-{groupName}".format(address=group["address"], groupName=group["groupName"]), fault=faultContent, notifyTime=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") ) self.record( faultCode=faultCode, description=faultContent, title="注意!继电器故障!", ) elif faultCode == 6: faultContent = "当前设备第%s号端口警告:插座工作,功率故障,继电器正常" % (port) self.notify_dealer( templateName="device_fault", title="注意!功率故障!", device=u"{groupNumber}组-{logicalCode}".format(groupNumber=self.device["groupNumber"], logicalCode=self.device["logicalCode"]), location=u"{address}-{groupName}".format(address=group["address"], groupName=group["groupName"]), fault=faultContent, notifyTime=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") ) self.record( faultCode=faultCode, description=faultContent, title="注意!功率故障!", ) elif faultCode == 7: faultContent = "当前设备第%s号端口警告:插座工作,功率故障,继电器故障" % (port) self.notify_dealer( templateName="device_fault", title="注意!功率故障,继电器故障!", device=u"{groupNumber}组-{logicalCode}".format(groupNumber=self.device["groupNumber"], logicalCode=self.device["logicalCode"]), location=u"{address}-{groupName}".format(address=group["address"], groupName=group["groupName"]), fault=faultContent, notifyTime=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") ) self.record( faultCode=faultCode, description=faultContent, title="注意!功率故障,继电器故障!", ) elif faultCode == 9: faultContent = "当前设备第%s号端口警告:投币工作,功率正常,继电器故障" % (port) self.notify_dealer( templateName="device_fault", title="注意!继电器故障!", device=u"{groupNumber}组-{logicalCode}".format(groupNumber=self.device["groupNumber"], logicalCode=self.device["logicalCode"]), location=u"{address}-{groupName}".format(address=group["address"], groupName=group["groupName"]), fault=faultContent, notifyTime=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") ) self.record( faultCode=faultCode, description=faultContent, title="注意!继电器故障!", ) elif faultCode == 10: faultContent = "当前设备第%s号端口警告:投币工作,功率故障,继电器正常" % (port) self.notify_dealer( templateName="device_fault", title="注意!功率故障!", device=u"{groupNumber}组-{logicalCode}".format(groupNumber=self.device["groupNumber"], logicalCode=self.device["logicalCode"]), location=u"{address}-{groupName}".format(address=group["address"], groupName=group["groupName"]), fault=faultContent, notifyTime=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") ) self.record( faultCode=faultCode, description=faultContent, title="注意!功率故障!", ) elif faultCode == 11: faultContent = "当前设备第%s号端口警告:投币工作,功率故障,继电器故障" % (port) self.notify_dealer( templateName="device_fault", title="注意!,功率故障,继电器故障!", device=u"{groupNumber}组-{logicalCode}".format(groupNumber=self.device["groupNumber"], logicalCode=self.device["logicalCode"]), location=u"{address}-{groupName}".format(address=group["address"], groupName=group["groupName"]), fault=faultContent, notifyTime=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") ) self.record( faultCode=faultCode, description=faultContent, title="注意!,功率故障,继电器故障!", ) def do_card_start(self): cardNo = self.event_data["cardNo"] mode = self.event_data["mode"] port = self.event_data["port"] portStr = str(port + 1) card = self.update_card_dealer_and_type(cardNo) # type: Card if not card: self.deviceAdapter._response_data('A7', '01') return balance = card.balance if balance <= RMB(0): self.deviceAdapter._response_data('A7', '02') return result = self.deviceAdapter._get_device_status_info() status = result['portStatusDict'][portStr]['status'] if status not in [Const.DEV_WORK_STATUS_IDLE, Const.DEV_WORK_STATUS_WORKING]: self.deviceAdapter._response_data('A7', '03') return needMoney = balance orderNo, cardOrderNo = self.record_consume_for_card(card, needMoney, desc=u"刷卡消费") serviceDict = { "orderNo": orderNo, "cardOrderNo": cardOrderNo } if mode == '00': billingType = self.device.otherConf.get('billingType') if billingType == 'time': method = '01' elif billingType == 'power': method = '02' elif billingType == 'elec': method = '03' elif billingType == 'free': method = '04' elif billingType == 'interval_quota': method = '05' self.deviceAdapter._start_remote(portStr, method, '02', needMoney, orderNo) else: self.deviceAdapter._update_charge_balance(portStr, needMoney) # 记录缓存 ServiceProgress.register_card_service( self.device, int(portStr), card, serviceDict ) portDict = { "orderNo": orderNo, # 订单号码 "cardOrderNo": cardOrderNo, "cardNo": cardNo, # 卡号 0-99999999 'startTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'coins': str(needMoney), 'price': str(needMoney), 'isStart': True, 'openId': card.openId, 'ZRYH': True, "cardId": str(card.id), "billingType": billingType } Device.update_dev_control_cache(self.device["devNo"], {portStr: portDict}) def do_card_balance_check(self): cardNo = self.event_data["cardNo"] card = self.update_card_dealer_and_type(cardNo) # 没有到账的卡给它充值, 应该用不上 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() cardBalance = card.balance cardBalanceHex = fill_2_hexByte(hex(float(cardBalance) * 100), 8) data = "00" + cardBalanceHex self.deviceAdapter._response_data(self.event_data['cmdCode'], data) def do_coins_start(self): Accounting.recordOfflineCoin(device=self.device, report_ts=int(time.time()), coins=int(self.event_data['coins']), port=self.event_data.get('port', None)) if self.device.ownerId is not None and self.device.ownerId != '': dealer = Dealer.objects(id=self.device.ownerId).first() if dealer is not None and 'show_device_offline_coins' in dealer.features: OfflineCoinStatistics.recordCoinEvent( self.device['logicalCode'], self.device.devNo, int(self.event_data.get('coins', 1)), self.device['groupId'] ) else: logger.error('undefined dealer id=%s' % self.device.ownerId) # 如果是投币,直接把端口状态刷新下 try: self.deviceAdapter.get_port_status_from_dev() except Exception, e: logger.info('some err=%s' % e) finally: self.deviceAdapter._ack(self.event_data.get('port')) def device_disconnected(self): deviceCode = self.event_data["deviceCode"] group = Group.get_group(self.device["groupId"]) faultContent = "设备%s警告,充电桩主电断开" % (deviceCode) self.notify_dealer( templateName="device_fault", title="注意!充电桩主电断开!", device=u"{groupNumber}组-{logicalCode}".format(groupNumber=self.device["groupNumber"], logicalCode=self.device["logicalCode"]), location=u"{address}-{groupName}".format(address=group["address"], groupName=group["groupName"]), fault=faultContent, notifyTime=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") ) self.record( faultCode="00", description=faultContent, title="注意!,功率故障,继电器故障!", )