changyuanCar2.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import logging
  5. import random
  6. import time
  7. import typing
  8. import simplejson as json
  9. from bson import ObjectId
  10. from mongoengine import DoesNotExist, ValidationError
  11. from apilib.monetary import VirtualCoin, RMB, Ratio
  12. from apps.web.agent.models import Agent
  13. from apps.web.common.models import TempValues
  14. from apps.web.constant import Const, DEALER_CONSUMPTION_AGG_KIND, USER_RECHARGE_TYPE
  15. from apps.web.core.device_define.changyuan import CYCardMixin
  16. from apps.web.core.payment import WithdrawGateway
  17. from apps.web.dealer.models import Dealer
  18. from apps.web.device.models import Group, Device
  19. from apps.web.eventer.base import WorkEvent
  20. from apps.web.eventer import EventBuilder
  21. from apps.web.report.ledger import Ledger
  22. from apps.web.user.models import ConsumeRecord
  23. from apps.web.user.transaction_deprecated import refund_money, refund_cash
  24. from apps.web.user.models import ServiceProgress, MyUser, RechargeRecord, Card, CardRechargeOrder, CardRechargeRecord
  25. if typing.TYPE_CHECKING:
  26. from apps.web.device.models import GroupDict
  27. logger = logging.getLogger(__name__)
  28. class builder(EventBuilder):
  29. def __getEvent__(self, device_event):
  30. event_data = self.deviceAdapter.analyze_event_data(device_event['data'])
  31. if event_data is None or 'cmdCode' not in event_data:
  32. return None
  33. if 'a8id' in device_event:
  34. event_data.update({
  35. 'a8id': device_event['a8id']
  36. })
  37. if device_event["cmd"] == 100: # zjl
  38. return ChangYuanCarWorkEventer(self.deviceAdapter, event_data)
  39. class ChangYuanCarWorkEventer(CYCardMixin, WorkEvent):
  40. def do(self, **args):
  41. cmdCode = self.event_data.get("cmdCode")
  42. if cmdCode == "A8":
  43. a8id = self.event_data.get("a8id", "")
  44. self._response_a8id(a8id)
  45. self.do_finished()
  46. elif cmdCode == "AE":
  47. self.do_status_change()
  48. elif cmdCode == "B1":
  49. self.do_card_start()
  50. elif cmdCode == "B2":
  51. self.do_status_report()
  52. elif cmdCode == "B3":
  53. self.do_card_refund()
  54. elif cmdCode == "F4":
  55. self.do_recharge_card()
  56. elif cmdCode == "F3":
  57. self.do_recharge_card_result()
  58. else:
  59. logger.error("error cmdCode".format(cmdCode))
  60. def do_finished(self):
  61. cardNo = self.event_data.get("cardNo")
  62. if cardNo == "00000000":
  63. self.do_net_pay_finish()
  64. else:
  65. self.do_card_pay_finish()
  66. def do_card_start(self):
  67. cardNo = self.event_data.get("cardNo")
  68. logger.info("card <{}>start report, event_info is {}".format(cardNo, self.event_data))
  69. if cardNo == "00000000":
  70. logger.info("cardNo is 00000000!")
  71. return
  72. card = self.update_card_dealer_and_type(self.event_data["cardNo"])
  73. if not self.check_card_can_use(card):
  74. logger.info("[ChangYuanCarWorkEventer do_event_start] check card not can use devNo = {}".format(self.device.devNo))
  75. self.notify_invalid_card_to_dealer(self.event_data["cardNo"], card)
  76. return self.deviceAdapter.stop()
  77. devCache = {
  78. "isStart": True,
  79. "startTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  80. 'status': Const.DEV_WORK_STATUS_WORKING,
  81. 'finishedTime': int(time.time()) + 12 * 60 * 60
  82. }
  83. Device.update_dev_control_cache(self.device["devNo"], devCache)
  84. if not card:
  85. return
  86. ServiceProgress.register_card_service(
  87. self.device,
  88. 0,
  89. card,
  90. )
  91. def do_status_report(self):
  92. devCache = Device.get_dev_control_cache(self.device.devNo) or dict()
  93. orderNo, openId = devCache.get("orderNo"), devCache.get("openId")
  94. if not orderNo or not openId:
  95. logger.info("dev <{}> heart beat but not orderNo, {}".format(self.device.devNo, self.event_data))
  96. leftMoney = RMB(self.event_data.get("leftBalance"))
  97. usedElec = self.event_data.get("usedElec")
  98. temperature = self.event_data.get("temperature")
  99. power = self.event_data.get("power")
  100. voltage = self.event_data.get("voltage")
  101. sid = self.event_data.get("sid")
  102. data = {
  103. "leftMoney": str(leftMoney),
  104. "usedElec": usedElec,
  105. "temperature": temperature,
  106. "power": power,
  107. "voltage": voltage,
  108. "sid": sid
  109. }
  110. try:
  111. record = ConsumeRecord.objects.get(orderNo=orderNo, openId=openId) # type:ConsumeRecord
  112. record.servicedInfo = data
  113. record.save()
  114. devCache.update(data)
  115. Device.update_dev_control_cache(self.device.devNo, devCache)
  116. except Exception as e:
  117. logger.error("[{} do_status_report, event = {}, error = {}]".format(self.__class__.__name__, self.event_data, e))
  118. return
  119. def do_card_refund(self):
  120. beforeRefund = self.event_data.get("beforeRefund")
  121. refund = self.event_data.get("refund")
  122. afterRefund = self.event_data.get("afterRefund")
  123. cardNo = self.event_data.get("cardNo")
  124. if VirtualCoin(beforeRefund) + VirtualCoin(refund) != VirtualCoin(afterRefund):
  125. logger.info("bad refund event, beforeRefund is {}, refund is {} afterRefund is {}".format(
  126. beforeRefund, refund, afterRefund
  127. ))
  128. return
  129. card = self.update_card_dealer_and_type(cardNo, cardType="IC")
  130. if not card:
  131. logger.info("bad cardNo cardNo is {}".format(cardNo))
  132. return
  133. if VirtualCoin(card.balance) != VirtualCoin(beforeRefund):
  134. logger.info(
  135. "beforeRefund isn't equal card balance, cardNo is {}, beforeRefund is {}, card balance is {}".format(
  136. cardNo, beforeRefund, card.balance
  137. ))
  138. return
  139. self.refund_money_for_card(RMB(refund), str(card.id))
  140. self.notify_user(
  141. card.managerialOpenId,
  142. 'refund_coins',
  143. **{
  144. 'title' : u"充电桩返费",
  145. 'backCount' : u'%s' % refund,
  146. 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  147. }
  148. )
  149. def do_status_change(self):
  150. status = self.event_data.get("status")
  151. Device.update_dev_control_cache(self.device.devNo, {"status": status})
  152. def do_net_pay_finish(self):
  153. devCache = Device.get_dev_control_cache(self.device["devNo"])
  154. openId = devCache.pop("openId", None)
  155. coins = devCache.get("coins")
  156. price = devCache.get("price")
  157. rechargeRcdId = devCache.get("rechargeRcdId")
  158. vCardId = devCache.get("vCardId")
  159. rechargeRecord = self.do_ledger(rechargeRcdId)
  160. if not openId:
  161. logger.info("ChangYuan net pay finish with no openId! {}".format(self.device["devNo"]))
  162. return
  163. nowTime = datetime.datetime.now()
  164. consumeDict = {
  165. "elec": self.event_data["usedElec"],
  166. "duration": self.event_data["chargeTime"],
  167. "coin": str(VirtualCoin(coins)),
  168. "spendMoney": str(RMB(price - self.event_data["balance"])),
  169. "reason": self.event_data["desc"],
  170. "finishedTime": datetime.datetime.strftime(nowTime, "%Y-%m-%d %H:%M:%S")
  171. }
  172. user = MyUser.objects.filter(openId = openId, groupId = self.device["groupId"]).first()
  173. group = self.device.group
  174. extra = [
  175. {u"使用时长": u"{} 分钟".format(self.event_data['chargeTime'])},
  176. {u"付款金额": u"{} {}".format(coins, u"金币" if not rechargeRcdId else u"元")}
  177. ]
  178. self.notify_user_service_complete(
  179. service_name=u"充电",
  180. openid=user.managerialOpenId if user else "",
  181. port=self.event_data['port'],
  182. address=group.address,
  183. reason=self.event_data["desc"],
  184. finished_time=nowTime.strftime("%Y-%m-%d %H:%M:%S"),
  185. extra=extra
  186. )
  187. # 修改, 只有快捷支付的 才能够 进行现金退款
  188. refundMoney = RMB(self.event_data["balance"])
  189. if refundMoney > RMB(0):
  190. # 现金支付的
  191. if rechargeRecord and rechargeRecord.isQuickPay:
  192. # 由于前边已经分账了 这个地方直接还是需要从经销商统计里面扣除的
  193. refund_order = refund_cash(
  194. rechargeRecord, refundMoney, VirtualCoin(0),
  195. user = user, minus_total_consume = VirtualCoin(rechargeRecord.coins))
  196. if refund_order:
  197. logger.info("refund cash apply success!")
  198. consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.REFUNDED_CASH: refundMoney.mongo_amount})
  199. self.notify_user(
  200. user.managerialOpenId if user else '',
  201. 'refund_coins',
  202. **{
  203. 'title': u"退款(退款金额将于1-5个工作日内返还)",
  204. 'backCount': u'%s(元)' % refundMoney,
  205. 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  206. }
  207. )
  208. else:
  209. logger.info("refund cash fail")
  210. # 金币支付的并且打开了金币退款的开关
  211. elif not vCardId and self.device.is_auto_refund:
  212. # 重新计算退币数量 防止coins不等于price
  213. refundMoney = VirtualCoin(coins) * Ratio(refundMoney.amount / RMB(price).amount)
  214. refund_money(self.device, refundMoney, openId)
  215. consumeDict.update({
  216. DEALER_CONSUMPTION_AGG_KIND.COIN: (VirtualCoin(coins) - refundMoney).mongo_amount,
  217. DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: refundMoney.mongo_amount
  218. })
  219. self.notify_user(
  220. user.managerialOpenId if user else '',
  221. 'refund_coins',
  222. **{
  223. 'title': u"退款",
  224. 'backCount': u'%s(金币)' % refundMoney,
  225. 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  226. }
  227. )
  228. # 剩下的 支付方式有 1.虚拟卡支付 2.金币支付但是没有打开金币支付的开关
  229. else:
  230. logger.info("not need to refund pay type! {}".format(self.event_data))
  231. ServiceProgress.update_progress_and_consume_rcd(
  232. self.device["ownerId"],
  233. {
  234. "open_id": openId,
  235. "device_imei": self.device["devNo"],
  236. "port": 0,
  237. "isFinished": False
  238. },
  239. consumeDict)
  240. # 推送订单结束信息
  241. startTime = Device.get_dev_control_cache(self.device.devNo).get("startTime")
  242. if startTime:
  243. consumeDict.update({"startTime": startTime})
  244. self.notify_to_sd_norther(portStr="1", consumeDict=consumeDict)
  245. Device.invalid_device_control_cache(self.device.devNo)
  246. def do_card_pay_finish(self):
  247. servicedInfo = {
  248. "cardNo": self.event_data["cardNo"],
  249. "usedElec": self.event_data["usedElec"],
  250. "duration": self.event_data["chargeTime"],
  251. "coin": str(self.event_data["payMoney"]),
  252. "cardBalance": str(self.event_data["cardBalance"]),
  253. "spendMoney": str(self.event_data["payMoney"] - self.event_data["balance"]),
  254. "reason": self.event_data["desc"],
  255. "finishedTime": datetime.datetime.strftime(datetime.datetime.now(), "%Y-%m-%d %H:%M:%S")
  256. }
  257. cardNo = self.event_data.get("cardNo")
  258. payMoney = self.event_data.get("payMoney")
  259. cardBalance = self.event_data.get("cardBalance")
  260. card = self.update_card_dealer_and_type(cardNo, cardType="IC")
  261. if not card: # zjl new
  262. return # zjl new
  263. card.balance = RMB(cardBalance)
  264. try:
  265. self.record_consume_for_card(card, RMB(payMoney), servicedInfo=servicedInfo)
  266. card.save()
  267. except Exception as e:
  268. logger.error(e)
  269. else:
  270. group = Group.get_group(self.device["groupId"])
  271. self.notify_user(
  272. managerialOpenId=card.managerialOpenId,
  273. templateName="service_complete",
  274. title=u"\\n\\n结束原因:\\t\\t{reason}\\n\\n设备编号:\\t\\t{logicalCode}\\n\\n服务地址:\\t\\t{group}\\n\\n使用时长:\\t\\t{duration}分钟\\n\\n付款金额:\\t\\t{coin}\\n\\n待返费:\\t\\t{refund}".format(
  275. reason=self.event_data["desc"],
  276. logicalCode=self.device["logicalCode"],
  277. group=group.get("address", ""),
  278. duration=self.event_data["chargeTime"],
  279. coin=u"%s" % self.event_data["payMoney"],
  280. refund=u"%s 请将卡片贴近充电桩进行返费" % self.event_data["balance"]
  281. ),
  282. service=u"本次充电结束,请将充电卡放在桩的刷卡区域返费,保持静止5秒以上,直到听到 返费成功 语音后,再把卡移开!",
  283. finishTime=datetime.datetime.strftime(datetime.datetime.now(), "%Y-%m-%d %H:%M:%S"),
  284. remark=u'谢谢您的支持'
  285. )
  286. finally:
  287. ServiceProgress.update_progress_and_consume_rcd(
  288. self.device["ownerId"],
  289. {
  290. "device_imei": self.device["devNo"],
  291. "cardId": str(card.id),
  292. "port": 0,
  293. "isFinished": False
  294. },
  295. {}
  296. )
  297. # 推送订单结束信息
  298. startTime = Device.get_dev_control_cache(self.device.devNo).get("startTime")
  299. if startTime:
  300. servicedInfo.update({"startTime": startTime})
  301. self.notify_to_sd_norther(portStr="1", consumeDict=servicedInfo)
  302. Device.invalid_device_control_cache(self.device["devNo"])
  303. def do_recharge_card(self):
  304. """
  305. 卡充值的发起指令 接收到此指令之后 判断该卡是否有效 是否存在有效订单 存在的情况下下发充值数据
  306. :return:
  307. """
  308. cardNo = self.event_data.get("cardNo")
  309. cardBalance = self.event_data.get("cardBalance")
  310. cardType = self.event_data.get("cardType")
  311. card = self.update_card_dealer_and_type(cardNo)
  312. if not card:
  313. logger.error("not card exist, can not async card balance <{}>".format(cardNo))
  314. return
  315. # 更新卡的余额 以设备侧的为准
  316. if RMB(cardBalance) != card.balance:
  317. logger.info('Card<{}> balance<{}> needs to be sync !!!'.format(cardNo, card.balance))
  318. card.balance = RMB(cardBalance)
  319. card = card.save()
  320. # orderNos 表示充值的订单 cardOrderNos 表示卡充值的订单
  321. money, coins, orderNos, cardOrderNos = CardRechargeOrder.get_to_do_list(str(card.id))
  322. # 一旦卡被冻结 立即下发 创建一张为 负数的金额订单 将卡的余额清空
  323. if card.frozen:
  324. group = Group.get_group(card.groupId)
  325. CardRechargeOrder.new_one(
  326. openId=card.openId,
  327. cardId=str(card.id),
  328. cardNo=card.cardNo,
  329. money=RMB(0),
  330. coins=VirtualCoin(0) - VirtualCoin(coins) - VirtualCoin(card.balance),
  331. group=group,
  332. rechargeId=ObjectId(),
  333. rechargeType=u"sendCoin"
  334. )
  335. money, coins, orderNos, cardOrderNos = CardRechargeOrder.get_to_do_list(str(card.id))
  336. # 没有需要同步的订单的情况下 直接退出
  337. if not orderNos:
  338. logger.info('Not card recharge record!, card is <{}>'.format(cardNo))
  339. # self._do_offline_recharge_by_dealer(cardNo, card, cardType)
  340. return
  341. orderNos = [str(item) for item in orderNos]
  342. sid = str(random.randint(0, 0xFFFF))
  343. TempValues.set('%s-%s' % (self.device.devNo, sid), value=json.dumps(orderNos))
  344. TempValues.set('%s-%s-%s' % (self.device.devNo, sid, cardNo), value=json.dumps(cardOrderNos))
  345. TempValues.set('%s-%s' % (self.device.devNo, cardNo), value=str(sid))
  346. # 计算总的需要同步的金额
  347. asyncMoney = RMB(cardBalance) + RMB(coins)
  348. logger.info('ready to recharge card, card is <{}>, money is <{}>'.format(cardNo, coins))
  349. # 已经下发金额后,先将订单的状态改为结束。如果主板上报失败,再将订单状态迁移回来否则就不再变化订单状态了
  350. try:
  351. CardRechargeOrder.update_card_order_has_finished(str(card.id))
  352. except Exception as e:
  353. logger.debug('%s' % e)
  354. else:
  355. self.deviceAdapter._async_card_balance(cardType, cardNo, asyncMoney)
  356. def do_recharge_card_result(self):
  357. """
  358. 卡充值的 设备回复指令 接收到此指令之后 表示订单充值已经成功了 这个时候就需要将充值订单的状态改变就行了
  359. :return:
  360. """
  361. asyncStatus = self.event_data.get("asyncStatus")
  362. answerSid = self.event_data.get("sid")
  363. cardBalance = self.event_data.get("cardBalance")
  364. cardNo = self.event_data.get("cardNo")
  365. card = self.update_card_dealer_and_type(cardNo)
  366. if not card:
  367. logger.error("not found card, event is <{}>".format(self.event_data))
  368. return
  369. sid = TempValues.get('{}-{}'.format(self.device.devNo, cardNo))
  370. if sid != answerSid:
  371. logger.error('answer sid is not equal sid <{}>-<{}>'.format(sid, answerSid))
  372. return
  373. # 根据sid 以及卡号 查询出此次充值的所有的订单
  374. orderNos = TempValues.get('{}-{}'.format(self.device.devNo, sid))
  375. cardOrderNos = TempValues.get('{}-{}-{}'.format(self.device.devNo, sid, cardNo))
  376. # 明确接收到了主板同步失败的情况下 直接退出 不在转换充值订单的问题 防止出错
  377. if not asyncStatus:
  378. logger.error("card async not success! event data is <{}>".format(self.event_data))
  379. return
  380. # 下面的都不会更改订单的状态 最多走售后
  381. money, coins = RMB(0), VirtualCoin(0)
  382. # 校验金额是否是相等的
  383. orderNos = [ObjectId(item) for item in orderNos]
  384. rds = RechargeRecord.objects.filter(ownerId=card.dealerId, id__in=orderNos)
  385. for item in rds:
  386. money += item.money
  387. coins += item.coins
  388. # 这个地方就是异常值处理了
  389. if VirtualCoin(coins + card.balance) != VirtualCoin(cardBalance):
  390. logger.error('card pre balance not equal now balance! event is <{}>'.format(self.event_data))
  391. return
  392. # 依次更改卡充值的订单 并创建充值订单
  393. cardOrders = CardRechargeOrder.objects.filter(orderNo__in=cardOrderNos)
  394. for _order in cardOrders: # type: CardRechargeOrder
  395. _order.update_after_recharge_ic_card(
  396. device=self.device,
  397. sendMoney=RMB(_order.coins),
  398. preBalance=card.balance
  399. )
  400. preBalance = card.balance
  401. card.balance = card.balance + _order.coins
  402. # 创建充值记录
  403. CardRechargeRecord.add_record(
  404. card=card,
  405. group=Group.get_group(_order.groupId),
  406. order=_order,
  407. device=self.device
  408. )
  409. # 保存
  410. card.save()
  411. # 完成之后将TempValue 的key value 清空
  412. TempValues.remove('%s-%s' % (self.device['devNo'], sid))
  413. TempValues.remove('%s-%s' % (self.device['devNo'], cardNo))
  414. TempValues.remove("%s-%s-%s" % (self.device["devNo"], sid, cardNo))
  415. def _response_a8id(self, a8id):
  416. self.deviceAdapter._response_a8id(a8id)
  417. def update_card_dealer_and_type(self, cardNo, cardType='IC', isHaveBalance=True, balance=None):
  418. """
  419. 更新卡的状态 重写目的 如果卡不存在 立即下发停止充电指令 而不是新建一张卡
  420. :param cardNo:
  421. :param cardType:
  422. :param isHaveBalance:
  423. :param balance:
  424. :return:
  425. """
  426. dealer = Dealer.objects.get(id=self.device["ownerId"])
  427. agent = Agent.objects.get(id=dealer.agentId)
  428. if not agent:
  429. logger.error('agent is not found, agentId=%s' % dealer.agentId)
  430. return
  431. try:
  432. card = Card.objects.get(cardNo=cardNo, agentId=dealer.agentId)
  433. # 如果卡没有被绑定,这个时候检查下绑定关系。如果卡已经被某个经销商认领了,就不要刷新,不要动了
  434. if card.cardType and card.dealerId and card.devNo:
  435. return card
  436. card.dealerId = self.device['ownerId']
  437. card.devNo = self.device['devNo']
  438. card.cardType = cardType
  439. card.devTypeCode = self.device['devType']['code']
  440. card.isHaveBalance = isHaveBalance
  441. return card.save()
  442. except DoesNotExist:
  443. return
  444. except Exception as e:
  445. logger.exception(e)
  446. return