123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835 |
- # -*- 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")
- )
|