# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import json import logging import time from arrow import Arrow from django.conf import settings from mongoengine import DoesNotExist from apilib.monetary import RMB, VirtualCoin, Ratio from apilib.systypes import StrEnum 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, APP_TYPE, DeviceCmdCode from apps.web.core.accounting import Accounting from apps.web.core.adapter.base import SmartBox from apps.web.core.adapter.hedong import ChargingHDBox 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.core.networking import MessageSender from apps.web.dealer.models import Dealer from apps.web.device.models import Group, Device, DeviceDict, GroupDict from apps.web.eventer import EventBuilder from apps.web.eventer.base import FaultEvent, WorkEvent, ComNetPayAckEvent, AckEventProcessorIntf, \ IcStartAckEvent, IcRechargeAckEvent, CardRefundAckEvent, IdStartAckEvent, VirtualCardStartAckEvent from apps.web.eventer.errors import InvalidOption, NoCommandHandlerAvailable from apps.web.helpers import get_wechat_auth_bridge 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.south_intf.zhongtian import report_zhongtian_service_complete, report_zhongtian_refund from apps.web.user.models import VCardConsumeRecord, ServiceProgress, CardRechargeOrder, MyUser, \ UserVirtualCard, Card, ConsumeRecord, RechargeRecord from apps.web.api.models import APIStartDeviceRecord from apps.web.user.utils import freeze_user_balance logger = logging.getLogger(__name__) class Cmd: # 刷卡类 SUCCESS_00 = 0 BALANCE_NOT_ENOUGH_01 = 1 INVALID_CARD_02 = 2 class CARD_TYPE(StrEnum): OFFLINE_CARD = "00" ONLINE_CARD = "01" MONTHLY_CARD = "02" FULL_CARD = "04" def is_server_refund(billingType, dev, dealer, agent): # type:(str, DeviceDict, Dealer, Agent)->bool if billingType != 'time': if 'jhCardElecRefund' in dealer.features: return False if dev.is_auto_refund: return True else: return False else: if 'huopo_card_time_refund' in agent.features: return True else: support_server_refund = dev.devType.get('features', {}).get( 'support_server_refund', False) if not support_server_refund: return False if dev.is_auto_refund: return True else: return False class builder(EventBuilder): def __getEvent__(self, device_event): if 'order_id' in device_event: if device_event['order_type'] == 'com_start': return MyComNetPayAckEvent(self.deviceAdapter, device_event) elif device_event['order_type'] == 'ic_recharge': return MyIcRechargeAckEvent(self.deviceAdapter, device_event) elif device_event['order_type'] == 'ic_start': return MyIcStartAckEvent(self.deviceAdapter, device_event) elif device_event['order_type'] == 'id_start': return MyIdStartAckEvent(self.deviceAdapter, device_event) elif device_event['order_type'] == 'vir_start': return MyVirtualCardStartAckEvent(self.deviceAdapter, device_event) elif device_event['order_type'] == 'card_refund': return MyCardRefundAckEvent(self.deviceAdapter, device_event) else: pass elif 'event_type' in device_event: if device_event['event_type'] == 'card': return CardEvent(self.deviceAdapter, device_event) else: return None event_data = self.deviceAdapter.analyze_event_data(device_event['data']) if event_data is None or 'cmdCode' not in event_data: return None if 'duration' in device_event: event_data.update({'duration': device_event['duration']}) if event_data['cmdCode'] in ['06', '16', '20', '2D', '2C']: 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, CMD_CODE.DEVICE_FAULT_ALTER ]: return JNDZEventerFailure(self.deviceAdapter, event_data) #if event_data['cmdCode'] == '21': # return ChargingJNDZReportEvent(self.deviceAdapter, event_data) class CardEvent(WorkEvent): def do(self): if not self.deviceAdapter.get_device_configs(): # type:ChargingHDBox.get_device_configs() self.deviceAdapter.get_dev_setting() if self.event_data['funCode'] == '10': self._do_get_balance() def _do_get_balance(self): cardNo = str(int(self.event_data["cardNo"], 16)) logger.info('[_do_get_balance] receive cardNo = {}'.format(cardNo)) card = self.update_card_dealer_and_type(cardNo) if not card or not card.openId or card.frozen: logger.info('[_do_get_balance] receive cardNo = {}, card invalid!'.format(cardNo)) data = { 'funCode': '10', 'cardNo': self.event_data.get('cardNo'), 'result': Cmd.INVALID_CARD_02, 'balance': 0, } else: # 是否存在没有到账的余额 进行充值 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() device_configs = self.deviceAdapter.get_device_configs() cst = RMB(device_configs.get('cst', 30) / 10.0) if card.balance >= RMB(0.1): pay = int(min(cst, card.balance) * 10) * 0.1 # 采用向下取整 rule = self.get_account_rule(pay) # 下发对应的额度 data = { 'funCode': '10', 'cardNo': self.event_data.get('cardNo'), 'result': Cmd.SUCCESS_00, 'balance': int(card.balance * 10.0), } data.update(rule) else: data = { 'funCode': '10', 'cardNo': self.event_data.get('cardNo'), 'result': Cmd.BALANCE_NOT_ENOUGH_01, 'balance': 0, } self.send_mqtt(data=data) def send_mqtt(self, data=None, cmd=DeviceCmdCode.OPERATE_DEV_SYNC, otherData=None): ''' 发送mqtt 指令默认210 返回data ''' result = MessageSender.send(self.device, cmd, data) if 'rst' in result and result['rst'] != 0: if result['rst'] == -1: raise ServiceException( {'result': 2, 'description': u'该设备正在玩命找网络,请您稍候再试', 'rst': -1}) elif result['rst'] == 1: raise ServiceException( {'result': 2, 'description': u'该设备忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能', 'rst': 1}) else: if cmd in [DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, DeviceCmdCode.PASSTHROUGH_OPERATE_DEV_NO_RESPONSE]: return return result.get('data', 'ok') def get_account_rule(self, coins): device_configs = self.deviceAdapter.get_device_configs() cardMin = device_configs.get('cardMin', 240) cardElec = device_configs.get('cardElec', 10) # 秒 timeMax = int(coins * 60 * cardMin) # 度 > 瓦 elecMax = int(coins * 0.1 * 3600000 * cardElec) powerMax1 = device_configs.get('powerMax1', 200) power1Ti = device_configs.get('power1Ti', 100) powerMax2 = device_configs.get('powerMax2', 300) power2Ti = device_configs.get('power2Ti', 75) powerMax3 = device_configs.get('powerMax3', 400) power3Ti = device_configs.get('power3Ti', 50) powerMax4 = device_configs.get('powerMax4', 500) power4Ti = device_configs.get('power4Ti', 25) return { 'timeMax': timeMax, 'elecMax': elecMax, 'accountRule': { 'powerStep': [ {'max': powerMax1, 'ratio': power1Ti}, {'max': powerMax2, 'ratio': power2Ti}, {'max': powerMax3, 'ratio': power3Ti}, {'max': powerMax4, 'ratio': power4Ti}, ] } } class JNDZEventerFailure(FaultEvent): def do(self, **args): cmdCode = self.event_data.get('cmdCode') faultType = self.event_data.get(u'fault') desc = self.event_data.get('desc', '') # 保证原有的故障处理逻辑不变 if cmdCode == CMD_CODE.DEVICE_SUBMIT_DEVICE_FAULT_0A: if self.event_data.get('FaultCode') == 1: # 不准确的继电器粘连上报 pass else: super(JNDZEventerFailure, self).do() group = Group.get_group(self.device.groupId) titleList = [ {u'告警名称': faultType}, {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 = faultType, notifyTime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), fault = '' ) # 记录错误故障 self.record( faultCode = cmdCode, description = desc, title = faultType, detail ={'faultType':faultType} ) class ChargingJNDZWorkEvent(WorkEvent): 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: if self.device.support_reliable: return self.do_id_response_support_reliable(**args) 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: self.response_use_card(SWIPE_CARD_RES.INVALID_CARD_02, 0) 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: logger.debug('virtual card is not do device refund.') return dev = Device.objects.get(devNo = self.device['devNo']) dealer = self.device.owner if not dealer: logger.error('dealer is not found, dealerId=%s' % self.device.ownerId) return agent = Agent.objects(id = dealer.agentId).first() if not agent: logger.error('agent is not found, agentId=%s' % dealer.agentId) return res = SWIPE_CARD_RES.SUCCESS_00 device_refund = False billingType = dev.otherConf.get('billingType', 'time') if is_server_refund(billingType, self.device, dealer, agent): logger.debug('{} in {} has card refund by server refund.'.format(repr(card), repr(self.device))) return logger.debug('{} card refund by {}.'.format(repr(card), repr(self.device))) 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'): logger.debug('port cache has no coins. no order in port {}'.format(lineInfo.get('port'))) return if 'duration' in self.event_data 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 = VirtualCoin(lineInfo['coins']) backCoins = VirtualCoin(0.0) price = RMB(lineInfo.get('price', 0.0)) refundRMB = RMB('0.0') leftTime = self.event_data['leftTime'] billingType = lineInfo.get('billingType', 'time') consumeType = lineInfo.get('consumeType', '') refundProtectionTime = lineInfo.get('refundProtectionTime', 5) isTempPackage = lineInfo.get('isTempPackage', False) needTime = 0 leftTimeStr = '' try: #: 刷卡或者万一缓存重启了,出现needElec为空的,作为1000度电吧,就会一定使用设备上报的电量 if lineInfo.get('elec', 0.0) < lineInfo.get('needElec', 0.0): spendElec = round(lineInfo['needElec'] - lineInfo['elec'], 2) backCoins_by_elec = round(self.event_data.get('elec',0) / lineInfo['needElec'] * float(money), 2) else: spendElec = 0.0 backCoins_by_elec = money if leftTime == 65535: actualNeedTime = 0 backCoins = money refundRMB = 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)) else: # TODO 整改退费 哪个时间电量取小的 needTime = lineInfo['needTime'] backCoins = money * (float(leftTime) / 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 = VirtualCoin(money.amount - money.amount * ratio) backCoins = VirtualCoin(min(float(backCoins), float(backCoins_by_elec))) isRefundProtection = False if usedTime < refundProtectionTime: backCoins = money isRefundProtection = True if backCoins > money: backCoins = money refundRMB = price * (float(backCoins) / float(money)) logger.debug( 'refund money is: {}; refund rmb is: {}'.format(str(backCoins.amount), str(refundRMB.amount))) #: 扫码的方式 if cardId == '' and vCardId is None and consumeType != 'coin': logger.info('finished with netPay') #: 这里需要考虑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'] user = MyUser.objects(openId = openId, groupId = self.device['groupId']).first() leftTimeStr = leftTime if leftTime != 65535 else u'空载' # todo 生成模板所需要的所有参数, 后续有客户自定义的话, 直接在这个字典里面添加, 不需要删掉之前的. # todo 这个变量仅仅用于下面generate_service_complete_title_by_devType()函数的参. 做成配置项更好, 下次不用改代码. templateMap = { 'reason': self.event_data['reason'], 'logicalCode': self.device['logicalCode'], 'port': self.event_data['port'], 'address': group['address'], 'actualNeedTime': actualNeedTime, 'leftTimeStr': leftTimeStr } # 先去获取devType上面的模板, 如果没有就走正常的流程 title = self.generate_service_complete_title_by_devType(self.device['devType']['id'], templateMap) if title == '': billingType = lineInfo.get('billingType', 'time') if billingType == 'time': 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) 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)}) 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 if self.event_data['elec'] == 0 or self.event_data['leftTime'] == 0: need_refund = False else: pass if not need_refund: 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 isTempPackage: self.refund_net_pay(user, lineInfo, refundRMB, VirtualCoin(0), 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': lineInfo['port'], 'isFinished': False }, consumeDict) if DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS in consumeDict: self.notify_user(user.managerialOpenId if user else '', 'refund_coins', **{ 'title': u'充电已经完成', 'backCount': u'%s(金币)' % str(consumeDict[DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS]), 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') }) elif DEALER_CONSUMPTION_AGG_KIND.REFUNDED_CASH in consumeDict: self.notify_user(user.managerialOpenId if user else '', 'refund_coins', **{ 'title': u'充电已经完成', 'backCount': u'%s(元)' % str(consumeDict[DEALER_CONSUMPTION_AGG_KIND.REFUNDED_CASH]), 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') }) elif vCardId is not None: # 使用的是虚拟卡 logger.info('finished with vCard!') 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 != '': # 刷的实体卡 logger.info('finished with card') card = Card.objects.get(id = cardId) virtual_card = card.bound_virtual_card # type: UserVirtualCard if billingType == 'time': logger.info('billingType is 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'谢谢您的支持') consumeDict = { 'chargeIndex': lineInfo['port'], 'leftTime': leftTimeStr, 'needTime': u'刷卡订购%s分钟' % needTime if virtual_card is None else u'绑定虚拟卡订购%s分钟' % needTime, 'reason': lineInfo['reason'], 'duration': usedTime, 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime, 'elec': spendElec, 'elecFee': self.calc_elec_fee(spendElec) } if virtual_card: self.refund_virtual_card(backCoins, cardId, consumeDict, lineInfo, spendElec, usedTime, virtual_card) elif 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}) ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], { 'cardId': cardId, 'device_imei': self.device['devNo'], 'port': lineInfo['port'], 'isFinished': False }, consumeDict) self.refund_money_for_card(backCoins, str(card.id)) desc = u'您使用的%s号端口充电,共付款:%s元,充电预定时间为:%s分钟,使用:%s分钟,给您退款:%s元' % ( lineInfo['port'], money, actualNeedTime, 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: ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], { 'cardId': cardId, 'device_imei': self.device['devNo'], 'port': lineInfo['port'], 'isFinished': False }, consumeDict) else: logger.info('billingType is elec') 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, 'elecFee': self.calc_elec_fee(spendElec), 'duration': usedTime, 'needElec': lineInfo['needElec'] } if virtual_card: self.refund_virtual_card(backCoins, cardId, consumeDict, lineInfo, spendElec, usedTime, virtual_card) elif 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)) self.refund_money_for_card(backCoins, str(card.id)) consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.REFUND_CARD: 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: logger.info('device not open refund switch') ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], {'cardId': cardId, 'device_imei': self.device['devNo'], 'port': lineInfo['port'], 'isFinished': False}, consumeDict ) 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分钟' % 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 = {'duration': usedTime} 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': str(refundRMB.mongo_amount), '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'])) 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.curDevInfo['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) elif cmdCode == '2D': self._do_charge_consume() elif cmdCode == '2C': self._do_coin_finished() else: raise NoCommandHandlerAvailable( '[JNDZ]] no command handler for cmd %s, curDevInfo=%s' % (cmdCode, self.event_data)) def _do_charge_consume(self): if self.event_data.get("result", '00') == '00': logger.info("receive failure card charge data <{}>".format(self.event_data.get("sourceData", ''))) return # 回复主板是正常的2D指令 sessionId = self.event_data.get("sessionId") self.deviceAdapter._response_to_2D("{:0>10}".format(sessionId)) # 判断是否这个 sessionId 是否已经被执行 判断方式是该端口中是否有这个 sessionId portStr = self.event_data.get("portStr") if not portStr: logger.info("receive card charge data <{}> without port !".format(self.event_data.get("sourceData", ''))) return portCache = Device.get_dev_control_cache(self.device.devNo).get(portStr, dict()) if portCache.get("status", Const.DEV_WORK_STATUS_WORKING) == Const.DEV_WORK_STATUS_WORKING and portCache.get( "sessionId") == sessionId: logger.info( "receive card charge data <{}> has been handle, <{}>!".format(self.event_data.get("sourceData", ''), sessionId)) return # 接下来判断上传成功启动的类别 由硬币/卡类型决定 chargeType = self.event_data.get("chargeType") if chargeType == "00": self._charge_with_coin() elif chargeType == "01": self._charge_with_card() elif chargeType == "02": self._charge_with_remote() else: logger.info("card recharge type <{}> undefined! <{}>".format(chargeType, self.event_data.get("sourceData"))) def refund_virtual_card(self, backCoins, cardId, consumeDict, lineInfo, spendElec, usedTime, virtual_card): logger.debug('server refund for virtual card<{}> in device<{}>'.format( str(virtual_card.id), self.device.devNo)) ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], { 'cardId': cardId, 'device_imei': self.device['devNo'], 'port': lineInfo['port'], 'isFinished': False }, consumeDict) consumeRcdId = lineInfo.get('consumeRcdId', None) # 不是虚拟卡启动的直接结束掉 if consumeRcdId: # 尝试进行虚拟卡退费 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: logger.info('can not find consume rcd id') def response_use_card(self, res, leftBalance): # 启动失败需要把金币还回去 try: self.deviceAdapter.response_use_card(res, leftBalance) except ServiceException as e: logger.exception(e) def do_id_response_support_reliable(self, **args): # TODO 预扣请求和余额回收,此处进行验证,通过则下发可以开启设备 cardNo = self.event_data['cardNo'] cardHex = self.event_data['sourceData'][8:16] fee = self.event_data['preFee'] oper = self.event_data['oper'] logger.info('Check card can be used cardNo={} ,cardHex={} fee={} oper={}'.format(cardNo, cardHex, fee, oper)) # 进来的时候检查过卡是否存在这里校验卡的合法型 card = self.update_card_dealer_and_type(cardNo) if not card or not card.openId or card.frozen: self.deviceAdapter.response_card(2, 0, fee, oper, cardHex) return # 2.检查卡片充值订单是否存在 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, fee, self.event_data)) virtual_card = self._get_virtual_card_by_card(card) # 3.检查当前操作为是否为扣费还是余额回收(00:扣费,01:充值) if oper == SWIPE_CARD_PARAM_OP.DECR_00: # 先检查虚拟卡是否能使用 leftCount = self._check_virtual_card_can_use_today(virtual_card, fee) if leftCount: return self.deviceAdapter.response_card(0, leftCount, fee, oper, cardHex, isVir=True , virtual_card_id=str(virtual_card.id)) # 校验实体卡能否使用 elif self._check_card_balance_can_use_today(card, fee): return self.deviceAdapter.response_card(0, card.balance, fee, oper, cardHex) else: return self.deviceAdapter.response_card(1, card.balance, fee, oper, cardHex) elif oper == SWIPE_CARD_PARAM_OP.INCR_01: if virtual_card: logger.debug('virtual card is not do device refund...') return dev = Device.objects.get(devNo=self.device['devNo']) dealer = self.device.owner if not dealer: logger.error('dealer is not found, dealerId=%s' % self.device.ownerId) return agent = Agent.objects(id=dealer.agentId).first() if not agent: logger.error('agent is not found, agentId=%s' % dealer.agentId) return res = SWIPE_CARD_RES.SUCCESS_00 billingType = dev.otherConf.get('billingType', 'time') if is_server_refund(billingType, self.device, dealer, agent): logger.debug('{} in {} has card refund by server refund.'.format(repr(card), repr(self.device))) return logger.debug('{} card refund by {}.'.format(repr(card), repr(self.device))) self.response_use_card(res, card.balance.mongo_amount + fee) self.refund_money_for_card(fee, str(card.id)) self.notify_user(card.managerialOpenId, 'refund_coins', **{ 'title': u'退币完成!!!您的卡号是%s,卡别名:%s' % (card.cardNo, card.nickName), 'backCount': u'金币:%s' % fee, 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') }) def _get_virtual_card_by_card(self, card): if not card.openId or float(card.balance) != 0: return try: dealer = self.device.owner agent = Agent.objects.get(id=dealer.agentId) features = agent.features except Exception as e: features = [] return card.bound_virtual_card if 'vCardNeedBind' in features else card.related_virtual_card def _check_virtual_card_can_use_today(self, virtual_card, fee): # 如果虚拟卡已经绑定,需要检查下今天是否可用,如果可用,有限使用虚拟卡 if virtual_card: unit = self.device['washConfig'].get('1', {}).get('unit','分钟') if unit == '分钟': cardMin = self.device['otherConf'].get('cardMin', 180) package = {'coins': float(fee), 'unit': unit, 'time': int(cardMin)} elif unit == '度': cardMin = self.device['otherConf'].get('cardMin', 180) package = {'coins': float(fee), 'unit': unit, 'time': int(cardMin)} else: return 0 if virtual_card.can_use_today(package): dayKey = datetime.datetime.now().strftime('%Y-%m-%d') leftDayQuota = virtual_card.calc_left_day_quota(dayKey) left_count = virtual_card.find_match_unit_and_can_use_count(leftDayQuota, package) return left_count def _check_card_balance_can_use_today(self, card, fee): if float(card.balance) >= fee: return True else: return False def _charge_with_card(self): """ 充值卡 的充值 :return: """ cardType = self.event_data.get("cardType") if cardType == CARD_TYPE.OFFLINE_CARD: self._charge_with_offline_card() elif cardType == CARD_TYPE.ONLINE_CARD: self._charge_with_online_card() elif cardType == CARD_TYPE.MONTHLY_CARD: self._charge_with_monthly_card() elif cardType == CARD_TYPE.FULL_CARD: self._charge_with_full_card() else: logger.info( "card recharge card type <{}> undefined! <{}>".format(cardType, self.event_data.get("sourceData"))) def _charge_with_coin(self): """ 投币的上报 :return: """ port = self.event_data.get('portStr') ctrInfo = Device.get_dev_control_cache(self.device.devNo) lineInfo = ctrInfo.get(port, {}) if lineInfo.get('status', 0) == Const.DEV_WORK_STATUS_WORKING and lineInfo.get('consumeType') == 'coin': #续充 lineInfo['coins'] += self.event_data.get('coins', 0) lineInfo['needTime'] += self.event_data.get('time', 0) lineInfo['needElec'] += self.event_data.get('elec', 0) if 'sub_orderNo' in lineInfo: lineInfo['sub_orderNo'].append( self.record_consume_for_coin(RMB(self.event_data['coins']))) else: lineInfo['sub_orderNo'] = [self.record_consume_for_coin(RMB(self.event_data['coins'])),] else: lineInfo = { 'startTime': datetime.datetime.now().strftime(Const.DATETIME_FMT), 'port': port, 'coins': self.event_data.get('coins', 0), 'needTime': self.event_data.get('time', 0), 'needElec': self.event_data.get('elec', 0), 'consumeOrderNo': self.record_consume_for_coin(RMB(self.event_data['coins'])), 'consumeType': 'coin', 'isStart': True, 'status': Const.DEV_WORK_STATUS_WORKING, 'sessionId': self.event_data.get('sessionId'), } # 记录该端口累计需要的时间和钱,cardId Device.update_port_control_cache(self.device['devNo'], lineInfo) Accounting.recordOfflineCoin(self.device, int(time.time()), int(self.event_data.get('coins', 0))) def _charge_with_remote(self): logger.info("not supper charge type! {}".format(self.event_data)) def _charge_with_online_card(self): """ 在线卡启动充值 :return: """ portStr = self.event_data.get("portStr") needElec = self.event_data.get("elec", 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 or card.frozen: 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 _charge_with_offline_card(self): """ 离线卡启动充值 :return: """ pass def _charge_with_monthly_card(self): """ 包月在线卡启动 :return: """ logger.info("not supper card charge type! {}".format(self.event_data)) def _charge_with_full_card(self): """ 充满自停卡 :return: """ logger.info("not supper card charge type! {}".format(self.event_data)) def _do_coin_finished(self): port = str(self.event_data.get('port')) ctrInfo = Device.get_dev_control_cache(self.device.devNo) lineInfo = ctrInfo.get(port, {}) now = datetime.datetime.now() if lineInfo and lineInfo.get('consumeType') == 'coin': # 只处理投币结束 if 'consumeOrderNo' in lineInfo: order = ConsumeRecord.objects.filter(orderNo=lineInfo['consumeOrderNo']).first() if 'duration' in self.event_data and self.event_data['duration'] > 0: usedTime = self.event_data['duration'] else: 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))) actualNeedTime = usedTime + self.event_data.get('leftTime', 0) consumeDict = { 'chargeIndex': port, 'reason': self.event_data.get('reasonDesc', ''), 'totalCount': lineInfo.get('coins', 0), 'needTime': lineInfo.get('needTime', 0), 'leftTime': self.event_data.get('leftTime', 0), 'leftElec': self.event_data.get('leftElec', 0), 'actualNeedTime': actualNeedTime, } order.update_service_info(consumeDict) ConsumeRecord.objects.filter(orderNo=lineInfo['consumeOrderNo']).update(status='finished', finishedTime=now) if 'sub_orderNo' in lineInfo: subConsumeDict = { 'desc': '关联主单号:{}'.format(order.orderNo) } ConsumeRecord.objects.filter(orderNo__in=lineInfo['sub_orderNo']).update(status='finished', finishedTime=now, servicedInfo=subConsumeDict) Device.clear_port_control_cache(self.device.devNo, port) class ChargingJNDZReportEvent(WorkEvent): def do(self, **args): # if not self.device.get('otherConf', {}).get('need_save_upload_data', None): # logger.info('No need to record!!!') # return portNum = self.event_data.get('portNum') portInfo = self.event_data.get('portInfo') ctrInfo = Device.get_dev_control_cache(self.device.devNo) now = datetime.datetime.now() for port, one_info in portInfo.items(): if one_info.get('portStatus') == '02': lineInfo = ctrInfo.get(port, {}) orderNo = lineInfo.get('orderNo') if not orderNo: continue try: order = ConsumeRecord.objects.filter(orderNo=orderNo).first() if hasattr(order, 'upload_data'): upload_data = order.upload_data else: upload_data = [] one_info['upload_time'] = now.strftime('%Y-%m-%d %H:%M:%S') if not upload_data: one_info['duration'] = round((now - order.startTime).total_seconds() / 60.0, 1) one_info['total_time'] = one_info['duration'] else: one_info['duration'] = round((now - to_datetime(upload_data[-1]['upload_time'])).total_seconds() / 60.0, 1) one_info['total_time'] = round((now - order.startTime).total_seconds() / 60.0, 1) upload_data.append(one_info) order.upload_data = upload_data order.save() except Exception as e: logger.error(e) pass class StartAckEventPreProcessor(AckEventProcessorIntf): def analysis_reason(self, reason): FINISHED_CHARGE_REASON_MAP = { '00': u'为了防止过度充电,您的充电已超过最大设定充电时长。', '01': u'充电满了或者用户手动停止(拔插头,或是按了停止按钮)', '02': u'充电满了,自动停止', '03': u'超功率自停', '04': u'远程断电', '05': u'刷卡断电', '0B': u'设备或是端口出现问题,被迫停止', # 服务器定义的停止事件 '90': u'用户手动点击结束按钮结束充电', '91': u'检测到设备未在充电工作状态,结束本次充电', -2: u'检测到设备未在充电工作状态,结束本次充电', } return FINISHED_CHARGE_REASON_MAP.get(reason) def pre_processing(self, device, event_data): # type:(DeviceDict, dict)->dict source = json.dumps(event_data) event_data['source'] = source if 'duration' in event_data and event_data['duration'] != 0: duration = event_data.get('duration') event_data['duration'] = ((duration + 59) / 60) elif 'fts' in event_data and 'sts' in event_data: duration = event_data['fts'] - event_data['sts'] event_data['duration'] = ((duration + 59) / 60) else: event_data['duration'] = 0 if 'elec' in event_data: event_data['elec'] = round(event_data.pop('elec') / 3600000.0, 3) else: event_data['elec'] = 0 # 初始化分档时间 if 'time' in event_data: event_data['time'] = event_data['time'] / 60.0 else: event_data['time'] = 0.0 if 'timeMax' in event_data: event_data['needTime'] = event_data.pop('timeMax') / 60.0 if 'elecMax' in event_data: event_data['needElec'] = round(event_data.pop('elecMax') / 3600000.0, 3) if 'port' in event_data: pass if 'sub' in event_data: subs = event_data.get('sub', []) for item in subs: if 'card_cst' in item: item['fee'] = RMB(item['card_cst']) * Ratio('0.1') # event_data['fee'] += item['fee'] if 'balance' in item: item['balance'] = RMB(item['balance']) * Ratio('0.1') if 'timeMax' in event_data: event_data['needTime'] = event_data.pop('timeMax') if 'elecMax' in event_data: event_data['needElec'] = event_data.pop('elecMax') if event_data['order_type'] != 'com_start': package = {'coins': float(event_data['fee']), 'unit': 'time', 'time': 999} event_data['package'] = package if event_data['status'] == 'finished': if 'reason' in event_data: # 正常结束 event_data['reasonDesc'] = self.analysis_reason(event_data['reason']) else: event_data['reasonDesc'] = self.analysis_reason('91') # 由轮询订单出来的结果 if 'time' in event_data and 'needTime' in event_data: if event_data['time'] >= event_data['needTime']: event_data['time'] = event_data['needTime'] event_data['reasonDesc'] = '购买的充电时间用完,功率超过额定值时间会相应缩短。' if 'elec' in event_data and 'needElec' in event_data: if event_data['elec'] >= event_data['needElec']: event_data['elec'] = event_data['needElec'] event_data['reasonDesc'] = '根据您的动态功率计算, 您订购的电量已用完, 当前充电服务已结束, 谢谢您的使用!!' if 'cardType' in event_data: pass if 'cardNo' in event_data: event_data['cardNoHex'] = str(event_data.pop('cardNo')) event_data['cardNo'] = str(int(event_data['cardNoHex'], 16)) event_data['cardType'] = 'ID' if 'balance' in event_data: if event_data['order_type'] == 'com_start': event_data['fee'] = RMB(event_data.pop('balance')) * Ratio('0.1') elif event_data['order_type'] == 'id_start': cst = float(device['otherConf'].get("deviceConfigs", {}).get('cst', 30.0)) * 0.1 event_data['fee'] = RMB(cst) event_data['fee'] = min(RMB(cst), RMB(event_data.pop('balance')) * Ratio('0.1')) else: event_data['fee'] = RMB(0) if event_data['needTime'] == 0: event_data['timeFee'] = RMB(0) else: event_data['timeFee'] = event_data['fee'] * round(event_data['time'] / event_data['needTime'], 2) if event_data['needElec'] == 0: event_data['elecFee'] = RMB(0) else: event_data['elecFee'] = event_data['fee'] * round(event_data['elec'] / event_data['needElec'], 2) # if event_data.get('reason') in ['00']: # event_data['timeFee'] = event_data['fee'] # event_data['elecFee'] = event_data['fee'] if event_data['order_type'] != 'com_start': package = {'coins': float(event_data['fee']), 'unit': 'time', 'time': 999} event_data['package'] = package return event_data class MyComNetPayAckEvent(ComNetPayAckEvent): def __init__(self, smartBox, event_data): super(MyComNetPayAckEvent, self).__init__(smartBox, event_data, StartAckEventPreProcessor()) def post_after_start(self, order = None): pass def post_after_finish(self, order = None): # TODO 订单结束处理后付费 self.pay_order(order) def merge_order(self, master_order, sub_orders): # type:(ConsumeRecord, list)->dict if self.is_postpaid: # TODO 显示费用调整 portDict start_time = Arrow.fromdatetime(master_order.startTime, tzinfo=settings.TIME_ZONE) portDict = {'billingType': 'time'} portDict['coins'] = 0 portDict['needKind'] = 'needTime' portDict['needValue'] = 999 portDict['unit'] = u'分钟' portDict['estimatedTs'] = int(start_time.timestamp + 720 * 60 * 60) return portDict else: needTime, needElec= self.deviceAdapter._check_package(master_order.package) start_time = Arrow.fromdatetime(master_order.startTime, tzinfo = settings.TIME_ZONE) portDict = { } if master_order.paymentInfo['via'] == 'virtualCard': portDict.update({ 'vCardId': master_order.virtual_card_id }) all_coins = master_order.package['coins'] all_price = master_order.package['price'] all_consume_time_value = needTime all_consume_elec_value = needElec for sub_order in sub_orders: all_coins += sub_order.package['coins'] all_price += sub_order.package['price'] sub_needTime, sub_needElec = self.deviceAdapter._check_package(sub_order.package) all_consume_time_value += sub_needTime all_consume_elec_value += sub_needElec portDict['coins'] = str(all_coins) portDict['price'] = str(all_price) portDict['all_consume_time_value'] = str(all_consume_time_value) portDict['all_consume_elec_value'] = str(all_consume_elec_value) portDict['needKind'] = 'needTime' portDict['needValue'] = 999 #显示充满自停 portDict['unit'] = u'分钟' portDict['estimatedTs'] = int(start_time.timestamp + 720 * 60) portDict['consumeType'] = 'mobile' return portDict def do_finished_event(self, master_order, sub_orders, merge_order_info): # type: (ConsumeRecord, [ConsumeRecord], dict)->None self._do_finished(master_order, sub_orders, merge_order_info) # billing_type = merge_order_info['billingType'] # if billing_type == 'time': # if self.is_postpaid: # self.do_postpaid_time_finished(master_order, sub_orders, merge_order_info) # else: # self.do_time_finished(master_order, sub_orders, merge_order_info) # else: # self.do_elec_finished(master_order, sub_orders, merge_order_info) @SmartBox.check_device_features(device_features=['Postpaid']) def pay_order(self, order): order.status = 'running' order.save() order.s_to_e() @property @SmartBox.check_device_features(device_features=['Postpaid']) def is_postpaid(self): return True def do_postpaid_time_finished(self, master_order, sub_orders=None, merge_order_info=None): user = MyUser.objects(openId=master_order.openId, groupId=master_order.groupId).first() # type: MyUser if not user: logger.error( 'user is not exist. openId = {}, groupId = {}'.format(master_order.openId, master_order.groupId)) return left = self.event_data.get('leftTime', 0) duration = self.event_data.get('duration', 0) elec = self.event_data.get('elec', 0) consumeDict = { 'reason': self.event_data.get('reasonDesc'), 'leftTime': str(left), 'chargeIndex': str(master_order.used_port), 'duration': duration, 'elec': elec, 'elecFee': self.deviceAdapter.calc_elec_fee(elec) } logger.debug( 'orderNo = {}, orderType = isPostpaid, usedTime = {}, needPayMoney = {}'.format(master_order.orderNo, duration, master_order.coin)) extra = [] desc = r'(已使用账户余额自动结算此次消费)' if master_order.is_finished() else r'(您的账户余额已不足以抵扣此次消费,请前往账单中心进行支付)' self.event_data['reasonDesc'] += desc extra.append({u'消费金额': '{}(金币)'.format(master_order.money)} if master_order.is_finished() else { u'消费金额': '{}(元)'.format(master_order.money)}) master_order.update_service_info(consumeDict) auth_bridge = get_wechat_auth_bridge(source=self.device, app_type=APP_TYPE.WECHAT_USER_MANAGER) self.notify_user_service_complete( service_name='充电', openid=user.get_bound_pay_openid(auth_bridge.bound_openid_key), port=str(master_order.used_port), address=master_order.address, reason=self.event_data.get('reasonDesc'), finished_time=master_order.finishedTime.strftime('%Y-%m-%d %H:%M:%S'), extra=extra) def insert_vCard_consume_record(self, vCard, order, success, consumeTotal,consumeDay): try: if success and consumeDay['count'] > 0: record = VCardConsumeRecord( orderNo=VCardConsumeRecord.make_no(order.logicalCode), openId=order.openId, nickname=order.nickname, cardId=str(vCard.id), dealerId=vCard.dealerId, devNo=order.devNo, devTypeCode = order.devTypeCode, devTypeName = order.dev_type_name, logicalCode=order.logicalCode, groupId=order.groupId, address=order.address, groupNumber=order.groupNumber, groupName=order.groupName, attachParas=order.attachParas, consumeData=consumeTotal, consumeDayData=consumeDay ) record.save() except Exception, e: logger.exception(e) def _do_finished(self, order, sub_orders, merge_order_info): # type: (ConsumeRecord, list, dict)->None duration, elec = self.event_data.get('duration',0), self.event_data.get('elec',0) coins = VirtualCoin(merge_order_info['coins']) timeFee = VirtualCoin(self.event_data.get('timeFee', 0.0)) elecFee = VirtualCoin(self.event_data.get('elecFee', 0.0)) usedFee = max(timeFee, elecFee) auto_refund = self.device.is_auto_refund refundProtectionTime = self.device.get('otherConf', {}).get('refundProtectionTime', 5) user = MyUser.objects(openId=order.openId, groupId=order.groupId).first() # type: MyUser backCoins = VirtualCoin(0) if duration < refundProtectionTime: backCoins = coins usedFee = VirtualCoin(0) else: if auto_refund: backCoins = coins - usedFee else: usedFee = coins logger.debug('{} auto refund enable switch is {}, refund protect time = {} backMoney={}'.format( repr(self.device), str(auto_refund), refundProtectionTime, backCoins)) extra = [] extra.append({u'本次使用时长':u'{}(分钟)'.format(duration)}) if order.paymentInfo['via'] == 'free': extra.append({u'消费金额': u'当前设备免费使用'}) elif order.paymentInfo['via'] in ['netPay', 'coins', 'cash', 'coin']: all_money = RMB(0) all_refund_money = RMB(0) orders = [order] + sub_orders refundCash = 'refundRMB_device_event' in self.device.owner.features for _order in orders[::-1]: consumeDict = { 'reason': self.event_data.get('reasonDesc', None), 'chargeIndex': str(order.used_port), 'source': self.event_data.get('source', None) } need_back_coins, need_consume_coins, backCoins = self._calc_refund_info(backCoins, _order.coin) rechargeRcdId = _order.attachParas.get('linkedRechargeRecordId', '') if rechargeRcdId != '': rechargeRcd = RechargeRecord.objects.filter(id=rechargeRcdId, isQuickPay=True).first() else: rechargeRcd = None if refundCash and rechargeRcd: # 退现金特征 + 有充值订单 # 退现金部分 user.clear_frozen_balance(str(_order.id), _order.paymentInfo['deduct'], back_coins=VirtualCoin(0), consume_coins=VirtualCoin(_order.coin)) refundRMB = rechargeRcd.money * (float(need_back_coins) / float(_order.coin)) self.refund_net_pay(user, {'rechargeRcdId': rechargeRcdId, 'openId': user.openId}, refundRMB, VirtualCoin(0), consumeDict, True) all_money += RMB(rechargeRcd.money) all_refund_money += RMB(refundRMB) else: user.clear_frozen_balance(str(_order.id), _order.paymentInfo['deduct'], back_coins=need_back_coins, consume_coins=need_consume_coins) consumeDict.update({ DEALER_CONSUMPTION_AGG_KIND.COIN: _order.coin.mongo_amount, DEALER_CONSUMPTION_AGG_KIND.SPEND_MONEY: need_consume_coins.mongo_amount, DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: need_back_coins.mongo_amount}) if _order.orderNo == order.orderNo: consumeDict.update({ DEALER_CONSUMPTION_AGG_KIND.DURATION: duration, DEALER_CONSUMPTION_AGG_KIND.ELEC: elec, DEALER_CONSUMPTION_AGG_KIND.ELECFEE: self.deviceAdapter.calc_elec_fee(elec), }) _order.update_service_info(consumeDict) if refundCash: extra.append({u'消费金额': '{}元'.format(all_money - all_refund_money)}) if all_refund_money > RMB(0): extra.append({u'退款金额': '{}元'.format(all_refund_money)}) else: extra.append({u'消费金额': '{}金币'.format(usedFee)}) if backCoins > VirtualCoin(0): extra.append({u'退款金额': '{}金币(当充电金币数量大于或等于扫码充电金额时可抵现金,金币不可提现)'.format(backCoins)}) else: logger.error('not net pay rather user virtual card pay. something is wrong.') return auth_bridge = get_wechat_auth_bridge(source=self.device, app_type=APP_TYPE.WECHAT_USER_MANAGER) self.notify_user_service_complete( service_name='充电', openid=user.get_bound_pay_openid(auth_bridge.bound_openid_key), port=str(order.used_port), address=order.address, reason=self.event_data.get('reasonDesc'), finished_time=order.finishedTime.strftime('%Y-%m-%d %H:%M:%S'), extra=extra) def _calc_refund_info(self, backCoins, orderCoin): if backCoins >= orderCoin: need_back_coins = orderCoin need_consume_coins = VirtualCoin(0) backCoins -= orderCoin else: need_back_coins = backCoins need_consume_coins = orderCoin - need_back_coins backCoins = VirtualCoin(0) return need_back_coins, need_consume_coins, backCoins class MyIcStartAckEvent(IcStartAckEvent): def __init__(self, smartBox, event_data): super(MyIcStartAckEvent, self).__init__(smartBox, event_data, StartAckEventPreProcessor()) def post_after_start(self, order = None): pass def post_after_finish(self, order = None): pass def merge_order(self, master_order, sub_orders): # type:(ConsumeRecord, list)->dict consumeModule = self.device.get('otherConf', dict()).get('consumeModule', 0) # 按时间计费 if consumeModule == 0: billingType = 'time' else: billingType = 'elec' start_time = Arrow.fromdatetime(master_order.startTime, tzinfo = settings.TIME_ZONE) portDict = { 'billingType': billingType } if master_order.paymentInfo['via'] == 'virtualCard': portDict.update({ 'vCardId': master_order.paymentInfo['itemId'] }) all_coins = master_order.coin all_money = master_order.money for sub_order in sub_orders: all_coins += sub_order.coin all_money += sub_order.money portDict['coins'] = str(all_coins) portDict['money'] = str(all_money) portDict['estimatedTs'] = int(start_time.timestamp + 12 * 60 * 60) return portDict def do_time_finished(self, card, order, merge_order_info): # type: (Card, ConsumeRecord, dict)->None duration, elec = self.event_data['duration'], self.event_data['elec'] coins = VirtualCoin(merge_order_info['coins']) left = self.event_data['left'] consumeDict = { 'reason': self.event_data['reason'], 'chargeIndex': str(order.used_port), 'duration': duration, 'elec': elec, 'elecFee': self.deviceAdapter.calc_elec_fee(elec) } # backMoney = VirtualCoin(self.event_data['backMoney']) group = self.device.group # type: GroupDict extra = [{ u'实体卡号': self.event_data['cardNo'] }] consumeDict.update({'balance': str(card.balance)}) order.update_service_info(consumeDict) self.notify_user_service_complete( service_name = '充电', openid = card.managerialOpenId if card else '', port = order.used_port, address = group.address, reason = self.event_data['reason'], finished_time = order.finishedTime.strftime('%Y-%m-%d %H:%M:%S'), extra = extra) def do_elec_finished(self, card, order, merge_order_info): # type:(Card, ConsumeRecord, dict)->None ''' 电川的板子 按电量退费 :return: ''' leftElec = self.event_data.get('left', 0) / 100.0 duration, elec = self.event_data['duration'], self.event_data['elec'] try: consumeDict = { 'reason': self.event_data['reasonDesc'], 'chargeIndex': order.used_port, 'duration': duration, 'elec': elec, 'elecFee': self.deviceAdapter.calc_elec_fee(elec) } # backMoney = self.event_data.get('backMoney') extra = [{u'实体卡号': self.event_data['cardNo']}] # if self.event_data['cardType'] == 'ID': # if backMoney > 0: # self.refund_money_for_card(RMB(backMoney), card.id) # extra.append({ # u'退款金额': u'{}(金币)'.format(backMoney) # }) # # consumeDict.update({'balance': str(card.balance + VirtualCoin(self.event_data['backMoney']))}) # else: consumeDict.update({'balance': str(card.balance)}) order.update_service_info(consumeDict) self.notify_user_service_complete( service_name = '充电', openid = self.get_managerialOpenId_by_openId(order.openId), port = order.used_port, address = self.device.group.address, reason = self.event_data['reason'], finished_time = order.finishedTime.strftime('%Y-%m-%d %H:%M:%S'), extra = extra) except Exception as e: logger.exception(e) def do_finished_event(self, card, order, merge_order_info): # type:(Card, ConsumeRecord, dict)->None if 'backMoney' in self.event_data and self.event_data['backMoney'] > RMB(0): refund_order = RechargeRecord.objects(orderNo = self.event_data['order_id']).first() if not refund_order: self.refund_money_for_card(RMB(self.event_data['backMoney']), card.id, self.event_data['order_id']) billing_type = merge_order_info['billingType'] if billing_type == 'time': self.do_time_finished(card, order, merge_order_info) else: self.do_elec_finished(card, order, merge_order_info) class MyIcRechargeAckEvent(IcRechargeAckEvent): def __init__(self, smartBox, event_data): super(MyIcRechargeAckEvent, self).__init__(smartBox, event_data, StartAckEventPreProcessor()) class MyCardRefundAckEvent(CardRefundAckEvent): def __init__(self, smartBox, event_data): super(MyCardRefundAckEvent, self).__init__(smartBox, event_data, CardRefundAckEventPreProcessor()) class CardRefundAckEventPreProcessor(AckEventProcessorIntf): def pre_processing(self, device, event_data): # type:(DeviceDict, dict)->dict if 'cardType' in event_data: if event_data['cardType'] == 'AA33': event_data['cardType'] = 'ID' else: event_data['cardType'] = 'IC' if 'cardNo' in event_data: event_data['cardNo'] = str(event_data['cardNo']) event_data['backMoney'] = (int(event_data['backMoney']) / 10.0) return event_data class MyIdStartAckEvent(IdStartAckEvent): def __init__(self, smartBox, event_data): super(MyIdStartAckEvent, self).__init__(smartBox, event_data, StartAckEventPreProcessor()) def post_after_start(self, order=None): pass def post_after_finish(self, order=None): pass def merge_order(self, master_order, sub_orders): # type:(ConsumeRecord, list)->dict billingType = self.device.get('otherConf', dict()).get('billingType', 'time') start_time = Arrow.fromdatetime(master_order.startTime, tzinfo = settings.TIME_ZONE) portDict = { 'billingType': billingType } all_coins = master_order.coin all_money = master_order.money all_consume_time = self.event_data.get('needTime', 0) all_consume_elec = self.event_data.get('needElec', 0) for sub_order in sub_orders: all_coins += sub_order.coin all_money += sub_order.money if self.event_data.get('sub', []): for _ in self.event_data['sub']: all_consume_time += _.get('needTime', 0) all_consume_elec += _.get('needElec', 0) portDict['coins'] = str(all_coins) portDict['money'] = str(all_money) portDict['all_consume_time_value'] = all_consume_time portDict['all_consume_elec_value'] = all_consume_elec # 直接显示充满自停 portDict['needValue'] = 999 portDict['needKind'] = 'needTime' portDict['unit'] = u'分钟' portDict['estimatedTs'] = int(start_time.timestamp + portDict['needValue'] * 60) return portDict def _do_finished(self, order, merge_order_info): # type: (ConsumeRecord, dict)->None duration, elec = self.event_data.get('duration', 0), self.event_data.get('elec', 0) coins = VirtualCoin(merge_order_info['coins']) timeFee = VirtualCoin(self.event_data.get('timeFee', 0.0)) elecFee = VirtualCoin(self.event_data.get('elecFee', 0.0)) usedFee = max(timeFee, elecFee) consumeDict = { 'reason': self.event_data.get('reasonDesc', None), 'chargeIndex': str(order.used_port), 'duration': duration, 'elec': elec, 'cardNo': self.event_data['cardNo'], 'elecFee': self.deviceAdapter.calc_elec_fee(elec), DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: coins.mongo_amount, 'source': self.event_data.get('source', None) } refundProtectionTime = int(self.device.get('otherConf', {}).get('refundProtectionTime', 5)) logger.info('[HDV3]{} refund protect time = {} duration={} coins={} usedFee={}'.format( repr(self.device), refundProtectionTime, duration, coins, usedFee)) if duration <= refundProtectionTime: backMoney = coins usedFee = VirtualCoin(0) else: backMoney = coins - usedFee logger.info('{} refund protect time = {} backMoney={}'.format( repr(self.device), refundProtectionTime, backMoney)) self.card.clear_frozen_balance(str(order.id), backMoney) self.card.reload() extra = [] extra.append({u'实体卡': '{}--No:{}'.format(self.card.cardName, self.card.cardNo)}) extra.append({u'本次使用时长': '{}(分钟)'.format(duration)}) extra.append({u'本次消费金额': '{}(金币)'.format(usedFee)}) extra.append({u'卡片当前余额': '{}(金币)'.format(self.card.balance.mongo_amount)}) if backMoney > VirtualCoin(0): self.record_refund_money_for_card(backMoney, str(self.card.id), orderNo=order.orderNo) consumeDict.update({ DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: usedFee.mongo_amount, DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: backMoney.mongo_amount }) order.update_service_info(consumeDict) self.notify_user_service_complete( service_name='充电', openid=self.card.managerialOpenId, port=str(order.used_port), address=order.address, reason=self.event_data.get('reasonDesc'), finished_time=order.finishedTime.strftime('%Y-%m-%d %H:%M:%S'), extra=extra) def do_finished_event(self, order, merge_order_info): # type:(ConsumeRecord, dict)->None self._do_finished(order, merge_order_info) def checkout_order(self, order): fee = VirtualCoin(order.coin) self.card.freeze_balance(transaction_id=str(order.id), fee=fee) class MyVirtualCardStartAckEvent(VirtualCardStartAckEvent): def __init__(self, smartBox, event_data): super(MyVirtualCardStartAckEvent, self).__init__(smartBox, event_data, StartAckEventPreProcessor()) def post_after_start(self, order = None, card = None): pass def post_after_finish(self, order = None): pass def checkout_order(self, order): group = Group.get_group(self.device.groupId) # type:GroupDict freeze_user_balance(self.device, group, order, self.virtualCard) def merge_order(self, master_order, sub_orders): # type:(ConsumeRecord, list)->dict billingType = self.device.get('otherConf', dict()).get('billingType', 'time') start_time = Arrow.fromdatetime(master_order.startTime, tzinfo = settings.TIME_ZONE) portDict = { 'billingType': billingType } all_coins = master_order.coin all_money = master_order.money all_consume_time = self.event_data.get('needTime', 0) all_consume_elec = self.event_data.get('needElec', 0) if self.event_data.get('sub', []): for _ in self.event_data['sub']: all_consume_time += _.get('needTime', 0) all_consume_elec += _.get('needElec', 0) portDict['coins'] = str(all_coins) portDict['money'] = str(all_money) if billingType == 'time': portDict['needKind'] = 'needTime' portDict['needValue'] = all_consume_time portDict['unit'] = u'分钟' portDict['estimatedTs'] = int(start_time.timestamp + all_consume_time * 60) elif billingType == 'elec': portDict['needKind'] = 'needElec' portDict['needValue'] = round(all_consume_elec / 100.0, 2) portDict['unit'] = u'度' portDict['estimatedTs'] = int(start_time.timestamp + 12 * 60 * 60) else: pass return portDict def do_time_finished(self, card, order, sub_orders, merge_order_info): # type: (Card, ConsumeRecord, list, dict)->None duration, elec = self.event_data['duration'], self.event_data['elec'] consumeDict = { 'reason': self.event_data['reasonDesc'], 'chargeIndex': str(order.used_port), 'duration': duration, 'elec': elec, 'cardNo': self.event_data['cardNo'], 'elecFee': self.deviceAdapter.calc_elec_fee(elec) } auto_refund = self.device.is_auto_refund refundProtection = self.device.get('otherConf', {}).get('refundProtection', 0) refundProtectionTime = self.device.get('otherConf', {}).get('refundProtectionTime', 5) logger.debug('{} auto refund enable switch is {}. refund protect = {}, refund protect time = {}'.format( repr(self.device), str(auto_refund), refundProtection, refundProtectionTime)) if auto_refund: order_need_time = order.package.get('time') if order_need_time >= duration: usedTime = duration duration = 0 else: usedTime = order_need_time duration -= order_need_time success, consumeTotal, consumeDay = self.virtualCard.clear_frozen_quota(transaction_id=str(order.id), usedTime=usedTime, spendElec=0, backCoins=VirtualCoin(0).mongo_amount) self.insert_vCard_consume_record(self.virtualCard, order, success, consumeTotal, consumeDay) for sub_order in sub_orders: order_need_time = sub_order.package.get('time') if order_need_time >= duration: usedTime = duration duration = 0 else: usedTime = order_need_time duration -= order_need_time success, consumeTotal, consumeDay = self.virtualCard.clear_frozen_quota(transaction_id=str(sub_order.id), usedTime=usedTime, spendElec=0, backCoins=VirtualCoin(0).mongo_amount) self.insert_vCard_consume_record(self.virtualCard, sub_order, success, consumeTotal, consumeDay) else: order_need_time = order.package.get('time') success, consumeTotal, consumeDay = self.virtualCard.clear_frozen_quota(transaction_id=str(order.id), usedTime=order_need_time, spendElec=0, backCoins=VirtualCoin( 0).mongo_amount) self.insert_vCard_consume_record(self.virtualCard, order, success, consumeTotal, consumeDay) for sub_order in sub_orders: order_need_time = sub_order.package.get('time') success, consumeTotal, consumeDay = self.virtualCard.clear_frozen_quota( transaction_id=str(sub_order.id), usedTime=order_need_time, spendElec=0, backCoins=VirtualCoin(0).mongo_amount) self.insert_vCard_consume_record(self.virtualCard, sub_order, success, consumeTotal, consumeDay) group = self.device.group # type: GroupDict extra = [{ u'虚拟卡号': self.virtualCard.cardNo }] order.update_service_info(consumeDict) self.notify_user_service_complete( service_name = '充电', openid = card.managerialOpenId if card else '', port = order.used_port, address = group.address, reason = self.event_data['reasonDesc'], finished_time = order.finishedTime.strftime('%Y-%m-%d %H:%M:%S'), extra = extra) def do_elec_finished(self, card, order, sub_orders, merge_order_info): # type:(Card, ConsumeRecord, list, dict)->None duration, elec = self.event_data['duration'], self.event_data['elec'] consumeDict = { 'reason': self.event_data['reasonDesc'], 'chargeIndex': str(order.used_port), 'duration': duration, 'elec': elec, 'cardNo': self.event_data['cardNo'], 'elecFee': self.deviceAdapter.calc_elec_fee(elec), } auto_refund = self.device.is_auto_refund refundProtection = self.device.get('otherConf', {}).get('refundProtection', 0) refundProtectionTime = self.device.get('otherConf', {}).get('refundProtectionTime', 5) logger.debug('{} auto refund enable switch is {}. refund protect = {}, refund protect time = {}'.format( repr(self.device), str(auto_refund), refundProtection, refundProtectionTime)) if auto_refund: order_need_time = order.package.get('time') if order_need_time >= elec: usedTime = duration duration = 0 else: usedTime = order_need_time elec -= order_need_time success, consumeTotal, consumeDay = self.virtualCard.clear_frozen_quota(transaction_id=str(order.id), usedTime=usedTime, spendElec=0, backCoins=VirtualCoin( 0).mongo_amount) self.insert_vCard_consume_record(self.virtualCard, order, success, consumeTotal, consumeDay) for sub_order in sub_orders: order_need_time = order.package.get('time') if order_need_time >= duration: usedTime = duration duration = 0 else: usedTime = order_need_time duration -= order_need_time success, consumeTotal, consumeDay = self.virtualCard.clear_frozen_quota( transaction_id=str(sub_order.id), usedTime=usedTime, spendElec=0, backCoins=VirtualCoin(0).mongo_amount) self.insert_vCard_consume_record(self.virtualCard, sub_order, success, consumeTotal, consumeDay) else: order_need_time = order.package.get('time') success, consumeTotal, consumeDay = self.virtualCard.clear_frozen_quota( transaction_id=str(order.id), usedTime=order_need_time, spendElec=0, backCoins=VirtualCoin( 0).mongo_amount) self.insert_vCard_consume_record(self.virtualCard, order, success, consumeTotal, consumeDay) for sub_order in sub_orders: order_need_time = sub_order.package.get('time') success, consumeTotal, consumeDay = self.virtualCard.clear_frozen_quota( transaction_id=str(sub_order.id), usedTime=order_need_time, spendElec=0, backCoins=VirtualCoin(0).mongo_amount) self.insert_vCard_consume_record(self.virtualCard, sub_order, success, consumeTotal, consumeDay) extra = [{ u'虚拟卡号': self.virtualCard.cardNo }] order.update_service_info(consumeDict) self.notify_user_service_complete( service_name = '充电', openid = self.get_managerialOpenId_by_openId(order.openId), port = order.used_port, address = self.device.group.address, reason = self.event_data['reason'], finished_time = order.finishedTime.strftime('%Y-%m-%d %H:%M:%S'), extra = extra) def do_finished_event(self, card, order, sub_orders, merge_order_info): # type:(Card, ConsumeRecord, list, dict)->None billing_type = merge_order_info['billingType'] if billing_type == 'time': self.do_time_finished(card, order, sub_orders,merge_order_info) else: self.do_elec_finished(card, order, sub_orders,merge_order_info) def insert_vCard_consume_record(self, vCard, order, success, consumeTotal, consumeDay): try: if success and consumeDay['count'] > 0: record = VCardConsumeRecord( orderNo=VCardConsumeRecord.make_no(order.logicalCode), openId=order.openId, nickname=order.nickname, cardId=str(vCard.id), dealerId=vCard.dealerId, devNo=order.devNo, devTypeCode = order.devTypeCode, devTypeName = order.dev_type_name, logicalCode=order.logicalCode, groupId=order.groupId, address=order.address, groupNumber=order.groupNumber, groupName=order.groupName, attachParas=order.attachParas, consumeData=consumeTotal, consumeDayData=consumeDay ) record.save() except Exception, e: logger.exception(e)