# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import logging import time from decimal import Decimal from mongoengine import DoesNotExist from apilib.monetary import RMB, VirtualCoin from apilib.utils_datetime import to_datetime from apilib.utils_string import make_title_from_dict from apps.web.agent.models import Agent from apps.web.api.models import APIStartDeviceRecord from apps.web.api.ykt_north.models import YiKaTongCard from apps.web.common.models import TempValues from apps.web.constant import Const, DEALER_CONSUMPTION_AGG_KIND from apps.web.core.device_define.jndz import CMD_CODE, SWIPE_CARD_PARAM_OP, SWIPE_CARD_RES, RESULT_CODE, CARD_TYPE, \ CARD_DEDUCTTYPE,CARD_REFUNDTYPE from apps.web.core.exceptions import ServiceException from apps.web.dealer.models import Dealer from apps.web.device.models import Group, Device, DeviceDict from apps.web.eventer import EventBuilder from apps.web.eventer.base import FaultEvent, WorkEvent from apps.web.eventer.errors import InvalidOption, NoCommandHandlerAvailable, RequestInvalid from apps.web.south_intf.platform import notify_event_to_north, notify_event_to_north_v2 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 ServiceProgress, CardRechargeOrder, MyUser, \ UserVirtualCard, Card, ConsumeRecord, VCardConsumeRecord, Redpack logger = logging.getLogger(__name__) class builder(EventBuilder): def __getEvent__(self, device_event): event_data = self.deviceAdapter.analyze_event_data(device_event) if event_data is None or 'cmdCode' not in event_data: return None if event_data['cmdCode'] in [ CMD_CODE.DEVICE_SUBMIT_CHARGE_FINISHED_06, CMD_CODE.DEVICE_SUBMIT_CHARGE_FINISHED_v2_16 ]: return ChargingJNDZWorkEvent(self.deviceAdapter, event_data) if event_data['cmdCode'] == CMD_CODE.DEVICE_SUBMIT_OFFLINE_COINS_20: # 如果支持一卡通,走一卡通的流程 if self.device.support_dev_type_features('support_wvtiykt'): return JNDZYKTCardChargeEvent(self.deviceAdapter, event_data) else: return ChargingJNDZWorkEvent(self.deviceAdapter, event_data) if event_data['cmdCode'] in [ # todo 0A的告警不要了,先注释掉,后续删除 # 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'] in [CMD_CODE.DEVICE_CARD_CHARGE_2D]: if self.device.support_dev_type_features('support_wvtiykt'): return JNDZYKTCardChargeEvent(self.deviceAdapter, event_data) else: return JNDZNewCardChargeEvent(self.deviceAdapter, event_data) # 对于0x10指令 新旧的处理流程是完全不一样的 if event_data['cmdCode'] in [CMD_CODE.SWIPE_CARD_10]: if self.deviceAdapter.device.devTypeCode == Const.DEVICE_TYPE_CODE_CHARGING_HD: return JNDZNewCardChargeEvent(self.deviceAdapter, event_data) elif self.deviceAdapter.device.driverCode == Const.DEVICE_TYPE_CODE_CHARGE_DOUBLE_SERIAL_JH: return JNDZDoubleSerialCardChargeEvent(self.deviceAdapter, event_data) elif self.device.support_dev_type_features('support_wvtiykt'): return JNDZYKTCardChargeEvent(self.deviceAdapter, event_data) else: return ChargingJNDZWorkEvent(self.deviceAdapter, event_data) if event_data['cmdCode'] in [CMD_CODE.DEVICE_REAL_TIME_REPORT_21]: return ChargingJNDZReportEvent(self.deviceAdapter, event_data) if event_data['cmdCode'] in [CMD_CODE.SWIPE_CARD_50,CMD_CODE.SWIPE_CARD_52,CMD_CODE.SWIPE_CARD_56]: return JNDZVirtualCardWorkEvent(self.deviceAdapter, event_data) 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", "") # todo 0A的告警不要了,先注释掉,后续删除 # 保证原有的故障处理逻辑不变 # if cmdCode == CMD_CODE.DEVICE_SUBMIT_DEVICE_FAULT_0A: # return self.handler_0A_fault() # 这些都是整机告警 part = "0" warningData = { "warningStatus": 2, "warningDesc": desc, "warningTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), } Device.update_dev_warning_cache(self.device.devNo, {part: warningData}) group = Group.get_group(self.device.groupId) titleList = [ {u"告警名称": faultType}, {u"地址名称": group["groupName"]} ] title = make_title_from_dict(titleList) # 接下来的都是整机告警,这个地方需要通知到经销商 self.notify_dealer( "device_fault", title=title, device=u"{}号设备".format(self.device.logicalCode), fault=faultType, notifyTime=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), location=u"{}".format(group["groupName"]) ) # 记录错误故障 fault_record = self.record( faultCode=cmdCode, description=desc, title=faultType, detail={"faultType": faultType, "errorCode": self.event_data.get("errorCode")} ) self.north_to_liangxi(fault_record) # todo 0A的告警不要了,先注释掉,后续删除 # def handler_0A_fault(self): # faultContent = self.event_data.get('faultContent', '') # level = self.event_data.get('level', '') # errCode = self.event_data.get('errCode') # # port = self.event_data.get('port') # if port and port != 255: # title = u'注意!您的设备{}号端口发出告警!'.format(port) # part = str(port) # else: # title = u'注意!您的设备发出告警!' # part = "0" # # if errCode in ['A3']: # 空载 无需显示在经销商后台 # return # # elif errCode in ['00']: # 老设备上报继电器粘连 100206 不上报!! # return # # # 设备告警打入缓存 # elif errCode in ['A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9', 'AA']: # warningData = { # "warningStatus": 2, # "warningDesc": faultContent, # "warningTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), # } # Device.update_dev_warning_cache(self.device.devNo, {part: warningData}) # # # 设备告警清除 # elif errCode in ['20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2A']: # 恢复信号 不操作 # Device.clear_part_warning_cache(self.device.devNo, part) # # ctrInfo = Device.get_dev_control_cache(self.device.devNo) # # if 'statusInfo' in ctrInfo and 'errCode' in ctrInfo: # # if ctrInfo['errCode'][-1] == errCode[-1]: # # ctrInfo['status'] = None # # ctrInfo['errCode'] = None # # ctrInfo['faultContent'] = None # # ctrInfo['level'] = None # # ctrInfo['statusInfo'] = None # # ctrInfo['cmdCode'] = None # # Device.update_dev_control_cache(self.device['devNo'], ctrInfo) # # else: # pass # # Device.update_dev_control_cache(self.device['devNo'], self.event_data) # # # 记录错误故障 # self.record( # title=title, # description=faultContent, # level=level # ) # # group = Group.get_group(self.device.groupId) # # if self.is_notify_dealer: # self.notify_dealer( # templateName="device_fault", # title=title, # device=u'{}-{}'.format(self.device.devTypeName, self.device.logicalCode), # fault=faultContent, # location=u'{}-{}-{}号设备({})'.format(group["address"], group["groupName"], self.device["groupNumber"], # self.device["logicalCode"]), # notifyTime=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") # ) 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 swipe_card_cache_key = lambda devNo: "swipe_card_{}".format(devNo) class ChargingJNDZWorkEvent(WorkEvent): def support_playback(self): return self.event_data['cmdCode'] in [ CMD_CODE.DEVICE_SUBMIT_CHARGE_FINISHED_06, CMD_CODE.DEVICE_SUBMIT_CHARGE_FINISHED_v2_16 ] def __get_swipe_card_cache(self): return TempValues.get(swipe_card_cache_key(self.device.devNo)) def __set_swipe_card_cache(self, cache_info): logger.debug('swipe cache info is: {}'.format(cache_info)) return TempValues.set(swipe_card_cache_key(self.device.devNo), cache_info, 900) def __can_used_virtual_card(self, card, package): dealer = self.device.owner agent = Agent.objects.get(id=dealer.agentId) features = agent.features billingType = self.device.my_obj.otherConf.get('billingType', 'time') if not is_server_refund(billingType, self.device, self.device.owner, agent): return False, None else: if "vCardNeedBind" in features: virtual_card = card.bound_virtual_card else: virtual_card = card.related_virtual_card if not virtual_card: return False, None if virtual_card.can_use_today(package): return True, virtual_card else: return False, virtual_card def __do_new_card_proc_10(self): """ 刷卡扣费的 新流程 预扣费 实际只是检查扣费 并没有真正的扣除 :return: """ cardNo = self.event_data['cardNo'] preFee = RMB(self.event_data['preFee']) card = self.update_card_dealer_and_type(cardNo) # type: Card if not card: return self.response_use_card(SWIPE_CARD_RES.INVALID_CARD_02, 0) 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)) if card.openId == '' or card.frozen: return self.response_use_card(SWIPE_CARD_RES.INVALID_CARD_02, 0) once_coins = float(preFee) once_time = int(self.device.my_obj.otherConf.get('cardMin', 180)) # 获取上一次的刷卡缓存 swipe_cache_info = self.__get_swipe_card_cache() # 对比刷卡缓存的卡ID 更新金额 if swipe_cache_info and swipe_cache_info['cardId'] == str(card.id): ts = swipe_cache_info['ts'] if ts >= (int(time.time()) - 600): total_coins = once_coins + swipe_cache_info['coins'] total_time = once_time + swipe_cache_info['time'] else: total_coins = once_coins total_time = once_time else: total_coins = once_coins total_time = once_time # 判断当前的支付金额 是否能被虚拟卡支付 is_enough, virtual_card = self.__can_used_virtual_card(card, { 'coins': float(total_coins), 'unit': u'分钟', 'time': total_time }) if not is_enough: if card.balance >= RMB(total_coins): is_enough = True leftBalance = max(float(card.balance) - total_coins, 0) else: leftBalance = 0 if is_enough: rv = { 'cardId': str(card.id), 'coins': total_coins, 'ts': int(time.time()), 'time': total_time } self.__set_swipe_card_cache(rv) logger.debug( '[card deduct]cardNo = {}; coins = {}; total time = {}'.format( card.cardNo, total_coins, total_time)) return self.response_use_card(SWIPE_CARD_RES.SUCCESS_00, leftBalance) else: return self.response_use_card(SWIPE_CARD_RES.BALANCE_NOT_ENOUGH_01, leftBalance) def __do_new_card_proc_20(self): """ 刷卡新流程真正扣费的地方 :return: """ swipe_cache_info = self.__get_swipe_card_cache() if not swipe_cache_info or 'cardId' not in swipe_cache_info: logger.warning('not find card id. ignore it.') return # 这一步暂时 不明白用意 self.__set_swipe_card_cache({'cardId': swipe_cache_info['cardId'], 'ts': 0}) card = Card.objects(id=str(swipe_cache_info['cardId'])).first() # type: Card if not card: logger.warning('card is not exist.'.format(str(card.id))) return port = str(self.event_data['port']) package = { 'coins': self.event_data['coins'], 'unit': u'分钟', 'time': self.event_data['needTime'], } is_enough, virtual_card = self.__can_used_virtual_card(card, package) if virtual_card and is_enough: # 记录卡消费记录以及消费记录 orderNo, cardConsumeOrderNo = self.record_consume_for_card( card=card, money=RMB(0.0), attachParas={ 'chargeIndex': port }, servicedInfo={ 'needTime': self.event_data['needTime'], 'needElec': self.event_data['elec'], 'startUart': self.event_data.get('uartData', '') }) vCardConsumeRecord = virtual_card.consume(openId=card.openId, group=self.device.group, dev=self.device, package=package, attachParas={}, nickname=card.cardName) if not vCardConsumeRecord: logger.error('use virtual card to consume failure. id = {}'.format(str(virtual_card.id))) return # 插入虚拟卡的ID 退费的时候使用 self.event_data.update({'vCardId': str(virtual_card.id)}) self.event_data.update({"consumeRcdId": str(vCardConsumeRecord.id)}) self.notify_balance_has_consume_for_card(card, self.event_data['coins'], desc=u'使用绑定的虚拟卡') else: orderNo, cardConsumeOrderNo = self.record_consume_for_card( card=card, money=RMB(self.event_data['coins']), attachParas={ "chargeIndex": port }, servicedInfo={ 'needTime': self.event_data['needTime'], 'needElec': self.event_data['elec'], 'startUart': self.event_data.get('uartData', '') }) res, _ = self.consume_money_for_card( card=card, money=RMB(self.event_data['coins'])) if res != 1: logger.warning("consume error!!!, cardNo is {}".format(card.cardNo)) return self.notify_balance_has_consume_for_card(card, self.event_data['coins']) # 通知微信,已经扣费 # 注册服务 ServiceProgress.register_card_service( self.device, int(port), card, { 'orderNo': orderNo, 'money': self.event_data['coins'], 'coin': self.event_data['coins'], 'needTime': self.event_data['needTime'], 'cardOrderNo': cardConsumeOrderNo }) # billingType = self.device.get("otherConf", dict()).get('billingType', 'time') self.event_data.update({'billingType': billingType}) self.event_data.update({'cardId': str(card.id)}) self.event_data.update({'openId': card.openId}) cInfo = Device.get_dev_control_cache(self.device.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}) Device.update_dev_control_cache(self.device['devNo'], {port: self.event_data}) # self.__set_swipe_card_cache({}) def __do_fault_record(self): """ TODO 梁溪消防的 紧急处理 后续需要统一这个流程 :return: """ if self.event_data["reasonCode"] == "03": desc = u"设备超功率运行,当前已停止充电" title = u"端口功率超限" elif self.event_data["reasonCode"] == "0B": desc = u"设备或是端口出现问题,当前已停止充电" title = u"设备端口故障" else: return from apps.web.device.models import FaultRecord faultRecord = FaultRecord( logicalCode=self.device.logicalCode, imei=self.device.devNo, portNo=str(self.event_data["port"]), faultCode=self.event_data["reasonCode"], title=title, description="", dealerId=self.device.ownerId, groupName=self.device.group.get('groupName', ''), address=self.device.group.get('address', ''), detail={"faultType": desc, "errorCode": "16{}".format(self.event_data["reasonCode"])} ).save() from taskmanager.mediator import task_caller task_caller( 'send_to_xf_falut', devNo=self.device.devNo, faultId=str(faultRecord.id) ) 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.event_data['oper'] == SWIPE_CARD_PARAM_OP.DECR_00: if "support_new_card_proc" in self.dealer.features: return self.__do_new_card_proc_10() cardNo = self.event_data['cardNo'] preFee = RMB(self.event_data['preFee']) #: 操作符,是充值还是减少 <- (00, 01) oper = self.event_data['oper'] card = self.update_card_dealer_and_type(cardNo) #: 经销商限制ID卡, 如果满足直接return if not card: 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=999) # 通知微信,已经扣费 # 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 devObj = 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 = devObj.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))) self.response_use_card(res, card.balance) 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]: # 结束的时候故障的上报 如果结束的原因是故障导致的 记录一条故障告警 if self.event_data.get("reasonCode") in ["03", "0B"]: self.__do_fault_record() port = str(self.event_data['port']) lineInfo = Device.clear_port_control_cache(devNo, port) if not lineInfo: logger.debug('port<{}> of device cache is null.'.format(port, self.device.logicalCode)) return logger.debug('port<{}> of device cache is: {}'.format(port, self.device.logicalCode, str(lineInfo))) # 红包退费的判断处理 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))) recvTime = to_datetime(self.recvTime) 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 'coins' not in lineInfo: logger.debug('port cache has no coins. no order in port {}'.format(port)) return if 'startTime' not in lineInfo: logger.debug('startTime is not in lineInfo') return startTime = to_datetime(lineInfo['startTime']) refundProtectionTime = int(self.device.get_other_conf_item('refundProtectionTime', 5)) if startTime > recvTime: # 如果web服务器时间和事件监控服务器时间不一致,导致开始时间比事件时间还大 logger.warning( 'finished event of dev<{}> error: start time<{}> is bigger than recv time<{}>'.format( self.device.devNo, startTime, recvTime)) if startTime > (recvTime + refundProtectionTime): return else: recvTime = startTime usedTime = int(round(((recvTime - startTime).total_seconds() / 60.0))) if usedTime <= 0: usedTime = 1 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', '') minUsedTime = self.device.get_other_conf_item('minUsedTime', 0) isTempPackage = lineInfo.get('isTempPackage', False) orderPower = self.event_data.get("orderPower") needTime = 0 leftTimeStr = '' spendElec = 0.0 leftElec = self.event_data.get('elec', 0.0) needElec = lineInfo.get('needElec', 0.0) try: #: 刷卡或者万一缓存重启了,出现needElec为空的,作为1000度电吧,就会一定使用设备上报的电量 if leftElec < needElec: spendElec = round(needElec - leftElec, 2) backCoins_by_elec = round(leftElec / 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 or 'alt_tech_refund_mode' in dealer.features) and \ billingType == 'time': needTime = lineInfo['needTime'] # 剩余时间不满 60 按照 0 算, 不满 120 按照 60 算... calcleftTime = (int(leftTime) // 60) * 60 backCoins = money * (float(calcleftTime) / float(actualNeedTime)) elif billingType == 'time': needTime = lineInfo['needTime'] backCoins = money * (float(leftTime) / float(actualNeedTime)) if refundProtectionTime < usedTime < minUsedTime: # 最小使用时长判断(不足改时长 按该时常算) backCoins = money * (float(actualNeedTime - minUsedTime) / float(actualNeedTime)) logger.info( 'usedTime less then minUsedTime (usedTime<{}> ==> minUsedTime<{}>) devNo=<{}>, port=<{}>, backCoins=<{}>'.format( usedTime, minUsedTime, devNo, port, backCoins)) else: needElec, elec = Decimal(lineInfo.get('needElec', 1000)), Decimal( str(self.event_data.get('elec'))) ratio = (needElec - elec) / needElec backCoins = VirtualCoin(money.amount - money.amount * ratio) if 'jiuheng_double_judgment_of_time_and_elec' in dealer.features: backCoins = money * (float(leftTime) / float(actualNeedTime)) backCoins = VirtualCoin(min(float(backCoins), float(backCoins_by_elec))) isRefundProtection = False if usedTime <= refundProtectionTime: backCoins = money isRefundProtection = True logger.info( 'exec protection refund devNo=<{}>, port=<{}>, backCoins=<{}>'.format(devNo, port, backCoins)) if backCoins > money: backCoins = money refundRMB = price * (float(backCoins) / float(money)) if money != VirtualCoin(0) else RMB(0) logger.debug( 'refund money is: {}; refund rmb is: {}'.format(str(backCoins.amount), str(refundRMB.amount))) #: 扫码的方式 if consumeType == 'server' and (not vCardId): 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: if 'openId' not in lineInfo or not lineInfo['openId']: logger.warning( 'openId not in cache of port<{}> in device<{}>'.format(port, self.device.devNo)) return openId = lineInfo['openId'] user = MyUser.objects(openId=openId, groupId=self.device['groupId']).first() extra = [] consumeDict = { 'chargeIndex': port, 'reason': self.event_data['reason'], 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime, 'duration': usedTime, 'endUart': self.event_data.get('uartData', ''), } orderPower and consumeDict.update({"orderPower": orderPower}) if billingType == 'time': leftTimeStr = leftTime if leftTime != 65535 else lineInfo['needTime'] consumeDict.update( {'leftTime': leftTimeStr, 'needTime': u'扫码订购%s分钟' % lineInfo['needTime']}) consumeDict.update({'elec': spendElec}) else: consumeDict.update({'needElec': lineInfo['needElec']}) consumeDict.update({'elec': spendElec}) consumeDict.update({'elecFee': self.calc_elec_fee(spendElec)}) # 判断是否需要退款 个人感觉目前的退款判断是有问题的 if 与 elif 之间非互斥条件 # 这个地方 建议抽象出公共方法 ,做分层处理 避免相互之间有矛盾 # 顺序如下: 经销商全局经营策略 < 地址经营策略 < 小于设备经营策略 < 订单经营策略 # 也就是说 优先判断经销商总开关(目前没有)然后判断地址组的免费,地址组的免费开关(目前没有) 然后判断设备退费开关,设备特性 ,最后是订单相关(套餐等) # 这样的话 业务退款逻辑 大部分就可以放在device 的features里面进行处理 """ 伪代码 if not dealer.support_refund: ---> switch true or false / features.support_refund return false if not group.support_refund: ---> group.isFree / switch true or false / features.support_refund return false if not device.support_refund: ---> device.is_auto_refund / device.features.support_refund return false if not order.support_refund: ---> isTempPackage / refund_production enable / ic or id card return false return true """ 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 else: pass logger.info("netPay need refund is {}".format(need_refund)) if not need_refund: ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], { 'open_id': openId, 'device_imei': self.device['devNo'], 'port': int(port), 'isFinished': False }, consumeDict ) extra.append({u'消费明细': u'消费{}(金币)'.format(money)}) else: if isTempPackage: backCoins = VirtualCoin(0) self.refund_net_pay(user, lineInfo, refundRMB, backCoins, 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': int(port), 'isFinished': False }, consumeDict) if billingType == 'elec': billAsService = self.device.bill_as_service_feature billAsServiceSwitch = billAsService.on else: billAsService = None billAsServiceSwitch = False if DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS in consumeDict: extra.append({u'消费明细': u'消费{}金币,退款{}金币'.format(money - backCoins, backCoins)}) if billAsServiceSwitch: elecExpense, serviceExpense = self.calc_service_fee( VirtualCoin(money - backCoins), billAsService.elec_charge, billAsService.service_charge) extra.append({u'电费': u'{}金币'.format(elecExpense)}) extra.append({u'服务费': u'{}金币'.format(serviceExpense)}) elif DEALER_CONSUMPTION_AGG_KIND.REFUNDED_CASH in consumeDict: extra.append({u'消费金额': u'消费{}元,退款{}元'.format(price - refundRMB, refundRMB)}) if billAsServiceSwitch: elecExpense, serviceExpense = self.calc_service_fee( RMB(price - refundRMB), billAsService.elec_charge, billAsService.service_charge) extra.append({u'电费': u'{}元'.format(elecExpense)}) extra.append({u'服务费': u'{}元'.format(serviceExpense)}) self.notify_to_user(self.get_managerialOpenId_by_openId(openId), extra) elif consumeType in ['server', 'card'] and vCardId: # 使用的是虚拟卡 logger.info("finished with vCard!") try: vCard = UserVirtualCard.objects.get(id=vCardId) except DoesNotExist: logger.info('can not find the vCard id = %s' % vCardId) return billingType = lineInfo.get('billingType', 'time') extra = [] extra.append({u'虚拟卡券': u'{}--{}'.format(vCard.cardName, vCard.cardNo)}) if billingType == 'time': extra.append({u'消费明细': u'消费{}分钟'.format(usedTime)}) else: extra.append({u'消耗明细': u'消费{}度'.format(spendElec)}) self.notify_to_user(self.get_managerialOpenId_by_openId(lineInfo["openId"]), extra) consumeDict = { 'chargeIndex': port, 'reason': self.event_data['reason'], 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime, 'duration': usedTime, } orderPower and consumeDict.update({"orderPower": orderPower}) 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': int(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 consumeType == 'card': # 刷的实体卡 logger.info("finished with card") if not cardId: logger.warning('{} finished with card, but no cardId.'.format(self.device)) return try: cardRefundProtectionTime = self.device.get('otherConf', {}).get('cardRefundProtectionTime', 0) if cardRefundProtectionTime > 0: if usedTime < cardRefundProtectionTime: backCoins = money logger.info( 'exec protection refund devNo=<{}>, port=<{}>, backCoins=<{}>'.format(devNo, port, backCoins)) if backCoins > money: backCoins = money except Exception as e: logger.exception(e) card = Card.objects.get(id=cardId) virtual_card = card.bound_virtual_card # type: UserVirtualCard extra = [] extra.append({u'实体卡': u'{}--{}'.format(card.cardName, card.cardNo)}) if billingType == 'time': logger.info("billingType is time.") consumeDict = { 'chargeIndex': port, 'leftTime': leftTimeStr, 'needTime': u'刷卡订购%s分钟' % needTime if virtual_card is None else u'绑定虚拟卡订购%s分钟' % needTime, 'reason': self.event_data['reason'], 'duration': usedTime, 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime, 'elec': spendElec, 'elecFee': self.calc_elec_fee(spendElec), 'cardId': cardId, 'endUart': self.event_data.get('uartData', '') } orderPower and consumeDict.update({"orderPower": orderPower}) try: if virtual_card: self.refund_virtual_card(backCoins, cardId, consumeDict, lineInfo, spendElec, usedTime, virtual_card) extra.append({u'虚拟卡券': u'{}--{}'.format(virtual_card.cardName, virtual_card.cardNo)}) extra.append({u'消费明细': u'消费{}分钟'.format(usedTime)}) else: if self.device.support_dev_type_features('support_wvtiykt'): tradeFare = str(int((money - backCoins) * 100)) extra.append({u'消费明细': u'消费{}金币'.format(money)}) teradeDate = datetime.datetime.now().strftime("%Y%m%d%H%M%S") orderNo = lineInfo.get('orderNo', '') if (datetime.datetime.now() - startTime).total_seconds() < 24 * 60 * 60: resrult = self.notify_to_ykt_norther(card.cardNo, orderNo, tradeFare, teradeDate) if resrult.get("resultCode") != "000000": raise RequestInvalid('卡扣费失败,失败编号%s,失败原因%s' % ( resrult.get("resultCode"), resrult.get("resultMsg"))) card.balance = RMB(float(str(card.balance)) - int(money - backCoins)) card.save() 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}) self.refund_money_for_card(backCoins, str(card.id)) extra.append({u'消费明细': u'消费{}金币,退款{}金币'.format(money - backCoins, backCoins)}) else: extra.append({u'消费明细': u'消费{}金币'.format(money)}) finally: ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], { 'cardId': cardId, 'device_imei': self.device['devNo'], 'port': int(port), 'isFinished': False }, consumeDict) else: logger.info("billingType is elec") consumeDict = { 'chargeIndex': port, 'leftTime': leftTimeStr, 'reason': self.event_data['reason'], 'elec': spendElec, 'elecFee': self.calc_elec_fee(spendElec), 'duration': usedTime, 'needElec': lineInfo['needElec'], 'cardId': cardId, 'endUart': self.event_data.get('uartData', '') } orderPower and consumeDict.update({"orderPower": orderPower}) try: if virtual_card: self.refund_virtual_card(backCoins, cardId, consumeDict, lineInfo, spendElec, usedTime, virtual_card) extra = [ { u'虚拟卡券': u'{}--{}'.format(virtual_card.cardName, virtual_card.cardNo) }, { u'消费明细': u'消费{}度'.format(spendElec) } ] 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}) extra.append({u'消费明细': u'消费{}金币,退款{}金币'.format(money - backCoins, backCoins)}) billAsServiceByCard = self.device.bill_as_service_feature if billAsServiceByCard: billAsServiceCardSwitch = billAsServiceByCard.on else: billAsServiceCardSwitch = False if billAsServiceCardSwitch: elecExpense, serviceExpense = self.calc_service_fee( VirtualCoin(money - backCoins), billAsServiceByCard.elec_charge, billAsServiceByCard.service_charge) extra.append({u'电费': u'{}金币'.format(elecExpense)}) extra.append({u'服务费': u'{}金币'.format(serviceExpense)}) else: extra.append({u'消费明细': u'消费{}金币'.format(money)}) finally: ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], {'cardId': cardId, 'device_imei': self.device['devNo'], 'port': int(port), 'isFinished': False}, consumeDict ) self.notify_to_user(card.managerialOpenId, extra) elif consumeType == 'coin': #: 消费类型为金币,则 logger.info("finished with coin") CoinConsumeRcd = ConsumeRecord.objects.get( orderNo=lineInfo['consumeOrderNo']) # type: ConsumeRecord CoinConsumeRcd.update_for_end(serviceInfo={ DEALER_CONSUMPTION_AGG_KIND.ELEC: spendElec, DEALER_CONSUMPTION_AGG_KIND.ELECFEE: self.calc_elec_fee(spendElec), 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime, 'needTime': u'投币订购%s分钟' % needTime, 'reason': self.event_data['reason'], 'chargeIndex': str(port), DEALER_CONSUMPTION_AGG_KIND.DURATION: usedTime }, finishedTime=recvTime.strftime('%Y-%m-%d %H:%M:%S')) else: logger.warning('{} has not invalid consume type<{}>'.format(self.device, consumeType)) return except Exception as e: logger.exception('deal with jingneng devNo=%s event e=%s' % (devNo, e)) finally: 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) self.event_data.update({'deviceCode': self.device["logicalCode"]}) self.event_data.update({'spendElec': spendElec}) notify_event_to_north_v2(self.device["devNo"], self.event_data) send_event_to_zhejiang(self.dealer, self.device, self.event_data) if self.device.owner.supports("supportBeiJingFengTai"): orderNo = lineInfo.get('orderNo') from apps.web.south_intf.bj_north.api import post_charging_record_info,delete_port_info,post_charging_meta_info post_charging_record_info(orderNo) delete_port_info(self.device.devNo,port) post_charging_meta_info(self.device.devNo,port) #: 启动了端口,主要记录下投币数据 elif cmdCode == CMD_CODE.DEVICE_SUBMIT_OFFLINE_COINS_20: consumeType = self.event_data['consumeType'] # 简易配置 如果配置了新卡流程特性的 并且是扣费的指令的 走新的流程 这个判断的优先级 低于设备的特性 if consumeType == "card": if "support_new_card_proc" in self.device.owner.features: return self.__do_new_card_proc_20() if consumeType == 'coin': service_info = { 'startTime': datetime.datetime.now().strftime(Const.DATETIME_FMT), 'needTime': self.event_data['needTime'], 'needElec': self.event_data['elec'], 'consumeType': consumeType, 'coins': self.event_data['coins'] } consume_order_no = self.record_consume_for_coin( money=RMB(self.event_data['coins']), remarks=u'投币或者刷卡消费', servicedInfo=service_info) service_info.update({'consumeOrderNo': consume_order_no}) Device.update_dev_control_cache(self.device.devNo, {str(self.event_data['port']): service_info}) 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] devObj = Device.objects.get(devNo=self.device['devNo']) billingType = devObj.otherConf.get('billingType', 'time') # 刷卡虚拟卡启动的时候,将consumeRcd 写入到缓存 退费的时候使用 consumeRcdId = rcd.get("consumeOrder", dict()).get("consumeRcdId") if consumeRcdId: self.event_data.update({"consumeRcdId": consumeRcdId}) try: vRcd = VCardConsumeRecord.objects(id=consumeRcdId).first() self.event_data.update({'vCardId': vRcd.cardId}) except Exception as e: # 防止报错退钱给实体卡, 强制添加vCardId self.event_data.update({'vCardId': 'true'}) 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) else: raise NoCommandHandlerAvailable( '[JNDZ]] no command handler for cmd %s, curDevInfo=%s' % (cmdCode, self.event_data)) 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)) 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 _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 notify_to_user(self, openId, extra): """ 推送通知 由于不是订单机制的版本 订单编号比较难以寻找,可能会不准 直接推送到消费记录里面 由客户自行定位 """ group = Group.get_group(self.device['groupId']) self.notify_user_service_complete( service_name='充电', openid=openId, port=self.event_data["port"], address=group["address"], reason=self.event_data.get('reason'), finished_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), extra=extra ) class JNDZNewCardChargeEvent(WorkEvent): """ 和动主板新的刷卡流程,设备类型不同,同时 协议流程也不相同 这个新的流程基本是: 1.用户刷卡,查询卡的余额,不进行扣费 2.用户按下相应的端口按钮,进行启动设备,设备启动后上报20指令,同时服务器对卡进行扣费,然后回复主板,否则主板会一直报 """ def do(self, **args): # 上传的失败指令 devNo = self.device.devNo logger.info('JNDZNewCardChargeEvent charging event detected, devNo=%s,curInfo=%s' % (devNo, self.event_data)) cmdCode = self.event_data.get('cmdCode') if cmdCode == CMD_CODE.SWIPE_CARD_10: self._do_card_balance() elif cmdCode == CMD_CODE.DEVICE_CARD_CHARGE_2D: self._do_charge_consume() else: logger.info("undefined JNDZNewCardChargeEvent cmd <{}>".format(cmdCode)) def _do_card_balance(self): """ 查询卡的余额 :return: """ cardNo = self.event_data.get("cardNo", "") cardCst = self.event_data.get("preFee", 25.6) card = self.update_card_dealer_and_type(cardNo) if not card or not card.openId or card.frozen: return self.deviceAdapter.response_use_card(SWIPE_CARD_RES.INVALID_CARD_02, 0) # 是否存在没有到账的余额 进行充值 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() # 检查卡的余额是否足够 if RMB(card.balance) >= RMB(cardCst): res = SWIPE_CARD_RES.SUCCESS_00 else: res = SWIPE_CARD_RES.BALANCE_NOT_ENOUGH_01 return self.deviceAdapter.response_use_card(res, card.balance) def _do_charge_consume(self): if self.event_data.get("result", RESULT_CODE.FAILURE) == RESULT_CODE.FAILURE: 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 _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: """ pass 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)) class JNDZDoubleSerialCardChargeEvent(ChargingJNDZWorkEvent): def response_use_card(self, res, leftBalance): sourceData = self.event_data.get("sourceData", "") # 与模块驱动约定 最后两个字节为uart_id uartId = sourceData[-2:] # 发送指令使用221 主要考虑的是帧头的问题 self.deviceAdapter.response_use_card(res, leftBalance, uartId) class ChargingJNDZReportEvent(WorkEvent): def do(self, **args): if self.device.owner.supports("supportBeiJingFengTai"): from apps.web.south_intf.bj_north.api import get_update_port_num get_update_port_num(self.device.devNo) class JNDZVirtualCardWorkEvent(WorkEvent): """ 处理新增的在线卡和虚拟卡指令 """ def _do_52(self): # 回复ak ackId = self.event_data['ack_id'] self.deviceAdapter.response_ak_5X(ackId) # 解析参数 cardNo = self.event_data['cardNo'] coins = self.event_data['coins'] needTime = self.event_data['needTime'] port = self.event_data['port'] deductType = self.event_data['deductType'] # 组装package package = { 'coins': coins, 'unit': u'分钟', 'time': needTime, } # 找卡 虚拟卡 card = self.update_card_dealer_and_type(cardNo) # type: Card virtual_card = card.bound_virtual_card # type: UserVirtualCard # 非使用虚拟卡的逻辑 if deductType == CARD_DEDUCTTYPE.DEDUCT_BALANCE: # 不需要组装package 直接card 扣除balance orderNo, cardConsumeOrderNo = self.record_consume_for_card( card=card, money=RMB(coins), attachParas={ "chargeIndex": port }, servicedInfo={ 'needTime': needTime }) res, _ = self.consume_money_for_card( card=card, money=RMB(coins)) # 记录开始充电时间 self.event_data.update({'startTime': time.time()}) if res != 1: logger.warning("consume error!!!, cardNo is {}".format(card.cardNo)) return elif deductType == CARD_DEDUCTTYPE.DEDUCT_TIME: # 使用虚拟卡的逻辑 # 建立订单 orderNo, cardConsumeOrderNo = self.record_consume_for_card( card=card, money=RMB(0.0), attachParas={ 'chargeIndex': port }, servicedInfo={ 'needTime': needTime }) # 进行扣费 vCardConsumeRecord = virtual_card.consume(openId=card.openId, group=self.device.group, dev=self.device, package=package, attachParas={}, nickname=card.cardName) # 记录开始充电时间 self.event_data.update({'startTime': time.time()}) if not vCardConsumeRecord: logger.error('use virtual card to consume failure. id = {}'.format(str(virtual_card.id))) return # 记录开始充电时间 self.event_data.update({'startTime':time.time()}) # 插入虚拟卡的ID 退费的时候使用 self.event_data.update({'vCardId': str(virtual_card.id)}) self.event_data.update({"consumeRcdId": str(vCardConsumeRecord.id)}) # 建立sp 建立缓存 # 注册服务 ServiceProgress.register_card_service( self.device, int(port), card, { 'orderNo': orderNo, 'money': coins, 'coin': coins, 'needTime': needTime, 'cardOrderNo': cardConsumeOrderNo }) billingType = self.device.get("otherConf", dict()).get('billingType', 'time') self.event_data.update({'billingType': billingType}) self.event_data.update({'cardId': str(card.id)}) self.event_data.update({'openId': card.openId}) Device.update_port_control_cache(self.device['devNo'],self.event_data) # 通知用户卡启动 self.notify_user(card.managerialOpenId,'start_device', **{ 'title': u'启动成功!您的卡号是%s,卡别名:%s' % (card.cardNo, card.nickName), 'startTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') }) self.deviceAdapter.response_use_card_52(cardNo) 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_50: cardNo = self.event_data['cardNo'] card = self.update_card_dealer_and_type(cardNo) virtual_card = card.bound_virtual_card if card == None: status = '00' balance = '0000' leftDayQuota = '0000' self.deviceAdapter.response_balance_inquiry_50(balance, leftDayQuota, cardNo, status) return if virtual_card: dayKey = datetime.datetime.now() leftDayQuota = None leftDayQuotaList = virtual_card.calc_left_day_quota(dayKey.strftime("%Y-%m-%d")) for leftDayQuotaDict in leftDayQuotaList: if leftDayQuotaDict['unit'] == u'分钟': leftDayQuota = leftDayQuotaDict['count'] if leftDayQuota > 0: status = '01' else: leftTotalQuotaList = virtual_card.calc_left_total_quota() for leftTotalQuotaDict in leftTotalQuotaList: if leftTotalQuotaDict['unit'] == u'分钟': leftTotalQuota = leftTotalQuotaDict['count'] if leftTotalQuota > 0: status = '04' else: status = '05' if leftDayQuota == None: logger.debug('leftDayQuota not have unit 分钟') status = '00' balance = '0000' leftDayQuota = '0000' power = virtual_card.power self.deviceAdapter.response_balance_inquiry_50(balance, leftDayQuota, cardNo, status,power) return if dayKey > virtual_card.expiredTime: status = '06' elif virtual_card.status == 'freeze': status = '03' power = virtual_card.power self.deviceAdapter.response_balance_inquiry_50('0000', leftDayQuota, cardNo, status,power) else: if int(card.balance) > 0: status = '02' else: status = '07' if card.status == 'freeze': status = '03' power = '0000' self.deviceAdapter.response_balance_inquiry_50(max(float(card.balance),0), '0000', cardNo, status,power) elif cmdCode == CMD_CODE.SWIPE_CARD_52: self._do_52() elif cmdCode == CMD_CODE.SWIPE_CARD_56: # 回复ak ackId = self.event_data['ack_id'] self.deviceAdapter.response_ak_5X(ackId) if self.event_data.get("reasonCode") in ["03", "0B"]: self.__do_fault_record() # 解析参数 port = self.event_data['port'] leftTime = self.event_data['usedTime'] leftElec = self.event_data['usedElec'] refundMoney = RMB(self.event_data['refundMoney']) * 0.1 refundTime = self.event_data['refundTime'] refundType = self.event_data['refundType'] # 找到缓存 lineInfo = Device.get_port_control_cache(devNo, port) # 找到卡 cardId = lineInfo.get('cardId', '') if not cardId: logger.error('cardId is not found') return card = Card.objects.get(id=cardId) virtualCard = card.related_virtual_card money = RMB(lineInfo['coins']) logger.debug('port<{}> cache is: {}'.format(port, str(lineInfo))) 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 'coins' not in lineInfo: logger.debug('port cache has no coins. no order in port {}'.format(port)) return extra = [] consumeDict = { 'chargeIndex': port, 'leftTime': leftTime, 'leftElec': leftElec, 'reason': self.event_data['reason'], } if self.device.is_auto_refund: # 非使用虚拟卡的逻辑 if refundType == CARD_REFUNDTYPE.REFUND_BALANCE: self.refund_money_for_card(refundMoney, str(card.id)) usedTime = int((time.time() - int(lineInfo['startTime'])) / 60) consumeDict.update({"usedTime":usedTime}) consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.REFUND_CARD: refundMoney.mongo_amount}) extra.append({u'消费明细': u'消费{}金币,退款{}金币'.format(money - refundMoney, refundMoney)}) # 使用虚拟卡的逻辑 elif refundType == CARD_REFUNDTYPE.REFUND_TIME: vCardId = lineInfo['vCardId'] spendElec = 0 consumeRcdId = lineInfo.get('consumeRcdId', None) if consumeRcdId is None: logger.info('can not find consume rcd id') return usedTime = int(lineInfo['needTime']) - int(refundTime) consumeDict.update({'usedTime':usedTime}) extra.append({u'虚拟卡券': u'{}--{}'.format(virtualCard.cardName, virtualCard.cardNo)}) extra.append({u'消费明细': u'消费{}分钟'.format(usedTime)}) try: virtualCardConsumeRcd = VCardConsumeRecord.objects.get(id=consumeRcdId) vCard = UserVirtualCard.objects.get(id=vCardId) except DoesNotExist, e: logger.info('can not find the consume rcd id = %s' % consumeRcdId) else: vCard.refund_quota(virtualCardConsumeRcd, usedTime, spendElec, RMB(refundMoney).mongo_amount) ServiceProgress.update_progress_and_consume_rcd( self.device['ownerId'], {'open_id': lineInfo['openId'], 'device_imei': self.device['devNo'], 'port': int(port), 'isFinished': False}, consumeDict ) self.notify_user(self.get_managerialOpenId_by_openId(lineInfo["openId"]), extra) self.deviceAdapter.respone_use_card_finished_56(card.cardNo) if self.device.owner.supports("supportBeiJingFengTai"): orderNo = lineInfo.get('orderNo') from apps.web.south_intf.bj_north import post_charging_record_info post_charging_record_info(orderNo) Device.clear_port_control_cache(self.device.devNo, port) class JNDZYKTCardChargeEvent(WorkEvent): def __get_swipe_card_cache(self): return TempValues.get(swipe_card_cache_key(self.device.devNo)) def __set_swipe_card_cache(self, cache_info): logger.debug('swipe cache info is: {}'.format(cache_info)) return TempValues.set(swipe_card_cache_key(self.device.devNo), cache_info, 900) def do(self, **args): devNo = self.device.devNo logger.info('JNDZNewCardChargeEvent charging event detected, devNo=%s,curInfo=%s' % (devNo, self.event_data)) cmdCode = self.event_data.get('cmdCode') if cmdCode == CMD_CODE.SWIPE_CARD_10: self._do_YKT_card_proc_10() elif cmdCode == CMD_CODE.DEVICE_SUBMIT_OFFLINE_COINS_20: self._do_YKT_card_start_20() def _do_YKT_card_proc_10(self): cardNo = hex(int(self.event_data['cardNo'])).upper()[2:] preFee = RMB(self.event_data['preFee']) devNo = self.device.devNo cardYKT = self.query_to_ykt_norther(cardNo) if cardYKT["code"] != "000000": return self.deviceAdapter.response_use_card(SWIPE_CARD_RES.INVALID_CARD_02, 0) if cardYKT["cardInfo"].get("custStatus") != 1: return self.deviceAdapter.response_use_card(SWIPE_CARD_RES.INVALID_CARD_02, 0) # 先把卡信息更新到YiKaTongCard cardInfo = cardYKT.get('cardInfo') YiKaTongCard.update_card(cardInfo) card = self.update_card_dealer_and_type(cardNo) if not card: pastCardNo = YiKaTongCard.get_cardNo(cardNo) card = self.update_card_dealer_and_type(pastCardNo) if not card: return self.deviceAdapter.response_use_card(SWIPE_CARD_RES.INVALID_CARD_02, 0) card.cardNo = cardNo card.save() card.reload() balance = cardYKT['cardInfo']['oddFare'] * 0.01 if balance <= 0: return self.deviceAdapter.response_use_card(SWIPE_CARD_RES.BALANCE_NOT_ENOUGH_01, balance) card.balance = RMB(balance) card.save() if card.openId == '' or card.frozen: return self.response_use_card(SWIPE_CARD_RES.INVALID_CARD_02, RMB(0)) # 记录卡消费记录以及消费记录 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 }) self.consume_money_for_card(card, preFee) self.deviceAdapter.response_use_card(SWIPE_CARD_RES.SUCCESS_00, balance) self.notify_balance_has_consume_for_card(card, preFee) def _do_YKT_card_start_20(self): """ 启动设备 :return: """ 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] devObj = Device.objects.get(devNo=self.device['devNo']) billingType = devObj.otherConf.get('billingType', 'time') # 刷卡虚拟卡启动的时候,将consumeRcd 写入到缓存 退费的时候使用 consumeRcdId = rcd.get("consumeOrder", dict()).get("consumeRcdId") if consumeRcdId: self.event_data.update({"consumeRcdId": consumeRcdId}) try: vRcd = VCardConsumeRecord.objects(id=consumeRcdId).first() self.event_data.update({'vCardId': vRcd.cardId}) except Exception as e: # 防止报错退钱给实体卡, 强制添加vCardId self.event_data.update({'vCardId': 'true'}) 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(self.device.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)