# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import logging import random import time import uuid import simplejson as json from bson import ObjectId from apilib.monetary import Ratio, RMB, VirtualCoin from apilib.utils_sys import memcache_lock from apps.web.api.models import APIServiceStartRecord from apps.web.common.models import TempValues from apps.web.constant import Const, DEALER_CONSUMPTION_AGG_KIND from apps.web.core.accounting import Accounting from apps.web.core.device_define.changyuan import CYCardMixin, CYConstant from apps.web.device.models import Group, Device from apps.web.eventer import EventBuilder from apps.web.eventer.base import FaultEvent, WorkEvent from apps.web.report.utils import record_consumption_stats from apps.web.south_intf.platform import handle_and_notify_event_to_north_cy4, handle_and_notify_event_to_north_cy4_new from apps.web.user.models import RefundMoneyRecord from apps.web.user.models import ServiceProgress, RechargeRecord, CardRechargeOrder, CardRechargeRecord, Card, MyUser, \ ConsumeRecord from apps.web.user.transaction_deprecated import refund_money, refund_cash 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 if event_data["cmdCode"] in ["D7"]: return ChargingCY4FaultEventer(self.deviceAdapter, event_data) if event_data['cmdCode'] in ['D8', 'D9', 'DA', 'D4', 'D3']: return ChargingCY4WorkEventer(self.deviceAdapter, event_data) return None class ChargingCY4FaultEventer(FaultEvent): def do(self, **args): # 收到指令之后优先回复 self.deviceAdapter.ack_event() group = Group.get_group(self.device["groupId"]) warningData = { "warningStatus": 2, "warningDesc": "设备温度超限警告", "warningTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") } # 整机告警 Device.update_dev_warning_cache(self.device.devNo, {"0": warningData}) self.notify_dealer( templateName = "device_fault", title = "注意!设备火警预告!", device = u"{groupNumber}组-{logicalCode}".format(groupNumber = self.device["groupNumber"], logicalCode = self.device["logicalCode"]), location = u"{address}-{groupName}".format(address = group["address"], groupName = group["groupName"]), fault = u"设备当前温度过高,可能发生了火警!", notifyTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") ) class ChargingCY4WorkEventer(CYCardMixin, WorkEvent): def do(self, **args): devNo = self.device['devNo'] logger.info('ChangYuan4 charging event detected, devNo=%s,curInfo=%s' % (devNo, self.event_data)) cmdCode = self.event_data['cmdCode'] with memcache_lock(key = "{devNo}.{cmdCode}.event".format(devNo = devNo, cmdCode = cmdCode), expire=10, value = '1') as acquired: if acquired: # 收到某系指令之后先回复确认 if self.event_data.get("cmdCode") in ["D3", "D9", "DA"]: self.deviceAdapter.ack_event() # 设备状态由主板主动上报,上报之后需要和之前的端口信息做对比,如果之前端口处于工作状态的,而现在出于空闲状态的,需要结束服务 if cmdCode == 'D8': devCache = Device.get_dev_control_cache(devNo) portInfo = self.event_data.get("portInfo") newDevCache = self.compare_port_cache(portInfo, devCache) self.temperature_check(self.event_data.get("temperature")) self.main_board_check(portInfo) newDevCache.update({"temperature": self.event_data.get("temperature")}) Device.update_dev_control_cache(self.device["devNo"], newDevCache) if "cy4EventApi" in self.dealer.features: for port, portData in newDevCache.items(): if port.isdigit(): orderNo = portData["consumeType"] + str(uuid.uuid4()) dataDict = {"deviceCode":self.device.logicalCode, "port":port, "notifyType": "portInfo", "status":portData["status"], "portInfo":{}} if portData["status"] == Const.DEV_WORK_STATUS_WORKING: portDict = { "deviceCode": self.device.logicalCode, "status": Const.DEV_WORK_STATUS_WORKING, "power" : portData.get('power',0), "leftTime": portData.get('leftTime',None), "startTime":portData["startTime"], "consumeType":portData["consumeType"], "orderNo":orderNo, "isAPI":portData.get("isAPI",False), "port":port, "extOrderNo":portData.get("extOrderNo",""), } dataDict["portInfo"] = portDict handle_and_notify_event_to_north_cy4_new(self.device,portDict) # 开始充电 状态上报 启动方式分为 刷卡 扫码 投币 对于刷卡需要创建相应的消费记录 其余的更新端口状态 elif cmdCode == 'D9': self.do_event_start() elif cmdCode == 'DA': # 充电停止 self.do_event_finish() elif cmdCode == 'D4': # 卡充值 self.do_event_recharge_card() return # D4充值,直接已经回应了报文,不需要再次回应报文。 elif cmdCode == 'D3': # 卡充值的回执 self.do_event_recharge_card_result() def compare_port_cache(self, portInfo, devCache): """ 主板要求 对比 上报上来的端口信息 和 缓存中的端口信息 如果端口的信息由工作变为空闲 ,则处理该端口停止 同时更新端口缓存信息 :param portInfo: 主板上报的信息 :param devCache: 服务器记录的信息 :return: """ newDevCache = dict() for portStr, portData in portInfo.items(): # 首先读取 服务器的缓存信息 如果存在等待D9的标记 则此次上报的数据不对 该端口信息产生任何影响 portCache = devCache.get(portStr) or dict() if portCache.get("waitD9"): newDevCache[portStr] = portCache continue # 存在剩余时间以及使用时间 if portData.get("leftTime", 0) and portCache.get("needTime", 0): portData["usedTime"] = portCache.get("needTime") - portData.get("leftTime") portCache.update(portData) newDevCache[portStr] = portCache # 非正常情况 清掉缓存 if portData.get("status") == Const.DEV_WORK_STATUS_IDLE and portCache.get("status") == Const.DEV_WORK_STATUS_WORKING: Device.update_dev_control_cache(self.device.devNo, {portStr: {'status': Const.DEV_WORK_STATUS_IDLE}}) return newDevCache def stop_port(self, portStr): """ 停止端口 状态 :param portStr: :return: """ self.deviceAdapter.stop(portStr) def do_event_start(self): """ 处理 D9 充电开始指令事件 :return: """ cardNo = self.event_data["cardNo"] portStr = self.event_data["port"] if not self.event_data["result"]: logger.info("start charging device reply error! devNo is %s, port is %s" % (self.device["devNo"], portStr)) return if self.event_data["consumeType"] == "card": card = self.update_card_dealer_and_type(cardNo) if "cy4EventApi" not in self.dealer.features: if not self.check_card_can_use(card): logger.info("[ChargingCY4WorkEventer do_event_start] check card not can use devNo = {}".format(self.device.devNo)) self.notify_invalid_card_to_dealer(self.event_data["cardNo"], card) return self.deviceAdapter.stop_charging_port(portStr) # 判断卡是否存在 如果不存在 直接更新缓存信息即可 if not card: Device.update_dev_control_cache(self.device.devNo, {portStr: self.event_data}) return # 更新卡的余额 self.update_card_balance(card, RMB(self.event_data["balance"])) # 记录卡的消费 attachParas = {"chargeIndex": portStr} servicedInfo = {"cardNo": cardNo, "chargeIndex": portStr} orderNo, cardOrderNo = self.record_consume_for_card(card, RMB(self.event_data["coins"]), servicedInfo=servicedInfo, attachParas=attachParas) # 记录当前的卡消费的 progress fee = RMB(self.event_data["coins"]) ServiceProgress.register_card_service( self.device, int(portStr), card, { "orderNo": orderNo, "money": fee.mongo_amount, "coin": fee.mongo_amount, "needTime": self.event_data["needTime"], "cardOrderNo": cardOrderNo } ) # 通知卡消费成功 self.notify_balance_has_consume_for_card(card, fee) cacheDict = { "isStart": True, "status": Const.DEV_WORK_STATUS_WORKING, "coins": float(fee), "price": float(fee), "startTime": datetime.datetime.now().strftime(Const.DATETIME_FMT), "openId": card.openId, "needTime": self.event_data.get("needTime"), "power": self.event_data.get("power"), "consumeType": "card", "payInfo": [{ "needTime": self.event_data.get("needTime"), "coins": float(fee), "price": float(fee), "cardId": str(card.id), "cardNo": self.event_data.get("cardNo") }] } Device.update_dev_control_cache(self.device['devNo'], {portStr: cacheDict}) # 记录该端口累计需要的时间和钱 else: # dataDict = { # 'orderNo': orderNo, # 'port': portStr, # 'devNo': self.device['devNo'], # 'logicalCode': self.device['logicalCode'], # 'money': float(fee), # 'startTime': datetime.datetime.now().strftime(Const.DATETIME_FMT), # 'openId': card.openId, # 'cardNo': cardNo, # 'needTime': self.event_data.get('needTime'), # 'consumeType': 'card', # 'status': 'PENDING', # } dataDict = { "data": self.event_data.get('data'), "logicalCode":self.device['logicalCode'], "notifyType":"swipCard" } handle_and_notify_event_to_north_cy4_new(self.device, dataDict) elif self.event_data["consumeType"] == "mobile": # 如果是扫码启动的设备 可能会存在续充问题 payInfo needTime 需要特殊处理 devCache = Device.get_dev_control_cache(self.device["devNo"]) portCache = devCache.get(portStr, {}) if portCache.get('isAPI', False) is False: # 没有 等待D9 的标记 说明已经处理过D9了 不需要再次处理 if not portCache.get("waitD9"): return # 如果是虚拟卡消费,在service处理消费记录的时候一定会写入到端口缓存中 consumeRcdId = portCache.pop("consumeRcdId", None) # 获取本阶段的充电时间 needTime = self.get_need_time(portCache, self.event_data.get("needTime")) payInfo = portCache.get("payInfo") payInfo[-1].update({ "needTime": needTime, }) if consumeRcdId: payInfo[-1].update({"consumeRcdId": consumeRcdId}) portCache.update({ "needTime": portCache.get("needTime", 0) + needTime, "power": self.event_data["power"], "waitD9": False }) Device.update_dev_control_cache(self.device["devNo"], {portStr: portCache}) else: portCache.update({ "needTime": self.event_data["needTime"] if self.event_data["needTime"] != 0 else 1, "power": self.event_data["power"], "waitD9": False }) Device.update_dev_control_cache(self.device["devNo"], {portStr: portCache}) dataDict = { 'orderNo': portCache.get('orderNo'), 'port': portStr, 'devNo': self.device.get("devNo"), 'logicalCode': self.device.get('logicalCode'), 'money': portCache.get('price'), 'startTime': datetime.datetime.now().strftime(Const.DATETIME_FMT), 'needTime': int(self.event_data.get('needTime', 0)), 'consumeType': 'mobile', 'status': 'PENDING', 'extOrderNo': portCache.get('extOrderNo') } handle_and_notify_event_to_north_cy4(self.device, dataDict) dataDict.update({"notifyType": "start"}) handle_and_notify_event_to_north_cy4_new(self.device, dataDict) # 投币的暂时只是更新一下端口缓存 elif self.event_data["consumeType"] == "coin": nowTime = datetime.datetime.now().strftime(Const.DATETIME_FMT) Accounting.recordOfflineCoin( self.device, int(time.time()), int(self.event_data["coins"]), port = portStr) orderNo = self.record_consume_for_coin(RMB(self.event_data["coins"])) Device.update_dev_control_cache( self.device["devNo"], { portStr: { "isStart": True, "coins": self.event_data["coins"], "needTime": self.event_data["needTime"], "power": self.event_data["power"], "consumeType": self.event_data["consumeType"], "startTime": datetime.datetime.now().strftime(Const.DATETIME_FMT), "status": Const.DEV_WORK_STATUS_WORKING, "consumeOrderNo": orderNo, "payInfo": [{ "coins": float(self.event_data["coins"]), "price": float(self.event_data["coins"]), "needTIme": self.event_data["needTime"], "consumeOrderNo": orderNo }] } } ) else: logger.error("error consumeType %s" % self.event_data["consumeType"]) def do_event_finish(self): """ 充电结束事件上报 :return: """ portStr = self.event_data["port"] leftTime = self.event_data["leftTime"] devCache = Device.get_dev_control_cache(self.device.devNo) portCache = devCache.get(portStr, {}) if not portCache: logger.error("[ChargingCY4WorkEventer do_event_finish] portCache is None, device = {}, port = {}".format(self.device.devNo, portStr)) return group = self.device.group dealer = self.device.owner if not dealer or not group: logger.error('[ChargingCY4WorkEventer do_event_finish] dealer is not found, dealerId = {}'.format(self.device.ownerId)) return if "coins" not in portCache: logger.error("[ChargingCY4WorkEventer do_event_finish] no coins info, device = {}, event data = {}".format(self.device.devNo, self.event_data)) return nowTime = datetime.datetime.now() openId = portCache.get("openId", None) consumeType = portCache.get("consumeType", None) if consumeType == "mobile": if portCache.get('isAPI', False) is False: if not openId: # 没有OPENID的情况下 仅仅是 清空端口缓存就OK Device.clear_port_control_cache(self.device.devNo, portStr) logger.info("[ChargingCY4WorkEventer do_event_finish] mobile consumeType finish but no openId, devNo = {}, port = {}".format(self.device.devNo, portStr)) return user = MyUser.objects(openId=openId, groupId=self.device.groupId).first() consumeDict = { 'chargeIndex': portStr, 'reason': self.event_data["reason"], 'actualNeedTime': portCache.get("needTime"), 'duration': int(portCache.get("needTime", 0)) - int(leftTime), 'elec': self.event_data["spendElec"], 'elecFee': self.calc_elec_fee(self.event_data["spendElec"]), 'leftTime': leftTime, 'needTime': u'扫码订购%s分钟' % portCache.get("needTime"), DEALER_CONSUMPTION_AGG_KIND.COIN: VirtualCoin(portCache.get("coins")).mongo_amount } # 扫码退费的情况处理 payInfo = portCache.get("payInfo", list()) allNeedTime = portCache.get("needTime", 0) usedTime = allNeedTime - leftTime extra = [ {u"订购时长": u"{} 分钟".format(allNeedTime)}, {u"使用时长": u"{} 分钟".format(usedTime)} ] # 剩余时间比总订购时间还要长 则证明没有使用 if usedTime <= 0: usedTime = 0 if usedTime <= CYConstant.REFUND_PRODUCTION_TIME: usedTime = 0 self.event_data.update({"reason": u"异常结束,如非自己停止,可能是插头松动"}) self.notify_user_service_complete( service_name=u"充电", openid=user.managerialOpenId if user else "", port=self.event_data['port'], address=group.address, reason=self.event_data["reason"], finished_time=nowTime.strftime("%Y-%m-%d %H:%M:%S"), extra=extra ) # 如果设备开启了退费并且结束原因在退费原因之列 或者时间不满5分钟 if (self.device.is_auto_refund and self.event_data.get( "reasonCode") in CYConstant.NEED_REFUND) or usedTime <= CYConstant.REFUND_PRODUCTION_TIME: # 总的退还 allRefundMoney = VirtualCoin(0) # 针对每一笔的支付信息不同进行退款 for item in payInfo: usedTime, refundMoney = self.refund_user(item, usedTime, openId) allRefundMoney += refundMoney if allRefundMoney > VirtualCoin(0): consumeDict.update({ DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: allRefundMoney.mongo_amount, DEALER_CONSUMPTION_AGG_KIND.COIN: ( VirtualCoin(portCache.get("coins")) - allRefundMoney).mongo_amount }) refundTitle = u'您使用的%s号端口充电,共付款:%s元,充电预定时间为:%s分钟,剩余时间:%s分钟,给您退款:%s元(退款金额将于1-5个工作日内返还)' % ( portStr, portCache.get("coins"), portCache.get('needTime'), leftTime, allRefundMoney) # self.notify_user(user.managerialOpenId if user else '', 'refund_coins', **{ 'title': refundTitle, 'backCount': u'%s' % allRefundMoney.amount, 'finishTime': datetime.datetime.strftime(nowTime, "%Y-%m-%d %H:%M:%S") }) # 没有退费的情况下 需要对经销商进行分账 else: for item in payInfo: rechargeRcdId = item.get("rechargeRcdId") self.do_ledger(rechargeRcdId) ServiceProgress.update_progress_and_consume_rcd( self.device["ownerId"], { "open_id": openId, "device_imei": self.device["devNo"], "port": int(portStr), "isFinished": False }, consumeDict ) else: try: asr = APIServiceStartRecord.objects(orderNo=portCache['orderNo']).first() needTime = asr.needTime dataDict = { 'port': portStr, 'devNo': self.device['devNo'], 'logicalCode': self.device['logicalCode'], 'finishReason': self.event_data["reason"], 'duration': int(needTime) - int(leftTime), 'spendElec': self.event_data["spendElec"], 'leftTime': int(leftTime), 'status': 'FINISHED', 'consumeType': 'mobile', 'endTime': datetime.datetime.now().strftime(Const.DATETIME_FMT), 'extOrderNo': asr.extOrderNo } handle_and_notify_event_to_north_cy4(self.device, dataDict) dataDict.update({"notifyType": "finished"}) handle_and_notify_event_to_north_cy4_new(self.device, dataDict) except Exception as e: logger.error('changyuan4 api finish error! e={}'.format(e)) Device.clear_port_control_cache(self.device['devNo'], portStr) elif consumeType == "card": cardId = portCache.get("payInfo")[0].get("cardId") if not cardId: logger.info("[ChargingCY4WorkEventer do_event_finish] card consumeType finish but no cardId, devNo = {}, port = {}".format(self.device.devNo, portStr)) else: card = Card.objects.get(id = cardId) extra = [ {u"刷卡卡号": "{}".format(card.cardNo)}, {u"订购时长": u"{} 分钟".format(portCache.get("needTime", 0))}, {u"使用时长": u"{} 分钟".format(int(portCache.get("needTime", 0)) - int(leftTime))} ] self.notify_user_service_complete( service_name=u"充电", openid=card.managerialOpenId, port=self.event_data['port'], address=group.address, reason=self.event_data["reason"], finished_time=nowTime.strftime("%Y-%m-%d %H:%M:%S"), extra=extra ) consumeDict = { 'chargeIndex': portStr, 'reason': self.event_data["reason"], 'actualNeedTime': portCache.get("needTime"), 'duration': int(portCache.get("needTime")) - int(leftTime), 'elec': self.event_data["spendElec"], 'elecFee': self.calc_elec_fee(self.event_data["spendElec"]), 'leftTime': leftTime, 'needTime': u'刷卡订购%s分钟' % portCache.get("needTime") } ServiceProgress.update_progress_and_consume_rcd( self.device["ownerId"], { "open_id": openId, "device_imei": self.device["devNo"], "port": int(portStr), "isFinished": False }, consumeDict ) dataDict = { 'port': portStr, 'cardNo': card.cardNo, 'devNo': self.device['devNo'], 'logicalCode': self.device['logicalCode'], 'finishReason': self.event_data["reason"], 'duration': int(portCache.get("needTime")) - int(leftTime), 'spendElec': self.event_data["spendElec"], 'leftTime': int(leftTime), 'status': 'FINISHED', 'consumeType': 'card', 'endTime': datetime.datetime.now().strftime(Const.DATETIME_FMT), } handle_and_notify_event_to_north_cy4(self.device, dataDict) dataDict.update({"notifyType": "finished"}) handle_and_notify_event_to_north_cy4_new(self.device, dataDict) elif consumeType == "coin": CoinConsumeRcd = ConsumeRecord.objects.get(orderNo = portCache.get('consumeOrderNo')) # type: ConsumeRecord CoinConsumeRcd.servicedInfo['elec'] = portCache.get("spendElec") CoinConsumeRcd.servicedInfo['actualNeedTime'] = u'动态功率计算为%s分钟' % str( int(portCache.get("needTime")) - int(leftTime)) CoinConsumeRcd.servicedInfo['needTime'] = u'投币订购%s分钟' % portCache.get("needTime") CoinConsumeRcd.servicedInfo['reason'] = self.event_data.get("reason") CoinConsumeRcd.servicedInfo['chargeIndex'] = portStr CoinConsumeRcd.finishedTime = datetime.datetime.strftime(nowTime, '%Y-%m-%d %H:%M:%S') CoinConsumeRcd.save() valueDict = { DEALER_CONSUMPTION_AGG_KIND.ELEC: portCache.get("spendElec"), DEALER_CONSUMPTION_AGG_KIND.ELECFEE: self.calc_elec_fee(portCache.get("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,)) else: logger.error("error consumeType! devNo is %s, port is %s" % (self.device["devNo"], portStr)) # 事件结束之后清空端口的信息 更新设备的服务信息 Device.clear_port_control_cache(self.device["devNo"], portStr) def do_event_recharge_card(self): """ 主板发起 卡金额同步指令 服务器对照 相应卡的 余额 状态 充值记录等等进行相应的操作 :return: """ cardNo = self.event_data["cardNo"] cardBalance = self.event_data["balance"] card = self.update_card_dealer_and_type(cardNo) if not card: logger.error("no card exist, can not async card balance") return # 更新卡的余额 以设备侧的为准 if RMB(cardBalance) != card.balance: logger.info('Card<{}> balance<{}> needs to be sync !!!'.format(cardNo, card.balance)) card.balance = RMB(cardBalance) card = card.save() # orderNos 表示充值的订单 cardOrderNos 表示卡充值的订单 money, coins, orderNos, cardOrderNos = CardRechargeOrder.get_to_do_list(str(card.id)) # 一旦卡被冻结 立即下发 创建一张为 负数的金额订单 将卡的余额清空 if card.frozen: group = Group.get_group(card.groupId) CardRechargeOrder.new_one( openId=card.openId, cardId=str(card.id), cardNo=card.cardNo, money=RMB(0), coins=VirtualCoin(0) - VirtualCoin(coins) - VirtualCoin(card.balance), group=group, rechargeId=ObjectId(), rechargeType=u"sendCoin" ) money, coins, orderNos, cardOrderNos = CardRechargeOrder.get_to_do_list(str(card.id)) # 没有需要同步的订单的情况下 直接退出 if not orderNos: logger.info('Not card recharge record!, card is <{}>'.format(cardNo)) # self._do_offline_recharge_by_dealer(cardNo, card, cardType) return orderNos = [str(item) for item in orderNos] sid = str(random.randint(0, 0xFFFF)) TempValues.set('%s-%s' % (self.device.devNo, sid), value=json.dumps(orderNos)) TempValues.set('%s-%s-%s' % (self.device.devNo, sid, cardNo), value=json.dumps(cardOrderNos)) TempValues.set('%s-%s' % (self.device.devNo, cardNo), value=str(sid)) # 计算总的需要同步的金额 asyncMoney = RMB(cardBalance) + RMB(coins) logger.info('ready to recharge card, card is <{}>, money is <{}>'.format(cardNo, coins)) # 已经下发金额后,先将订单的状态改为结束。如果主板上报失败,再将订单状态迁移回来否则就不再变化订单状态了 try: CardRechargeOrder.update_card_order_has_finished(str(card.id)) except Exception as e: logger.debug('%s' % e) else: self.deviceAdapter.recharge_card(cardNo, asyncMoney) def do_event_recharge_card_result(self): """ 同步卡余额 D3指令的应答指令 表示主板已经确认卡的余额已经被更新 :return: """ result = self.event_data["result"] if not result: logger.error("async card balance error, device reply error") return answerSid = self.event_data["sid"] cardNo = self.event_data["cardNo"] cardBalance = self.event_data["balance"] sid = TempValues.get("%s-%s" % (self.device["devNo"], cardNo)) if sid != answerSid: logger.error("answer sid is not equal sid! %s\t%s" % (answerSid, sid)) card = self.update_card_dealer_and_type(cardNo) if not card: logger.error("not found card, event is <{}>".format(self.event_data)) return # 根据sid 以及卡号 查询出此次充值的所有的订单 orderNos = TempValues.get('{}-{}'.format(self.device.devNo, sid)) cardOrderNos = TempValues.get('{}-{}-{}'.format(self.device.devNo, sid, cardNo)) # 下面的都不会更改订单的状态 最多走售后 money, coins = RMB(0), VirtualCoin(0) # 校验金额是否是相等的 orderNos = [ObjectId(item) for item in orderNos] rds = RechargeRecord.objects.filter(ownerId=card.dealerId, id__in=orderNos) for item in rds: money += item.money coins += item.coins # 这个地方就是异常值处理了 if VirtualCoin(coins + card.balance) != VirtualCoin(cardBalance): logger.error('card pre balance not equal now balance! event is <{}>'.format(self.event_data)) return # 依次更改卡充值的订单 并创建充值订单 cardOrders = CardRechargeOrder.objects.filter(orderNo__in=cardOrderNos) for _order in cardOrders: # type: CardRechargeOrder _order.update_after_recharge_ic_card( device=self.device, sendMoney=RMB(_order.coins), preBalance=card.balance ) preBalance = card.balance card.balance = card.balance + _order.coins # 创建充值记录 CardRechargeRecord.add_record( card=card, group=Group.get_group(_order.groupId), order=_order, device=self.device ) # 保存 card.save() # 完成之后将TempValue 的key value 清空 TempValues.remove('%s-%s' % (self.device['devNo'], sid)) TempValues.remove('%s-%s' % (self.device['devNo'], cardNo)) TempValues.remove("%s-%s-%s" % (self.device["devNo"], sid, cardNo)) def temperature_check(self, temperature): """ 温度监测 :param temperature :return: """ # 黑龙江温度 -19 度 昌原让屏蔽低温告警 if temperature > self.deviceAdapter.MAX_TEMPERATURE: group = Group.get_group(self.device["groupId"]) # self.notify_dealer( # 'device_fault', # **{ # 'title': u'注意!注意!设备温度不正常', # 'device': u'组号::%s, 二维码编号: %s, 当前温度: %s' % (self.device['groupNumber'], self.device['logicalCode'], temperature), # 'location': u'组名称:%s, 地址:%s' % (group['groupName'], group['address']), # 'fault': u"设备温度超限", # 'notifyTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') # } # ) self.notify_dealer( templateName = "device_fault", title = "注意!设备当前温度过高", device = u"{groupNumber}组-{logicalCode}".format(groupNumber = self.device["groupNumber"], logicalCode = self.device["logicalCode"]), location = u"{address}-{groupName}".format(address = group["address"], groupName = group["groupName"]), fault = u"设备当前温度过高,请小心火警发生!", notifyTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") ) def main_board_check(self, portInfo): """ 继电器粘连 监测 :param portInfo: :return: """ group = Group.get_group(self.device["groupId"]) for portStr, portData in portInfo.items(): if portData.get("status") == Const.DEV_WORK_STATUS_FAULT_RELAY_CONNECT: # self.notify_dealer( # 'device_fault', # **{ # 'title': u'注意!注意!您的设备发生继电器粘连', # 'device': u'组号::%s, 二维码编号: %s, 端口号: %s' % (self.device['groupNumber'], self.device['logicalCode'], portStr), # 'location': u'组名称:%s, 地址:%s' % (group['groupName'], group['address']), # 'fault': u"设备端口发生继电器粘连", # 'notifyTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') # } # ) self.notify_dealer( templateName = "device_fault", title = "注意!设备继电器发生粘连!", device = u"{groupNumber}组-{logicalCode}-{port}".format(groupNumber = self.device["groupNumber"], logicalCode = self.device["logicalCode"], port = portStr), location = u"{address}-{groupName}".format(address = group["address"], groupName = group["groupName"]), fault = u"设备继电器粘连,端口将一直处于供电状态!", notifyTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") ) @staticmethod def get_need_time(portCache, allLeftTime): """ 获取此次支付的 needTime 协议中上报的NeedTime的理解:第一次支付获得了300分钟,使用了100分钟,第二次支付又获得了300分钟 然后上传过来的needTime是500分钟 :param portCache: 端口缓存值 :param allLeftTime: 上报过来的needTime :return: """ leftTime = portCache.get("leftTime", 0) needTime = portCache.get("needTime") # 如果端口缓存中不存在剩余时间 说明是第一次支付 # 绝大多数是这种情况 if not leftTime and not needTime: return allLeftTime # 如果存在剩余时间 则可以说明此次获得的消费时间 = 上传的总剩余时间-之前的剩余时间(可能会有误差 已经和主板沟通) # 充电了一段时间之后的续充 if leftTime and needTime: return allLeftTime - leftTime # 如果不存在剩余时间 但是端口缓存中已经存在了总的消费时间 说明是在1-5分钟之内续充 也就是D8指令还没有上来 连续续充 D9, D9 # 连续续充 连续两次D9 if not leftTime and needTime: return allLeftTime - needTime # 这种情况应该是不可能发生的,但是还是以防万一,出现的情况可能是 设备启动后,定时上传指令 比 上传充电指令更早到 # D8 先 D9后 通过标记应该已经解决 if leftTime and not needTime: return allLeftTime - leftTime def refund_user(self, payInfo, usedTime, openId): """ 退钱给用户 每一笔退还 如果不是虚拟卡支付 则说明是金币或者现金启动 后分账方式都需要对经销商进行分账 :param payInfo: 单笔支付的信息 :param usedTime: 使用的时间 :param openId: :return: usedTime 退还此笔消费之后还剩余的使用时间 refundMoney 此笔退款退还的钱(币种可能不同 后续需要统一设计) """ needTime = payInfo.get("needTime", 0) rechargeRcdId = payInfo.get("rechargeRcdId") vCardId = payInfo.get("vCardId") coins = payInfo.get("coins") # 进入退款后,还是优先分账 分完帐再说 rechargeRecord = self.do_ledger(rechargeRcdId) # 接下来 计算退款率 leftTime = needTime - usedTime # 情况一: 计算出来的剩余时间小于等于0 说明本单使用完毕 无需退款 但有可能下一单还需要退 if leftTime <= 0: return usedTime - needTime, VirtualCoin(0) # 情况二:虚拟卡的直接本单不退 由于虚拟卡算1次 所以这一单直接不处理 if vCardId: return 0, VirtualCoin(0) # 计算退款率 所需的退款即退款全额 * 退款率 refundRate = Ratio(float(leftTime) / float(needTime)) if rechargeRecord and rechargeRecord.isQuickPay: # 情况三: 现金退费 refundMoney = refundRate * RMB(rechargeRecord.money) refund_cash( rechargeRecord, RMB(refundMoney), VirtualCoin(0), minus_total_consume = VirtualCoin(rechargeRecord.coins)) # type: RefundMoneyRecord return 0, refundMoney else: # 情况四:金币退费 refundMoney = refundRate * VirtualCoin(coins) refund_money(self.device, refundMoney, openId) return 0, refundMoney