# -*- 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 CardRechargeOrder from apps.web.user.models import ConsumeRecord, CardConsumeRecord, Card, RechargeRecord, RefundMoneyRecord, \ ServiceProgress 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'), )