# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import json import logging import time from copy import deepcopy from typing import TYPE_CHECKING from apilib.monetary import RMB, VirtualCoin, Ratio from apilib.utils_string import make_title_from_dict from apilib.utils_sys import memcache_lock from apps.web.common.transaction import UserConsumeSubType from apps.web.constant import FAULT_LEVEL, DEALER_CONSUMPTION_AGG_KIND, Const from apps.web.device.models import DeviceUploadInfo, Group from apps.web.eventer import EventBuilder from apps.web.eventer.base import WorkEvent, FaultEvent from apps.web.eventer.weifuleCommon import WeiFuLeStatusEvent, WeiFuLeCarProcess from apps.web.user.models import ConsumeRecord, CardConsumeRecord, Card, RechargeRecord, RefundMoneyRecord, \ ServiceProgress from apps.web.user.models import CardRechargeOrder from apps.web.utils import concat_user_login_entry_url if TYPE_CHECKING: pass logger = logging.getLogger(__name__) def log_obj(obj): obj = deepcopy(obj) if isinstance(obj, dict): for k, v in obj.items(): if isinstance(v, object): obj[k] = str(v) if isinstance(obj, list) or isinstance(obj, tuple) or isinstance(obj, set): obj = map(lambda x: str(x) if isinstance(x, object) else x, obj) if isinstance(obj, unicode): obj = str(obj) # print('\33[33m' + json.dumps(obj,ensure_ascii=True,encoding='utf-8') + '\33[0m') return '\33[33m' + json.dumps(obj, ensure_ascii=False, encoding='utf-8') + '\33[0m' card_is_normal = 1 card_not_in_db = 2 card_is_forzen = 3 card_has_not_order = 4 # IC卡适用 card_less_balance = 5 # ID卡适用 class builder(EventBuilder): def __getEvent__(self, device_event): logger.info('reve event, event_data:{}'.format(log_obj(device_event))) event_data = device_event.get('data') fun_code = event_data.get('fun_code') if not event_data: return if event_data['fun_code'] in [44]: return WeiFuLeStatusEvent(self.deviceAdapter, device_event) if fun_code in [40, 41, 42, 43]: # 40温度告警/41电流超限/42电压超限/43功率过载 return ChargingWeiFuLeCarFaultEvent(self.deviceAdapter, event_data) else: return ChargingWeiFuLeCarWorkEvent(self.deviceAdapter, event_data) class Process(WeiFuLeCarProcess): def pre_pay(self, order): openId = order.openId used_time = round(self.order_info.get('time') / 60.0, 2) used_elec = self.order_info.get('elec') / 10.0 ** 6 reason = self.desc_map.get(self.order_info.get('cause')) execTimeStamp = self.order_info.get('exec_time') finishedTimeStamp = self.order_info.get('over_time') finishedTime = datetime.datetime.fromtimestamp(finishedTimeStamp) if not execTimeStamp: startTime = finishedTime else: startTime = datetime.datetime.fromtimestamp(execTimeStamp) amount = RMB.fen_to_yuan(self.order_info.get('amount', 0)) records = self.order_info.get('records', []) elecFee = round(sum(map(lambda _: _['elec_money'], records)) * 0.01, 2) serviceFee = round(sum(map(lambda _: _['serv_money'], records)) * 0.01, 2) extra = [ {u'使用时长': u'{}(分钟)'.format(used_time)}, {u'消费明细': u'电费{}元, 服务费{}元'.format(elecFee, serviceFee)}, ] # for record in records: # sts = to_datetime(record['init']).strftime('%H:%M') # fts = to_datetime(record['last']).strftime('%H:%M') # extra.append({u'时段'.format(sts, fts): u'{}-{}'.format(sts, fts)}) # extra.append({u'费用'.format(sts, fts): u'电费{}, 服务费{}'.format(round(record['elec_money'] * 0.01, 2), # round(record['serv_money'] * 0.01, 2))}) usedMoney = min(RMB(elecFee + serviceFee), amount) leftMoney = amount - usedMoney status = self.order_info.get('status') consumeDict = { DEALER_CONSUMPTION_AGG_KIND.DURATION: '%d' % used_time, DEALER_CONSUMPTION_AGG_KIND.ELEC: '%.2f' % used_elec, DEALER_CONSUMPTION_AGG_KIND.COIN: amount.mongo_amount, 'reason': reason } order.status = status order.startTime = startTime order.finishedTime = finishedTime order.save() user = order.user self.smartbox.notify_user_service_complete( service_name='充电', openid=user.managerialOpenId if user else '', port='', address=self.dev.group['address'], reason=reason, finished_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), extra=extra ) if order.attachParas.get('is_auto_refund', False): recharge_record = None if order.rechargeRcdId: recharge_record = RechargeRecord.objects( id=order.rechargeRcdId, isQuickPay=True).first() # type: RechargeRecord if recharge_record: refundMoneyRecord = RefundMoneyRecord.objects.filter(openId=openId, rechargeObjId=recharge_record.id).first() if not refundMoneyRecord: self._do_refund_money(recharge_record, order, leftMoney, consumeDict) else: self._do_refund_coin(order, leftMoney, consumeDict) order.update_service_info(consumeDict) ServiceProgress.objects.filter(device_imei=self.devNo, weifuleOrderNo=order.orderNo).update( isFinished=True, finished_time=int(time.time()) ) self.adapter.async_update_portinfo_from_dev() def after_pay(self, order): """ 只针对设备下发之后,没有回复的故障单为后付费 :return: """ used_time = round(self.order_info.get('time') / 60.0, 2) used_elec = self.order_info.get('elec') / 10.0 ** 6 reason = self.desc_map.get(self.order_info.get('cause')) execTimeStamp = self.order_info.get('exec_time') finishedTimeStamp = self.order_info.get('over_time') port = self.order_info.get('port') finishedTime = datetime.datetime.fromtimestamp(finishedTimeStamp) if not execTimeStamp: startTime = finishedTime else: startTime = datetime.datetime.fromtimestamp(execTimeStamp) consumeDict = { DEALER_CONSUMPTION_AGG_KIND.DURATION: '%d' % used_time, DEALER_CONSUMPTION_AGG_KIND.ELEC: '%.2f' % used_elec, 'reason': reason, } amount = RMB.fen_to_yuan(self.order_info.get('amount', 0)) records = self.order_info.get('records', []) elecFee = int(sum(map(lambda _: _['elec_money'], records))) * 0.01 serviceFee = int(sum(map(lambda _: _['serv_money'], records))) * 0.01 extra = [ {u'使用时长': u'{}(分钟)'.format(used_time)}, {u'消费明细': u'电费{}元, 服务费{}元'.format(elecFee, serviceFee)}, ] usedMoney = min(RMB(elecFee + serviceFee), amount) order.startTime = startTime order.finishedTime = finishedTime coins = VirtualCoin(order.package.get('coins', 0)) price = RMB(order.package.get('price')) ratio = Ratio(float(usedMoney) / float(amount)) order.coin = coins * ratio order.money = price * ratio if order.money == RMB(0): order.coin = VirtualCoin(0) order.save() if order.money > RMB(0): self._pay_by_coins(order) else: order.update(status=self.FINISHED) order.reload() if order.status == self.FINISHED: extra.append({u'本单消费金额': '{}金币(已使用账户余额自动结算本次消费)'.format(order.coin.mongo_amount)}) consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.COIN: order.coin.mongo_amount}) else: extra.append({u'本单消费金额': '{}元((您的账户余额不足以抵扣本次消费,请前往账单中心进行支付))'.format(order.money.mongo_amount)}) consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.SPEND_MONEY: order.money.mongo_amount}) self.smartbox.notify_user_service_complete( service_name='充电结束', openid=order.user.managerialOpenId, port=str(port), address=self.dev.group['address'], reason=reason, finished_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), extra=extra ) order.update_service_info(consumeDict) ServiceProgress.objects.filter(device_imei=self.devNo, weifuleOrderNo=order.orderNo).update( isFinished=True, finished_time=int(time.time()) ) def _card_start_do_record_running_32(self): amount = RMB.fen_to_yuan(self.order_info.get('amount', 0)) card_balance = RMB.fen_to_yuan(self.order_info.get('balance')) start_time_stamp = self.order_info.get('create_time') start_time = datetime.datetime.fromtimestamp(start_time_stamp) order_id = self.order_info.get('id') card_no = self.order_info.get('card_no') port = self.order_info.get('port') card = Card.objects.filter(cardNo=card_no, cardType='ID', dealerId=self.dev.ownerId).first() if not card: logger.info('no this card cardNo:{}'.format(card_no)) return # 做一次订单号去重 order = ConsumeRecord.objects.filter(sequanceNo=order_id, devNo=self.dev.devNo).first() if order: logger.info('order already exists cardNo:{}, order_id'.format(card_no, order_id)) return servicedInfo = { 'cardNo': card_no, 'port': '1', } attachParas = { 'chargeIndex': '1', 'sequanceNo': order_id, 'is_auto_refund': self.dev.is_auto_refund } # 记录卡消费记录以及消费记录 order, cardOrder = self.smartbox.record_consume_for_card(card, amount, servicedInfo=servicedInfo, attachParas=attachParas) card.freeze_balance(str(order.id), VirtualCoin(amount)) consumeOrder = { 'orderNo': order.orderNo, 'cardOrderNo': cardOrder.orderNo, 'coin': str(amount), 'consumeType': 'card', } self._register_card_service_progress(card, port, consumeOrder, order_id) title = make_title_from_dict([ {u'设备地址': u'{}'.format(self.dev.group.address)}, {u'设备编号': u'{}'.format(self.dev['logicalCode'])}, {u'实体卡': u'{}--No:{}'.format(card.cardName or card.nickName, card.cardNo)}, # {u'本次消费': u'{} 元'.format(amount)}, {u'卡余额': u'{} 元'.format(card_balance)}, ]) self.smartbox.notify_user( card.managerialOpenId, 'dev_start', **{ 'title': title, 'things': u'刷卡消费', 'remark': u'感谢您的支持!', 'time': start_time.strftime(Const.DATETIME_FMT) } ) def _card_start_do_record_finished_34(self): used_time = round(self.order_info.get('time') / 60.0, 2) finishedTimeStamp = self.order_info.get('over_time') finishedTime = datetime.datetime.fromtimestamp(finishedTimeStamp) execTimeStamp = self.order_info.get('exec_time') if not execTimeStamp: startTime = finishedTime else: startTime = datetime.datetime.fromtimestamp(execTimeStamp) reason = self.desc_map.get(self.order_info.get('cause')) card_no = self.order_info.get('card_no') used_elec = self.order_info.get('elec') / 10 ** 6 order_id = self.order_info.get('id') amount = RMB.fen_to_yuan(self.order_info.get('amount', 0)) records = self.order_info.get('records', []) elecFee = round(sum(map(lambda _: _['elec_money'], records)) * 0.01, 2) serviceFee = round(sum(map(lambda _: _['serv_money'], records)) * 0.01, 2) usedMoney = min(RMB(elecFee + serviceFee), amount) leftMoney = amount - usedMoney card = Card.objects.filter(cardNo=card_no, cardType='ID', dealerId=self.dev.ownerId).first() consumeOrder = ConsumeRecord.objects.filter(devNo=self.devNo, sequanceNo=order_id, status__ne=ConsumeRecord.Status.FINISHED).first() if not card or not consumeOrder: logger.info('do card finished --> no order found or repeated submit orderNo:{}'.format(order_id)) return if not consumeOrder.attachParas.get('is_auto_refund', False): leftMoney = RMB(0) card.clear_frozen_balance(str(consumeOrder.id), VirtualCoin(leftMoney)) card.reload() openId = consumeOrder.openId consumeOrder.servicedInfo.update({ DEALER_CONSUMPTION_AGG_KIND.REFUND_CARD: leftMoney.mongo_amount, DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: amount.mongo_amount, DEALER_CONSUMPTION_AGG_KIND.DURATION: '%d' % used_time, DEALER_CONSUMPTION_AGG_KIND.ELEC: '%.2f' % used_elec, 'reason': reason, 'cardNo': card_no }) consumeOrder.status = self.FINISHED consumeOrder.startTime = startTime consumeOrder.finishedTime = finishedTime consumeOrder.save() ServiceProgress.update_progress_and_consume_rcd( self.dev['ownerId'], { 'open_id': openId, 'device_imei': self.devNo, 'isFinished': False, 'weifuleOrderNo': order_id, }, consumeOrder.servicedInfo, progressDict={'isFinished': True, 'finished_time': finishedTimeStamp} ) user = consumeOrder.user extra = [ # {u'订单号': '{}'.format(consumeOrder.orderNo)}, {u'本次使用时长': '{}(分钟)'.format(used_time)}, {u'消费明细': u'电费{}元, 服务费{}元'.format(elecFee, serviceFee)}, {u'卡片余额': '{}元'.format(card.balance)}, ] self.smartbox.notify_user_service_complete( service_name='刷卡充电', openid=user.managerialOpenId if user else '', port='', address=self.dev.group.address, reason=reason, finished_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), extra=extra ) logger.info('card start event orderNo:{} is over'.format(order_id)) self.adapter.async_update_portinfo_from_dev() class ChargingWeiFuLeCarWorkEvent(WorkEvent): def do(self, **args): fun_code = self.event_data.get('fun_code') order_id = self.event_data.get('order', {}).get('id') key = '{}.{}.event'.format(self.device.devNo, order_id) if fun_code == 37: # 处理卡充值 self.deal_with_id_charge_event() elif fun_code == 44: # 上报结束后的状态 pass elif fun_code == 45: # 上传要链接 self.response_the_link() elif fun_code == 48: self.price_rule_change() else: with memcache_lock(key=key, value='1', expire=15) as acquired: if acquired: if fun_code == 32 or fun_code == 34 or fun_code == 33: self._do_order_change_event_32_33_34() self._do_ack_order_32_or_remove_order_from_device_34() else: logger.debug('fun_code<{}> is doing. cache_key:{}'.format(repr(fun_code), key)) return def deal_with_id_charge_event(self): cardNo = self.event_data.get('card_no') card = self.update_card_dealer_and_type(cardNo) # type: Card logger.info(log_obj('Start card recharge operation')) if not card: logger.info(log_obj('Start card recharge operation --> no such card !!! ')) return self.deviceAdapter.send_mqtt({ 'fun_code': 37, 'card_no': cardNo, 'result': card_not_in_db, }) elif card.frozen: logger.info(log_obj('Start card recharge operation --> card is frozen !!! ')) return self.deviceAdapter.send_mqtt({ 'fun_code': 37, 'card_no': cardNo, 'result': card_is_forzen, }) elif not self.device.owner: return self.deviceAdapter.send_mqtt({ 'fun_code': 37, 'card_no': cardNo, 'result': card_not_in_db, }) # 是否存在没有到账的余额 进行充值 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() # { # "cmd": 100, # "IMEI": "863488058983340", # "time": 1664519145, # "data": { # "card_no": "3427873049", # "fun_code": 37, # "action": "query" # } # } action = self.event_data.get('action') if action == 'query': return self.deviceAdapter.send_mqtt({ 'fun_code': 37, 'card_no': cardNo, 'result': card_is_normal, 'balance': RMB.yuan_to_fen(card.balance), "amount": RMB.yuan_to_fen(card.balance) }) elif action == 'start': ongoingList = getattr(card, 'ongoingList', []) # 有冻结未结束的订单 if card.balance <= RMB(0) or ongoingList: return self.deviceAdapter.send_mqtt({ 'fun_code': 37, 'card_no': cardNo, 'result': card_less_balance, 'balance': RMB.yuan_to_fen(card.balance), "amount": RMB.yuan_to_fen(card.balance), }) return self.deviceAdapter.send_mqtt({ 'fun_code': 37, 'card_no': cardNo, 'result': card_is_normal, 'balance': RMB.yuan_to_fen(card.balance), "amount": RMB.yuan_to_fen(card.balance) }) def _do_order_change_event_32_33_34(self): order_info = self.event_data.get('order') if not order_info: return logger.info(log_obj('no order info,do over!!')) else: order_processing = Process(self) order_type = order_info.get('order_type') self.save_upload_log() if hasattr(order_processing, order_type): event = getattr(order_processing, order_type) try: event() except Exception: import traceback logger.info(traceback.format_exc()) else: logger.info(log_obj('no this order_type')) def _do_ack_order_32_or_remove_order_from_device_34(self): order_info = self.event_data.get('order') order_id = order_info.get('id') fun_code = self.event_data.get('fun_code') if (time.time() - order_info.get('create_time', 0)) < 300 and order_info.get('order_type') == 'apps_start': return if fun_code == 32: self.deviceAdapter.do_ack_order_32(order_id) elif fun_code == 34: self.deviceAdapter.do_ack_remove_order_from_device_34(order_id) else: pass def save_upload_log(self): order_info = self.event_data.get('order') order_info = deepcopy(order_info) order_info['order_id'] = order_info.pop('id') DeviceUploadInfo(**order_info).save() def response_the_link(self): qr_code_url = concat_user_login_entry_url(l=self.device['logicalCode']) data = { 'fun_code': 45, 'qrcode': qr_code_url } self.deviceAdapter.send_mqtt(data) def record_consume_for_card(self, card, money, desc=u'', servicedInfo=None, sid=None, attachParas=None): servicedInfo = {} if servicedInfo is None else servicedInfo attachParas = {} if attachParas is None else attachParas group = Group.get_group(self.device['groupId']) address = group['address'] group_number = self.device['groupNumber'] now = datetime.datetime.now() sequanceNo = attachParas.get('sequanceNo') new_record = { 'orderNo': ConsumeRecord.make_no(card.cardNo, UserConsumeSubType.CARD), 'time': now.strftime('%Y-%m-%d %H:%M:%S'), 'dateTimeAdded': now, 'openId': card.openId, 'ownerId': self.device['ownerId'], 'coin': money.mongo_amount, 'money': money.mongo_amount, 'devNo': self.device['devNo'], 'logicalCode': self.device['logicalCode'], 'groupId': self.device['groupId'], 'address': address, 'groupNumber': group_number, 'groupName': group['groupName'], 'devTypeCode': self.device.devTypeCode, 'devTypeName': self.device.devTypeName, 'isNormal': True, 'status': ConsumeRecord.Status.RUNNING, 'remarks': u'刷卡消费', 'errorDesc': '', 'sequanceNo': sequanceNo, 'desc': desc, 'attachParas': attachParas, 'servicedInfo': servicedInfo } order = ConsumeRecord.objects.create(**new_record) # 刷卡消费也记录一条数据 new_card_record = { 'orderNo': new_record['orderNo'], 'openId': card.openId, 'cardId': str(card.id), 'money': money.mongo_amount, 'balance': card.balance.mongo_amount, 'devNo': self.device['devNo'], 'devType': self.device['devType']['name'], 'logicalCode': self.device['logicalCode'], 'groupId': self.device['groupId'], 'address': address, 'groupNumber': group_number, 'groupName': group['groupName'], 'result': 'success', 'remarks': u'刷卡消费', 'sequanceNo': '', 'dateTimeAdded': datetime.datetime.now(), 'desc': desc, 'servicedInfo': servicedInfo, 'linkedConsumeRcdOrderNo': str(new_record['orderNo']) } if sid is not None: new_card_record.update({'sid': sid}) card_order = CardConsumeRecord.objects.create(**new_card_record) return order, card_order def price_rule_change(self): pass class ChargingWeiFuLeCarFaultEvent(FaultEvent): def do(self, **args): # 40温度告警/41电流超限/42电压超限/43功率过载 group = Group.get_group(self.device['groupId']) fun_code = self.event_data.get('fun_code') if fun_code == 40: item = self.event_data.get('temp') faultName = r'设备火灾预警' desc = r'主板上报设备过热,设备温度超限(设备温度:{} 度)'.format(item) title = r'告警名称:\t\t{}\n\n地址名称:\t\t{}-{}\n'.format(faultName, group['address'], group['groupName']) elif fun_code == 41: item = self.event_data.get('ampr') faultName = r'设备电流超过最大限制' desc = r'主板上报电流超限,设备电流超限(设备电流:{} 安)'.format(item) title = r'告警名称:\t\t{}\n\n地址名称:\t\t{}-{}\n'.format(faultName, group['address'], group['groupName']) elif fun_code == 42: item = self.event_data.get('volt') faultName = r'设备电压超过最限制' desc = r'主板上报电压超限,设备电压超限(设备电压:{} 伏)'.format(item) title = r'告警名称:\t\t{}\n\n地址名称:\t\t{}-{}\n'.format(faultName, group['address'], group['groupName']) elif fun_code == 43: item = self.event_data.get('watt') faultName = r'设备功率超过最限制' desc = r'主板上报功率超限,设备功率超限(设备电压:{} 瓦)'.format(item) title = r'告警名称:\t\t{}\n\n地址名称:\t\t{}-{}\n'.format(faultName, group['address'], group['groupName']) else: return self.notify_dealer( templateName='device_fault', title=title, device=r'{}号设备({})\n'.format(self.device['groupNumber'], self.device['logicalCode']), location=r'设备告警\n', notifyTime=desc + r'\n', fault=r'%s\n' % datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') ) self.record( faultCode=self.event_data.get('FaultCode'), description=desc, title=faultName, level=self.event_data.get('level', FAULT_LEVEL.NORMAL), # detail=self.event_data.get('statusInfo'), )