123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512 |
- # -*- coding: utf-8 -*-
- # !/usr/bin/env python
- import logging
- import datetime
- import random
- 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.dealer.models import Dealer
- from apps.web.device.models import OfflineCoinStatistics, Group, Device
- from apps.web.south_intf.liangxi_fire import LiangXiXiaoFang
- from apps.web.south_intf.yuhuan_fire import YuhuanNorther
- from apps.web.common.models import District
- from apps.web.eventer.base import FaultEvent, WorkEvent
- from apps.web.eventer import EventBuilder
- from apps.web.south_intf.platform import notify_event_to_north
- from apps.web.south_intf.zhejiang_fire import send_event_to_zhejiang
- from apps.web.user.models import ServiceProgress, UserVirtualCard, VCardConsumeRecord, Card, MyUser, CardRechargeOrder, RechargeRecord
- from apps.web.user.transaction_deprecated import refund_money
- 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 or 'cmdCode' not in event_data:
- return None
- 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 'elec' in device_event:
- event_data.update({'elec': device_event['elec']})
- if event_data['cmdCode'] in ['03', '05', '11', '12', '30', '31', '32']:
- return ChargingZHIXIAWorkEvent(self.deviceAdapter, event_data)
- if event_data['cmdCode'] == '0D':
- return ZHIXIAFaultEvent(self.deviceAdapter, event_data)
- return None
- class ZHIXIAFaultEvent(FaultEvent):
- LX_FAULE_CODE_MAP = {
- "01": "07",
- "02": "03",
- "03": "05"
- }
- def do_norther(self):
- """
- 上报其他平台的,都在这个地方处理 可迁移至异步任务
- :return:
- """
- # 玉环的消防对接
- YuhuanNorther.send_dev_event(self.device, self.event_data['FaultCode'], self.event_data['port'])
- # 梁溪消防局的对接
- faultContent = self.event_data["statusInfo"]
- faultCode = self.LX_FAULE_CODE_MAP.get(self.event_data["FaultCode"], "")
- districtInfo = District.get_district(self.device.group["districtId"])
- self.device.update({"districtInfo": districtInfo, "groupAddr": self.device.group["address"]})
- LiangXiXiaoFang.send_to_xf_fault(self.device, faultCode, faultContent, self.event_data["port"])
- def do(self, **args):
- # 将告警的消息打入相应的缓存
- port = self.event_data["port"]
- # 0 表示整机
- if port == 0xFF:
- part = str(0)
- else:
- part = str(port)
- warningData = {
- "warningStatus": 1,
- "warningDesc": self.event_data["statusInfo"],
- "warningTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
- "warningUart": self.event_data["uart"]
- }
- Device.update_dev_warning_cache(self.device.devNo, {part: warningData})
- self.do_norther()
- super(ZHIXIAFaultEvent, self).do()
- class ChargingZHIXIAWorkEvent(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
- def do(self, **args):
- logger.info('zhixiakeji charging event detected, devNo=%s,info=%s' % (self.device.devNo, self.event_data))
- devNo = self.device.devNo
- if self.event_data['cmdCode'] == '03': # 电川的无法记录投币的端口数据
- Accounting.recordOfflineCoin(self.device,
- int(time.time()),
- int(self.event_data['coins']))
- if self.device.ownerId is not None and self.device.ownerId != '':
- dealer = Dealer.objects(id = self.device.ownerId).first()
- if dealer is not None and 'show_device_offline_coins' in dealer.features:
- OfflineCoinStatistics.recordCoinEvent(
- self.device['logicalCode'],
- self.device.devNo,
- int(self.event_data.get('coins', 1)),
- self.device['groupId']
- )
- else:
- logger.error('undefined dealer id=%s' % self.device.ownerId)
- # 如果是投币,直接把端口状态刷新下
- self.deviceAdapter.get_port_status_from_dev()
- elif self.event_data['cmdCode'] == '05':
- devNo = self.device.devNo
- port = str(self.event_data['port'])
- ctrInfo = Device.get_dev_control_cache(self.device.devNo)
- lineInfo = ctrInfo.get(port)
- if not lineInfo:
- logger.info('aaaaaaaaaaaaaaaaa,the ctrInfo is empty,devNo=%s' % devNo)
- try:
- return self.do_time_finish(devNo, port, lineInfo, self.event_data)
- finally:
- Device.clear_port_control_cache(devNo, str(port))
- notify_event_to_north(self.dealer, self.device, level = Const.EVENT_NORMAL,
- desc = self.event_data['reason'])
- send_event_to_zhejiang(self.dealer, self.device, self.event_data)
- if self.event_data.get('reasonCode', '') == '04': # 功率过载导致的断电,一条告警,一条恢复告警
- YuhuanNorther.send_dev_event(self.device, '97', port)
- YuhuanNorther.send_dev_event(self.device, '98', port)
- # 发送一条端口使用结束的告警
- YuhuanNorther.send_dev_status(self.device, port, 2) # 结束使用
- elif self.event_data['cmdCode'] == '11':
- cardNo = self.event_data['cardNo']
- card = self.update_card_dealer_and_type(cardNo, 'IC')
- if not card:
- return
- lineInfo = Device.update_port_control_cache(self.device.devNo, self.event_data)
- lineInfo['openId'] = card.openId
- lineInfo = Device.update_port_control_cache(self.device.devNo, lineInfo)
- balance = RMB(self.event_data['balance'])
- consumeMoney = RMB(self.event_data['coins'])
- if self.event_data['status'] == '01': # 扣款成功,表示马上需要使用卡了,需要登记
- 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': self.event_data['balance'],
- 'coin': self.event_data['coins'],
- 'port': self.event_data['port']})
- # 记录当前服务的progress.没有上报端口号,所以先用-1标记,表示端口未知。端口等消费的时候会报上来
- ServiceProgress.register_card_service(self.device, self.event_data['port'], card,
- {'orderNo': orderNo, 'coin': self.event_data['coins'],
- 'cardOrderNo': cardOrderNo})
- self.notify_balance_has_consume_for_card(card, consumeMoney, desc)
- # 更新下balance,便于刷卡记录中,更新到数据库中
- lineInfo['balance'] = str(balance)
- lineInfo['cardId'] = str(card.id)
- lineInfo['startTime'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
- lineInfo['isStart'] = True
- lineInfo['status'] = Const.DEV_WORK_STATUS_WORKING
- Device.update_port_control_cache(self.device.devNo, lineInfo)
- # 如果卡挂失掉了,立马把端口关闭掉
- if card.frozen:
- logger.debug('{} has been frozen.'.format(repr(card)))
- self.deviceAdapter.stop_charging_port(lineInfo['port'])
- elif self.event_data['status'] == '02': # 扣款失败,余额不足
- pass
- elif self.event_data['status'] == '03': # 返现
- balance = balance + consumeMoney
- self.update_card_balance(card, balance)
- elif self.event_data['cmdCode'] == '12': # 电川的板子充值是覆写方式,需要把上次的余额一起累加进来
- cardNo = self.event_data['cardNo']
- card = self.update_card_dealer_and_type(cardNo, 'IC') # type: Card
- if not card:
- return
- if card.frozen:
- logger.debug('{} has been frozen.'.format(repr(card)))
- return
- preBalance = RMB(self.event_data['balance'])
- card_recharge_order = CardRechargeOrder.get_last_to_do_one(str(card.id)) # type: CardRechargeOrder
- self.recharge_ic_card(card = card,
- preBalance = preBalance,
- rechargeType = 'overwrite',
- order = card_recharge_order)
- elif self.event_data['cmdCode'] == '20': # 启动设备后,需要把状态刷新下,便于实时的把状态查询出来
- lineInfo = Device.update_port_control_cache(self.device.devNo, self.event_data)
- lineInfo['startTime'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
- lineInfo['isStart'] = True
- lineInfo['status'] = Const.DEV_WORK_STATUS_WORKING
- Device.update_port_control_cache(self.device.devNo, lineInfo)
- # 发送事件给北向,表示已经开始工作
- YuhuanNorther.send_dev_status(self.device, self.event_data['port'], 1)
- elif self.event_data['cmdCode'] == '30':
- try:
- lastValue = Device.get_dev_control_cache(devNo)
- lastValue.update({'elecValue': self.event_data})
- Device.update_dev_control_cache(self.device["devNo"], lastValue)
- finally:
- notify_event_to_north(self.dealer, self.device, level = Const.EVENT_NORMAL, desc = u'电流主动上报事件',
- dataDict = self.event_data)
- elif self.event_data['cmdCode'] == '31':
- try:
- lastValue = Device.get_dev_control_cache(devNo)
- lastValue.update({'temperatureValue': self.event_data})
- Device.update_dev_control_cache(self.device["devNo"], lastValue)
- finally:
- notify_event_to_north(self.dealer, self.device, level = Const.EVENT_NORMAL, desc = u'温度主动上报事件',
- dataDict = self.event_data)
- elif self.event_data['cmdCode'] == '32':
- try:
- lastValue = Device.get_dev_control_cache(devNo)
- lastValue.update(self.event_data)
- Device.update_dev_control_cache(self.device["devNo"], lastValue)
- finally:
- if self.event_data['isYangan']:
- notify_event_to_north(self.dealer, self.device, level = Const.EVENT_NORMAL, desc = u'烟感事件',
- dataDict = self.event_data)
- elif self.event_data['cmdCode'] == '17': # 余额回收
- card = self.update_card_dealer_and_type(self.event_data['cardNo'], 'IC') # type: Card
- self.refund_money_for_card(RMB(self.event_data['backMoney']), card.id)
- def do_time_finish(self, devNo, port, lineInfo, event_data):
- try:
- refundProtectionTime = self.device.get_other_conf_item('refundProtectionTime', 5)
- deviceDuration, deviceElec = self.__parse_device_finished_data(event_data)
- if lineInfo is not None and 'startTime' in lineInfo:
- startTime = to_datetime(lineInfo['startTime'])
- nowTime = datetime.datetime.now()
- if startTime > nowTime:
- logger.error('start time is bigger than now time,devNo={}'.format(devNo))
- serverDuration = -1
- else:
- serverDuration = int(round((((nowTime - startTime).total_seconds() + 59) / 60.0)))
- else:
- logger.info('lineinfo has not startTime,devNo=%s' % devNo)
- serverDuration = -1
- leftTime = self.event_data['leftTime']
- logger.debug('serverDuration = {}; deviceDuration = {}; leftTime = {}; lineInfo = {}'.format(
- serverDuration, deviceDuration, leftTime, lineInfo))
- if leftTime == 65535: # 设备返回65535说明端口没有启动
- leftTimeStr = u'端口未使用'
- actualNeedTime = 0
- usedTime = 0
- leftTime = 65535
- elif serverDuration < 0 and deviceDuration < 0: # duration参数错误
- logger.debug('serverDuration and deviceDuration is valid.')
- actualNeedTime = -1
- else:
- usedTime = max(serverDuration, deviceDuration)
- leftTime = self.event_data['leftTime']
- leftTimeStr = leftTime
- actualNeedTime = usedTime + leftTime
- # 通过使用的时间来判断是否可以进行保险的退款
- if actualNeedTime != -1 and usedTime <= 5:
- rechargeIds = list()
- payInfo = lineInfo.get('payInfo', list())
- for item in payInfo:
- if 'rechargeRcdId' not in item:
- continue
- rechargeIds.append(item['rechargeRcdId'])
- group = self.device.group # type: GroupDict
- if actualNeedTime < 0:
- if 'cardNo' in self.event_data and self.event_data['cardNo']:
- card = Card.objects(cardNo = str(self.event_data['cardNo']), agentId = self.dealer.agentId).first()
- if card:
- 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 = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
- extra = [{
- u'实体卡号': self.event_data['cardNo']
- }]
- )
- logger.debug('has no actual need time. ignore this event.')
- return
- consumeDict = {
- 'reason': self.event_data['reason'],
- 'leftTime': leftTimeStr,
- 'chargeIndex': port,
- 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime,
- 'duration': usedTime,
- 'deviceDuration': deviceDuration,
- 'serverDuration': serverDuration,
- 'uartData': self.event_data.get('uartData', '')
- }
- try:
- groupObj = Group.objects(id=self.device.groupId).first()
- if groupObj.otherConf.get('zhuxing', None) is not None:
- spendElec = round((float(random.randint(15, 19)) / 100) * (float(usedTime) / 60), 3)
- consumeDict.update({'elec': spendElec})
- consumeDict.update({'elecFee': self.deviceAdapter.calc_elec_fee(spendElec)})
- else:
- if deviceElec != -1:
- consumeDict.update({'elec': deviceElec})
- if deviceElec == 0 and usedTime > 0:
- consumeDict.update({'elec': round((float(random.randint(15, 19)) / 100) * (float(usedTime) / 60), 3)})
- else:
- consumeDict.update({'elec': 0.0})
- consumeDict.update({'elecFee': RMB(0.0).mongo_amount})
- except Exception as e:
- logger.error("device <{}> elec add error! <{}>".format(self.device.devNo, event_data))
- if 'cardNo' in lineInfo and lineInfo['cardNo']:
- # 如果是刷卡的,直接更新消费记录,发通知消息,支持ID卡的退费。
- # IC卡的退费, 如果结束原因是刷卡退费(05), 则会把退费信息返回;
- # 否则不会把退费信息传过来,会通过11号命令返回
- card = Card.objects(cardNo = lineInfo['cardNo'], agentId = self.dealer.agentId).first()
- if not card:
- logger.error('not exist this card no.')
- return
- consumeDict.update({'balance': lineInfo['balance']})
- ServiceProgress.update_progress_and_consume_rcd(self.device.ownerId,
- {'open_id': lineInfo['openId'], 'port': int(port),
- 'device_imei': self.device.devNo,
- 'isFinished': False}, consumeDict)
- extra = [{
- u'实体卡号': lineInfo['cardNo']
- }]
- 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 = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
- extra = extra)
- else:
- if 'coins' not in lineInfo:
- logger.warning('coins not in cache of port<{}> of device<devNo={}>'.format(port, self.device.devNo))
- return
- # 计算退费信息
- coins = VirtualCoin(lineInfo['coins'])
- refundCoins = VirtualCoin(0)
- if self.device.is_auto_refund:
- if leftTime != 65535:
- refundCoins = coins * (float(leftTime) / float(actualNeedTime))
- else:
- refundCoins = coins
- # if (refundProtection == 1 and usedTime < refundProtectionTime) and backCoins != VirtualCoin(0):
- if usedTime < refundProtectionTime and refundCoins != VirtualCoin(0):
- refundCoins = coins
- if refundCoins > coins:
- refundCoins = coins
- logger.debug(
- 'lefttime = {}, usedTime = {}, need time = {}, back coins = {}'.format(leftTime, usedTime,
- actualNeedTime,
- refundCoins))
- if '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 refundCoins > VirtualCoin(0):
- vCardConsumeRcd = VCardConsumeRecord.objects.get(id = lineInfo['consumeRcdId'])
- vCard.refund_quota(vCardConsumeRcd, usedTime, 0.0, refundCoins.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}
- ]
- )
- elif 'openId' in lineInfo and lineInfo['openId']:
- try:
- consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.COIN: VirtualCoin(coins).mongo_amount})
- if refundCoins > VirtualCoin(0):
- consumeDict.update({
- DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: refundCoins.mongo_amount,
- DEALER_CONSUMPTION_AGG_KIND.COIN: (VirtualCoin(coins) - refundCoins).mongo_amount
- })
- refund_money(self.device, refundCoins, lineInfo['openId'])
- 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 refundCoins > VirtualCoin(0):
- extra.append({u'消费金额': '{}(金币)'.format(coins - refundCoins)})
- extra.append({u'退款金额': '{}(金币)'.format(refundCoins)})
- else:
- extra.append({u'消费金额': '{}(金币)'.format(coins)})
- 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 = extra
- )
- else:
- logger.error('not net pay rather user virtual card pay. something is wrong.')
- except Exception as e:
- logger.exception(e)
|