jndzCar.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import logging
  5. import time
  6. from mongoengine import DoesNotExist
  7. from apilib.monetary import RMB, VirtualCoin, Ratio
  8. from apilib.utils_datetime import to_datetime
  9. from apilib.utils_string import make_title_from_dict
  10. from apilib.utils_sys import memcache_lock
  11. from apps.web.constant import Const, FAULT_LEVEL, DeviceCmdCode, DEALER_CONSUMPTION_AGG_KIND
  12. from apps.web.device.models import Group, Device
  13. from apps.web.eventer import EventBuilder
  14. from apps.web.eventer.base import FaultEvent, WorkEvent
  15. from apps.web.user.models import ServiceProgress, CardRechargeOrder, MyUser, UserVirtualCard
  16. logger = logging.getLogger(__name__)
  17. class builder(EventBuilder):
  18. def __getEvent__(self, device_event):
  19. event_data = self.deviceAdapter.analyze_event_data(device_event['data'])
  20. if event_data is None or 'cmdCode' not in event_data:
  21. return None
  22. if 'level' in device_event:
  23. event_data.update({'level': device_event['level']})
  24. if event_data["cmdCode"] in ["0A"]:
  25. return ChargingJNCarFaultEventer(self.deviceAdapter, event_data)
  26. else:
  27. return ChargingJNCarWorkEventer(self.deviceAdapter, event_data)
  28. class ChargingJNCarWorkEventer(WorkEvent):
  29. def support_playback(self):
  30. return self.event_data.get("cmdCode") == "2C"
  31. def do(self, **args):
  32. """
  33. 主驱动函数
  34. :param **args:
  35. :return:
  36. """
  37. cmdCode = self.event_data.get("cmdCode")
  38. # 充电状态上报
  39. if cmdCode == "26":
  40. self._do_port_report()
  41. # 结束事件上报
  42. elif cmdCode == "2C":
  43. self._do_finished()
  44. # 在线卡启动后上报
  45. elif cmdCode == "2D":
  46. self._do_card_consume()
  47. # 查询在线卡余额是否足够
  48. elif cmdCode == "10":
  49. self._do_card_balance()
  50. else:
  51. logger.error("<{}> receive an invalid cmdCode <{}>".format(self.device.devNo, cmdCode))
  52. def _do_port_report(self):
  53. """
  54. 处理端口状态上报的事件, 端口状态一律不再这里面更新,防止主板误报造成订单异常
  55. :return:
  56. """
  57. self.event_data.pop("cmdCode", None)
  58. portNum = self.event_data.get("portNum", 0)
  59. devCache = Device.get_dev_control_cache(self.device.devNo)
  60. for i in xrange(portNum):
  61. portStr = str(i + 1)
  62. portCache = devCache.get(portStr, dict())
  63. tempInfo = self.event_data.get(portStr)
  64. usedTime = tempInfo.get("usedTime", 0)
  65. power = tempInfo.get("power", 0)
  66. usedElec = tempInfo.get("usedElec", 0)
  67. voltage = tempInfo.get("voltage", 0)
  68. portCache.update({
  69. "usedTime": usedTime,
  70. "power": power,
  71. "usedElec": usedElec,
  72. "voltage": voltage
  73. })
  74. # devCache 中会有不是 dict 的项目
  75. Device.update_dev_control_cache(self.device.devNo, {portStr:portCache})
  76. # TODO zjl 端口上报记录 功率曲线绘制
  77. def _do_finished(self):
  78. """
  79. 处理结束事件 拿到sessionId 作为去重标准
  80. :return:
  81. """
  82. # TODO zjl 区别到底是 刷卡的结束还是扫码的结束 依赖缓存或者依赖主板上报?
  83. cardNo = self.event_data.get("cardNo")
  84. sessionId = self.event_data.get("sessionId")
  85. portStr = self.event_data.get("portStr")
  86. lock_id = "{}-{}-{}".format(self.device.devNo, portStr, sessionId)
  87. try:
  88. with memcache_lock(lock_id, value = '1', expire = 60) as acquired:
  89. if not acquired:
  90. return
  91. portCache = Device.clear_port_control_cache(self.device.devNo, portStr)
  92. if not portCache:
  93. return
  94. try:
  95. if int(cardNo):
  96. self._do_card_finished(portCache=portCache)
  97. else:
  98. self._do_net_pay_finished(portCache=portCache)
  99. except Exception as e:
  100. logger.exception(e)
  101. finally:
  102. openId = portCache.get("openId")
  103. user = MyUser.objects.filter(openId=openId, groupId=self.device.groupId).first()
  104. reason = self.event_data.get("desc", "")
  105. group = Group.get_group(self.device.groupId)
  106. # 通知用户充电结束
  107. self.notify_user(
  108. managerialOpenId=user.managerialOpenId if user else "",
  109. templateName="service_complete",
  110. title=u"\\n\\n结束原因:\\t\\t{reason}\\n\\n设备编号:\\t\\t{logicalCode}-{port}\\n\\n服务地址:\\t\\t{group}".format(
  111. reason=reason,
  112. logicalCode=self.device["logicalCode"],
  113. port=portStr,
  114. group=group.get("address", "") if group else "",
  115. ),
  116. service=u"充电服务",
  117. finishTime=to_datetime(self.recvTime).strftime('%Y-%m-%d %H:%M:%S'),
  118. remark = u'谢谢您的支持'
  119. )
  120. finally:
  121. self.deviceAdapter._response_finished(portStr, sessionId)
  122. def _do_card_balance(self):
  123. """
  124. ID 卡刷卡信号上报 用于检测是否是有效卡还是无效卡 卡消费金额默认大一些 防止出错
  125. :return:
  126. """
  127. cardNo = self.event_data.get("cardNo", "")
  128. cardCst = self.event_data.get("cardCst", 256)
  129. card = self.update_card_dealer_and_type(cardNo)
  130. if not card or not card.openId:
  131. return self.deviceAdapter._response_card_balance(0, "02")
  132. # 没有到账的卡给它充值, 应该用不上
  133. card_recharge_order = CardRechargeOrder.get_last_to_do_one(str(card.id))
  134. self.recharge_id_card(
  135. card=card,
  136. rechargeType='append',
  137. order=card_recharge_order
  138. )
  139. card.reload()
  140. # 检查卡的余额是否足够
  141. if float(card.balance) >= float(cardCst):
  142. res = "00"
  143. else:
  144. res = "01"
  145. return self.deviceAdapter._response_card_balance(float(card.balance), res)
  146. def _do_card_consume(self):
  147. """
  148. 刷卡投币成功启动设备后上报 目前只有ID卡 到了此命令之后 设备已经启动 此处需要处理的就是扣费以及记录
  149. :return:
  150. """
  151. self.deviceAdapter._send_data("2D", "{:0>10}".format(self.event_data.get("sessionId", "")), cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  152. portStr = self.event_data.get("portStr")
  153. needElec = self.event_data.get("needElec", 0)
  154. cardNo = self.event_data.get("cardNo")
  155. cardCst = self.event_data.get("cardCst")
  156. card = self.update_card_dealer_and_type(cardNo)
  157. if not card or not card.openId:
  158. logger.warning("error card, cardNo is {}".format(cardNo))
  159. return
  160. res, cardBalance = self.consume_money_for_card(card, RMB(cardCst))
  161. if res != 1:
  162. logger.warning("consume error!!!, cardNo is {}".format(cardNo))
  163. return
  164. consumeDict = {
  165. "chargeIndex": portStr,
  166. "needElec": needElec
  167. }
  168. orderNo, cardOrderNo = self.record_consume_for_card(card, RMB(cardCst), servicedInfo=consumeDict)
  169. portCache = {
  170. "isStart": True,
  171. "status": Const.DEV_WORK_STATUS_WORKING,
  172. "openId": card.openId,
  173. "price": cardCst,
  174. "coins": cardCst,
  175. "needElec": needElec,
  176. "startTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  177. "startTimeStamp": int(time.time()),
  178. "consumeType": "card",
  179. }
  180. Device.update_dev_control_cache(self.device.devNo, {portStr: portCache})
  181. ServiceProgress.register_card_service(
  182. self.device,
  183. int(portStr),
  184. card,
  185. consumeOrder={
  186. "orderNo": orderNo,
  187. "cardOrderNo": cardOrderNo,
  188. }
  189. )
  190. def _do_card_finished(self, portCache):
  191. """
  192. 处理刷卡结束的上报事件
  193. 卡的结束退费金额会在事件里面上报 这个地方不再需要去计算
  194. :return:
  195. """
  196. portStr = self.event_data.get("portStr")
  197. cardNo = self.event_data.get("cardNo")
  198. cardLeftBalance = self.event_data.get("cardLeftBalance")
  199. desc = self.event_data.get("desc")
  200. price = portCache.get("price", 0)
  201. if not price:
  202. return
  203. card = self.update_card_dealer_and_type(cardNo)
  204. consumeDict = {
  205. "chargeIndex": portStr,
  206. "cardId": str(card.id),
  207. "reason": desc
  208. }
  209. leftBalance = RMB(cardLeftBalance)
  210. # 处理退额超限问题
  211. if leftBalance > RMB(price):
  212. leftBalance = RMB(0)
  213. if leftBalance > RMB(0):
  214. self.refund_money_for_card(leftBalance, str(card.id))
  215. consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.REFUND_CARD: leftBalance.mongo_amount})
  216. self.notify_user(
  217. managerialOpenId=card.managerialOpenId,
  218. templateName="refund_coins",
  219. title=u"卡退款",
  220. backCount=u"{}".format(leftBalance.mongo_amount),
  221. finishTime=to_datetime(self.recvTime).strftime('%Y-%m-%d %H:%M:%S')
  222. )
  223. ServiceProgress.update_progress_and_consume_rcd(
  224. self.device.ownerId,
  225. {
  226. "open_id": card.openId,
  227. "device_imei": self.device.devNo,
  228. "port": int(portStr),
  229. "cardId": str(card.id),
  230. "isFinished": False
  231. },
  232. consumeDict
  233. )
  234. def _do_net_pay_finished(self, portCache):
  235. """
  236. 处理网络支付的结束事件
  237. :return:
  238. """
  239. portStr = self.event_data.get("portStr")
  240. openId = portCache.get("openId")
  241. # 没有 openId 的说明已经被处理过了
  242. if not openId:
  243. logger.error("portCache has no openId , dev is <{}>, port is <{}>".format(self.device.devNo, portStr))
  244. user = MyUser.objects.filter(openId=openId, groupId=self.device.groupId).first()
  245. # 消费相关信息的记录
  246. consumeDict = {
  247. "chargeIndex": portStr,
  248. "reason": self.event_data.get("desc"),
  249. }
  250. billingType = portCache.get("billingType", "elec").capitalize()
  251. leftKey = "left{}".format(billingType)
  252. needKey = "need{}".format(billingType)
  253. consumeDict.update({
  254. leftKey: self.event_data.get(leftKey)
  255. })
  256. # 退费的相应处理
  257. refundInfo = self._get_refund_money(
  258. self.event_data.get(leftKey, 0),
  259. portCache.get(needKey, 0),
  260. portCache
  261. )
  262. refundMoney = refundInfo.get("money")
  263. logger.info("refund Money is <{}>".format(refundMoney.mongo_amount))
  264. refundType = refundInfo.get("type")
  265. if refundType == 'vCard':
  266. vCardId = portCache.get('vCardId')
  267. logger.info("finished with vCard!")
  268. try:
  269. vCard = UserVirtualCard.objects.get(id=vCardId)
  270. except DoesNotExist:
  271. logger.info('can not find the vCard id = %s' % vCardId)
  272. return
  273. extra = []
  274. extra.append({u'虚拟卡券': u'{}--{}'.format(vCard.cardName, vCard.cardNo)})
  275. if billingType == 'time':
  276. extra.append({u'消费明细': u'消费{}分钟'.format(portCache.get(needKey, 0))})
  277. else:
  278. extra.append({u'消耗明细': u'消费{}度'.format(portCache.get(needKey, 0))})
  279. group = Group.get_group(self.device['groupId'])
  280. self.notify_user_service_complete(
  281. service_name='充电',
  282. openid=openId,
  283. port=self.event_data["port"],
  284. address=group["address"],
  285. reason=self.event_data.get('reason'),
  286. finished_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  287. extra=extra
  288. )
  289. else:
  290. if refundMoney and refundMoney > VirtualCoin(0):
  291. self.refund_net_pay(user, portCache, RMB(refundMoney.amount), refundMoney, consumeDict, (refundType == "cash"))
  292. if DEALER_CONSUMPTION_AGG_KIND.REFUNDED_CASH in consumeDict:
  293. refundTitle = u"退款"
  294. backCount = "{} 元".format(refundMoney.mongo_amount)
  295. elif DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS in consumeDict:
  296. refundTitle = u"金币退款"
  297. backCount =u"金币: {}".format(refundMoney.mongo_amount)
  298. else:
  299. backCount = None
  300. if backCount:
  301. self.notify_user(
  302. managerialOpenId=user.managerialOpenId or "",
  303. templateName="refund_coins",
  304. title=refundTitle,
  305. backCount=backCount,
  306. finishTime=to_datetime(self.recvTime).strftime('%Y-%m-%d %H:%M:%S'))
  307. ServiceProgress.update_progress_and_consume_rcd(
  308. self.device.ownerId,
  309. {
  310. "open_id": openId,
  311. "device_imei": self.device.devNo,
  312. "port": int(portStr),
  313. "isFinished": False
  314. },
  315. consumeDict
  316. )
  317. def _get_refund_money(self, left, need, portCache, **kwargs):
  318. """
  319. 获取结束时候需要退还的 金币 或者 现金
  320. :param left: 剩余的
  321. :param need: 订购的
  322. :param portCache: 端口的缓存数据
  323. :param kwargs: 预留的扩充字段
  324. :return:
  325. """
  326. if not self.device.is_auto_refund:
  327. return {
  328. "type": "coin",
  329. "money": VirtualCoin(0)
  330. }
  331. if 'vCardId' in portCache and portCache["vCardId"]:
  332. return {
  333. "type": 'vCard',
  334. "money": VirtualCoin(0)
  335. }
  336. refund_recharge_ids = []
  337. if 'rechargeRcdId' in portCache:
  338. refund_recharge_ids.append(portCache['rechargeRcdId'])
  339. else:
  340. pay_info = portCache.get('payInfo', list())
  341. for item in pay_info:
  342. if 'rechargeRcdId' in item:
  343. refund_recharge_ids.append(item['rechargeRcdId'])
  344. price = portCache.get("price", 0)
  345. coins = portCache.get("coins", 0)
  346. if len(refund_recharge_ids) > 0:
  347. _type = "cash"
  348. payMoney = VirtualCoin(price)
  349. else:
  350. _type = "coin"
  351. payMoney = VirtualCoin(coins)
  352. try:
  353. refundMoney = Ratio(float(left) / float(need)) * VirtualCoin(payMoney)
  354. except ZeroDivisionError:
  355. refundMoney = VirtualCoin(0)
  356. # 防止退大额度
  357. if refundMoney > payMoney:
  358. refundMoney = VirtualCoin(0)
  359. return {
  360. "type": _type,
  361. "money": refundMoney
  362. }
  363. class ChargingJNCarFaultEventer(FaultEvent):
  364. def do(self, **args):
  365. group = Group.get_group(self.device['groupId'])
  366. if self.is_notify_dealer():
  367. titleDictList = [
  368. {u'告警名称': u'设备告警'},
  369. {u'设备编号': self.device["logicalCode"]},
  370. {u'地址名称': group["groupName"]}
  371. ]
  372. self.notify_dealer('device_fault', **{
  373. 'title': make_title_from_dict(titleDictList),
  374. 'device': u'%s号设备\\n' % self.device['groupNumber'],
  375. 'faultType': u'设备告警\\n',
  376. 'notifyTime': u'%s\\n' % datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  377. 'fault': u'%s(详细情况,建议您到管理后台的告警模块进行查看)\\n' % self.event_data.get("errorDesc"),
  378. })
  379. self.record(
  380. faultCode=self.event_data.get("errorCode"),
  381. description=self.event_data.get("errorDesc"),
  382. title=u"设备告警",
  383. detail=self.event_data.get("errorDesc"),
  384. level=self.event_data.get('level', FAULT_LEVEL.NORMAL),
  385. portNo=self.event_data.get("portStr")
  386. )