# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import logging import time from typing import TYPE_CHECKING from apilib.monetary import RMB, VirtualCoin from apilib.utils_datetime import to_datetime from apps.web.constant import Const, DEALER_CONSUMPTION_AGG_KIND from apps.web.core.accounting import Accounting from apps.web.device.models import Device from apps.web.device.timescale import FluentedEngine from apps.web.eventer import EventBuilder from apps.web.eventer.base import FaultEvent, WorkEvent from apps.web.user.models import ServiceProgress, UserVirtualCard, VCardConsumeRecord, Card, MyUser, CardRechargeOrder, \ Redpack, RechargeRecord if TYPE_CHECKING: from apps.web.device.models import GroupDict 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 'v' in device_event: event_data.update({'v': device_event['v']}) if event_data['cmdCode'] in ['03', '04', '05', '25', '26', '28']: return ChargingYUNCHONGWorkEvent(self.deviceAdapter, event_data) if event_data['cmdCode'] == '0D': return FaultEvent(self.deviceAdapter, event_data) class ChargingYUNCHONGWorkEvent(WorkEvent): def __parse_device_finished_data(self, event_data): duration = event_data.get('duration', -1) if duration != -1: if 'v' in event_data: duration = (duration / 60) elec = event_data.get('elec', -1) if elec != -1: if 'v' in event_data: elec = round(elec / (10000.0 * 3600.0), 3) else: elec = round(elec / 3600.0, 3) logger.debug('device duration is {}, device elec is {}'.format(duration, elec)) return duration, elec @property def support_playback(self): if self.event_data['cmdCode'] in ['05']: return True else: return False def get_back_info(self, leftTime, actualNeedTime, lineInfo): coins = VirtualCoin(lineInfo['coins']) price = RMB(lineInfo['price']) refundedCoins = VirtualCoin(0) refundedMoney = RMB(0) if leftTime != 65535: refundedCoins = coins * (float(leftTime) / float(actualNeedTime)) refundedMoney = RMB(price * (float(leftTime) / float(actualNeedTime))) else: refundedCoins = coins refundedMoney = RMB(price) if refundedCoins > coins: refundedCoins = coins if refundedMoney > price: refundedMoney = price return refundedMoney, refundedCoins def do(self, **args): logger.info('[yunchong]charging event detected, devNo=%s,info=%s' % (self.device.devNo, self.event_data)) if self.event_data['cmdCode'] == '03': # 投币数据 if 'today_coins' in self.event_data and 'ts' in self.event_data: Accounting.syncOfflineCoin( self.device, datetime.datetime.fromtimestamp(self.event_data['ts']).strftime('%Y-%m-%d'), self.event_data['today_coins']) if self.event_data['coins'] > 0: FluentedEngine().in_put_coins_udp(devNo=self.device.devNo, ts=int(time.time()), coins=self.event_data['coins'], mode='uart') else: # 老的流程会单条记录上报记录 Accounting.recordOfflineCoin(device=self.device, report_ts=int(time.time()), coins=int(self.event_data['coins']), mode='uart', port=self.event_data.get('port', None)) elif self.event_data['cmdCode'] == '05': devNo = self.device.devNo port = str(self.event_data['port']) try: lineInfo = Device.clear_port_control_cache(devNo, port) if not lineInfo: logger.debug('get null control cache from {}'.format(repr(self.device))) return if 'openId' not in lineInfo: logger.debug('openId not in line info. ignore it.') return if 'redpackInfo' in lineInfo: for _info in lineInfo['redpackInfo']: redpack = Redpack.get_one(_info['redpackId']) redpackCoins = VirtualCoin(redpack['redpackCoins']) lineInfo['coins'] = float(VirtualCoin(lineInfo['coins']) - redpackCoins) redpackMoney = RMB(redpack['redpackMoney']) lineInfo['price'] = float(RMB(lineInfo['price']) - redpackMoney) logger.debug('redpack is <{}> redpack money is: {}; redpack coins is: {}'.format( redpack.get('id'), str(redpackMoney.amount), str(redpackCoins.amount))) lineInfo['coins'] = 0 if lineInfo['coins'] < 0 else lineInfo['coins'] lineInfo['price'] = 0 if lineInfo['price'] < 0 else lineInfo['price'] return self.do_time_finish(devNo, port, lineInfo) finally: pass elif self.event_data['cmdCode'] == '26': cardNo = self.event_data['cardNo'] card = self.update_card_dealer_and_type(cardNo, 'ID') if not card: return #: 首先检查订单,并进行充值 #: 用户首先在手机客户端充值,需要这个时刻刷卡上报事件将充值的订单同步上来 card_recharge_order = CardRechargeOrder.get_last_to_do_one(str(card.id)) if card_recharge_order: self.recharge_id_card(card=card, rechargeType='append', order=card_recharge_order) card.reload() try: # 直接回复余额和随机码 if not card.frozen: if card.balance <= VirtualCoin(0): self.deviceAdapter.response_card_balance(cardNo, 0) else: self.deviceAdapter.response_card_balance(cardNo, int(card.balance)) else: self.deviceAdapter.response_card_balance(cardNo, 0) # 挂失卡,直接回复余额为0 except Exception, e: pass elif self.event_data['cmdCode'] == '28': cardNo = self.event_data['cardNo'] card = self.update_card_dealer_and_type(cardNo, 'ID') if not card: return port = str(self.event_data['port']) Device.update_dev_control_cache(self.device.devNo, { port: { 'openId': card.openId, 'price': self.event_data['coins'], 'coins': self.event_data['coins'], 'cardId': str(card.id), 'startTime': int(time.time()), 'status': Const.DEV_WORK_STATUS_WORKING } }) balance = card.balance - RMB(self.event_data['coins']) # type: RMB consumeMoney = RMB(self.event_data['coins']) desc = u'正在刷卡使用,卡号:%s,卡名称:%s,端口号:%s,扣费:%s,余额:%s' % ( card.cardNo, card.cardName, self.event_data['port'], self.event_data['coins'], balance) orderNo, cardOrderNo = \ self.record_consume_for_card(card=card, money=consumeMoney, desc=desc, servicedInfo={ 'balance': balance.mongo_amount, 'coin': self.event_data['coins'], 'port': self.event_data['port']}) self.update_card_balance(card, balance) ServiceProgress.register_card_service(self.device, self.event_data['port'], card, { 'orderNo': orderNo, 'coin': str(self.event_data['coins']), 'cardOrderNo': cardOrderNo }) self.notify_balance_has_consume_for_card(card, consumeMoney, desc) def do_time_finish(self, devNo, port, lineInfo): logger.info("[{} do_time_finish] devNo = {}, port = {}, lineInfo = {}, event = {}".format( self.__class__.__name__, devNo, port, lineInfo, self.event_data)) try: if not self.dealer: logger.error( "[{} do_time_finish] dealer {} is not found!".format(self.__class__.__name__, self.device.ownerId)) return recvTime = to_datetime(self.recvTime) if 'startTime' in lineInfo: startTime = to_datetime(lineInfo['startTime']) if startTime > recvTime: logger.error('start time is bigger than now time,devNo={}'.format(devNo)) serverDuration = -1 else: serverDuration = int((recvTime - startTime).total_seconds() / 60.0) else: logger.info('lineinfo has not startTime,devNo=%s' % devNo) serverDuration = -1 leftTime = self.event_data['leftTime'] logger.debug('serverDuration = {}; leftTime = {}; lineInfo = {}'.format( serverDuration, leftTime, lineInfo)) if leftTime == 65535: # 设备返回65535说明端口没有启动 leftTimeStr = u'端口未使用' actualNeedTime = 0 usedTime = 0 leftTime = 65535 elif serverDuration < 0: # duration参数错误 logger.debug('serverDuration and deviceDuration is valid.') actualNeedTime = -1 usedTime = -1 leftTimeStr = '' else: usedTime = serverDuration leftTime = self.event_data['leftTime'] leftTimeStr = leftTime actualNeedTime = usedTime + leftTime if actualNeedTime == -1: logger.debug('has no actual need time. ignore this event.') return refundProtectionTime = self.device.my_obj.otherConf.get('refundProtectionTime', 5) if usedTime < refundProtectionTime: rechargeIds = list() payInfo = lineInfo.get('payInfo', list()) for item in payInfo: if 'rechargeRcdId' not in item: continue rechargeIds.append(item['rechargeRcdId']) refundedMoney, refundedCoins = self.get_back_info( leftTime=leftTime, actualNeedTime=actualNeedTime, lineInfo=lineInfo) logger.debug('refund switch = {}, protect time = {}, lefttime = {}, usedTime = {}, need time = {}, back coins = {}, back money = {}'.format( self.device.is_auto_refund, leftTime, usedTime, actualNeedTime, refundedCoins, refundedMoney, refundProtectionTime)) if usedTime < refundProtectionTime: refundedCoins = VirtualCoin(lineInfo['coins']) refundedMoney = RMB(lineInfo['price']) elif not self.device.is_auto_refund: refundedCoins = VirtualCoin(0) refundedMoney = RMB(0) group = self.device.group # type: GroupDict consumeDict = { 'reason': self.event_data['reason'], 'leftTime': leftTimeStr, 'chargeIndex': port, 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime, 'duration': usedTime, 'serverDuration': serverDuration, 'uartData': self.event_data.get('uartData', '') } if 'cardId' in lineInfo: cardId = lineInfo['cardId'] card = Card.objects.get(id=cardId) consumeDict = { 'chargeIndex': port, 'reason': self.event_data['reason'], 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime, 'duration': usedTime, 'leftTime': leftTimeStr } self.notify_user_service_complete( service_name=u'充电', openid=card.managerialOpenId if card else '', port=port, address=group['address'], reason=self.event_data['reason'], finished_time=recvTime.strftime('%Y-%m-%d %H:%M:%S'), extra=[{ u'实体卡号': card.cardNo }] ) coins = VirtualCoin(lineInfo['coins']) if refundedCoins > VirtualCoin(0): consumeDict.update({ DEALER_CONSUMPTION_AGG_KIND.REFUND_CARD: refundedCoins.mongo_amount, DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: (coins - refundedCoins).mongo_amount }) self.refund_money_for_card(refundedCoins, str(card.id)) else: consumeDict.update({ DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: coins.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 'consumeRcdId' in lineInfo and lineInfo['consumeRcdId']: vCardId = lineInfo['vCardId'] vCard = UserVirtualCard.objects(id=vCardId).first() # type: UserVirtualCard if not vCard: logger.info('can not find the vCard id = %s' % vCardId) return try: ServiceProgress.update_progress_and_consume_rcd(self.device.ownerId, {'open_id': lineInfo['openId'], 'port': int(port), 'device_imei': self.device.devNo, 'isFinished': False}, consumeDict) if refundedCoins > VirtualCoin(0): vCardConsumeRcd = VCardConsumeRecord.objects.get(id=lineInfo['consumeRcdId']) vCard.refund_quota(vCardConsumeRcd, usedTime, 0.0, refundedCoins.mongo_amount) finally: user = MyUser.objects(openId=lineInfo['openId'], groupId=self.device.groupId).first() self.notify_user_service_complete( service_name=u'充电', openid=user.managerialOpenId if user else '', port=port, address=group['address'], reason=self.event_data['reason'], finished_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), extra=[ {u'虚拟卡号': vCard.cardNo} ] ) else: user = MyUser.objects(openId=lineInfo['openId'], groupId=self.device.groupId).first() if not user: logger.warning( 'not find user'.format(lineInfo['openId'], self.device.groupId)) return coins = VirtualCoin(lineInfo['coins']) try: is_cash = False if 'refundRMB_device_event' in self.device.owner.features and refundedMoney > RMB(0): is_cash = True consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.COIN: VirtualCoin(coins).mongo_amount}) if refundedMoney > RMB(0) or refundedCoins > VirtualCoin(0): consumeDict.update( {DEALER_CONSUMPTION_AGG_KIND.COIN: (VirtualCoin(coins) - refundedCoins).mongo_amount}) self.refund_net_pay(user, lineInfo, refundedMoney, refundedCoins, consumeDict, is_cash) ServiceProgress.update_progress_and_consume_rcd(self.device.ownerId, {'open_id': lineInfo['openId'], 'port': int(port), 'device_imei': self.device.devNo, 'isFinished': False}, consumeDict) finally: extra = [] if DEALER_CONSUMPTION_AGG_KIND.REFUNDED_CASH in consumeDict: real_refund = RMB(consumeDict[DEALER_CONSUMPTION_AGG_KIND.REFUNDED_CASH]) if real_refund > RMB(0): extra.append({u'消费金额': '{}(元)'.format(RMB(lineInfo['price']) - real_refund)}) extra.append({u'退款金额': '{}(元)'.format(real_refund)}) else: extra.append({u'消费金额': '{}(元)'.format(lineInfo['price'])}) elif DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS in consumeDict: real_refund = VirtualCoin(consumeDict[DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS]) if real_refund > VirtualCoin(0): extra.append({u'消费金额': '{}(金币)'.format(VirtualCoin(coins) - real_refund)}) extra.append({u'退款金额': '{}(金币)'.format(real_refund)}) else: extra.append({u'消费金额': '{}(金币)'.format(VirtualCoin(coins))}) else: extra.append({u'消费金额': '{}(金币)'.format(VirtualCoin(coins))}) self.notify_user_service_complete( service_name=u'充电', openid=user.managerialOpenId if user else '', port=port, address=group['address'], reason=self.event_data['reason'], finished_time=recvTime.strftime('%Y-%m-%d %H:%M:%S'), extra=extra) except Exception as e: logger.exception(e)