# -*- coding: utf-8 -*- # !/usr/bin/env python import logging import datetime from apilib.monetary import RMB, VirtualCoin from apilib.utils_string import make_title_from_dict from apilib.utils_sys import memcache_lock from apps.web.constant import Const, DeviceCmdCode, DEALER_CONSUMPTION_AGG_KIND from apps.web.core.exceptions import ServiceException from apps.web.device.models import Group, Device, DevicePortReport, DeviceType from apps.web.eventer import EventBuilder, powerRecorder from apps.web.eventer.base import WorkEvent, FaultEvent from apps.web.user.models import ServiceProgress, MyUser, ConsumeRecord, CardConsumeRecord, CardRechargeOrder, \ UserVirtualCard, VCardConsumeRecord from apps.web.user.transaction_deprecated import refund_money from apps.web.user.utils import vCard_pay_coin logger = logging.getLogger(__name__) def reverse_hex(s): if len(s) == 0: return "" return s[-2:] + reverse_hex(s[:-2]) class builder(EventBuilder): def __getEvent__(self, device_event): event_data = self.deviceAdapter.analyze_event_data(device_event['data']) if not event_data: return event_data['raw_msg'] = device_event # 粤万通的版本比较复杂 没有一个统一的版本 这次力争升级到一个统一的版本 # TODO 补丁 if "ack_id" in device_event and self.device.driverVersion not in [ "v1.0.0", "v1.0.1", "v1.0.2", "v3.0.1", "v3.0.2", "v3.0.3", "v4.0.0", "v4.0.1" ]: self.deviceAdapter._ack(device_event["ack_id"]) funCode = event_data.get("cmdCode") if funCode == "E0": return YueWanTongFaultEvent(self.deviceAdapter, event_data) else: return YueWanTongWorkerEvent(self.deviceAdapter, event_data) class YueWanTongFaultEvent(FaultEvent): def do(self, **args): group = Group.get_group(self.device["groupId"]) if self.is_notify_dealer(): self.notify_dealer( "device_fault", title = u"注意注意您的设备发生故障", device = u"组号:{},二维码编号:{}".format(self.device["groupNumber"], self.device["logicalCode"]), location = u"组名称:{},地址:{}".format(group["groupName"], group["address"]), fault = self.event_data["statusInfo"], notifyTime = str(datetime.datetime.now())[:19] ) warningData = { "warningStatus": 2, "warningDesc": self.event_data["statusInfo"], "warningTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") } # 整机告警 part = self.event_data.get("portStr", "0") Device.update_dev_warning_cache(self.device.devNo, {part: warningData}) try: faultRecord = self.record(faultCode=self.event_data.get("faultCode", ""), detail={"errorCode": self.event_data["faultCode"]}) self.north_to_liangxi(faultRecord) except Exception as e: logger.error(e) finally: sendData = self.event_data.get("portHex") + "01" self.deviceAdapter._send_data("E0", sendData = sendData, cmd = DeviceCmdCode.OPERATE_DEV_NO_RESPONSE) class YueWanTongWorkerEvent(WorkEvent): @staticmethod def _get_cache_key(devNo, port, cmd): return "{}-{}-{}".format(devNo, port, cmd) def _card_start_repeat(self, _type, portStr, cardCode, balance = None, orderNo = None, chargeTime = None, virtualCardLeftDays=None, cardType=None): """ 卡启动的回复 :param _type: 正常卡 非法卡 余额不足卡 :param portStr: 端口号 :param cardCode: 带ICSetupCode的卡号 :param balance: 卡上余额 :param orderNo: 卡启动的订单号 :param chargeTime: 充电的时间 :return: """ if balance is None: balance = "00000000" else: balance = reverse_hex("{:0>8X}".format(int(balance * 100) & 0xFFFFFFFF)) if orderNo is None: orderNo = "00000000000000" else: orderNo = reverse_hex("{:0>14X}".format(int(orderNo))) if chargeTime is None: chargeTime = "0000" else: chargeTime = reverse_hex("{:0>4X}".format(int(chargeTime))) if virtualCardLeftDays is None: leftDays = "0000" else: leftDays = reverse_hex("{:0>4X}".format(int(virtualCardLeftDays))) if cardType is None: cardType = "01" # 随机开锁密码无用 pw = "0000" sendData = cardCode + portStr + _type + balance + cardType + leftDays + orderNo + chargeTime + pw self.deviceAdapter._send_data(funCode = "B0", sendData = sendData, cmd = DeviceCmdCode.OPERATE_DEV_NO_RESPONSE) def _get_port_charge_status(self, port): """ 获取端口的详细信息,向设备请求 :param port: :return: """ result = self.deviceAdapter._read_port_charge_status() portChargeStatus = result.get(str(port), dict()) return portChargeStatus def _card_stop_repeat(self, _type, portStr, cardCode, balance = None, money = None, virtualCardLeftDays=None, cardType=None): """ 刷卡结束充电请求回复, 回复设备后, 设备会将该端口的该单号的充电记录清零 重新记录 :param _type: 回复设备的状态 :param portStr: 端口号 16进制 :param cardCode: 卡号带前缀 ascii :param balance: 余额 可能为负数 :param money: 消费金额 :return: """ if balance is None: balance = "00000000" else: balance = reverse_hex("{:0>8X}".format(int(balance * 100) & 0xFFFFFFFF)) if money is None: money = "00000000" else: money = reverse_hex("{:0>8X}".format(int(money * 100))) if virtualCardLeftDays is None: leftDays = "0000" else: leftDays = reverse_hex("{:0>4X}".format(int(virtualCardLeftDays))) if cardType is None: cardType = "01" sendData = cardCode + portStr + _type + balance + cardType + leftDays + money self.deviceAdapter._send_data(funCode = "B1", sendData = sendData, cmd = DeviceCmdCode.OPERATE_DEV_NO_RESPONSE) def _stop_repeat(self, orderHex, portHex): """ 充电结束回复设备 格式固定 :param orderHex: :param portHex: :return: """ # TODO 补丁 if self.device.driverVersion not in ["v1.0.0", "v1.0.1", "v1.0.2", "v3.0.1", "v3.0.2", "v3.0.3", "v4.0.0", "v4.0.1"]: return sendData = orderHex + portHex + "01" self.deviceAdapter._send_data(funCode = "C1", sendData = sendData, cmd = DeviceCmdCode.OPERATE_DEV_NO_RESPONSE) def _handle_card_stop(self, cardOrderNo, orderNo, totalConsume, chargeTime, elec, card, portStr, reason=None): """ 结束充电事件和刷卡请求结束充电都有这一部分代码 抽出来 :param cardOrderNo: :param orderNo: :param totalConsume: :param chargeTime: :param elec: :param card: :param portStr: :param reason: :return: """ logger.info("[_handle_card_stop] cardOrderNo={}, orderNo={}, totalConsume={}, chargeTime={}, port={}".format(cardOrderNo, orderNo, totalConsume, chargeTime, portStr)) consumeOrder = ConsumeRecord.objects.get(orderNo = orderNo) # 虚拟卡没有支付的情况下 if not consumeOrder.virtual_card_id: consumeOrder.coin = totalConsume.mongo_amount consumeOrder.money = totalConsume.mongo_amount try: consumeOrder.save() except Exception as e: logger.error(e) cardConsumeRecord = CardConsumeRecord.objects.get(orderNo = cardOrderNo) cardConsumeRecord.money = RMB(totalConsume) cardConsumeRecord.balance = card.balance - RMB(totalConsume) try: cardConsumeRecord.save() except Exception as e: logger.error(e) # 更新此次卡的余额 self.update_card_balance(card, card.balance - RMB(totalConsume)) # 结束服务,同时更新cardRecord和consumeRecord的serviceInfo, 以及聚合信息 if reason is None: reason = "刷卡结束充电" consumeDict = { 'chargeIndex': portStr, 'reason': reason, 'actualNeedTime': chargeTime, 'duration': chargeTime, 'elec': elec, 'elecFee': self.calc_elec_fee(float(elec)), DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: RMB(totalConsume).mongo_amount } self._finished_service(portStr, card.openId, consumeDict) def _handle_min_consume(self, totalConsume, chargeTime=None): """ 检测支付值和最小支付值 :param totalConsume: :return: """ if not chargeTime: chargeTime = self.event_data["chargeTime"] otherConf = self.device.get("otherConf", dict()) minConsume = VirtualCoin(int(otherConf.get("minConsume", self.deviceAdapter.DEFAULT_MIN_CONSUME))) refundProtectTime = int(otherConf.get("refundProtectTime", self.deviceAdapter.DEFAULT_MIN_CHARGE_TIME)) if int(chargeTime) < int(refundProtectTime): return VirtualCoin(0) return max(VirtualCoin(minConsume), VirtualCoin(totalConsume)) def _finished_service(self, portStr, openId, consumeInfo): ServiceProgress.update_progress_and_consume_rcd( self.device["ownerId"], { "open_id": openId, "device_imei": self.device.devNo, "port": int(portStr), "isFinished": False }, consumeInfo ) # 清空端口信息 Device.clear_port_control_cache(self.device["devNo"], portStr) def _notify_user_finished(self, user, extra): """ 通知用户 服务结束 :return: """ title = make_title_from_dict(extra) self.notify_user( managerialOpenId=user.managerialOpenId if user else "", templateName="service_complete", title=title, service=u"充电桩充电服务", finishTime=str(datetime.datetime.now())[: 19], remark=u'谢谢您的支持' ) def do(self, **args): cmd = self.event_data['cmdCode'] portStr = self.event_data.get("portStr") cacheKey = self._get_cache_key(self.device["devNo"], portStr, cmd) with memcache_lock(cacheKey, value = '1', expire = 5) as required: if required: if cmd == "B0": self.do_card_start() elif cmd == "B1": self.do_card_stop() elif cmd == "C1": self.do_stop() elif cmd == "B2": self.do_stop() elif cmd == "C0": self.do_report() # 自定义 elif cmd == "CA": self.do_all_report() else: logger.error("error cmd, cmd is {}".format(cmd)) def do_card_start(self): """ 刷卡启动,刷卡的时候设备上报命令,等待设备回复后成功启动 由于是后付费,这个地方仅仅做卡启动记录,费用在结束的时候结算 但是还是需要校验 卡上的钱是否是小于0,负数卡不予启动 :return: """ cardNo = self.event_data.get("cardNo") portStr = self.event_data.get("portStr") portStrHex = self.event_data['portStrHex'] cardCode = self.event_data['cardCode'] lockPorts = self.device.otherConf.get("lockPorts") or list() if portStr in lockPorts: self._card_start_repeat(_type = "03", portStr = portStrHex, cardCode = cardCode) return card = self.update_card_dealer_and_type(cardNo) if not card or not card.openId: self._card_start_repeat(_type = "03", portStr = portStrHex, cardCode = cardCode) logger.info("bad card, cardNo is %s, cannot start port!" % cardNo) return 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() # 这个地方处理包月卡的问题 package = {"unit": u"次", "count": 1} virtualCardCanUse = False virtualCard = card.bound_virtual_card # 虚拟卡使用的四个条件 绑定虚拟卡没有过期、绑定虚拟卡设备组允许、绑定虚拟卡设备种类允许、绑定虚拟卡额度足够使用 if virtualCard and (self.device.get("groupId") in virtualCard.groupIds or "*" in virtualCard.groupIds) and DeviceType.objects.filter(code=self.device.devType.get("code"), id__in=virtualCard.devTypeList) and virtualCard.can_use_today(package): virtualCardCanUse = True # 包月卡不可用并且余额不足的时候 if not virtualCardCanUse and float(card.balance) <= 0: self._card_start_repeat(_type = "02", portStr = portStrHex, cardCode = cardCode, balance = card.balance) self.deviceAdapter._stop_device("00000000000000", portStr) logger.info("negative card, card No is %s, card balance is %s" % (cardNo, card.balance)) return # 可以启动的情况下 优先将卡消费订单创建出来,获取单号, 消费先是0,结束的时候再算 try: orderNo, cardOrderNo = self.record_consume_for_card(card, RMB(0), desc="包月卡消费" if virtualCardCanUse else u"刷卡消费", attachParas={"chargeIndex": portStr}) except Exception as e: logger.error("record to database error!".format(e)) return # 虚拟卡可用的情况下 直接扣费吧 virtualCardPaid = False if virtualCardCanUse: order = ConsumeRecord.objects.get(orderNo=orderNo) # type: ConsumeRecord group = Group.get_group(self.device.groupId) try: vCard_pay_coin(order, virtualCard, self.device, group, package, {}) except ServiceException: # 支付失败的情况况下 重新将虚拟卡支付标记为 否 结算的时候从卡上扣除相应的金额 order.desc = u"虚拟卡支付失败" order.save() else: virtualCardPaid = True # 发送报文回应设备,启动, 刷卡启动时候余额单位为分,充电模式为充满自停,所以时间为最大值 chargeTime = 2 ** 32 - 1 virtualCardLeftDays = (virtualCard.expiredTime - datetime.datetime.now()).days if virtualCardPaid else None cardType = "02" if virtualCardPaid else "01" self._card_start_repeat("01", portStrHex, cardCode=cardCode, balance=card.balance, orderNo=orderNo, chargeTime=chargeTime, virtualCardLeftDays=virtualCardLeftDays, cardType=cardType) logger.info("card start ok! event has been report!") # 启动设备之后,注册服务,并更新端口缓存值 serviceDict = { "orderNo": orderNo, "cardOrderNo": cardOrderNo } if virtualCardPaid: serviceDict.update({"vCardId": str(virtualCard.id)}) ServiceProgress.register_card_service( self.device, int(portStr), card, serviceDict ) portDict = { "status": Const.DEV_WORK_STATUS_WORKING, "isStart": True, "orderNo": orderNo, # 订单号码 "cardOrderNo": cardOrderNo, "cardNo": cardNo, # 卡号 0-99999999 "openId": card.openId, "startTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), # 刷卡启动的初始时间 "vCardId": str(virtualCard.id) if virtualCardPaid else None, "chargeType": "power", # 刷卡的只能是按功率收费, 并且是后付费 "payAfterUse": True } self.notify_user( managerialOpenId=card.managerialOpenId, templateName="consume_notify", title=u"您正在刷卡启动充电桩业务", money=u"使用结束后计算费用", serviceType=u"刷卡消费, 当前启动为: {}端口".format(portStr), finishTime=datetime.datetime.now().strftime(Const.DATETIME_FMT) ) Device.update_dev_control_cache(self.device["devNo"], {portStr: portDict}) @powerRecorder() def do_report(self): """ 处理端口上报消息的事件 :return: """ # 获取原始参数 orderNo = self.event_data.get("orderNo") orderNoHex = self.event_data.get("orderNoHex") portStr = self.event_data.get("portStr") voltage = self.event_data.get("voltage") power = self.event_data.get("power") elec = self.event_data.get("elec") chargeTime = self.event_data.get("chargeTime") temperature = self.event_data.get("temperature") # 获取端口缓存 devCache = Device.get_dev_control_cache(self.device["devNo"]) portCache = devCache.get(portStr, dict()) # 回复设备报文 新版本的设备已经在驱动侧回复,但是可能存在部分老设备 此段回复代码首先保留 迭代几个版本之后可以删除 if self.device.driverVersion in ["v1.0.0", "v1.0.1"]: sendData = orderNoHex + "{:0>2X}".format(int(portStr)) + "01" self.deviceAdapter._send_data(funCode = "C0", sendData = sendData, cmd = DeviceCmdCode.OPERATE_DEV_NO_RESPONSE) # 筛选无效报文 if orderNo != portCache.get("orderNo"): logger.info("port report event, not equal orderNo, useless info! event={} portCache = {}".format(self.event_data, portCache)) return # 更新端口缓存 portCache.update({"voltage": voltage, "elec": elec, "temperature": temperature, "power": power}) Device.update_dev_control_cache(self.device["devNo"], {portStr: portCache}) # 获取计费模式以及消费模式 chargeType = portCache.get("chargeType", self.deviceAdapter.DEFAULT_CHARGE_TYPE) payAfterUse = portCache.get("payAfterUse", self.deviceAdapter.DEFAULT_PAY_AFTER_USE) # 保存上报的心跳数据到日志数据库 reportRecord = DevicePortReport.create( devNo = self.device["devNo"], port = portStr, orderNo = orderNo, openId = portCache["openId"], voltage = voltage, power = power, elec = elec, chargeTime = chargeTime ) if not reportRecord: logger.info("port report event, save db error!") return # 后付费的 一律不处理 因为后付费的费用结算是在使用完毕之后 if payAfterUse: return # 计算各个模式下面 到目前为止使用的金钱 if chargeType == "time": useMoney = self.deviceAdapter._get_time_use_money(reportRecord) elif chargeType == "elec": useMoney = self.deviceAdapter._get_elec_use_money(reportRecord) elif chargeType == "billAsService": elecCharge, serviceCharge = self.deviceAdapter._get_billAsService_use_money(reportRecord) useMoney = round(sum(elecCharge, serviceCharge), 2) else: useMoney = self.deviceAdapter._get_power_use_money(reportRecord) # 当预付费模式下 使用的金钱大于 预付的金钱 立即下发停止指令 if VirtualCoin(useMoney) >= VirtualCoin(portCache["coins"]): result = self.deviceAdapter._stop_device(orderNo, portStr) data = result.get("data") stopData = self.deviceAdapter._parse_device_stop(data) # 能走到这个地方说明钱已经花完了 不需要处理退费 但是需要对用户进行通知 以及清除响应的端口状态 reason = u"动态功率计算,服务已经结束" # 推送通知信息组织 extra = [ {u'结束原因': u'{}'.format(reason)}, {u'设备编号': u'{}-{}'.format(self.device.logicalCode, portStr)}, {u'服务地址': u'{}'.format(self.device.group["address"])}, {u'充电时间': u'{}分钟'.format(chargeTime)}, ] consumeDict = { "chargeIndex": portStr, "reason": reason, 'actualNeedTime': stopData.get("chargeTime"), 'duration': stopData.get("chargeTime"), 'elec': stopData.get("elec"), 'elecFee': self.calc_elec_fee(stopData.get("elec")), } if chargeType == "billAsService": consumeDict.update({ 'elecCharge': elecCharge, 'serviceCharge': serviceCharge, }) extra.extend([ {u'电量费用': u'{}'.format(elecCharge)}, {u'服务费用': u'{}'.format(serviceCharge)}, ]) user = MyUser.objects.filter(openId=portCache["openId"], groupId=self.device.groupId).first() self._notify_user_finished(user, extra) openId = portCache.get("openId") self._finished_service(portStr, openId, consumeDict) @powerRecorder(stop=True) def do_card_stop(self): """ 刷卡结束的充电行为 累计充电的总时长 总时长小于5分钟不予扣费,剩下的正常扣除费用 费用的扣除需要分段累加进行 因为是后付费行为,所以不会存在退费 :return: """ portStr = self.event_data.get("portStr") orderNo = self.event_data.get("orderNo") cardNo = self.event_data.get("cardNo") devCache = Device.get_dev_control_cache(self.device["devNo"]) portCache = devCache.get(portStr, dict()) portStrHex = self.event_data['portStrHex'] cardCode = self.event_data['cardCode'] # 检查订单号 if portCache.get("orderNo") != orderNo or cardNo != portCache.get("cardNo"): # TODO ZJL 这个地方会有可能出现吗?需要回应设备确认么 logger.info("card stop event, orderNo or carNo not equal!, event = {}".format(self.event_data)) return card = self.update_card_dealer_and_type(cardNo) # 向设备发送报文,获取该端口充电的信息,时间是累加值 portChargeStatus = self._get_port_charge_status(portStr) # 检查充电的累计时长, 小于一定的值不予扣费 chargeTime = int(portChargeStatus.get("chargeTime", 0)) if chargeTime < self.deviceAdapter.DEFAULT_MIN_CHARGE_TIME: logger.info("card stop event, chargeTime lt min charge time!") self._card_stop_repeat( _type = "01", portStr = portStrHex, cardCode = cardCode, balance = card.balance, money = 0) extra = [{u"消费金额": u"{}元".format(0),"余额": "{}元".format(RMB(card.balance))}] self.notify_user_service_complete( service_name='充电', openid=card.managerialOpenId, port=portStr, address=self.device.group['address'], reason=self.event_data.get('reasonDesc'), finished_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), extra=extra) # 不扣费的情况下也属于正常结束 端口状态需要清空 Device.clear_port_control_cache(self.device["devNo"], portStr) return # 计算该次刷卡的累计消费 totalConsume = DevicePortReport.calculate_consume( orderNo = orderNo, allChargeTime = chargeTime, lastPower = portChargeStatus.get("power"), interval_map = self.deviceAdapter._get_power_interval_map(), devNo = self.device["devNo"], port = portStr, openId = card.openId, voltage = portChargeStatus.get("voltage"), elec = portChargeStatus.get("elec") ) totalConsume = self._handle_min_consume(totalConsume, chargeTime) # 最后回复设备表示已经接收到刷卡请求结束充电的信息,以便主板清空端口累计信息 orderNo = portCache.get("orderNo") consumeOrder = ConsumeRecord.objects.get(orderNo=orderNo) virtualCard = None virtualCardLeftDays = None if consumeOrder.virtual_card_id: vConsumeRecord = VCardConsumeRecord.objects.filter(id=consumeOrder.virtual_card_id).first() virtualCard = UserVirtualCard.objects.filter(id=vConsumeRecord.cardId).first() virtualCardLeftDays = (virtualCard.expiredTime - datetime.datetime.now()).days if virtualCard: _type = "01" balance = card.balance elif int(card.balance-totalConsume) < 0: _type = "02" balance = card.balance - totalConsume else: _type = "01" balance = card.balance - totalConsume self._card_stop_repeat( _type = _type, portStr = portStrHex, cardCode = cardCode, balance = balance, money = float(totalConsume), virtualCardLeftDays=virtualCardLeftDays, cardType="02" if virtualCard else "01" ) self._handle_card_stop( cardOrderNo = portCache.get("cardOrderNo"), orderNo = portCache.get("orderNo"), totalConsume = totalConsume, chargeTime = chargeTime, elec = portChargeStatus.get("elec"), card = card, portStr = portStr ) extra = [{u"消费金额": "{}元".format(totalConsume),"余额": "{}元".format(RMB(card.balance))}] self.notify_user_service_complete( service_name='充电', openid=card.managerialOpenId, port=portStr, address=self.device.group['address'], reason=self.event_data.get('reasonDesc'), finished_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), extra=extra) @powerRecorder(stop=True) def do_stop(self): """ 处理充电结束上报 充电结束上报和刷卡请求结束充电的区别在于: 充电结束表示主板在该次充电服务已经结束,端口信息已经清空,信息做最后上报 刷卡请求结束充电,主板端口信息未清空,但是端口服务已经停止,需要请求最后一次数据 处理流程与 刷卡请求结束充电 基本类似 :return: """ # 获取原始参数 orderNo = self.event_data.get("orderNo") orderNoHex = self.event_data.get("orderNoHex") portStr = self.event_data.get("portStr") portHex = self.event_data.get("portHex") voltage = self.event_data.get("voltage") power = self.event_data.get("power") elec = self.event_data.get("elec") chargeTime = self.event_data.get("chargeTime") reason = self.event_data.get("reason") portCache = Device.get_port_control_cache(self.device.devNo, portStr) try: self._stop_repeat(orderNoHex, portHex) except Exception as e: logger.exception("devNo is <{}>, orderNoHex is <{}> port is <{}>, error is {}".format(self.device.devNo, orderNoHex, portHex, e)) # 检查订单号 if portCache.get("orderNo") != orderNo: logger.info("stop event, orderNo not equal! event = {} portCache = {}".format(self.event_data, portCache)) return # 将最后一次的充电信息存储 reportRecord = DevicePortReport.create( devNo = self.device["devNo"], port = portStr, orderNo = orderNo, openId = portCache["openId"], voltage = voltage, power = power, elec = elec, chargeTime = chargeTime ) # 推送通知信息组织 extra = [ {u'结束原因': u'{}'.format(reason)}, {u'设备编号': u'{}-{}'.format(self.device.logicalCode, portStr)}, {u'服务地址': u'{}'.format(self.device.group["address"])}, {u'充电时间': u'{}分钟'.format(chargeTime)}, ] # 订单信息组织 consumeDict = { "chargeIndex": portStr, "reason": self.event_data.get("reason"), 'actualNeedTime': chargeTime, 'duration': chargeTime, 'elec': self.event_data.get("elec"), 'elecFee': self.calc_elec_fee(self.event_data.get("elec")), } # 获取计费模式以及消费模式 chargeType = portCache.get("chargeType", self.deviceAdapter.DEFAULT_CHARGE_TYPE) payAfterUse = portCache.get("payAfterUse", self.deviceAdapter.DEFAULT_PAY_AFTER_USE) # 计算各个模式下面 到目前为止使用的金钱 if chargeType == "time": useMoney = self.deviceAdapter._get_time_use_money(reportRecord) elif chargeType == "elec": useMoney = self.deviceAdapter._get_elec_use_money(reportRecord) elif chargeType == "billAsService": elecCharge, serviceCharge = self.deviceAdapter._get_billAsService_use_money(reportRecord) useMoney = elecCharge + serviceCharge extra.extend([ {u'电量费用': u'{}'.format(elecCharge)}, {u'服务费用': u'{}'.format(serviceCharge)}, ]) consumeDict.update({ 'elecCharge': elecCharge, 'serviceCharge': serviceCharge, }) else: useMoney = self.deviceAdapter._get_power_use_money(reportRecord) # 根据用户使用的时间 以及经销商所设置的最小使用金额等等信息 判定最终所需支付的金额 usedCoins = self._handle_min_consume(useMoney) consumeDict.update({ DEALER_CONSUMPTION_AGG_KIND.COIN: usedCoins.mongo_amount }) # 刷卡的单独走 if portCache.get("cardNo"): card = self.update_card_dealer_and_type(portCache.get("cardNo")) self._handle_card_stop( cardOrderNo = portCache.get("cardOrderNo"), orderNo = portCache.get("orderNo"), totalConsume = usedCoins, chargeTime = chargeTime, elec = self.event_data.get("elec"), card = card, portStr = portStr, reason = self.event_data.get("reason") ) return user = MyUser.objects.filter(openId=portCache["openId"], groupId=self.device.groupId).first() # type: MyUser self._notify_user_finished(user, extra) # 对金钱的处理 先付费 如果自动退款打开并且存在退款金额 if not payAfterUse: backCoins = VirtualCoin(portCache["coins"]) - VirtualCoin(usedCoins) refundProtectTime = int(self.device.get("otherConf", dict()).get("refundProtectTime", self.deviceAdapter.DEFAULT_MIN_CHARGE_TIME)) if int(chargeTime) < int(refundProtectTime): refund_money(self.device, backCoins, portCache.get("openId")) self._notify_user_refund(user, backCoins, orderNo) consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: backCoins.mongo_amount}) elif self.device.is_auto_refund and backCoins > VirtualCoin(0): refund_money(self.device, backCoins, portCache.get("openId")) self._notify_user_refund(user, backCoins, orderNo) consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: backCoins.mongo_amount}) else: logger.info("device not refund, devNo = {}, refund coins = {}, autoRefund = {}, chargeTime= {}".format(self.device.devNo, backCoins, self.device.is_auto_refund, chargeTime)) # 后付费 修改订单的金额 并对用户的个人账户进行扣款 else: consumeOrder = ConsumeRecord.objects.get(orderNo=orderNo) consumeOrder.money = usedCoins.mongo_amount consumeOrder.coin = usedCoins.mongo_amount try: consumeOrder.save() except Exception as e: logger.error(e) # 对于个人账户的金币减去 相应的消费值 try: user.total_consumed = user.total_consumed + usedCoins user.balance = user.balance - usedCoins user.save() except Exception as e: logger.error(e) self._finished_service(portStr, user.openId, consumeDict) def do_all_report(self): """ 自定义的指令 将主板的主动上报修改为 模块侧轮询然后组装 """ subChargeList = self.event_data["subChargeList"] for _sub in subChargeList: self.__class__(self.deviceAdapter, _sub).do_report() def _notify_user_refund(self, user, coins, orderNo, refundType=u"金币退款"): """ 通知用户金币退回 :return: """ group = Group.get_group(self.device.groupId) self.notify_user( managerialOpenId=user.managerialOpenId if user else "", templateName="refund_coins", title=u"\\n\\n退款方式:\\t\\t{refundType}\\n\\n退款地址:\\t\\t{group}\\n\\n退款设备:\\t\\t{logicalCode}\\n\\n消费单号:\\t\\t{orderNo}".format( refundType=refundType, group=group["address"], logicalCode=self.device.logicalCode, orderNo=orderNo ), backCount="{:.2f}".format(float(coins)), finishTime=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") )