# coding=utf-8 import datetime import logging import typing from arrow import Arrow from django.conf import settings from apilib.monetary import VirtualCoin from apps.web.constant import START_DEVICE_STATUS, DEALER_CONSUMPTION_AGG_KIND, DeviceCmdCode from apps.web.core.device_define.cmCZ import FINISH_REASON_MAP, CARD_RESPONSE, CARD_CST_MIN, CARD_CST from apps.web.core.networking import MessageSender from apps.web.device.models import Device from apps.web.eventer.base import WorkEvent, ComNetPayAckEvent, IdStartAckEvent, AckEventProcessorIntf from apps.web.user.utils import clear_frozen_user_balance from apps.web.utils import set_start_key_status from apps.web.user.models import ConsumeRecord, Card, CardRechargeOrder logger = logging.getLogger(__name__) class cmCZSubEventWorker(WorkEvent): def do(self, **args): funCode = self.event_data["cmdCode"] if funCode == "26": return self._do_report() if funCode == "10": return self._do_card() def _do_report(self): self.device.set_online(31) portInfo = self.event_data.pop("portInfo") Device.update_dev_control_cache(self.device.devNo, portInfo) def _do_card(self): cardNo = self.event_data["cardNo"] cardCst = self.device.otherConf.get("cardCst", CARD_CST) cardCstMin = self.device.otherConf.get("cardCstMin", CARD_CST_MIN) card = self.update_card_dealer_and_type(cardNo) # type: Card if not card: logger.info("[cmCZSubEventWorker _do_card] not find card, devNo = {}, cardNo = {}".format(self.device.devNo, cardNo)) self.device.deviceAdapter.response_card(CARD_RESPONSE.FAIL, 0, 0) return if card.frozen: logger.info("[cmCZSubEventWorker _do_card] card is frozen, devNo = {}, cardNo = {}".format(self.device.devNo, cardNo)) self.device.deviceAdapter.response_card(CARD_RESPONSE.FAIL, 0, 0) return # 如果存在的话 充值一次卡 order = CardRechargeOrder.get_last_to_do_one(str(card.id)) self.recharge_id_card(card=card, rechargeType='append', order=order) card.reload() if VirtualCoin(card.balance) <= VirtualCoin(cardCstMin): logger.info("[cmCZSubEventWorker _do_card] card not enough, devNo = {}, cardNo = {}, balance = {}, min = {}".format(self.device.devNo, cardNo, card.balance, cardCstMin)) self.device.deviceAdapter.response_card(CARD_RESPONSE.NOT_ENOUGH, 0, 0) return # 剩下的情况回复主板 启动卡的金额为卡上余额和卡启动金额的最小值 cst = min(VirtualCoin(card.balance), VirtualCoin(cardCst)) logger.info("[cmCZSubEventWorker _do_card] card success, devNo = {}, cardNo = {}, balance = {}, cst = {}".format(self.device.devNo, cardNo, card.balance, cst)) self.device.deviceAdapter.response_card(CARD_RESPONSE.SUCCESS, cst, card.balance, cardNo) class cmCZSubComNetPayAckEvent(ComNetPayAckEvent): def ack_msg(self): payload = { 'order_id': self.event_data['order_id'], 'order_type': self.event_data['order_type'], 'status': self.event_data['status'] } MessageSender.send_no_wait( device=self.device.deviceAdapter.masterDevice, cmd=DeviceCmdCode.EVENT_ACK, payload=payload ) def do_impl(self): order_id = self.event_data['order_id'] order = ConsumeRecord.objects(ownerId = self.device.ownerId, orderNo = order_id).first() # type: ConsumeRecord if not order: logger.debug('order is not exist.'.format(self.event_data['order_id'])) return if order.status == 'finished': logger.debug('order<{}> has been fished.'.format(repr(order))) return addr, port = self.event_data['port'].split("-") self.event_data["port"] = int(port) if order.used_port != self.event_data['port']: logger.error('port is not equal. {} != {}'.format(self.event_data['port'], order.used_port)) return if self.pre_processor: self.event_data = self.pre_processor.pre_processing(self.device, self.event_data) if self.event_data['status'] in ['running', 'finishing']: return self.deal_running_event(order) if self.event_data['status'] == 'finished': return self.deal_finished_event(order) def do_running_order(self, order, result): # type: (ConsumeRecord, dict)->None """ 处理正在运行的订单 """ # 订单消息已经被回复过 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"" 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 """ 处理结束的订单 """ portCache = Device.get_port_control_cache(self.device.devNo, str(order.used_port)) self.event_data["portCache"] = portCache 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 deal with'.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): """ 订单状态已经变更完成 进一步处理 完成返费 结束订单等等 """ order.reload() totalCst = self.event_data["totalCst"] / 3600.0 sts = self.event_data["sts"] fts = self.event_data["fts"] coins = merge_order_info["coins"] cst = min(VirtualCoin(totalCst), VirtualCoin(coins)) backCoins = VirtualCoin(coins) - cst duration = (fts - sts) / 60 extra = [ {u"本次订购金额": "{}".format(VirtualCoin(coins).amount)}, {u"本次实际使用时长": "{}分钟".format(duration)}, {u"消费金额": "{}(金币)".format(cst.amount)}, ] if backCoins > VirtualCoin(0): extra.append({u"退费金额": u"{}(金币)".format(VirtualCoin(backCoins).amount)}) clear_frozen_user_balance(self.device, order, duration, spendElec=0, backCoins=backCoins, user=order.user) # 组织消费信息 consumeDict = { "reason": self._get_finish_reason(), DEALER_CONSUMPTION_AGG_KIND.DURATION: duration, DEALER_CONSUMPTION_AGG_KIND.SPEND_MONEY: VirtualCoin(cst).mongo_amount, } order.update_service_info(consumeDict) self.notify_user_service_complete( service_name='充电', openid=order.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): """ 诚马的插座不会有续充 """ start_time = Arrow.fromdatetime(master_order.startTime, tzinfo=settings.TIME_ZONE) coins = master_order.package["coins"] price = master_order.package["price"] portCache = { "openId": master_order.openId, "consumeType": "mobile", "coins": coins, "price": price, "estimatedTs": int(start_time.timestamp + 720 * 60 * 60), } return portCache def _get_finish_reason(self): if "reason" in self.event_data: return FINISH_REASON_MAP.get(self.event_data["reason"], u"未知原因") return u"未知原因" class cmCZSubIdStartAckEvent(IdStartAckEvent): # def do_impl(self, **args): # addr, port = self.event_data['port'].split("-") # self.event_data["port"] = int(port) # # super(cmCZSubIdStartAckEvent, self).do_impl(**args) def post_after_start(self, order=None): pass def post_after_finish(self, order=None): pass def merge_order(self, master_order, sub_orders): # type:(ConsumeRecord, Iterable[ConsumeRecord])->dict start_time = Arrow.fromdatetime(master_order.startTime, tzinfo=settings.TIME_ZONE) coins = self.event_data["fee"] cardNo = self.event_data["cardNo"] portCache = { "openId": master_order.openId, "consumeType": "card", "coins": coins, "price": coins, "cardNo": cardNo, "estimatedTs": int(start_time.timestamp + 720 * 60 * 60), } return portCache def do_finished_event(self, order, merge_order_info): # type:(ConsumeRecord, dict)->None order.reload() totalCst = self.event_data["totalCst"] / 3600.0 sts = self.event_data["sts"] fts = self.event_data["fts"] coins = merge_order_info["coins"] cst = min(VirtualCoin(totalCst), VirtualCoin(coins)) backCoins = VirtualCoin(coins) - cst duration = (fts - sts) / 60 extra = [ {u"本次订购金额": "{}".format(VirtualCoin(coins).amount)}, {u"本次实际使用时长": "{}分钟".format(duration)}, {u"消费金额": "{}(金币)".format(cst.amount)}, {u'实体卡': '{}--No:{}'.format(self.card.cardName, self.card.cardNo)} ] if backCoins > VirtualCoin(0): extra.append({u"退费金额": u"{}(金币)".format(VirtualCoin(backCoins).amount)}) self.card.clear_frozen_balance(str(order.id), backCoins) self.card.reload() # 组织消费信息 consumeDict = { "reason": self._get_finish_reason(), DEALER_CONSUMPTION_AGG_KIND.DURATION: duration, DEALER_CONSUMPTION_AGG_KIND.SPEND_MONEY: VirtualCoin(cst).mongo_amount, } order.update_service_info(consumeDict) self.notify_user_service_complete( service_name='充电', openid=order.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 checkout_order(self, order): fee = VirtualCoin(order.coin) self.card.freeze_balance(transaction_id=str(order.id), fee=fee) def _get_finish_reason(self): if "reason" in self.event_data: return FINISH_REASON_MAP.get(self.event_data["reason"], u"未知原因") return u"未知原因" class cmCZSubAckEventProcessorIntf(AckEventProcessorIntf): def pre_processing(self, device, event_data): # type:(DeviceDict, dict)->dict addr, port = event_data['port'].split("-") event_data["port"] = int(port) event_data["fee"] = event_data["balance"] return event_data