yunchong.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import logging
  5. import time
  6. from typing import TYPE_CHECKING
  7. from apilib.monetary import RMB, VirtualCoin
  8. from apilib.utils_datetime import to_datetime
  9. from apps.web.constant import Const, DEALER_CONSUMPTION_AGG_KIND
  10. from apps.web.core.accounting import Accounting
  11. from apps.web.device.models import Device
  12. from apps.web.device.timescale import FluentedEngine
  13. from apps.web.eventer import EventBuilder
  14. from apps.web.eventer.base import FaultEvent, WorkEvent
  15. from apps.web.user.models import ServiceProgress, UserVirtualCard, VCardConsumeRecord, Card, MyUser, CardRechargeOrder, \
  16. Redpack, RechargeRecord
  17. if TYPE_CHECKING:
  18. from apps.web.device.models import GroupDict
  19. logger = logging.getLogger(__name__)
  20. class builder(EventBuilder):
  21. def __getEvent__(self, device_event):
  22. event_data = self.deviceAdapter.analyze_event_data(device_event['data'])
  23. if event_data is None:
  24. return None
  25. if not event_data.has_key('cmdCode'):
  26. return
  27. if 'duration' in device_event:
  28. event_data.update({'duration': device_event['duration']})
  29. if 'v' in device_event:
  30. event_data.update({'v': device_event['v']})
  31. if event_data['cmdCode'] in ['03', '04', '05', '25', '26', '28']:
  32. return ChargingYUNCHONGWorkEvent(self.deviceAdapter, event_data)
  33. if event_data['cmdCode'] == '0D':
  34. return FaultEvent(self.deviceAdapter, event_data)
  35. class ChargingYUNCHONGWorkEvent(WorkEvent):
  36. def __parse_device_finished_data(self, event_data):
  37. duration = event_data.get('duration', -1)
  38. if duration != -1:
  39. if 'v' in event_data:
  40. duration = (duration / 60)
  41. elec = event_data.get('elec', -1)
  42. if elec != -1:
  43. if 'v' in event_data:
  44. elec = round(elec / (10000.0 * 3600.0), 3)
  45. else:
  46. elec = round(elec / 3600.0, 3)
  47. logger.debug('device duration is {}, device elec is {}'.format(duration, elec))
  48. return duration, elec
  49. @property
  50. def support_playback(self):
  51. if self.event_data['cmdCode'] in ['05']:
  52. return True
  53. else:
  54. return False
  55. def get_back_info(self, leftTime, actualNeedTime, lineInfo):
  56. coins = VirtualCoin(lineInfo['coins'])
  57. price = RMB(lineInfo['price'])
  58. refundedCoins = VirtualCoin(0)
  59. refundedMoney = RMB(0)
  60. if leftTime != 65535:
  61. refundedCoins = coins * (float(leftTime) / float(actualNeedTime))
  62. refundedMoney = RMB(price * (float(leftTime) / float(actualNeedTime)))
  63. else:
  64. refundedCoins = coins
  65. refundedMoney = RMB(price)
  66. if refundedCoins > coins:
  67. refundedCoins = coins
  68. if refundedMoney > price:
  69. refundedMoney = price
  70. return refundedMoney, refundedCoins
  71. def do(self, **args):
  72. logger.info('[yunchong]charging event detected, devNo=%s,info=%s' % (self.device.devNo, self.event_data))
  73. if self.event_data['cmdCode'] == '03': # 投币数据
  74. if 'today_coins' in self.event_data and 'ts' in self.event_data:
  75. Accounting.syncOfflineCoin(
  76. self.device,
  77. datetime.datetime.fromtimestamp(self.event_data['ts']).strftime('%Y-%m-%d'),
  78. self.event_data['today_coins'])
  79. if self.event_data['coins'] > 0:
  80. FluentedEngine().in_put_coins_udp(devNo=self.device.devNo,
  81. ts=int(time.time()),
  82. coins=self.event_data['coins'],
  83. mode='uart')
  84. else:
  85. # 老的流程会单条记录上报记录
  86. Accounting.recordOfflineCoin(device=self.device,
  87. report_ts=int(time.time()),
  88. coins=int(self.event_data['coins']),
  89. mode='uart',
  90. port=self.event_data.get('port', None))
  91. elif self.event_data['cmdCode'] == '05':
  92. devNo = self.device.devNo
  93. port = str(self.event_data['port'])
  94. try:
  95. lineInfo = Device.clear_port_control_cache(devNo, port)
  96. if not lineInfo:
  97. logger.debug('get null control cache from {}'.format(repr(self.device)))
  98. return
  99. if 'openId' not in lineInfo:
  100. logger.debug('openId not in line info. ignore it.')
  101. return
  102. if 'redpackInfo' in lineInfo:
  103. for _info in lineInfo['redpackInfo']:
  104. redpack = Redpack.get_one(_info['redpackId'])
  105. redpackCoins = VirtualCoin(redpack['redpackCoins'])
  106. lineInfo['coins'] = float(VirtualCoin(lineInfo['coins']) - redpackCoins)
  107. redpackMoney = RMB(redpack['redpackMoney'])
  108. lineInfo['price'] = float(RMB(lineInfo['price']) - redpackMoney)
  109. logger.debug('redpack is <{}> redpack money is: {}; redpack coins is: {}'.format(
  110. redpack.get('id'), str(redpackMoney.amount), str(redpackCoins.amount)))
  111. lineInfo['coins'] = 0 if lineInfo['coins'] < 0 else lineInfo['coins']
  112. lineInfo['price'] = 0 if lineInfo['price'] < 0 else lineInfo['price']
  113. return self.do_time_finish(devNo, port, lineInfo)
  114. finally:
  115. pass
  116. elif self.event_data['cmdCode'] == '26':
  117. cardNo = self.event_data['cardNo']
  118. card = self.update_card_dealer_and_type(cardNo, 'ID')
  119. if not card:
  120. return
  121. #: 首先检查订单,并进行充值
  122. #: 用户首先在手机客户端充值,需要这个时刻刷卡上报事件将充值的订单同步上来
  123. card_recharge_order = CardRechargeOrder.get_last_to_do_one(str(card.id))
  124. if card_recharge_order:
  125. self.recharge_id_card(card=card, rechargeType='append', order=card_recharge_order)
  126. card.reload()
  127. try:
  128. # 直接回复余额和随机码
  129. if not card.frozen:
  130. if card.balance <= VirtualCoin(0):
  131. self.deviceAdapter.response_card_balance(cardNo, 0)
  132. else:
  133. self.deviceAdapter.response_card_balance(cardNo, int(card.balance))
  134. else:
  135. self.deviceAdapter.response_card_balance(cardNo, 0) # 挂失卡,直接回复余额为0
  136. except Exception, e:
  137. pass
  138. elif self.event_data['cmdCode'] == '28':
  139. cardNo = self.event_data['cardNo']
  140. card = self.update_card_dealer_and_type(cardNo, 'ID')
  141. if not card:
  142. return
  143. port = str(self.event_data['port'])
  144. Device.update_dev_control_cache(self.device.devNo, {
  145. port: {
  146. 'openId': card.openId,
  147. 'price': self.event_data['coins'],
  148. 'coins': self.event_data['coins'],
  149. 'cardId': str(card.id),
  150. 'startTime': int(time.time()),
  151. 'status': Const.DEV_WORK_STATUS_WORKING
  152. }
  153. })
  154. balance = card.balance - RMB(self.event_data['coins']) # type: RMB
  155. consumeMoney = RMB(self.event_data['coins'])
  156. desc = u'正在刷卡使用,卡号:%s,卡名称:%s,端口号:%s,扣费:%s,余额:%s' % (
  157. card.cardNo, card.cardName,
  158. self.event_data['port'],
  159. self.event_data['coins'], balance)
  160. orderNo, cardOrderNo = \
  161. self.record_consume_for_card(card=card,
  162. money=consumeMoney,
  163. desc=desc,
  164. servicedInfo={
  165. 'balance': balance.mongo_amount,
  166. 'coin': self.event_data['coins'],
  167. 'port': self.event_data['port']})
  168. self.update_card_balance(card, balance)
  169. ServiceProgress.register_card_service(self.device, self.event_data['port'], card,
  170. {
  171. 'orderNo': orderNo,
  172. 'coin': str(self.event_data['coins']),
  173. 'cardOrderNo': cardOrderNo
  174. })
  175. self.notify_balance_has_consume_for_card(card, consumeMoney, desc)
  176. def do_time_finish(self, devNo, port, lineInfo):
  177. logger.info("[{} do_time_finish] devNo = {}, port = {}, lineInfo = {}, event = {}".format(
  178. self.__class__.__name__, devNo, port, lineInfo, self.event_data))
  179. try:
  180. if not self.dealer:
  181. logger.error(
  182. "[{} do_time_finish] dealer {} is not found!".format(self.__class__.__name__, self.device.ownerId))
  183. return
  184. recvTime = to_datetime(self.recvTime)
  185. if 'startTime' in lineInfo:
  186. startTime = to_datetime(lineInfo['startTime'])
  187. if startTime > recvTime:
  188. logger.error('start time is bigger than now time,devNo={}'.format(devNo))
  189. serverDuration = -1
  190. else:
  191. serverDuration = int((recvTime - startTime).total_seconds() / 60.0)
  192. else:
  193. logger.info('lineinfo has not startTime,devNo=%s' % devNo)
  194. serverDuration = -1
  195. leftTime = self.event_data['leftTime']
  196. logger.debug('serverDuration = {}; leftTime = {}; lineInfo = {}'.format(
  197. serverDuration, leftTime, lineInfo))
  198. if leftTime == 65535: # 设备返回65535说明端口没有启动
  199. leftTimeStr = u'端口未使用'
  200. actualNeedTime = 0
  201. usedTime = 0
  202. leftTime = 65535
  203. elif serverDuration < 0: # duration参数错误
  204. logger.debug('serverDuration and deviceDuration is valid.')
  205. actualNeedTime = -1
  206. usedTime = -1
  207. leftTimeStr = ''
  208. else:
  209. usedTime = serverDuration
  210. leftTime = self.event_data['leftTime']
  211. leftTimeStr = leftTime
  212. actualNeedTime = usedTime + leftTime
  213. if actualNeedTime == -1:
  214. logger.debug('has no actual need time. ignore this event.')
  215. return
  216. refundProtectionTime = self.device.my_obj.otherConf.get('refundProtectionTime', 5)
  217. if usedTime < refundProtectionTime:
  218. rechargeIds = list()
  219. payInfo = lineInfo.get('payInfo', list())
  220. for item in payInfo:
  221. if 'rechargeRcdId' not in item:
  222. continue
  223. rechargeIds.append(item['rechargeRcdId'])
  224. refundedMoney, refundedCoins = self.get_back_info(
  225. leftTime=leftTime, actualNeedTime=actualNeedTime, lineInfo=lineInfo)
  226. logger.debug('refund switch = {}, protect time = {}, lefttime = {}, usedTime = {}, need time = {}, back coins = {}, back money = {}'.format(
  227. self.device.is_auto_refund, leftTime, usedTime, actualNeedTime, refundedCoins, refundedMoney, refundProtectionTime))
  228. if usedTime < refundProtectionTime:
  229. refundedCoins = VirtualCoin(lineInfo['coins'])
  230. refundedMoney = RMB(lineInfo['price'])
  231. elif not self.device.is_auto_refund:
  232. refundedCoins = VirtualCoin(0)
  233. refundedMoney = RMB(0)
  234. group = self.device.group # type: GroupDict
  235. consumeDict = {
  236. 'reason': self.event_data['reason'],
  237. 'leftTime': leftTimeStr,
  238. 'chargeIndex': port,
  239. 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime,
  240. 'duration': usedTime,
  241. 'serverDuration': serverDuration,
  242. 'uartData': self.event_data.get('uartData', '')
  243. }
  244. if 'cardId' in lineInfo:
  245. cardId = lineInfo['cardId']
  246. card = Card.objects.get(id=cardId)
  247. consumeDict = {
  248. 'chargeIndex': port,
  249. 'reason': self.event_data['reason'],
  250. 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime,
  251. 'duration': usedTime,
  252. 'leftTime': leftTimeStr
  253. }
  254. self.notify_user_service_complete(
  255. service_name=u'充电',
  256. openid=card.managerialOpenId if card else '',
  257. port=port,
  258. address=group['address'],
  259. reason=self.event_data['reason'],
  260. finished_time=recvTime.strftime('%Y-%m-%d %H:%M:%S'),
  261. extra=[{
  262. u'实体卡号': card.cardNo
  263. }]
  264. )
  265. coins = VirtualCoin(lineInfo['coins'])
  266. if refundedCoins > VirtualCoin(0):
  267. consumeDict.update({
  268. DEALER_CONSUMPTION_AGG_KIND.REFUND_CARD: refundedCoins.mongo_amount,
  269. DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: (coins - refundedCoins).mongo_amount
  270. })
  271. self.refund_money_for_card(refundedCoins, str(card.id))
  272. else:
  273. consumeDict.update({
  274. DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: coins.mongo_amount
  275. })
  276. ServiceProgress.update_progress_and_consume_rcd(
  277. self.device['ownerId'],
  278. {'open_id': lineInfo['openId'], 'device_imei': self.device['devNo'],
  279. 'port': lineInfo['port'], 'isFinished': False}, consumeDict)
  280. elif 'consumeRcdId' in lineInfo and lineInfo['consumeRcdId']:
  281. vCardId = lineInfo['vCardId']
  282. vCard = UserVirtualCard.objects(id=vCardId).first() # type: UserVirtualCard
  283. if not vCard:
  284. logger.info('can not find the vCard id = %s' % vCardId)
  285. return
  286. try:
  287. ServiceProgress.update_progress_and_consume_rcd(self.device.ownerId,
  288. {'open_id': lineInfo['openId'],
  289. 'port': int(port),
  290. 'device_imei': self.device.devNo,
  291. 'isFinished': False}, consumeDict)
  292. if refundedCoins > VirtualCoin(0):
  293. vCardConsumeRcd = VCardConsumeRecord.objects.get(id=lineInfo['consumeRcdId'])
  294. vCard.refund_quota(vCardConsumeRcd, usedTime, 0.0, refundedCoins.mongo_amount)
  295. finally:
  296. user = MyUser.objects(openId=lineInfo['openId'], groupId=self.device.groupId).first()
  297. self.notify_user_service_complete(
  298. service_name=u'充电',
  299. openid=user.managerialOpenId if user else '',
  300. port=port,
  301. address=group['address'],
  302. reason=self.event_data['reason'],
  303. finished_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  304. extra=[
  305. {u'虚拟卡号': vCard.cardNo}
  306. ]
  307. )
  308. else:
  309. user = MyUser.objects(openId=lineInfo['openId'],
  310. groupId=self.device.groupId).first()
  311. if not user:
  312. logger.warning(
  313. 'not find user<openId={}, groupId={}>'.format(lineInfo['openId'], self.device.groupId))
  314. return
  315. coins = VirtualCoin(lineInfo['coins'])
  316. try:
  317. is_cash = False
  318. if 'refundRMB_device_event' in self.device.owner.features and refundedMoney > RMB(0):
  319. is_cash = True
  320. consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.COIN: VirtualCoin(coins).mongo_amount})
  321. if refundedMoney > RMB(0) or refundedCoins > VirtualCoin(0):
  322. consumeDict.update(
  323. {DEALER_CONSUMPTION_AGG_KIND.COIN: (VirtualCoin(coins) - refundedCoins).mongo_amount})
  324. self.refund_net_pay(user, lineInfo, refundedMoney, refundedCoins, consumeDict, is_cash)
  325. ServiceProgress.update_progress_and_consume_rcd(self.device.ownerId,
  326. {'open_id': lineInfo['openId'],
  327. 'port': int(port),
  328. 'device_imei': self.device.devNo,
  329. 'isFinished': False}, consumeDict)
  330. finally:
  331. extra = []
  332. if DEALER_CONSUMPTION_AGG_KIND.REFUNDED_CASH in consumeDict:
  333. real_refund = RMB(consumeDict[DEALER_CONSUMPTION_AGG_KIND.REFUNDED_CASH])
  334. if real_refund > RMB(0):
  335. extra.append({u'消费金额': '{}(元)'.format(RMB(lineInfo['price']) - real_refund)})
  336. extra.append({u'退款金额': '{}(元)'.format(real_refund)})
  337. else:
  338. extra.append({u'消费金额': '{}(元)'.format(lineInfo['price'])})
  339. elif DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS in consumeDict:
  340. real_refund = VirtualCoin(consumeDict[DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS])
  341. if real_refund > VirtualCoin(0):
  342. extra.append({u'消费金额': '{}(金币)'.format(VirtualCoin(coins) - real_refund)})
  343. extra.append({u'退款金额': '{}(金币)'.format(real_refund)})
  344. else:
  345. extra.append({u'消费金额': '{}(金币)'.format(VirtualCoin(coins))})
  346. else:
  347. extra.append({u'消费金额': '{}(金币)'.format(VirtualCoin(coins))})
  348. self.notify_user_service_complete(
  349. service_name=u'充电',
  350. openid=user.managerialOpenId if user else '',
  351. port=port,
  352. address=group['address'],
  353. reason=self.event_data['reason'],
  354. finished_time=recvTime.strftime('%Y-%m-%d %H:%M:%S'),
  355. extra=extra)
  356. except Exception as e:
  357. logger.exception(e)