cmCZSub.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. # coding=utf-8
  2. import datetime
  3. import logging
  4. import typing
  5. from arrow import Arrow
  6. from django.conf import settings
  7. from apilib.monetary import VirtualCoin
  8. from apps.web.constant import START_DEVICE_STATUS, DEALER_CONSUMPTION_AGG_KIND, DeviceCmdCode
  9. from apps.web.core.device_define.cmCZ import FINISH_REASON_MAP, CARD_RESPONSE, CARD_CST_MIN, CARD_CST
  10. from apps.web.core.networking import MessageSender
  11. from apps.web.device.models import Device
  12. from apps.web.eventer.base import WorkEvent, ComNetPayAckEvent, IdStartAckEvent, AckEventProcessorIntf
  13. from apps.web.user.utils import clear_frozen_user_balance
  14. from apps.web.utils import set_start_key_status
  15. from apps.web.user.models import ConsumeRecord, Card, CardRechargeOrder
  16. logger = logging.getLogger(__name__)
  17. class cmCZSubEventWorker(WorkEvent):
  18. def do(self, **args):
  19. funCode = self.event_data["cmdCode"]
  20. if funCode == "26":
  21. return self._do_report()
  22. if funCode == "10":
  23. return self._do_card()
  24. def _do_report(self):
  25. self.device.set_online(31)
  26. portInfo = self.event_data.pop("portInfo")
  27. Device.update_dev_control_cache(self.device.devNo, portInfo)
  28. def _do_card(self):
  29. cardNo = self.event_data["cardNo"]
  30. cardCst = self.device.otherConf.get("cardCst", CARD_CST)
  31. cardCstMin = self.device.otherConf.get("cardCstMin", CARD_CST_MIN)
  32. card = self.update_card_dealer_and_type(cardNo) # type: Card
  33. if not card:
  34. logger.info("[cmCZSubEventWorker _do_card] not find card, devNo = {}, cardNo = {}".format(self.device.devNo, cardNo))
  35. self.device.deviceAdapter.response_card(CARD_RESPONSE.FAIL, 0, 0)
  36. return
  37. if card.frozen:
  38. logger.info("[cmCZSubEventWorker _do_card] card is frozen, devNo = {}, cardNo = {}".format(self.device.devNo, cardNo))
  39. self.device.deviceAdapter.response_card(CARD_RESPONSE.FAIL, 0, 0)
  40. return
  41. # 如果存在的话 充值一次卡
  42. order = CardRechargeOrder.get_last_to_do_one(str(card.id))
  43. self.recharge_id_card(card=card, rechargeType='append', order=order)
  44. card.reload()
  45. if VirtualCoin(card.balance) <= VirtualCoin(cardCstMin):
  46. logger.info("[cmCZSubEventWorker _do_card] card not enough, devNo = {}, cardNo = {}, balance = {}, min = {}".format(self.device.devNo, cardNo, card.balance, cardCstMin))
  47. self.device.deviceAdapter.response_card(CARD_RESPONSE.NOT_ENOUGH, 0, 0)
  48. return
  49. # 剩下的情况回复主板 启动卡的金额为卡上余额和卡启动金额的最小值
  50. cst = min(VirtualCoin(card.balance), VirtualCoin(cardCst))
  51. logger.info("[cmCZSubEventWorker _do_card] card success, devNo = {}, cardNo = {}, balance = {}, cst = {}".format(self.device.devNo, cardNo, card.balance, cst))
  52. self.device.deviceAdapter.response_card(CARD_RESPONSE.SUCCESS, cst, card.balance, cardNo)
  53. class cmCZSubComNetPayAckEvent(ComNetPayAckEvent):
  54. def ack_msg(self):
  55. payload = {
  56. 'order_id': self.event_data['order_id'],
  57. 'order_type': self.event_data['order_type'],
  58. 'status': self.event_data['status']
  59. }
  60. MessageSender.send_no_wait(
  61. device=self.device.deviceAdapter.masterDevice,
  62. cmd=DeviceCmdCode.EVENT_ACK,
  63. payload=payload
  64. )
  65. def do_impl(self):
  66. order_id = self.event_data['order_id']
  67. order = ConsumeRecord.objects(ownerId = self.device.ownerId, orderNo = order_id).first() # type: ConsumeRecord
  68. if not order:
  69. logger.debug('order<no={}> is not exist.'.format(self.event_data['order_id']))
  70. return
  71. if order.status == 'finished':
  72. logger.debug('order<{}> has been fished.'.format(repr(order)))
  73. return
  74. addr, port = self.event_data['port'].split("-")
  75. self.event_data["port"] = int(port)
  76. if order.used_port != self.event_data['port']:
  77. logger.error('port is not equal. {} != {}'.format(self.event_data['port'], order.used_port))
  78. return
  79. if self.pre_processor:
  80. self.event_data = self.pre_processor.pre_processing(self.device, self.event_data)
  81. if self.event_data['status'] in ['running', 'finishing']:
  82. return self.deal_running_event(order)
  83. if self.event_data['status'] == 'finished':
  84. return self.deal_finished_event(order)
  85. def do_running_order(self, order, result): # type: (ConsumeRecord, dict)->None
  86. """
  87. 处理正在运行的订单
  88. """
  89. # 订单消息已经被回复过
  90. if order.status in ["running", "finished"]:
  91. logger.debug('order<{}> no need to deal. this has done.'.format(repr(order)))
  92. return
  93. # 启动设备的时候 设备实际启动成功 但是订单串口超时 不知道订单的明确状态 后面启动时间又重新上报
  94. if order.status == "unknown":
  95. errorDesc = u"设备信号恢复,订单正常运行"
  96. logger.info("order <{}> timeout to running")
  97. # 正常运行的订单
  98. else:
  99. errorDesc = u""
  100. order.errorDesc = errorDesc
  101. order.isNormal = True
  102. order.status = 'running'
  103. order.startTime = datetime.datetime.fromtimestamp(result['sts'])
  104. order.save()
  105. set_start_key_status(start_key=order.startKey, state=START_DEVICE_STATUS.FINISHED, order_id=str(order.id))
  106. def do_finished_order(self, order, result): # type: (ConsumeRecord, dict)->None
  107. """
  108. 处理结束的订单
  109. """
  110. portCache = Device.get_port_control_cache(self.device.devNo, str(order.used_port))
  111. self.event_data["portCache"] = portCache
  112. if order.status == "running":
  113. order.isNormal = True
  114. order.status = "finished"
  115. order.errorDesc = u""
  116. order.finishedTime = datetime.datetime.fromtimestamp(result['fts'])
  117. # 非正常状态 相当于订单最开始串口超时 然后直接变为结束
  118. elif order.status == "unknown":
  119. order.isNormal = True
  120. order.status = "finished"
  121. order.errorDesc = u"设备信号恢复,订单正常结束(0001)"
  122. order.startTime = datetime.datetime.fromtimestamp(result['sts'])
  123. order.finishedTime = datetime.datetime.fromtimestamp(result['fts'])
  124. # 正常状态 相当于订单启动失败或者是中间running单没有上来
  125. elif order.status == "created":
  126. order.isNormal = True
  127. order.status = "finished"
  128. order.errorDesc = u"设备信号恢复,订单正常结束(0002)"
  129. order.startTime = datetime.datetime.fromtimestamp(result['sts'])
  130. order.finishedTime = datetime.datetime.fromtimestamp(result['fts'])
  131. else:
  132. logger.warning('order<{}> status = <{}> to finished. no deal with'.format(repr(order), order.status))
  133. order.save()
  134. set_start_key_status(start_key=order.startKey, state=START_DEVICE_STATUS.FINISHED, order_id=str(order.id))
  135. def do_finished_event(self, order, sub_orders, merge_order_info):
  136. """
  137. 订单状态已经变更完成 进一步处理 完成返费 结束订单等等
  138. """
  139. order.reload()
  140. totalCst = self.event_data["totalCst"] / 3600.0
  141. sts = self.event_data["sts"]
  142. fts = self.event_data["fts"]
  143. coins = merge_order_info["coins"]
  144. cst = min(VirtualCoin(totalCst), VirtualCoin(coins))
  145. backCoins = VirtualCoin(coins) - cst
  146. duration = (fts - sts) / 60
  147. extra = [
  148. {u"本次订购金额": "{}".format(VirtualCoin(coins).amount)},
  149. {u"本次实际使用时长": "{}分钟".format(duration)},
  150. {u"消费金额": "{}(金币)".format(cst.amount)},
  151. ]
  152. if backCoins > VirtualCoin(0):
  153. extra.append({u"退费金额": u"{}(金币)".format(VirtualCoin(backCoins).amount)})
  154. clear_frozen_user_balance(self.device, order, duration, spendElec=0, backCoins=backCoins, user=order.user)
  155. # 组织消费信息
  156. consumeDict = {
  157. "reason": self._get_finish_reason(),
  158. DEALER_CONSUMPTION_AGG_KIND.DURATION: duration,
  159. DEALER_CONSUMPTION_AGG_KIND.SPEND_MONEY: VirtualCoin(cst).mongo_amount,
  160. }
  161. order.update_service_info(consumeDict)
  162. self.notify_user_service_complete(
  163. service_name='充电',
  164. openid=order.user.managerialOpenId,
  165. port=str(order.used_port),
  166. address=order.address,
  167. reason=consumeDict["reason"],
  168. finished_time=order.finishedTime.strftime('%Y-%m-%d %H:%M:%S'),
  169. extra=extra
  170. )
  171. def merge_order(self, master_order, sub_orders):
  172. """
  173. 诚马的插座不会有续充
  174. """
  175. start_time = Arrow.fromdatetime(master_order.startTime, tzinfo=settings.TIME_ZONE)
  176. coins = master_order.package["coins"]
  177. price = master_order.package["price"]
  178. portCache = {
  179. "openId": master_order.openId,
  180. "consumeType": "mobile",
  181. "coins": coins,
  182. "price": price,
  183. "estimatedTs": int(start_time.timestamp + 720 * 60 * 60),
  184. }
  185. return portCache
  186. def _get_finish_reason(self):
  187. if "reason" in self.event_data:
  188. return FINISH_REASON_MAP.get(self.event_data["reason"], u"未知原因")
  189. return u"未知原因"
  190. class cmCZSubIdStartAckEvent(IdStartAckEvent):
  191. # def do_impl(self, **args):
  192. # addr, port = self.event_data['port'].split("-")
  193. # self.event_data["port"] = int(port)
  194. #
  195. # super(cmCZSubIdStartAckEvent, self).do_impl(**args)
  196. def post_after_start(self, order=None):
  197. pass
  198. def post_after_finish(self, order=None):
  199. pass
  200. def merge_order(self, master_order, sub_orders): # type:(ConsumeRecord, Iterable[ConsumeRecord])->dict
  201. start_time = Arrow.fromdatetime(master_order.startTime, tzinfo=settings.TIME_ZONE)
  202. coins = self.event_data["fee"]
  203. cardNo = self.event_data["cardNo"]
  204. portCache = {
  205. "openId": master_order.openId,
  206. "consumeType": "card",
  207. "coins": coins,
  208. "price": coins,
  209. "cardNo": cardNo,
  210. "estimatedTs": int(start_time.timestamp + 720 * 60 * 60),
  211. }
  212. return portCache
  213. def do_finished_event(self, order, merge_order_info): # type:(ConsumeRecord, dict)->None
  214. order.reload()
  215. totalCst = self.event_data["totalCst"] / 3600.0
  216. sts = self.event_data["sts"]
  217. fts = self.event_data["fts"]
  218. coins = merge_order_info["coins"]
  219. cst = min(VirtualCoin(totalCst), VirtualCoin(coins))
  220. backCoins = VirtualCoin(coins) - cst
  221. duration = (fts - sts) / 60
  222. extra = [
  223. {u"本次订购金额": "{}".format(VirtualCoin(coins).amount)},
  224. {u"本次实际使用时长": "{}分钟".format(duration)},
  225. {u"消费金额": "{}(金币)".format(cst.amount)},
  226. {u'实体卡': '{}--No:{}'.format(self.card.cardName, self.card.cardNo)}
  227. ]
  228. if backCoins > VirtualCoin(0):
  229. extra.append({u"退费金额": u"{}(金币)".format(VirtualCoin(backCoins).amount)})
  230. self.card.clear_frozen_balance(str(order.id), backCoins)
  231. self.card.reload()
  232. # 组织消费信息
  233. consumeDict = {
  234. "reason": self._get_finish_reason(),
  235. DEALER_CONSUMPTION_AGG_KIND.DURATION: duration,
  236. DEALER_CONSUMPTION_AGG_KIND.SPEND_MONEY: VirtualCoin(cst).mongo_amount,
  237. }
  238. order.update_service_info(consumeDict)
  239. self.notify_user_service_complete(
  240. service_name='充电',
  241. openid=order.user.managerialOpenId,
  242. port=str(order.used_port),
  243. address=order.address,
  244. reason=consumeDict["reason"],
  245. finished_time=order.finishedTime.strftime('%Y-%m-%d %H:%M:%S'),
  246. extra=extra
  247. )
  248. def checkout_order(self, order):
  249. fee = VirtualCoin(order.coin)
  250. self.card.freeze_balance(transaction_id=str(order.id), fee=fee)
  251. def _get_finish_reason(self):
  252. if "reason" in self.event_data:
  253. return FINISH_REASON_MAP.get(self.event_data["reason"], u"未知原因")
  254. return u"未知原因"
  255. class cmCZSubAckEventProcessorIntf(AckEventProcessorIntf):
  256. def pre_processing(self, device, event_data): # type:(DeviceDict, dict)->dict
  257. addr, port = event_data['port'].split("-")
  258. event_data["port"] = int(port)
  259. event_data["fee"] = event_data["balance"]
  260. return event_data