zhixia.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import logging
  4. import datetime
  5. import random
  6. import time
  7. from typing import TYPE_CHECKING
  8. from apilib.monetary import RMB, VirtualCoin
  9. from apilib.utils_datetime import to_datetime
  10. from apps.web.constant import Const, DEALER_CONSUMPTION_AGG_KIND
  11. from apps.web.core.accounting import Accounting
  12. from apps.web.dealer.models import Dealer
  13. from apps.web.device.models import OfflineCoinStatistics, Group, Device
  14. from apps.web.south_intf.liangxi_fire import LiangXiXiaoFang
  15. from apps.web.south_intf.yuhuan_fire import YuhuanNorther
  16. from apps.web.common.models import District
  17. from apps.web.eventer.base import FaultEvent, WorkEvent
  18. from apps.web.eventer import EventBuilder
  19. from apps.web.south_intf.platform import notify_event_to_north
  20. from apps.web.south_intf.zhejiang_fire import send_event_to_zhejiang
  21. from apps.web.user.models import ServiceProgress, UserVirtualCard, VCardConsumeRecord, Card, MyUser, CardRechargeOrder, RechargeRecord
  22. from apps.web.user.transaction_deprecated import refund_money
  23. if TYPE_CHECKING:
  24. from apps.web.device.models import GroupDict
  25. logger = logging.getLogger(__name__)
  26. class builder(EventBuilder):
  27. def __getEvent__(self, device_event):
  28. event_data = self.deviceAdapter.analyze_event_data(device_event['data'])
  29. if event_data is None or 'cmdCode' not in event_data:
  30. return None
  31. if 'duration' in device_event:
  32. event_data.update({'duration': device_event['duration']})
  33. if 'v' in device_event:
  34. event_data.update({'v': device_event['v']})
  35. if 'elec' in device_event:
  36. event_data.update({'elec': device_event['elec']})
  37. if event_data['cmdCode'] in ['03', '05', '11', '12', '30', '31', '32']:
  38. return ChargingZHIXIAWorkEvent(self.deviceAdapter, event_data)
  39. if event_data['cmdCode'] == '0D':
  40. return ZHIXIAFaultEvent(self.deviceAdapter, event_data)
  41. return None
  42. class ZHIXIAFaultEvent(FaultEvent):
  43. LX_FAULE_CODE_MAP = {
  44. "01": "07",
  45. "02": "03",
  46. "03": "05"
  47. }
  48. def do_norther(self):
  49. """
  50. 上报其他平台的,都在这个地方处理 可迁移至异步任务
  51. :return:
  52. """
  53. # 玉环的消防对接
  54. YuhuanNorther.send_dev_event(self.device, self.event_data['FaultCode'], self.event_data['port'])
  55. # 梁溪消防局的对接
  56. faultContent = self.event_data["statusInfo"]
  57. faultCode = self.LX_FAULE_CODE_MAP.get(self.event_data["FaultCode"], "")
  58. districtInfo = District.get_district(self.device.group["districtId"])
  59. self.device.update({"districtInfo": districtInfo, "groupAddr": self.device.group["address"]})
  60. LiangXiXiaoFang.send_to_xf_fault(self.device, faultCode, faultContent, self.event_data["port"])
  61. def do(self, **args):
  62. # 将告警的消息打入相应的缓存
  63. port = self.event_data["port"]
  64. # 0 表示整机
  65. if port == 0xFF:
  66. part = str(0)
  67. else:
  68. part = str(port)
  69. warningData = {
  70. "warningStatus": 1,
  71. "warningDesc": self.event_data["statusInfo"],
  72. "warningTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  73. "warningUart": self.event_data["uart"]
  74. }
  75. Device.update_dev_warning_cache(self.device.devNo, {part: warningData})
  76. self.do_norther()
  77. super(ZHIXIAFaultEvent, self).do()
  78. class ChargingZHIXIAWorkEvent(WorkEvent):
  79. def __parse_device_finished_data(self, event_data):
  80. duration = event_data.get('duration', -1)
  81. if duration != -1:
  82. if 'v' in event_data:
  83. duration = (duration / 60)
  84. elec = event_data.get('elec', -1)
  85. if elec != -1:
  86. if 'v' in event_data:
  87. elec = round(elec / (10000.0 * 3600.0), 3)
  88. else:
  89. elec = round(elec / 3600.0, 3)
  90. logger.debug('device duration is {}, device elec is {}'.format(duration, elec))
  91. return duration, elec
  92. def do(self, **args):
  93. logger.info('zhixiakeji charging event detected, devNo=%s,info=%s' % (self.device.devNo, self.event_data))
  94. devNo = self.device.devNo
  95. if self.event_data['cmdCode'] == '03': # 电川的无法记录投币的端口数据
  96. Accounting.recordOfflineCoin(self.device,
  97. int(time.time()),
  98. int(self.event_data['coins']))
  99. if self.device.ownerId is not None and self.device.ownerId != '':
  100. dealer = Dealer.objects(id = self.device.ownerId).first()
  101. if dealer is not None and 'show_device_offline_coins' in dealer.features:
  102. OfflineCoinStatistics.recordCoinEvent(
  103. self.device['logicalCode'],
  104. self.device.devNo,
  105. int(self.event_data.get('coins', 1)),
  106. self.device['groupId']
  107. )
  108. else:
  109. logger.error('undefined dealer id=%s' % self.device.ownerId)
  110. # 如果是投币,直接把端口状态刷新下
  111. self.deviceAdapter.get_port_status_from_dev()
  112. elif self.event_data['cmdCode'] == '05':
  113. devNo = self.device.devNo
  114. port = str(self.event_data['port'])
  115. ctrInfo = Device.get_dev_control_cache(self.device.devNo)
  116. lineInfo = ctrInfo.get(port)
  117. if not lineInfo:
  118. logger.info('aaaaaaaaaaaaaaaaa,the ctrInfo is empty,devNo=%s' % devNo)
  119. try:
  120. return self.do_time_finish(devNo, port, lineInfo, self.event_data)
  121. finally:
  122. Device.clear_port_control_cache(devNo, str(port))
  123. notify_event_to_north(self.dealer, self.device, level = Const.EVENT_NORMAL,
  124. desc = self.event_data['reason'])
  125. send_event_to_zhejiang(self.dealer, self.device, self.event_data)
  126. if self.event_data.get('reasonCode', '') == '04': # 功率过载导致的断电,一条告警,一条恢复告警
  127. YuhuanNorther.send_dev_event(self.device, '97', port)
  128. YuhuanNorther.send_dev_event(self.device, '98', port)
  129. # 发送一条端口使用结束的告警
  130. YuhuanNorther.send_dev_status(self.device, port, 2) # 结束使用
  131. elif self.event_data['cmdCode'] == '11':
  132. cardNo = self.event_data['cardNo']
  133. card = self.update_card_dealer_and_type(cardNo, 'IC')
  134. if not card:
  135. return
  136. lineInfo = Device.update_port_control_cache(self.device.devNo, self.event_data)
  137. lineInfo['openId'] = card.openId
  138. lineInfo = Device.update_port_control_cache(self.device.devNo, lineInfo)
  139. balance = RMB(self.event_data['balance'])
  140. consumeMoney = RMB(self.event_data['coins'])
  141. if self.event_data['status'] == '01': # 扣款成功,表示马上需要使用卡了,需要登记
  142. desc = u'正在刷卡使用,卡号:%s,卡名称:%s,端口号:%s,扣费:%s,余额:%s' % (
  143. card.cardNo, card.cardName,
  144. self.event_data['port'],
  145. self.event_data['coins'], balance)
  146. orderNo, cardOrderNo = \
  147. self.record_consume_for_card(card = card,
  148. money = consumeMoney,
  149. desc = desc,
  150. servicedInfo = {
  151. 'balance': self.event_data['balance'],
  152. 'coin': self.event_data['coins'],
  153. 'port': self.event_data['port']})
  154. # 记录当前服务的progress.没有上报端口号,所以先用-1标记,表示端口未知。端口等消费的时候会报上来
  155. ServiceProgress.register_card_service(self.device, self.event_data['port'], card,
  156. {'orderNo': orderNo, 'coin': self.event_data['coins'],
  157. 'cardOrderNo': cardOrderNo})
  158. self.notify_balance_has_consume_for_card(card, consumeMoney, desc)
  159. # 更新下balance,便于刷卡记录中,更新到数据库中
  160. lineInfo['balance'] = str(balance)
  161. lineInfo['cardId'] = str(card.id)
  162. lineInfo['startTime'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  163. lineInfo['isStart'] = True
  164. lineInfo['status'] = Const.DEV_WORK_STATUS_WORKING
  165. Device.update_port_control_cache(self.device.devNo, lineInfo)
  166. # 如果卡挂失掉了,立马把端口关闭掉
  167. if card.frozen:
  168. logger.debug('{} has been frozen.'.format(repr(card)))
  169. self.deviceAdapter.stop_charging_port(lineInfo['port'])
  170. elif self.event_data['status'] == '02': # 扣款失败,余额不足
  171. pass
  172. elif self.event_data['status'] == '03': # 返现
  173. balance = balance + consumeMoney
  174. self.update_card_balance(card, balance)
  175. elif self.event_data['cmdCode'] == '12': # 电川的板子充值是覆写方式,需要把上次的余额一起累加进来
  176. cardNo = self.event_data['cardNo']
  177. card = self.update_card_dealer_and_type(cardNo, 'IC') # type: Card
  178. if not card:
  179. return
  180. if card.frozen:
  181. logger.debug('{} has been frozen.'.format(repr(card)))
  182. return
  183. preBalance = RMB(self.event_data['balance'])
  184. card_recharge_order = CardRechargeOrder.get_last_to_do_one(str(card.id)) # type: CardRechargeOrder
  185. self.recharge_ic_card(card = card,
  186. preBalance = preBalance,
  187. rechargeType = 'overwrite',
  188. order = card_recharge_order)
  189. elif self.event_data['cmdCode'] == '20': # 启动设备后,需要把状态刷新下,便于实时的把状态查询出来
  190. lineInfo = Device.update_port_control_cache(self.device.devNo, self.event_data)
  191. lineInfo['startTime'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  192. lineInfo['isStart'] = True
  193. lineInfo['status'] = Const.DEV_WORK_STATUS_WORKING
  194. Device.update_port_control_cache(self.device.devNo, lineInfo)
  195. # 发送事件给北向,表示已经开始工作
  196. YuhuanNorther.send_dev_status(self.device, self.event_data['port'], 1)
  197. elif self.event_data['cmdCode'] == '30':
  198. try:
  199. lastValue = Device.get_dev_control_cache(devNo)
  200. lastValue.update({'elecValue': self.event_data})
  201. Device.update_dev_control_cache(self.device["devNo"], lastValue)
  202. finally:
  203. notify_event_to_north(self.dealer, self.device, level = Const.EVENT_NORMAL, desc = u'电流主动上报事件',
  204. dataDict = self.event_data)
  205. elif self.event_data['cmdCode'] == '31':
  206. try:
  207. lastValue = Device.get_dev_control_cache(devNo)
  208. lastValue.update({'temperatureValue': self.event_data})
  209. Device.update_dev_control_cache(self.device["devNo"], lastValue)
  210. finally:
  211. notify_event_to_north(self.dealer, self.device, level = Const.EVENT_NORMAL, desc = u'温度主动上报事件',
  212. dataDict = self.event_data)
  213. elif self.event_data['cmdCode'] == '32':
  214. try:
  215. lastValue = Device.get_dev_control_cache(devNo)
  216. lastValue.update(self.event_data)
  217. Device.update_dev_control_cache(self.device["devNo"], lastValue)
  218. finally:
  219. if self.event_data['isYangan']:
  220. notify_event_to_north(self.dealer, self.device, level = Const.EVENT_NORMAL, desc = u'烟感事件',
  221. dataDict = self.event_data)
  222. elif self.event_data['cmdCode'] == '17': # 余额回收
  223. card = self.update_card_dealer_and_type(self.event_data['cardNo'], 'IC') # type: Card
  224. self.refund_money_for_card(RMB(self.event_data['backMoney']), card.id)
  225. def do_time_finish(self, devNo, port, lineInfo, event_data):
  226. try:
  227. refundProtectionTime = self.device.get_other_conf_item('refundProtectionTime', 5)
  228. deviceDuration, deviceElec = self.__parse_device_finished_data(event_data)
  229. if lineInfo is not None and 'startTime' in lineInfo:
  230. startTime = to_datetime(lineInfo['startTime'])
  231. nowTime = datetime.datetime.now()
  232. if startTime > nowTime:
  233. logger.error('start time is bigger than now time,devNo={}'.format(devNo))
  234. serverDuration = -1
  235. else:
  236. serverDuration = int(round((((nowTime - startTime).total_seconds() + 59) / 60.0)))
  237. else:
  238. logger.info('lineinfo has not startTime,devNo=%s' % devNo)
  239. serverDuration = -1
  240. leftTime = self.event_data['leftTime']
  241. logger.debug('serverDuration = {}; deviceDuration = {}; leftTime = {}; lineInfo = {}'.format(
  242. serverDuration, deviceDuration, leftTime, lineInfo))
  243. if leftTime == 65535: # 设备返回65535说明端口没有启动
  244. leftTimeStr = u'端口未使用'
  245. actualNeedTime = 0
  246. usedTime = 0
  247. leftTime = 65535
  248. elif serverDuration < 0 and deviceDuration < 0: # duration参数错误
  249. logger.debug('serverDuration and deviceDuration is valid.')
  250. actualNeedTime = -1
  251. else:
  252. usedTime = max(serverDuration, deviceDuration)
  253. leftTime = self.event_data['leftTime']
  254. leftTimeStr = leftTime
  255. actualNeedTime = usedTime + leftTime
  256. # 通过使用的时间来判断是否可以进行保险的退款
  257. if actualNeedTime != -1 and usedTime <= 5:
  258. rechargeIds = list()
  259. payInfo = lineInfo.get('payInfo', list())
  260. for item in payInfo:
  261. if 'rechargeRcdId' not in item:
  262. continue
  263. rechargeIds.append(item['rechargeRcdId'])
  264. group = self.device.group # type: GroupDict
  265. if actualNeedTime < 0:
  266. if 'cardNo' in self.event_data and self.event_data['cardNo']:
  267. card = Card.objects(cardNo = str(self.event_data['cardNo']), agentId = self.dealer.agentId).first()
  268. if card:
  269. self.notify_user_service_complete(
  270. service_name = u'充电',
  271. openid = card.managerialOpenId if card else '',
  272. port = port,
  273. address = group['address'],
  274. reason = self.event_data['reason'],
  275. finished_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  276. extra = [{
  277. u'实体卡号': self.event_data['cardNo']
  278. }]
  279. )
  280. logger.debug('has no actual need time. ignore this event.')
  281. return
  282. consumeDict = {
  283. 'reason': self.event_data['reason'],
  284. 'leftTime': leftTimeStr,
  285. 'chargeIndex': port,
  286. 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime,
  287. 'duration': usedTime,
  288. 'deviceDuration': deviceDuration,
  289. 'serverDuration': serverDuration,
  290. 'uartData': self.event_data.get('uartData', '')
  291. }
  292. try:
  293. groupObj = Group.objects(id=self.device.groupId).first()
  294. if groupObj.otherConf.get('zhuxing', None) is not None:
  295. spendElec = round((float(random.randint(15, 19)) / 100) * (float(usedTime) / 60), 3)
  296. consumeDict.update({'elec': spendElec})
  297. consumeDict.update({'elecFee': self.deviceAdapter.calc_elec_fee(spendElec)})
  298. else:
  299. if deviceElec != -1:
  300. consumeDict.update({'elec': deviceElec})
  301. if deviceElec == 0 and usedTime > 0:
  302. consumeDict.update({'elec': round((float(random.randint(15, 19)) / 100) * (float(usedTime) / 60), 3)})
  303. else:
  304. consumeDict.update({'elec': 0.0})
  305. consumeDict.update({'elecFee': RMB(0.0).mongo_amount})
  306. except Exception as e:
  307. logger.error("device <{}> elec add error! <{}>".format(self.device.devNo, event_data))
  308. if 'cardNo' in lineInfo and lineInfo['cardNo']:
  309. # 如果是刷卡的,直接更新消费记录,发通知消息,支持ID卡的退费。
  310. # IC卡的退费, 如果结束原因是刷卡退费(05), 则会把退费信息返回;
  311. # 否则不会把退费信息传过来,会通过11号命令返回
  312. card = Card.objects(cardNo = lineInfo['cardNo'], agentId = self.dealer.agentId).first()
  313. if not card:
  314. logger.error('not exist this card no.')
  315. return
  316. consumeDict.update({'balance': lineInfo['balance']})
  317. ServiceProgress.update_progress_and_consume_rcd(self.device.ownerId,
  318. {'open_id': lineInfo['openId'], 'port': int(port),
  319. 'device_imei': self.device.devNo,
  320. 'isFinished': False}, consumeDict)
  321. extra = [{
  322. u'实体卡号': lineInfo['cardNo']
  323. }]
  324. self.notify_user_service_complete(
  325. service_name = u'充电',
  326. openid = card.managerialOpenId if card else '',
  327. port = port,
  328. address = group['address'],
  329. reason = self.event_data['reason'],
  330. finished_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  331. extra = extra)
  332. else:
  333. if 'coins' not in lineInfo:
  334. logger.warning('coins not in cache of port<{}> of device<devNo={}>'.format(port, self.device.devNo))
  335. return
  336. # 计算退费信息
  337. coins = VirtualCoin(lineInfo['coins'])
  338. refundCoins = VirtualCoin(0)
  339. if self.device.is_auto_refund:
  340. if leftTime != 65535:
  341. refundCoins = coins * (float(leftTime) / float(actualNeedTime))
  342. else:
  343. refundCoins = coins
  344. # if (refundProtection == 1 and usedTime < refundProtectionTime) and backCoins != VirtualCoin(0):
  345. if usedTime < refundProtectionTime and refundCoins != VirtualCoin(0):
  346. refundCoins = coins
  347. if refundCoins > coins:
  348. refundCoins = coins
  349. logger.debug(
  350. 'lefttime = {}, usedTime = {}, need time = {}, back coins = {}'.format(leftTime, usedTime,
  351. actualNeedTime,
  352. refundCoins))
  353. if 'consumeRcdId' in lineInfo and lineInfo['consumeRcdId']:
  354. vCardId = lineInfo['vCardId']
  355. vCard = UserVirtualCard.objects(id = vCardId).first() # type: UserVirtualCard
  356. if not vCard:
  357. logger.info('can not find the vCard id = %s' % vCardId)
  358. return
  359. try:
  360. ServiceProgress.update_progress_and_consume_rcd(self.device.ownerId,
  361. {'open_id': lineInfo['openId'],
  362. 'port': int(port),
  363. 'device_imei': self.device.devNo,
  364. 'isFinished': False}, consumeDict)
  365. if refundCoins > VirtualCoin(0):
  366. vCardConsumeRcd = VCardConsumeRecord.objects.get(id = lineInfo['consumeRcdId'])
  367. vCard.refund_quota(vCardConsumeRcd, usedTime, 0.0, refundCoins.mongo_amount)
  368. finally:
  369. user = MyUser.objects(openId = lineInfo['openId'],
  370. groupId = self.device.groupId).first()
  371. self.notify_user_service_complete(
  372. service_name = u'充电',
  373. openid = user.managerialOpenId if user else '',
  374. port = port,
  375. address = group['address'],
  376. reason = self.event_data['reason'],
  377. finished_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  378. extra = [
  379. {u'虚拟卡号': vCard.cardNo}
  380. ]
  381. )
  382. elif 'openId' in lineInfo and lineInfo['openId']:
  383. try:
  384. consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.COIN: VirtualCoin(coins).mongo_amount})
  385. if refundCoins > VirtualCoin(0):
  386. consumeDict.update({
  387. DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: refundCoins.mongo_amount,
  388. DEALER_CONSUMPTION_AGG_KIND.COIN: (VirtualCoin(coins) - refundCoins).mongo_amount
  389. })
  390. refund_money(self.device, refundCoins, lineInfo['openId'])
  391. ServiceProgress.update_progress_and_consume_rcd(self.device.ownerId,
  392. {'open_id': lineInfo['openId'],
  393. 'port': int(port),
  394. 'device_imei': self.device.devNo,
  395. 'isFinished': False}, consumeDict)
  396. finally:
  397. extra = []
  398. if refundCoins > VirtualCoin(0):
  399. extra.append({u'消费金额': '{}(金币)'.format(coins - refundCoins)})
  400. extra.append({u'退款金额': '{}(金币)'.format(refundCoins)})
  401. else:
  402. extra.append({u'消费金额': '{}(金币)'.format(coins)})
  403. user = MyUser.objects(openId = lineInfo['openId'],
  404. groupId = self.device.groupId).first()
  405. self.notify_user_service_complete(
  406. service_name = u'充电',
  407. openid = user.managerialOpenId if user else '',
  408. port = port,
  409. address = group['address'],
  410. reason = self.event_data['reason'],
  411. finished_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  412. extra = extra
  413. )
  414. else:
  415. logger.error('not net pay rather user virtual card pay. something is wrong.')
  416. except Exception as e:
  417. logger.exception(e)