# coding=utf-8 import datetime import logging from arrow import Arrow from django.conf import settings from typing import TYPE_CHECKING from apilib.monetary import VirtualCoin, RMB from apps.web.constant import DEALER_CONSUMPTION_AGG_KIND, START_DEVICE_STATUS from apps.web.core.device_define.wxlz import DefaultParams from apps.web.eventer import EventBuilder from apps.web.eventer.base import WorkEvent, ComNetPayAckEvent from apps.web.user.utils import freeze_user_balance, clear_frozen_user_balance from apps.web.utils import set_start_key_status from apps.web.user.models import MyUser if TYPE_CHECKING: from apps.web.eventer import Event from apps.web.user.models import ConsumeRecord logger = logging.getLogger(__name__) class builder(EventBuilder): def __getEvent__(self, device_event): # type:(dict)->Event if "funCode" in device_event: if device_event["funCode"] == "async_settings": return AysncSettingsEvent(self.deviceAdapter, device_event) # 启动时候的回复解析 elif "order" in device_event: return ComStartOrderAckEvent(self.deviceAdapter, device_event["order"]) # 需要ack的信息 elif "order_id" in device_event: return ComStartOrderAckEvent(self.deviceAdapter, device_event) else: logger.warning("device = {}, receive not registered event = {}".format(self.device.devNo, device_event)) return class ComStartOrderAckEvent(ComNetPayAckEvent): def do_running_order(self, order, result): # type: (ConsumeRecord, dict) -> None """ 处理运行订单 :param order: 用户服务器订单 :param result: device_event 设备侧订单 :return: """ # 订单消息已经被回复过 if order.status in ["running", "finished"]: logger.debug('order<{}> no need to deal. this has done.'.format(repr(order))) return # 启动设备的时候 设备实际启动成功 但是订单串口超时 不知道订单的明确状态 后面启动时间又重新上报 if order.status == "unknown": errorDesc = u"设备信号恢复,订单正常运行" logger.info("order <{}> timeout to running") # 正常运行的订单 else: errorDesc = u"" if 'master' in result: order.association = { 'master': result['master'] } order.servicedInfo.update({'masterOrderNo': result['master']}) order.errorDesc = errorDesc order.isNormal = True order.status = 'running' order.startTime = datetime.datetime.fromtimestamp(result['sts']) order.save() set_start_key_status(start_key=order.startKey, state=START_DEVICE_STATUS.FINISHED, order_id=str(order.id)) def do_finished_order(self, order, result): # type: (ConsumeRecord, dict) -> None """ 处理结束运行订单 :param order: 用户服务器订单 :param result: device_event 设备侧订单 :return: """ # 子单归并主单 if 'sub' in result: order.association = { 'sub': [item['order_id'] for item in self.event_data['sub']] } order.servicedInfo.update( {'subOrderNo': '{}'.format(', '.join([item['order_id'] for item in self.event_data['sub']]))}) # 主单归并自身 elif 'master' in result: order.association = { 'master': result['master'] } order.servicedInfo.update({'masterOrderNo': result['master']}) # 此时理论上服务器订单状态有三种可能(finished在上层已经被排除) # 正常的状态 相当于订单由运行状态 即将切换为finished状态 if order.status == "running": order.isNormal = True order.status = "finished" order.errorDesc = u"" order.finishedTime = datetime.datetime.fromtimestamp(result['fts']) # 非正常状态 相当于订单最开始串口超时 然后直接变为结束 elif order.status == "unknown": order.isNormal = True order.status = "finished" order.errorDesc = u"设备信号恢复,订单正常结束(0001)" order.startTime = datetime.datetime.fromtimestamp(result['sts']) order.finishedTime = datetime.datetime.fromtimestamp(result['fts']) # 正常状态 相当于订单启动失败或者是中间running单没有上来 elif order.status == "created": order.isNormal = True order.status = "finished" order.errorDesc = u"设备信号恢复,订单正常结束(0002)" order.startTime = datetime.datetime.fromtimestamp(result['sts']) order.finishedTime = datetime.datetime.fromtimestamp(result['fts']) else: logger.warning('order<{}> status = <{}> to finished. no dealwith'.format(repr(order), order.status)) order.save() set_start_key_status(start_key=order.startKey, state=START_DEVICE_STATUS.FINISHED, order_id=str(order.id)) def do_finished_event(self, order, sub_orders, merge_order_info): # type:(ConsumeRecord, list, dict) -> None """ 订单的状态已经完成 进一步事件 扣费等等 :param order: 处理完毕的订单(主订单) :param sub_orders: 子订单 :param merge_order_info: 合并单的信息 :return: """ order.reload() needTime = self.event_data["charge_time"] # 订购的总时长 单位分钟 duration = self.event_data["duration"] # 订单从开始运行到结束的时间 单位秒 cst = self.event_data["cst"] # 订单的花费 单位 0.001分 方便模块累加 money = self.event_data["money"] # 订单的花费 单位 1分 实际需要扣除的费用 user = MyUser.objects.filter(openId=order.openId, groupId=self.device.groupId).first() # type:MyUser chargeTime = duration / 60 if self.device.group.is_free: consumeMoney = VirtualCoin(0) paymentInfo = { 'via': 'free', 'money': RMB(0.00).mongo_amount, 'coins': VirtualCoin(0.00).mongo_amount, 'deduct': [] } else: consumeMoney = VirtualCoin(money / 100.0) paymentInfo = { 'via': 'coins', 'money': RMB(consumeMoney).mongo_amount, 'coins': VirtualCoin(consumeMoney).mongo_amount, 'deduct': [{ 'id': str(user.id), 'field': 'balance', 'before': VirtualCoin(user.balance).mongo_amount, 'coins': VirtualCoin(consumeMoney).mongo_amount }] } order.paymentInfo = paymentInfo order.coin = consumeMoney order.money = consumeMoney order.save() # 对于用户的金额进行结算 freeze_user_balance(self.device, self.device.group, order) clear_frozen_user_balance(self.device, order, duration, spendElec=0, backCoins=VirtualCoin(0), user=user) user.pay(consumeMoney) # 组织消费信息 consumeDict = { "cst": cst, "reason": self._get_finish_reason(), "billing_power": self.event_data.get("billing_power") or 0, DEALER_CONSUMPTION_AGG_KIND.DURATION: chargeTime, DEALER_CONSUMPTION_AGG_KIND.SPEND_MONEY: consumeMoney.mongo_amount, } extra = [ {u"本次订购时长": "{}分钟".format(needTime)}, {u"本次实际使用时长": "{}分钟".format(chargeTime)}, {u"消费金额": "{}(金币)".format(consumeMoney.amount)} ] order.update_service_info(consumeDict) self.notify_user_service_complete( service_name='充电', openid=user.managerialOpenId, port=str(order.used_port), address=order.address, reason=consumeDict["reason"], finished_time=order.finishedTime.strftime('%Y-%m-%d %H:%M:%S'), extra=extra ) def merge_order(self, master_order, sub_orders): # type:(ConsumeRecord, list)->dict """ 后付费 合单 只合并订购时间 :param master_order: :param sub_orders: :return: """ start_time = Arrow.fromdatetime(master_order.startTime, tzinfo=settings.TIME_ZONE) portCache = { "openId": master_order.openId, "consumeType": "mobile", "needKind": "needTime", "estimatedTs": int(start_time.timestamp + 720 * 60 * 60), "coins": str(master_order.coin) } needTime = self.deviceAdapter._check_package(master_order.package) for _sub in sub_orders: needTime += self.deviceAdapter._check_package(_sub.package) portCache["needValue"] = needTime return portCache def _get_finish_reason(self): if "reason" in self.event_data: return DefaultParams.REASON_MAP.get(self.event_data["reason"], DefaultParams.DEFAULT_REASON) return DefaultParams.DEFAULT_REASON class AysncSettingsEvent(WorkEvent): def do(self, **args): self.deviceAdapter._async_settings() return