# -*- coding: utf-8 -*- # !/usr/bin/env python import logging from decimal import Decimal import datetime import time from mongoengine import DoesNotExist from apilib.monetary import Ratio, RMB, VirtualCoin from apilib.utils_datetime import to_datetime from apilib.utils_string import make_title_from_dict from apps.web.agent.models import Agent from apps.web.constant import Const, DEALER_CONSUMPTION_AGG_KIND from apps.web.core.accounting import Accounting from apps.web.core.device_define.jndz import CMD_CODE, SWIPE_CARD_PARAM_OP, SWIPE_CARD_RES from apps.web.core.exceptions import ServiceException from apps.web.dealer.models import Dealer 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.eventer.errors import InvalidOption, NoCommandHandlerAvailable from apps.web.report.utils import record_consumption_stats 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.user.models import VCardConsumeRecord, CardRechargeOrder, ServiceProgress, MyUser, \ UserVirtualCard, Card, ConsumeRecord from apps.web.api.models import APIStartDeviceRecord from apps.web.user.transaction_deprecated import refund_money from apps.web.south_intf.zhongtian import report_zhongtian_service_complete, report_zhongtian_refund 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: return None if not event_data.has_key('cmdCode'): return if 'duration' in device_event: event_data.update({'duration': device_event['duration']}) if event_data['cmdCode'] in [ CMD_CODE.SWIPE_CARD_10, CMD_CODE.DEVICE_SUBMIT_CHARGE_FINISHED_06, CMD_CODE.DEVICE_SUBMIT_CHARGE_FINISHED_v2_16, CMD_CODE.DEVICE_SUBMIT_OFFLINE_COINS_20 ]: return ChargingJNDZWorkEvent(self.deviceAdapter, event_data) if event_data['cmdCode'] in [ CMD_CODE.DEVICE_SUBMIT_DEVICE_FAULT_0A, CMD_CODE.DEVICE_FAULT_TEMPERATURE, CMD_CODE.DEVICE_FAULT_POWER, CMD_CODE.DEVICE_FAULT_SMOKE, CMD_CODE.DEVICE_ELEC, ]: return JNDZEventerFailure(self.deviceAdapter, event_data) class JNDZEventerFailure(FaultEvent): def do(self, **args): cmdCode = self.event_data.get("cmdCode") faultType=self.event_data.get("FaultCode") desc=self.event_data.get("statusInfo", "") # 保证原有的故障处理逻辑不变 if cmdCode == CMD_CODE.DEVICE_SUBMIT_DEVICE_FAULT_0A: super(JNDZEventerFailure, self).do() group = Group.get_group(self.device.groupId) titleList = [ {u"告警名称": desc}, {u"地址名称": group["groupName"]} ] title = make_title_from_dict(titleList) # 接下来的都是整机告警,这个地方需要通知到经销商 # TODO zjl 需要知道 这个告警标志位是否有具体含义 self.notify_dealer( "device_fault", title=title, device=u" 号设备".format(self.device.logicalCode), faultType=desc, notifyTime=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), fault=desc ) # 记录错误故障 self.record( faultCode=cmdCode, description=desc, title=faultType, detail=faultType, ) class ChargingJNDZWorkEvent(WorkEvent): def time_ratio_pricing(self, value, leftTime, actualNeedTime): return value * Ratio(leftTime) * Ratio( 1 / float(actualNeedTime)) def get_backCoins(self, coins, leftTime, actualNeedTime): return self.time_ratio_pricing(value=coins, leftTime=leftTime, actualNeedTime=actualNeedTime) def get_backMoney(self, money, leftTime, actualNeedTime): return self.time_ratio_pricing(value=money, leftTime=leftTime, actualNeedTime=actualNeedTime) def do(self, **args): devNo = self.device['devNo'] logger.info('JingNengDianZi charging event detected, devNo=%s,curInfo=%s' % (devNo, self.event_data)) cmdCode = self.event_data['cmdCode'] #: 刷卡消费的,分为扣费和退费两种 if cmdCode == CMD_CODE.SWIPE_CARD_10: cardNo = self.event_data['cardNo'] preFee = RMB(self.event_data['preFee']) #: 操作符,是充值还是减少 <- (00, 01) oper = self.event_data['oper'] card = self.update_card_dealer_and_type(cardNo) #: 经销商限制ID卡, 如果满足直接return if not card: return self.event_data['openId'] = card.openId self.event_data['cardId'] = str(card.id) self.event_data['startTime'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') Device.update_dev_control_cache(devNo, self.event_data) #: 首先检查订单,并进行充值 #: 用户首先在手机客户端充值,需要这个时刻刷卡上报事件将充值的订单同步上来 card_recharge_order = CardRechargeOrder.get_last_to_do_one(str(card.id)) result = self.recharge_id_card(card = card, rechargeType = 'append', order = card_recharge_order) card.reload() logger.info('JingNengDianZi cmdNo(10) - cardNo=%s, openId=%s, result=%s, preFee=%s, curinfo=%s' % (cardNo, card.openId, result, preFee, self.event_data)) # TODO 这个地方为了同时满足劲能电子和霍珀的需求 先使用特性 vCardNeedBind 后续需要统一规则 try: dealer = Dealer.get_dealer(card.dealerId) agent = Agent.objects.get(id=dealer.get("agentId")) features = agent.features except Exception as e: features = list() if "vCardNeedBind" in features: virtual_card = card.bound_virtual_card else: virtual_card = card.related_virtual_card #如果虚拟卡已经绑定,需要检查下今天是否可用,如果可用,有限使用虚拟卡 vCardCanUse = False package = {'coins':float(preFee),'unit':u'分钟','time':180} if virtual_card is not None and card.openId is not None: devObj = Device.objects.get(devNo = devNo) cardMin = devObj.otherConf.get('cardMin',None) if cardMin is not None: package = {'coins':float(preFee),'unit':u'分钟','time':int(cardMin)} vCardCanUse = virtual_card.can_use_today(package) #: 扣费 if oper == SWIPE_CARD_PARAM_OP.DECR_00: if card.openId == '' or card.frozen: res = SWIPE_CARD_RES.INVALID_CARD_02 leftBalance = RMB(0) return self.response_use_card(res, leftBalance) #如果虚拟卡可用,卡的费用不要扣掉,仅仅做记录,但是虚拟卡的额度需要扣掉 elif vCardCanUse: # 记录卡消费记录以及消费记录 orderNo, cardOrderNo = self.record_consume_for_card(card, money=RMB(0.0),desc = u'使用绑定的虚拟卡') group = Group.get_group(self.device['groupId']) consumeRcd = virtual_card.consume(openId=card.openId, group=group, dev=self.device, package=package, attachParas={}, nickname=card.cardName) # 记录当前服务的progress.没有上报端口号,所以先用-1标记,表示端口未知。端口等消费的时候会报上来 ServiceProgress.register_card_service(self.device, -1, card, { 'orderNo': orderNo, 'money': self.event_data['preFee'], 'coin': self.event_data['preFee'], 'needTime': 0, 'cardOrderNo': cardOrderNo, 'consumeRcdId': str(consumeRcd.id) }) self.consume_money_for_card(card, money=RMB(0.0)) if consumeRcd is None:#如果额度没有扣除成功,就用卡 pass else: self.response_use_card(SWIPE_CARD_RES.SUCCESS_00, leftBalance=0) # 通知微信,已经扣费 self.notify_balance_has_consume_for_card(card, preFee,desc=u'使用绑定的虚拟卡') return elif result['balance'] < preFee: res = SWIPE_CARD_RES.BALANCE_NOT_ENOUGH_01 leftBalance = result['balance'] return self.response_use_card(res, leftBalance) else: # 记录卡消费记录以及消费记录 orderNo, cardOrderNo = self.record_consume_for_card(card, preFee) # 记录当前服务的progress.没有上报端口号,所以先用-1标记,表示端口未知。端口等消费的时候会报上来 ServiceProgress.register_card_service(self.device, -1, card, { 'orderNo': orderNo, 'money': self.event_data['preFee'], 'coin': self.event_data['preFee'], 'needTime': 0, 'cardOrderNo': cardOrderNo }) res = SWIPE_CARD_RES.SUCCESS_00 leftBalance = result['balance'] self.consume_money_for_card(card, preFee) leftBalance -= preFee self.response_use_card(res, leftBalance) # 通知微信,已经扣费 self.notify_balance_has_consume_for_card(card, preFee) # 退费.卡的退费比较特殊:如果需要按照电量进行扣费退费,需要在设备管理后台,设置成按电量方式计费。然后把卡的余额回收关闭掉。 #充电结束后,上报事件,然后把钱退到卡里。如果是按照时间计费(霍柏的特殊),完全由设备决定,设备告诉我退费,我就退。针对霍柏 #绑定虚拟卡的情况下,按照时间退费,需要直接取消掉余额回收,靠结束事件触发结束 elif oper == SWIPE_CARD_PARAM_OP.INCR_01: if virtual_card is not None: pass else: dev = Device.objects.get(devNo = self.device['devNo']) billingType = dev.otherConf.get('billingType', 'time') if billingType != 'time': logger.debug('dev<{}> billing type is elec. not to card refund.') return res = SWIPE_CARD_RES.SUCCESS_00 self.response_use_card(res, card.balance + preFee) self.refund_money_for_card(preFee, str(card.id)) # 通知微信,已经退费 self.notify_user(card.managerialOpenId, 'refund_coins', **{ 'title': u'退币完成!您的卡号是%s,卡别名:%s' % (card.cardNo, card.nickName), 'backCount': u'金币:%s' % preFee, 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') }) else: raise InvalidOption('oper has be to 00 or 01, %s was given' % (oper,)) #: 结束的命令 elif cmdCode in [CMD_CODE.DEVICE_SUBMIT_CHARGE_FINISHED_06, CMD_CODE.DEVICE_SUBMIT_CHARGE_FINISHED_v2_16]: lineInfo = Device.update_port_control_cache(devNo, self.event_data) group = Group.get_group(self.device['groupId']) dealer = Dealer.objects(id=group['ownerId']).first() if not dealer: logger.error('dealer is not found, dealerId=%s' % group['ownerId']) return agent = Agent.objects(id=dealer.agentId).first() if not agent: logger.error('agent is not found, agentId=%s' % dealer.agentId) return if not lineInfo.has_key('coins'): return if self.event_data.has_key('duration') and self.event_data['duration'] > 0: usedTime = self.event_data['duration'] else: if (not lineInfo) or (not lineInfo.has_key('startTime')): return startTime = to_datetime(lineInfo['startTime']) nowTime = datetime.datetime.now() if startTime > nowTime:#如果web服务器时间和事件监控服务器时间不一致,导致开始时间比事件时间还大 usedTime = 0 else: usedTime = int(round(((nowTime - startTime).total_seconds() / 60.0))) cardId = lineInfo.get('cardId', '') vCardId = lineInfo.get('vCardId', None) money = RMB(lineInfo['coins']) price = lineInfo.get('price', 0.0) leftTime = self.event_data['leftTime'] billingType = lineInfo.get('billingType', 'time') consumeType = lineInfo.get('consumeType', '') refundProtection = lineInfo.get('refundProtection', 0) refundProtectionTime = lineInfo.get('refundProtectionTime', 5) backCoins, backPrice = RMB(0.0), RMB(0.0) # todo 初始化, 防止变量before assignment needTime = 0 leftTimeStr = '' try: #: 刷卡或者万一缓存重启了,出现needElec为空的,作为1000度电吧,就会一定使用设备上报的电量 if lineInfo.get('elec', 0.0) < lineInfo.get('needElec', 0.0): spendElec = round(lineInfo['needElec'] - lineInfo['elec'], 2) else: spendElec = 0.0 if leftTime == 65535: actualNeedTime = 0 backCoins = money backPrice = price usedTime = 0 spendElec = 0.0 else: actualNeedTime = usedTime + leftTime leftTimeStr = leftTime if 'alt_tech_refund_mode' in agent.features and billingType == 'time': needTime = lineInfo['needTime'] # 剩余时间不满 60 按照 0 算, 不满 120 按照 60 算... calcleftTime = (int(leftTime) // 60) * 60 backCoins = money * (float(calcleftTime) / float(actualNeedTime)) elif billingType == 'time': needTime = lineInfo['needTime'] backCoins = money * (float(leftTime) / float(actualNeedTime)) else: needElec, elec = Decimal(lineInfo.get('needElec', 1000)), Decimal(str(lineInfo.get('elec'))) ratio = (needElec - elec) / needElec backCoins = RMB(money.amount - money.amount * ratio) # refundProtection 开关, 控制 refundProtectionTime 内全额退款 if (refundProtection == 1 and usedTime < refundProtectionTime) and backCoins != 0: backCoins = money if backCoins > money: backCoins = money backPrice = round(price * float(backCoins) / float(money), 2) #: 扫码的方式 if cardId == '' and vCardId is None and consumeType != 'coin': #: 这里需要考虑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 # 中天的结束状态匹配 reasonCode = self.event_data['reasonCode'] if reasonCode == '0B': reasonCode = '03' elif reasonCode == '03': reasonCode = '04' else: pass # 中天空载需要这样写 if leftTime == 65535: leftTime = lineInfo['needTime'] report_zhongtian_service_complete( event_code = '16', record=record, orderNo=lineInfo['extOrderNo'], deviceCode=self.device['logicalCode'], groupName=group['groupName'], address=group['address'], actualNeedTime=lineInfo['needTime'], leftTime=leftTime, finishedState=reasonCode ) record.update(servicedInfo={'spendElec': str(spendElec), 'backCoins': '0'}) if self.device.is_auto_refund: coins = VirtualCoin(lineInfo['coins']) money = RMB(lineInfo['price']) backCoins = self.get_backCoins(coins=coins, leftTime=leftTime, actualNeedTime=lineInfo['needTime']) backMoney = self.get_backMoney(money=money, leftTime=leftTime, actualNeedTime=lineInfo['needTime']) 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=lineInfo['needTime'], leftTime=leftTime, finishedState=reasonCode ) record.update(servicedInfo={'spendElec': str(spendElec), 'backCoins': str(backCoins)}) else: openId = lineInfo['openId'] billingType = lineInfo.get('billingType', 'time') if billingType == 'time': leftTimeStr = leftTime if leftTime != 65535 else lineInfo['needTime'] title = u"\\n\\n结束原因:\\t\\t{reason}\\n\\n设备编号:\\t\\t{logicalCode}-{port}\\n\\n设备地址:\\t\\t{address}\\n\\n消费时间:\\t\\t动态功率计算{actualNeedTime}分钟\\n\\n剩余时间:\\t\\t{leftTimeStr}分钟".format( reason=self.event_data["reason"], logicalCode=self.device["logicalCode"], port=self.event_data["port"], address=group["address"], actualNeedTime=actualNeedTime, leftTimeStr=leftTimeStr ) else: title = u"\\n\\n结束原因:\\t\\t{reason}\\n\\n设备编号:\\t\\t{logicalCode}-{port}\\n\\n设备地址:\\t\\t{address}\\n\\n购买电量:\\t\\t{needElec}\\n\\n消耗电量:\\t\\t{spendElec}".format( reason=self.event_data["reason"], logicalCode=self.device["logicalCode"], port=self.event_data["port"], address=group["address"], needElec=lineInfo.get("needElec", 1000), spendElec=spendElec) # 通知充电完成 user = MyUser.objects(openId=openId, groupId=self.device['groupId']).first() self.notify_user( managerialOpenId=user.managerialOpenId if user else "", templateName="service_complete", title=title, service=u"充电服务", finishTime=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), remark = u'谢谢您的支持') consumeDict = {'chargeIndex': lineInfo['port'], 'reason': lineInfo['reason'], 'actualNeedTime':u'动态功率计算为%s分钟' % actualNeedTime, 'duration':usedTime} if billingType == 'time': leftTimeStr = leftTime if leftTime != 65535 else lineInfo['needTime'] consumeDict.update({'leftTime': leftTimeStr, 'needTime': u'扫码订购%s分钟' % lineInfo['needTime']}) consumeDict.update({'elec': spendElec}) else: consumeDict.update({'needElec': lineInfo['needElec']}) consumeDict.update({'elec':spendElec}) consumeDict.update({'elecFee':self.calc_elec_fee(spendElec)}) # 如果需要退款,计算退款数据. if not self.device.is_auto_refund: consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.COIN: money.mongo_amount}) ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], {'open_id': openId, 'device_imei': self.device['devNo'], 'port': lineInfo['port'], 'isFinished': False}, consumeDict ) else: if group.get('isFree', False) is True: backCoins = RMB('0.0') desc = u'免费充电。' else: desc = u'' # 扫码退钱, 退到个人账号 refund_money(self.device, backCoins, lineInfo['openId']) consumeDict.update({ DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: backCoins.mongo_amount, DEALER_CONSUMPTION_AGG_KIND.COIN: (money - backCoins).mongo_amount }) ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], {'open_id': openId, 'device_imei': self.device['devNo'], 'port': lineInfo['port'], 'isFinished': False}, consumeDict) if billingType == 'time': leftTimeStr = leftTime if leftTime != 65535 else lineInfo['needTime'] desc += u'您使用的%s号端口充电,共付款:%s元,充电预定时间为:%s分钟,剩余时间:%s分钟,给您退款:%s元' % ( lineInfo['port'], money, lineInfo['needTime'], leftTimeStr, backCoins) else: desc += u'您使用的%s号端口充电,共付款:%s元,充电预定电量为:%s度,使用:%s度,给您退款:%s元' % ( lineInfo['port'], money, lineInfo.get('needElec', 1), spendElec, backCoins) self.notify_user(user.managerialOpenId if user else '', 'refund_coins', **{ 'title': desc, 'backCount': u'金币:%s' % backCoins, 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') }) #: 使用的是虚拟卡 elif vCardId is not None: billingType = lineInfo.get('billingType', 'time') if billingType == 'time': leftTimeStr = leftTime if leftTime != 65535 else lineInfo['needTime'] title = u"\\n\\n结束原因:\\t\\t{reason}\\n\\n设备编号:\\t\\t{logicalCode}-{port}\\n\\n设备地址:\\t\\t{address}\\n\\n消费时间:\\t\\t动态功率计算{actualNeedTime}分钟\\n\\n剩余时间:\\t\\t{leftTimeStr}分钟".format( reason=self.event_data["reason"], logicalCode=self.device["logicalCode"], port=self.event_data["port"], address=group["address"], actualNeedTime=actualNeedTime, leftTimeStr=leftTimeStr ) else: title = u"\\n\\n结束原因:\\t\\t{reason}\\n\\n设备编号:\\t\\t{logicalCode}-{port}\\n\\n设备地址:\\t\\t{address}\\n\\n购买电量:\\t\\t{needElec}\\n\\n消耗电量:\\t\\t{spendElec}".format( reason=self.event_data["reason"], logicalCode=self.device["logicalCode"], port=self.event_data["port"], address=group["address"], needElec=lineInfo.get("needElec", 1000), spendElec=spendElec ) # 通知充电完成 try: vCard = UserVirtualCard.objects.get(id=vCardId) except DoesNotExist: logger.info('can not find the vCard id = %s' % vCardId) return self.notify_user( managerialOpenId=self.get_managerialOpenId_by_openId(lineInfo["openId"]), templateName="service_complete", title=title, service=u"充电服务", finishTime=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), remark = u'谢谢您的支持' ) consumeDict = {'chargeIndex': lineInfo['port'], 'reason': lineInfo['reason'], 'actualNeedTime':u'动态功率计算为%s分钟' % actualNeedTime, 'duration':usedTime} consumeDict.update({'elec': spendElec}) if billingType != 'time': consumeDict.update({'elec':spendElec}) consumeDict.update({'elecFee':self.calc_elec_fee(spendElec)}) ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], {'open_id': lineInfo['openId'], 'device_imei': self.device['devNo'], 'port': lineInfo['port'], 'isFinished': False}, consumeDict ) consumeRcdId = lineInfo.get('consumeRcdId', None) if consumeRcdId is None: logger.info('can not find consume rcd id') return # 尝试进行虚拟卡退费 try: vCardConsumeRcd = VCardConsumeRecord.objects.get(id=consumeRcdId) except DoesNotExist, e: logger.info('can not find the consume rcd id = %s' % consumeRcdId) else: vCard.refund_quota(vCardConsumeRcd, usedTime, spendElec, backCoins.mongo_amount) #: 刷的实体卡 elif cardId != '': card = Card.objects.get(id=cardId) agent = Agent.objects.get(id=card.agentId) virtual_card = card.bound_virtual_card consumeDict = {'chargeIndex': lineInfo['port'], 'reason': lineInfo['reason'], 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime, 'duration': usedTime} consumeDict.update({'elec': spendElec}) if billingType == 'time': self.notify_user( managerialOpenId=card.managerialOpenId, templateName="service_complete", title=u"\\n\\n结束原因:\\t\\t{reason}\\n\\n设备编号:\\t\\t{logicalCode}-{port}\\n\\n设备地址:\\t\\t{address}\\n\\n消费卡号:\\t\\t{cardNo}\\n\\n持卡姓名:\\t\\t{cardName}\\n\\n消费时间:\\t\\t动态功率计算{actualNeedTime}分钟\\n\\n剩余时间:\\t\\t{leftTimeStr}分钟".format( reason=self.event_data["reason"], logicalCode=self.device["logicalCode"], port=self.event_data["port"], address=group["address"], cardNo=card.cardNo, cardName=card.cardName, actualNeedTime=actualNeedTime, leftTimeStr=leftTimeStr ), service=u"充电服务", finishTime=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), remark = u'谢谢您的支持') if 'huopo_card_time_refund' in agent.features and virtual_card is None: consumeDict.update({ DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: (money - backCoins).mongo_amount, DEALER_CONSUMPTION_AGG_KIND.REFUND_CARD: backCoins.mongo_amount }) ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], {'cardId': cardId, 'device_imei': self.device['devNo'], 'port': lineInfo['port'], 'isFinished': False}, {'chargeIndex': lineInfo['port'], 'leftTime': leftTimeStr, 'needTime': u'刷卡订购%s分钟' % needTime if virtual_card is None else u'绑定虚拟卡订购%s分钟' % needTime, 'reason': lineInfo['reason'], 'elec': spendElec, 'duration': usedTime, 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime, 'elecFee':self.calc_elec_fee(spendElec), 'refundedMoney': str(backCoins) }) else: ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], {'cardId': cardId, 'device_imei': self.device['devNo'], 'port': lineInfo['port'], 'isFinished': False}, {'chargeIndex': lineInfo['port'], 'leftTime': leftTimeStr, 'needTime': u'刷卡订购%s分钟' % needTime if virtual_card is None else u'绑定虚拟卡订购%s分钟' % needTime, 'reason': lineInfo['reason'], 'elec': spendElec, 'duration': usedTime, 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime, 'elecFee':self.calc_elec_fee(spendElec)}) if 'huopo_card_time_refund' in agent.features: if virtual_card is None: self.refund_money_for_card(backCoins, str(card.id)) desc = u'您使用的%s号端口充电,共付款:%s元,充电预定时间为:%s分钟,使用:%s分钟,给您退款:%s元' % ( lineInfo['port'], money, needTime, usedTime, backCoins) self.notify_user(card.managerialOpenId if card else '', 'refund_coins', **{ 'title': desc, 'backCount': u'金币:%s' % backCoins, 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}) else: consumeRcdId = lineInfo.get('consumeRcdId', None) if consumeRcdId is None: logger.info('can not find consume rcd id') return # 尝试进行虚拟卡退费 try: vCardConsumeRcd = VCardConsumeRecord.objects.get(id=consumeRcdId) except DoesNotExist, e: logger.info('can not find the consume rcd id = %s' % consumeRcdId) else: virtual_card.refund_quota(vCardConsumeRcd, usedTime, spendElec, backCoins.mongo_amount) # 非霍珀特性 else: consumeRcdId = lineInfo.get('consumeRcdId', None) # 不是虚拟卡启动的直接结束掉 if consumeRcdId is None: logger.info('can not find consume rcd id') return # 尝试进行虚拟卡退费 try: vCardConsumeRcd = VCardConsumeRecord.objects.get(id=consumeRcdId) except DoesNotExist, e: logger.info('can not find the consume rcd id = %s' % consumeRcdId) else: virtual_card.refund_quota(vCardConsumeRcd, usedTime, spendElec, backCoins.mongo_amount) return else: self.notify_user( managerialOpenId=card.managerialOpenId, templateName="service_complete", title=u"\\n\\n结束原因:\\t\\t{reason}\\n\\n设备编号:\\t\\t{logicalCode}-{port}\\n\\n设备地址:\\t\\t{address}\\n\\n消费卡号:\\t\\t{cardNo}\\n\\n持卡姓名:\\t\\t{cardName}\\n\\n购买电量:\\t\\t{needElec}\\n\\n消耗电量:\\t\\t{spendElec}".format( reason=self.event_data["reason"], logicalCode=self.device["logicalCode"], port=self.event_data["port"], address=group["address"], cardNo=card.cardNo, cardName=card.cardName, needElec=lineInfo.get("needElec"), spendElec=spendElec ), service=u"充电服务", finishTime=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), remark = u'谢谢您的支持') consumeDict = {'chargeIndex': lineInfo['port'], 'leftTime': leftTimeStr, 'reason': lineInfo['reason'], 'elec': spendElec, 'duration': usedTime, 'needElec': lineInfo['needElec']} consumeDict.update({'elecFee':self.calc_elec_fee(spendElec)}) if not self.device.is_auto_refund: ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], {'cardId': cardId, 'device_imei': self.device['devNo'], 'port': lineInfo['port'], 'isFinished': False}, consumeDict ) else: if virtual_card is None: # 扫码退钱, 退到个人卡号 self.refund_money_for_card(backCoins, str(card.id)) consumeDict.update({ DEALER_CONSUMPTION_AGG_KIND.REFUND_CARD: backCoins.mongo_amount, DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: (money - backCoins).mongo_amount }) desc = u'您使用的%s号端口充电,共付款:%s元,充电预定电量为:%s度,使用:%s度,给您退款:%s元' % ( lineInfo['port'], money, lineInfo.get('needElec', 0.0), spendElec, backCoins) self.notify_user(card.managerialOpenId if card else '', 'refund_coins', **{ 'title': desc, 'backCount': u'金币:%s' % backCoins, 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') }) else: consumeRcdId = lineInfo.get('consumeRcdId', None) if consumeRcdId is None: logger.info('can not find consume rcd id') return # 尝试进行虚拟卡退费 try: vCardConsumeRcd = VCardConsumeRecord.objects.get(id=consumeRcdId) except DoesNotExist, e: logger.info('can not find the consume rcd id = %s' % consumeRcdId) else: virtual_card.refund_quota(vCardConsumeRcd, usedTime, spendElec, backCoins.mongo_amount) ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], {'open_id': lineInfo['openId'], 'device_imei': self.device['devNo'], 'port': lineInfo['port'], 'isFinished': False}, consumeDict) #: 消费类型为金币,则 elif consumeType == 'coin': CoinConsumeRcd = ConsumeRecord.objects.get( orderNo = lineInfo['consumeOrderNo']) # type: ConsumeRecord CoinConsumeRcd.servicedInfo['elec'] = spendElec CoinConsumeRcd.servicedInfo['actualNeedTime'] = u'动态功率计算为%s分钟' % actualNeedTime CoinConsumeRcd.servicedInfo['needTime'] = u'投币订购%s分钟' % needTime CoinConsumeRcd.servicedInfo['reason'] = lineInfo['reason'] CoinConsumeRcd.servicedInfo['chargeIndex'] = lineInfo['port'] CoinConsumeRcd.finishedTime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') CoinConsumeRcd.save() valueDict = { 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: Device.clear_port_control_cache(devNo, str(self.event_data['port'])) dataDict = {'backMoney': backPrice, 'backCoins': str(backCoins.mongo_amount)} if lineInfo.has_key('orderNo'): dataDict.update({'orderNo': lineInfo['orderNo']}) notify_event_to_north(self.dealer, self.device, level = Const.EVENT_NORMAL, desc = self.event_data['reason'], dataDict=dataDict) send_event_to_zhejiang(self.dealer, self.device, self.event_data) #: 启动了端口,主要记录下投币数据 elif cmdCode == CMD_CODE.DEVICE_SUBMIT_OFFLINE_COINS_20: consumeType = self.event_data['consumeType'] if consumeType == 'coin': self.event_data.update({'startTime': datetime.datetime.now().strftime(Const.DATETIME_FMT)}) # 记录该端口累计需要的时间和钱,cardId Device.update_port_control_cache(self.device['devNo'], self.event_data) Accounting.recordOfflineCoin(self.device, int(time.time()), int(self.event_data['coins']), port = self.event_data.get('port', None)) self.event_data.update({'needElec': self.event_data['elec']}) self.event_data.update({'consumeOrderNo': self.record_consume_for_coin(RMB(self.event_data['coins']))}) Device.update_port_control_cache(self.device['devNo'], self.event_data) elif consumeType == 'card': port = self.event_data['port'] consumeDict = {'chargeIndex': port, 'elec': self.event_data['elec'], 'money': self.event_data['coins'], 'needTime': u'刷卡订购%s分钟' % self.event_data['needTime']} queryDict = {'device_imei': self.device['devNo'], 'port': -1, 'isFinished': False, 'cardId': {'$ne': ''}, 'start_time': {'$gte': int(time.time()) - 3600}} progressDict = {'port': port} ServiceProgress.update_progress_and_consume_rcd(ownerId=self.device['ownerId'], queryDict=queryDict, consumeDict=consumeDict, updateConsume=True, progressDict=progressDict) # 找出对应的卡的ID记录到端口内存数据 queryDict.update(progressDict) rcds = ServiceProgress.get_collection().find(queryDict, {'cardId': 1, 'open_id': 1, "consumeOrder": 1}, sort=[('start_time', -1)]) if rcds.count() == 0: return rcd = rcds[0] dev = Device.objects.get(devNo=self.device['devNo']) billingType = dev.otherConf.get('billingType', 'time') # 刷卡虚拟卡启动的时候,将consumeRcd 写入到缓存 退费的时候使用 consumeRcdId = rcd.get("consumeOrder", dict()).get("consumeRcdId") if consumeRcdId: self.event_data.update({"consumeRcdId": consumeRcdId}) self.event_data.update({'billingType': billingType}) self.event_data.update({'cardId': rcd['cardId']}) self.event_data.update({'openId': rcd['open_id']}) cInfo = Device.get_dev_control_cache(devNo) lastPortInfo = cInfo.get(str(port), {}) # 钱需要累计 lastCoins = lastPortInfo.get('coins', 0.0) self.event_data.update({'coins': self.event_data['coins'] + lastCoins}) # 电量需要累加 lastNeedElec = lastPortInfo.get('needElec', 0.0) self.event_data.update({'needElec': self.event_data['elec'] + lastNeedElec}) # 时间需要累加 lastNeedTime = lastPortInfo.get('needTime', 0.0) self.event_data.update({'needTime': self.event_data['needTime'] + lastNeedTime}) self.event_data.update({'startTime': datetime.datetime.now().strftime(Const.DATETIME_FMT)}) self.event_data.update({'isStart': True, 'status': Const.DEV_WORK_STATUS_WORKING}) #: 记录该端口累计需要的时间和钱,cardId Device.update_port_control_cache(self.device['devNo'], self.event_data) elif consumeType == 'server': port = self.event_data['port'] #: 记录该端口累计需要的时间和钱 Device.update_port_control_cache(self.device['devNo'], self.event_data) consumeDict = {'chargeIndex': self.event_data['port'], 'elec': self.event_data['elec'], 'needTime': u'订购%s分钟' % self.event_data['needTime']} queryDict = {'device_imei': self.device['devNo'], 'port': port, 'isFinished': False, 'start_time': {'$gte': int(time.time()) - 3600}} progressDict = {'port': self.event_data['port']} ServiceProgress.update_progress_and_consume_rcd(ownerId=self.device['ownerId'], queryDict=queryDict, consumeDict=consumeDict, updateConsume=True, progressDict=progressDict) # todo 功率过低告警还没做完 # openId = self.event_data['openId'] # if openId is not None: # user = MyUser.objects(openId=openId, groupId = self.device['groupId']).first() # # report_to_user_low_power_wechat.delay(self.notify_low_power_to_user(user, 1, 20), 15) else: raise NoCommandHandlerAvailable('[JNDZ]] no command handler for cmd %s, curDevInfo=%s' % (cmdCode, self.event_data)) def response_use_card(self, res, leftBalance): # 启动失败需要把金币还回去 try: self.deviceAdapter.response_use_card(res, leftBalance) except ServiceException as e: logger.exception(e) raise JNDZEventerFailure('failed to connect to device') # todo 功率过低告警还没做完 # def notify_low_power_to_user(self, user, port, power): # self.notify_user(user.managerialOpenId if user else '', 'device_fault', **{ # 'title': u'设备充电功率过小预警', # 'device': u'二维码编号:%s, 端口:%s' % (self.device['logicalCode'], port), # 'location': u'您的电动车充电所在地', # 'fault': u'端口功率过小, 当前功率检测: %s 瓦, 可能会影响正常充电。' % power, # 'notifyTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') # })