# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import logging import time from mongoengine import DoesNotExist from typing import Optional from apilib.monetary import RMB, VirtualCoin from apps.web.constant import Const from apps.web.core.device_define.huopo import HuopoCacheMgr, DefaultParams from apps.web.core.exceptions import ServiceException from apps.web.device.models import DeviceDict, Group, Device from apps.web.eventer.base import WorkEvent from apps.web.eventer import EventBuilder from apps.web.user.models import ServiceProgress, MyUser, ConsumeRecord, CardConsumeRecord, MonthlyPackage, Card logger = logging.getLogger(__name__) class builder(EventBuilder): def __getEvent__(self, device_event): event_data = self.deviceAdapter.analyze_event_data(device_event['data']) return HuoPoEvent(self.deviceAdapter, event_data) """ 经销商设置 功能 方向 进入 左(1) 出门 右(2) """ class HuoPoEvent(WorkEvent): def __init__(self, smartBox, event_data): super(HuoPoEvent, self).__init__(smartBox, event_data) @staticmethod def _get_service_progress(openId): """检查是否是出门""" devTypeCode = Const.DEVICE_TYPE_CODE_HP_GATE sp = ServiceProgress.objects.filter(open_id = openId, devTypeCode = devTypeCode, isFinished = False) if not sp: return return sp @staticmethod def _check_parking(dev): # type:(DeviceDict)->bool _key = "maxParking" maxParking = dev["otherConf"].get(_key, 250) serviceInfo = HuopoCacheMgr.get_parking_cache(dev.groupId) if serviceInfo is not None: parkingInfo = serviceInfo.get("parkingInfo", {}) parkingNum = parkingInfo.get(_key, 0) else: parkingNum = 0 return int(parkingNum) < int(maxParking) @staticmethod def _update_parking(dev, enter = True): # type:(DeviceDict, Optional[bool])->None _key = "maxParking" serviceInfo = HuopoCacheMgr.get_parking_cache(dev.groupId) if serviceInfo is not None: parkingInfo = serviceInfo.get("parkingInfo", {}) parkingNum = parkingInfo.get(_key, 0) else: parkingInfo = {} parkingNum = 0 if enter: parkingNum += 1 else: if parkingNum > 0: parkingNum -= 1 parkingInfo.update({_key: parkingNum}) HuopoCacheMgr.set_parking_cache(dev.groupId, {"parkingInfo": parkingInfo}) def do_virtual_card_user(self, card, vCard, dutou): """ 虚拟卡用户直接启动,做单次记录 :param dutou: :param card: :param vCard: :return: """ # 开启道闸 self.deviceAdapter._open() oper = 'enter' if dutou == '1' else 'out' # 记录相应的消费情况 group = Group.get_group(self.device["groupId"]) if oper == 'enter': attachParas = { "cardId": str(card.id), "vCardId": str(vCard.id), "startOper": oper, "startDutou": dutou } orderNo, cardOrderNo = self.record_consume_for_card(card, money=RMB(0.0), desc=u'使用绑定的虚拟卡') vCard.consume_hp_gate(openId = card.openId, group = group, dev = self.device, attachParas=attachParas) consumeRecord = ConsumeRecord.objects.filter(orderNo=orderNo).first() consumeRecord.attachParas = attachParas consumeRecord.attachParas.update({ "orderNo": orderNo, "cardOrderNo": cardOrderNo }) consumeRecord.servicedInfo.update({ "oper": u'进门 卡号为%s' % card.cardNo if oper == 'enter' else u'读头数据异常' }) try: consumeRecord.save() except Exception as e: logger.exception(e) else: # TODO: 只查3个月内的 consumeRecord = ConsumeRecord.objects(openId = card.openId, dateTimeAdded__gte = ( datetime.datetime.now() - datetime.timedelta(days = 90)), devNo = self.device['devNo']).order_by('-dateTimeAdded').hint( [('openId', 1), ('dateTimeAdded', -1)]).first() if not consumeRecord: logger.warning("[do_virtual_card_user] not find consumeRecord, openId={}, devNo={}".format(card.openId, self.device.devNo) ) return duration = int((datetime.datetime.now() - consumeRecord.dateTimeAdded).total_seconds() / 60) if consumeRecord.servicedInfo != {"oper": u'进门 卡号为%s' % card.cardNo}: return else: consumeRecord.finishedTime = datetime.datetime.now() consumeRecord.attachParas.update({ "endOper": oper, "endDutou": dutou }) consumeRecord.servicedInfo.update({ "oper": u'出门 卡号为%s' % card.cardNo if oper == 'out' else u'读头数据异常', "duration": duration, "finishedTime": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') }) try: consumeRecord.save() except Exception as e: logger.exception(e) # 将之前临时启动的服务清空 try: for sp in self._get_service_progress(card.openId): sp.update(isFinished = True) except Exception as e: logger.error(e) def do_monthly_package_user(self, card, monthlyPackage, dutou): # type:(Card, MonthlyPackage, str) -> None """ 包月用户使用包月卡 :param card: :param monthlyPackage: :param dutou: :return: """ # 尝试启动 try: self.deviceAdapter._open() except ServiceException: return except Exception as e: logger.exception(e) return # 启动成功的情况下记录消费 attachParas = { "cardId": str(card.id), "monthlyPackageId": str(monthlyPackage.id), } servicedInfo = { "oper": u'{} 卡号为 {}'.format(u"进门" if dutou == "1" else u"出门", card.cardNo) } orderNo, cardOrderNo = self.record_consume_for_card( card, money=RMB(0), desc=u"使用包月套餐", attachParas=attachParas, servicedInfo=servicedInfo ) order = ConsumeRecord.objects.get(orderNo=orderNo) # 包月套餐抵扣 monthlyPackage.deduct(order) def do_normal_user(self, card, dutou): sp = self._get_service_progress(card.openId) dev = Device.get_dev(self.device["devNo"]) user = MyUser.objects.filter(openId = card.openId, group = dev.get("groupId")).first() oper = 'enter' if dutou == '1' else 'out' if not sp: # 校验停车人数 if not self._check_parking(dev): self.notify_service_fault(card, u"停车人数已经达到上限!") return # 校验卡上是否还有余额,没有余额的卡不允许启动 if RMB(card.balance) <= RMB(10): self.notify_service_fault(card, u"月票已过期或者尚未购买月票!当前实体卡内余额{},小于最低启动金额{}!".format(card.balance, 10)) return washConfig = self.device["washConfig"] attachParas = { "washConfig": washConfig, "cardId": str(card.id), "startOper": oper, "startDutou": dutou } orderNo, cardOrderNo = self.record_consume_for_card(card, money = RMB(0.0)) consumeRecord = ConsumeRecord.objects.filter(orderNo = orderNo).first() consumeRecord.attachParas = attachParas consumeRecord.attachParas.update({ "orderNo": orderNo, "cardOrderNo": cardOrderNo }) consumeRecord.servicedInfo.update({ "oper": u'进门 卡号为%s' % card.cardNo if oper == 'enter' else u'读头数据异常' }) # 开门 self.deviceAdapter._open() # 建立当前服务信息 new_service_progress = ServiceProgress( open_id = card.openId, device_imei = self.device["devNo"], devTypeCode = Const.DEVICE_TYPE_CODE_HP_GATE, attachParas = attachParas, start_time = int(time.time()), finished_time = int(time.time()) + 3600 * 24 * 365, consumes = [str(consumeRecord.id)], cardId = str(card.id), ) try: new_service_progress.save() consumeRecord.save() except Exception as e: logger.exception(e) # 更新最大停车数量 self._update_parking(self.device) else: sp = sp.first() if sp.cardId != str(card.id): return now_time = time.time() start_time = int(sp.start_time) duration = int(now_time - start_time) # 时间求整 # 校验是否足够支付 consumeRecord = ConsumeRecord.objects.get(id=sp.consumes[0]) cardOrderNo = consumeRecord.attachParas.get("cardOrderNo") needBalance = self.deviceAdapter.calculate_consume(duration=duration, package=self.device.package("1", False).get("1", dict())) if card.balance < RMB(needBalance): self.notify_service_fault(card, u"卡内余额不足以支付此次消费,请先充值") return # 开启闸门 self.deviceAdapter._open() # 扣钱 self.update_card_balance(card, RMB(card.balance - RMB(needBalance))) # 完善消费订单 consumeRecord.money = RMB(needBalance) consumeRecord.coin = VirtualCoin(needBalance) consumeRecord.finishedTime = datetime.datetime.now() consumeRecord.attachParas.update({ "endOper": oper, "endDutou": dutou }) consumeRecord.servicedInfo.update({ "oper": u'出门 卡号为%s' % card.cardNo if oper == 'out' else u'读头数据异常', }) if cardOrderNo: try: cardConsumeRecord = CardConsumeRecord.objects.get(orderNo = cardOrderNo) cardConsumeRecord.balance = card.balance cardConsumeRecord.money = RMB(needBalance) cardConsumeRecord.finishedTime = datetime.datetime.now() except DoesNotExist: pass # 结束服务进程 sp.isFinished = True sp.finishedTime = int(time.time()) try: consumeRecord.save() sp.save() except Exception as e: logger.exception(e) self._update_parking(self.device, enter = False) self.deviceAdapter.notify_user(consumeRecord, True) def notify_service_fault(self, card, service): self.notify_user( card.managerialOpenId if card else "", "service_complete", **{ "title": u"服务失败", "service": service, "finishTime": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'remark': u'谢谢您的支持' } ) def _registe_sn(self, snNum): conf = self.device.get("otherConf", dict()) defaultSnNum = conf.get("snNum", DefaultParams.DEFAULT_SN_NUM) if defaultSnNum != defaultSnNum: return conf.update({"snNum": snNum}) Device.objects.get(devNo=self.device.devNo).update(otherConf=conf) Device.invalid_device_cache(self.device.devNo) def do(self, **args): snNum = self.event_data.get("snNum") self._registe_sn(snNum) cardNo = self.event_data.get("cardNo") dutou = self.event_data.get("dutou") # 查询虚拟卡 card = self.update_card_dealer_and_type(cardNo) if not card or not card.openId or card.frozen: logger.info("card can not be used, cardNo is %s , devNo is %s" % (cardNo, self.device.devNo)) return # 查询该地址下是否有可用虚拟卡 vCard = card.related_virtual_card if not vCard or not vCard.can_use_hp_gate(): self.do_normal_user(card, dutou) # 虚拟卡用户处理 else: self.do_virtual_card_user(card, vCard, dutou)