yuewantong.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import logging
  4. import datetime
  5. from apilib.monetary import RMB, VirtualCoin
  6. from apilib.utils_string import make_title_from_dict
  7. from apilib.utils_sys import memcache_lock
  8. from apps.web.constant import Const, DeviceCmdCode, DEALER_CONSUMPTION_AGG_KIND
  9. from apps.web.core.exceptions import ServiceException
  10. from apps.web.device.models import Group, Device, DevicePortReport, DeviceType
  11. from apps.web.eventer import EventBuilder, powerRecorder
  12. from apps.web.eventer.base import WorkEvent, FaultEvent
  13. from apps.web.user.models import ServiceProgress, MyUser, ConsumeRecord, CardConsumeRecord, CardRechargeOrder, \
  14. UserVirtualCard, VCardConsumeRecord
  15. from apps.web.user.transaction_deprecated import refund_money
  16. from apps.web.user.utils import vCard_pay_coin
  17. logger = logging.getLogger(__name__)
  18. def reverse_hex(s):
  19. if len(s) == 0:
  20. return ""
  21. return s[-2:] + reverse_hex(s[:-2])
  22. class builder(EventBuilder):
  23. def __getEvent__(self, device_event):
  24. event_data = self.deviceAdapter.analyze_event_data(device_event['data'])
  25. if not event_data:
  26. return
  27. event_data['raw_msg'] = device_event
  28. # 粤万通的版本比较复杂 没有一个统一的版本 这次力争升级到一个统一的版本
  29. # TODO 补丁
  30. if "ack_id" in device_event and self.device.driverVersion not in [
  31. "v1.0.0", "v1.0.1", "v1.0.2", "v3.0.1", "v3.0.2", "v3.0.3", "v4.0.0", "v4.0.1"
  32. ]:
  33. self.deviceAdapter._ack(device_event["ack_id"])
  34. funCode = event_data.get("cmdCode")
  35. if funCode == "E0":
  36. return YueWanTongFaultEvent(self.deviceAdapter, event_data)
  37. else:
  38. return YueWanTongWorkerEvent(self.deviceAdapter, event_data)
  39. class YueWanTongFaultEvent(FaultEvent):
  40. def do(self, **args):
  41. group = Group.get_group(self.device["groupId"])
  42. if self.is_notify_dealer():
  43. self.notify_dealer(
  44. "device_fault",
  45. title = u"注意注意您的设备发生故障",
  46. device = u"组号:{},二维码编号:{}".format(self.device["groupNumber"], self.device["logicalCode"]),
  47. location = u"组名称:{},地址:{}".format(group["groupName"], group["address"]),
  48. fault = self.event_data["statusInfo"],
  49. notifyTime = str(datetime.datetime.now())[:19]
  50. )
  51. warningData = {
  52. "warningStatus": 2,
  53. "warningDesc": self.event_data["statusInfo"],
  54. "warningTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  55. }
  56. # 整机告警
  57. part = self.event_data.get("portStr", "0")
  58. Device.update_dev_warning_cache(self.device.devNo, {part: warningData})
  59. try:
  60. faultRecord = self.record(faultCode=self.event_data.get("faultCode", ""), detail={"errorCode": self.event_data["faultCode"]})
  61. self.north_to_liangxi(faultRecord)
  62. except Exception as e:
  63. logger.error(e)
  64. finally:
  65. sendData = self.event_data.get("portHex") + "01"
  66. self.deviceAdapter._send_data("E0", sendData = sendData, cmd = DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  67. class YueWanTongWorkerEvent(WorkEvent):
  68. @staticmethod
  69. def _get_cache_key(devNo, port, cmd):
  70. return "{}-{}-{}".format(devNo, port, cmd)
  71. def _card_start_repeat(self, _type, portStr, cardCode, balance = None, orderNo = None, chargeTime = None, virtualCardLeftDays=None, cardType=None):
  72. """
  73. 卡启动的回复
  74. :param _type: 正常卡 非法卡 余额不足卡
  75. :param portStr: 端口号
  76. :param cardCode: 带ICSetupCode的卡号
  77. :param balance: 卡上余额
  78. :param orderNo: 卡启动的订单号
  79. :param chargeTime: 充电的时间
  80. :return:
  81. """
  82. if balance is None:
  83. balance = "00000000"
  84. else:
  85. balance = reverse_hex("{:0>8X}".format(int(balance * 100) & 0xFFFFFFFF))
  86. if orderNo is None:
  87. orderNo = "00000000000000"
  88. else:
  89. orderNo = reverse_hex("{:0>14X}".format(int(orderNo)))
  90. if chargeTime is None:
  91. chargeTime = "0000"
  92. else:
  93. chargeTime = reverse_hex("{:0>4X}".format(int(chargeTime)))
  94. if virtualCardLeftDays is None:
  95. leftDays = "0000"
  96. else:
  97. leftDays = reverse_hex("{:0>4X}".format(int(virtualCardLeftDays)))
  98. if cardType is None:
  99. cardType = "01"
  100. # 随机开锁密码无用
  101. pw = "0000"
  102. sendData = cardCode + portStr + _type + balance + cardType + leftDays + orderNo + chargeTime + pw
  103. self.deviceAdapter._send_data(funCode = "B0", sendData = sendData, cmd = DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  104. def _get_port_charge_status(self, port):
  105. """
  106. 获取端口的详细信息,向设备请求
  107. :param port:
  108. :return:
  109. """
  110. result = self.deviceAdapter._read_port_charge_status()
  111. portChargeStatus = result.get(str(port), dict())
  112. return portChargeStatus
  113. def _card_stop_repeat(self, _type, portStr, cardCode, balance = None, money = None, virtualCardLeftDays=None, cardType=None):
  114. """
  115. 刷卡结束充电请求回复, 回复设备后, 设备会将该端口的该单号的充电记录清零 重新记录
  116. :param _type: 回复设备的状态
  117. :param portStr: 端口号 16进制
  118. :param cardCode: 卡号带前缀 ascii
  119. :param balance: 余额 可能为负数
  120. :param money: 消费金额
  121. :return:
  122. """
  123. if balance is None:
  124. balance = "00000000"
  125. else:
  126. balance = reverse_hex("{:0>8X}".format(int(balance * 100) & 0xFFFFFFFF))
  127. if money is None:
  128. money = "00000000"
  129. else:
  130. money = reverse_hex("{:0>8X}".format(int(money * 100)))
  131. if virtualCardLeftDays is None:
  132. leftDays = "0000"
  133. else:
  134. leftDays = reverse_hex("{:0>4X}".format(int(virtualCardLeftDays)))
  135. if cardType is None:
  136. cardType = "01"
  137. sendData = cardCode + portStr + _type + balance + cardType + leftDays + money
  138. self.deviceAdapter._send_data(funCode = "B1", sendData = sendData, cmd = DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  139. def _stop_repeat(self, orderHex, portHex):
  140. """
  141. 充电结束回复设备 格式固定
  142. :param orderHex:
  143. :param portHex:
  144. :return:
  145. """
  146. # TODO 补丁
  147. if self.device.driverVersion not in ["v1.0.0", "v1.0.1", "v1.0.2", "v3.0.1", "v3.0.2", "v3.0.3", "v4.0.0", "v4.0.1"]:
  148. return
  149. sendData = orderHex + portHex + "01"
  150. self.deviceAdapter._send_data(funCode = "C1", sendData = sendData, cmd = DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  151. def _handle_card_stop(self, cardOrderNo, orderNo, totalConsume, chargeTime, elec, card, portStr, reason=None):
  152. """
  153. 结束充电事件和刷卡请求结束充电都有这一部分代码 抽出来
  154. :param cardOrderNo:
  155. :param orderNo:
  156. :param totalConsume:
  157. :param chargeTime:
  158. :param elec:
  159. :param card:
  160. :param portStr:
  161. :param reason:
  162. :return:
  163. """
  164. logger.info("[_handle_card_stop] cardOrderNo={}, orderNo={}, totalConsume={}, chargeTime={}, port={}".format(cardOrderNo, orderNo, totalConsume, chargeTime, portStr))
  165. consumeOrder = ConsumeRecord.objects.get(orderNo = orderNo)
  166. # 虚拟卡没有支付的情况下
  167. if not consumeOrder.virtual_card_id:
  168. consumeOrder.coin = totalConsume.mongo_amount
  169. consumeOrder.money = totalConsume.mongo_amount
  170. try:
  171. consumeOrder.save()
  172. except Exception as e:
  173. logger.error(e)
  174. cardConsumeRecord = CardConsumeRecord.objects.get(orderNo = cardOrderNo)
  175. cardConsumeRecord.money = RMB(totalConsume)
  176. cardConsumeRecord.balance = card.balance - RMB(totalConsume)
  177. try:
  178. cardConsumeRecord.save()
  179. except Exception as e:
  180. logger.error(e)
  181. # 更新此次卡的余额
  182. self.update_card_balance(card, card.balance - RMB(totalConsume))
  183. # 结束服务,同时更新cardRecord和consumeRecord的serviceInfo, 以及聚合信息
  184. if reason is None:
  185. reason = "刷卡结束充电"
  186. consumeDict = {
  187. 'chargeIndex': portStr,
  188. 'reason': reason,
  189. 'actualNeedTime': chargeTime,
  190. 'duration': chargeTime,
  191. 'elec': elec,
  192. 'elecFee': self.calc_elec_fee(float(elec)),
  193. DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: RMB(totalConsume).mongo_amount
  194. }
  195. self._finished_service(portStr, card.openId, consumeDict)
  196. def _handle_min_consume(self, totalConsume, chargeTime=None):
  197. """
  198. 检测支付值和最小支付值
  199. :param totalConsume:
  200. :return:
  201. """
  202. if not chargeTime:
  203. chargeTime = self.event_data["chargeTime"]
  204. otherConf = self.device.get("otherConf", dict())
  205. minConsume = VirtualCoin(int(otherConf.get("minConsume", self.deviceAdapter.DEFAULT_MIN_CONSUME)))
  206. refundProtectTime = int(otherConf.get("refundProtectTime", self.deviceAdapter.DEFAULT_MIN_CHARGE_TIME))
  207. if int(chargeTime) < int(refundProtectTime):
  208. return VirtualCoin(0)
  209. return max(VirtualCoin(minConsume), VirtualCoin(totalConsume))
  210. def _finished_service(self, portStr, openId, consumeInfo):
  211. ServiceProgress.update_progress_and_consume_rcd(
  212. self.device["ownerId"],
  213. {
  214. "open_id": openId,
  215. "device_imei": self.device.devNo,
  216. "port": int(portStr),
  217. "isFinished": False
  218. },
  219. consumeInfo
  220. )
  221. # 清空端口信息
  222. Device.clear_port_control_cache(self.device["devNo"], portStr)
  223. def _notify_user_finished(self, user, extra):
  224. """
  225. 通知用户 服务结束
  226. :return:
  227. """
  228. title = make_title_from_dict(extra)
  229. self.notify_user(
  230. managerialOpenId=user.managerialOpenId if user else "",
  231. templateName="service_complete",
  232. title=title,
  233. service=u"充电桩充电服务",
  234. finishTime=str(datetime.datetime.now())[: 19],
  235. remark=u'谢谢您的支持'
  236. )
  237. def do(self, **args):
  238. cmd = self.event_data['cmdCode']
  239. portStr = self.event_data.get("portStr")
  240. cacheKey = self._get_cache_key(self.device["devNo"], portStr, cmd)
  241. with memcache_lock(cacheKey, value = '1', expire = 5) as required:
  242. if required:
  243. if cmd == "B0":
  244. self.do_card_start()
  245. elif cmd == "B1":
  246. self.do_card_stop()
  247. elif cmd == "C1":
  248. self.do_stop()
  249. elif cmd == "B2":
  250. self.do_stop()
  251. elif cmd == "C0":
  252. self.do_report()
  253. # 自定义
  254. elif cmd == "CA":
  255. self.do_all_report()
  256. else:
  257. logger.error("error cmd, cmd is {}".format(cmd))
  258. def do_card_start(self):
  259. """
  260. 刷卡启动,刷卡的时候设备上报命令,等待设备回复后成功启动
  261. 由于是后付费,这个地方仅仅做卡启动记录,费用在结束的时候结算
  262. 但是还是需要校验 卡上的钱是否是小于0,负数卡不予启动
  263. :return:
  264. """
  265. cardNo = self.event_data.get("cardNo")
  266. portStr = self.event_data.get("portStr")
  267. portStrHex = self.event_data['portStrHex']
  268. cardCode = self.event_data['cardCode']
  269. lockPorts = self.device.otherConf.get("lockPorts") or list()
  270. if portStr in lockPorts:
  271. self._card_start_repeat(_type = "03", portStr = portStrHex, cardCode = cardCode)
  272. return
  273. card = self.update_card_dealer_and_type(cardNo)
  274. if not card or not card.openId:
  275. self._card_start_repeat(_type = "03", portStr = portStrHex, cardCode = cardCode)
  276. logger.info("bad card, cardNo is %s, cannot start port!" % cardNo)
  277. return
  278. card_recharge_order = CardRechargeOrder.get_last_to_do_one(str(card.id))
  279. self.recharge_id_card(card=card, rechargeType='append', order=card_recharge_order)
  280. card.reload()
  281. # 这个地方处理包月卡的问题
  282. package = {"unit": u"次", "count": 1}
  283. virtualCardCanUse = False
  284. virtualCard = card.bound_virtual_card
  285. # 虚拟卡使用的四个条件 绑定虚拟卡没有过期、绑定虚拟卡设备组允许、绑定虚拟卡设备种类允许、绑定虚拟卡额度足够使用
  286. if virtualCard and (self.device.get("groupId") in virtualCard.groupIds or "*" in virtualCard.groupIds) and DeviceType.objects.filter(code=self.device.devType.get("code"), id__in=virtualCard.devTypeList) and virtualCard.can_use_today(package):
  287. virtualCardCanUse = True
  288. # 包月卡不可用并且余额不足的时候
  289. if not virtualCardCanUse and float(card.balance) <= 0:
  290. self._card_start_repeat(_type = "02", portStr = portStrHex, cardCode = cardCode, balance = card.balance)
  291. self.deviceAdapter._stop_device("00000000000000", portStr)
  292. logger.info("negative card, card No is %s, card balance is %s" % (cardNo, card.balance))
  293. return
  294. # 可以启动的情况下 优先将卡消费订单创建出来,获取单号, 消费先是0,结束的时候再算
  295. try:
  296. orderNo, cardOrderNo = self.record_consume_for_card(card, RMB(0), desc="包月卡消费" if virtualCardCanUse else u"刷卡消费", attachParas={"chargeIndex": portStr})
  297. except Exception as e:
  298. logger.error("record to database error!".format(e))
  299. return
  300. # 虚拟卡可用的情况下 直接扣费吧
  301. virtualCardPaid = False
  302. if virtualCardCanUse:
  303. order = ConsumeRecord.objects.get(orderNo=orderNo) # type: ConsumeRecord
  304. group = Group.get_group(self.device.groupId)
  305. try:
  306. vCard_pay_coin(order, virtualCard, self.device, group, package, {})
  307. except ServiceException:
  308. # 支付失败的情况况下 重新将虚拟卡支付标记为 否 结算的时候从卡上扣除相应的金额
  309. order.desc = u"虚拟卡支付失败"
  310. order.save()
  311. else:
  312. virtualCardPaid = True
  313. # 发送报文回应设备,启动, 刷卡启动时候余额单位为分,充电模式为充满自停,所以时间为最大值
  314. chargeTime = 2 ** 32 - 1
  315. virtualCardLeftDays = (virtualCard.expiredTime - datetime.datetime.now()).days if virtualCardPaid else None
  316. cardType = "02" if virtualCardPaid else "01"
  317. self._card_start_repeat("01", portStrHex, cardCode=cardCode, balance=card.balance, orderNo=orderNo, chargeTime=chargeTime, virtualCardLeftDays=virtualCardLeftDays, cardType=cardType)
  318. logger.info("card start ok! event has been report!")
  319. # 启动设备之后,注册服务,并更新端口缓存值
  320. serviceDict = {
  321. "orderNo": orderNo,
  322. "cardOrderNo": cardOrderNo
  323. }
  324. if virtualCardPaid:
  325. serviceDict.update({"vCardId": str(virtualCard.id)})
  326. ServiceProgress.register_card_service(
  327. self.device,
  328. int(portStr),
  329. card,
  330. serviceDict
  331. )
  332. portDict = {
  333. "status": Const.DEV_WORK_STATUS_WORKING,
  334. "isStart": True,
  335. "orderNo": orderNo, # 订单号码
  336. "cardOrderNo": cardOrderNo,
  337. "cardNo": cardNo, # 卡号 0-99999999
  338. "openId": card.openId,
  339. "startTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), # 刷卡启动的初始时间
  340. "vCardId": str(virtualCard.id) if virtualCardPaid else None,
  341. "chargeType": "power", # 刷卡的只能是按功率收费, 并且是后付费
  342. "payAfterUse": True
  343. }
  344. self.notify_user(
  345. managerialOpenId=card.managerialOpenId,
  346. templateName="consume_notify",
  347. title=u"您正在刷卡启动充电桩业务",
  348. money=u"使用结束后计算费用",
  349. serviceType=u"刷卡消费, 当前启动为: {}端口".format(portStr),
  350. finishTime=datetime.datetime.now().strftime(Const.DATETIME_FMT)
  351. )
  352. Device.update_dev_control_cache(self.device["devNo"], {portStr: portDict})
  353. @powerRecorder()
  354. def do_report(self):
  355. """
  356. 处理端口上报消息的事件
  357. :return:
  358. """
  359. # 获取原始参数
  360. orderNo = self.event_data.get("orderNo")
  361. orderNoHex = self.event_data.get("orderNoHex")
  362. portStr = self.event_data.get("portStr")
  363. voltage = self.event_data.get("voltage")
  364. power = self.event_data.get("power")
  365. elec = self.event_data.get("elec")
  366. chargeTime = self.event_data.get("chargeTime")
  367. temperature = self.event_data.get("temperature")
  368. # 获取端口缓存
  369. devCache = Device.get_dev_control_cache(self.device["devNo"])
  370. portCache = devCache.get(portStr, dict())
  371. # 回复设备报文 新版本的设备已经在驱动侧回复,但是可能存在部分老设备 此段回复代码首先保留 迭代几个版本之后可以删除
  372. if self.device.driverVersion in ["v1.0.0", "v1.0.1"]:
  373. sendData = orderNoHex + "{:0>2X}".format(int(portStr)) + "01"
  374. self.deviceAdapter._send_data(funCode = "C0", sendData = sendData, cmd = DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  375. # 筛选无效报文
  376. if orderNo != portCache.get("orderNo"):
  377. logger.info("port report event, not equal orderNo, useless info! event={} portCache = {}".format(self.event_data, portCache))
  378. return
  379. # 更新端口缓存
  380. portCache.update({"voltage": voltage, "elec": elec, "temperature": temperature, "power": power})
  381. Device.update_dev_control_cache(self.device["devNo"], {portStr: portCache})
  382. # 获取计费模式以及消费模式
  383. chargeType = portCache.get("chargeType", self.deviceAdapter.DEFAULT_CHARGE_TYPE)
  384. payAfterUse = portCache.get("payAfterUse", self.deviceAdapter.DEFAULT_PAY_AFTER_USE)
  385. # 保存上报的心跳数据到日志数据库
  386. reportRecord = DevicePortReport.create(
  387. devNo = self.device["devNo"],
  388. port = portStr,
  389. orderNo = orderNo,
  390. openId = portCache["openId"],
  391. voltage = voltage,
  392. power = power,
  393. elec = elec,
  394. chargeTime = chargeTime
  395. )
  396. if not reportRecord:
  397. logger.info("port report event, save db error!")
  398. return
  399. # 后付费的 一律不处理 因为后付费的费用结算是在使用完毕之后
  400. if payAfterUse:
  401. return
  402. # 计算各个模式下面 到目前为止使用的金钱
  403. if chargeType == "time":
  404. useMoney = self.deviceAdapter._get_time_use_money(reportRecord)
  405. elif chargeType == "elec":
  406. useMoney = self.deviceAdapter._get_elec_use_money(reportRecord)
  407. elif chargeType == "billAsService":
  408. elecCharge, serviceCharge = self.deviceAdapter._get_billAsService_use_money(reportRecord)
  409. useMoney = round(sum(elecCharge, serviceCharge), 2)
  410. else:
  411. useMoney = self.deviceAdapter._get_power_use_money(reportRecord)
  412. # 当预付费模式下 使用的金钱大于 预付的金钱 立即下发停止指令
  413. if VirtualCoin(useMoney) >= VirtualCoin(portCache["coins"]):
  414. result = self.deviceAdapter._stop_device(orderNo, portStr)
  415. data = result.get("data")
  416. stopData = self.deviceAdapter._parse_device_stop(data)
  417. # 能走到这个地方说明钱已经花完了 不需要处理退费 但是需要对用户进行通知 以及清除响应的端口状态
  418. reason = u"动态功率计算,服务已经结束"
  419. # 推送通知信息组织
  420. extra = [
  421. {u'结束原因': u'{}'.format(reason)},
  422. {u'设备编号': u'{}-{}'.format(self.device.logicalCode, portStr)},
  423. {u'服务地址': u'{}'.format(self.device.group["address"])},
  424. {u'充电时间': u'{}分钟'.format(chargeTime)},
  425. ]
  426. consumeDict = {
  427. "chargeIndex": portStr,
  428. "reason": reason,
  429. 'actualNeedTime': stopData.get("chargeTime"),
  430. 'duration': stopData.get("chargeTime"),
  431. 'elec': stopData.get("elec"),
  432. 'elecFee': self.calc_elec_fee(stopData.get("elec")),
  433. }
  434. if chargeType == "billAsService":
  435. consumeDict.update({
  436. 'elecCharge': elecCharge,
  437. 'serviceCharge': serviceCharge,
  438. })
  439. extra.extend([
  440. {u'电量费用': u'{}'.format(elecCharge)},
  441. {u'服务费用': u'{}'.format(serviceCharge)},
  442. ])
  443. user = MyUser.objects.filter(openId=portCache["openId"], groupId=self.device.groupId).first()
  444. self._notify_user_finished(user, extra)
  445. openId = portCache.get("openId")
  446. self._finished_service(portStr, openId, consumeDict)
  447. @powerRecorder(stop=True)
  448. def do_card_stop(self):
  449. """
  450. 刷卡结束的充电行为 累计充电的总时长 总时长小于5分钟不予扣费,剩下的正常扣除费用
  451. 费用的扣除需要分段累加进行
  452. 因为是后付费行为,所以不会存在退费
  453. :return:
  454. """
  455. portStr = self.event_data.get("portStr")
  456. orderNo = self.event_data.get("orderNo")
  457. cardNo = self.event_data.get("cardNo")
  458. devCache = Device.get_dev_control_cache(self.device["devNo"])
  459. portCache = devCache.get(portStr, dict())
  460. portStrHex = self.event_data['portStrHex']
  461. cardCode = self.event_data['cardCode']
  462. # 检查订单号
  463. if portCache.get("orderNo") != orderNo or cardNo != portCache.get("cardNo"):
  464. # TODO ZJL 这个地方会有可能出现吗?需要回应设备确认么
  465. logger.info("card stop event, orderNo or carNo not equal!, event = {}".format(self.event_data))
  466. return
  467. card = self.update_card_dealer_and_type(cardNo)
  468. # 向设备发送报文,获取该端口充电的信息,时间是累加值
  469. portChargeStatus = self._get_port_charge_status(portStr)
  470. # 检查充电的累计时长, 小于一定的值不予扣费
  471. chargeTime = int(portChargeStatus.get("chargeTime", 0))
  472. if chargeTime < self.deviceAdapter.DEFAULT_MIN_CHARGE_TIME:
  473. logger.info("card stop event, chargeTime lt min charge time!")
  474. self._card_stop_repeat(
  475. _type = "01",
  476. portStr = portStrHex,
  477. cardCode = cardCode,
  478. balance = card.balance,
  479. money = 0)
  480. extra = [{u"消费金额": u"{}元".format(0),"余额": "{}元".format(RMB(card.balance))}]
  481. self.notify_user_service_complete(
  482. service_name='充电',
  483. openid=card.managerialOpenId,
  484. port=portStr,
  485. address=self.device.group['address'],
  486. reason=self.event_data.get('reasonDesc'),
  487. finished_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  488. extra=extra)
  489. # 不扣费的情况下也属于正常结束 端口状态需要清空
  490. Device.clear_port_control_cache(self.device["devNo"], portStr)
  491. return
  492. # 计算该次刷卡的累计消费
  493. totalConsume = DevicePortReport.calculate_consume(
  494. orderNo = orderNo,
  495. allChargeTime = chargeTime,
  496. lastPower = portChargeStatus.get("power"),
  497. interval_map = self.deviceAdapter._get_power_interval_map(),
  498. devNo = self.device["devNo"],
  499. port = portStr,
  500. openId = card.openId,
  501. voltage = portChargeStatus.get("voltage"),
  502. elec = portChargeStatus.get("elec")
  503. )
  504. totalConsume = self._handle_min_consume(totalConsume, chargeTime)
  505. # 最后回复设备表示已经接收到刷卡请求结束充电的信息,以便主板清空端口累计信息
  506. orderNo = portCache.get("orderNo")
  507. consumeOrder = ConsumeRecord.objects.get(orderNo=orderNo)
  508. virtualCard = None
  509. virtualCardLeftDays = None
  510. if consumeOrder.virtual_card_id:
  511. vConsumeRecord = VCardConsumeRecord.objects.filter(id=consumeOrder.virtual_card_id).first()
  512. virtualCard = UserVirtualCard.objects.filter(id=vConsumeRecord.cardId).first()
  513. virtualCardLeftDays = (virtualCard.expiredTime - datetime.datetime.now()).days
  514. if virtualCard:
  515. _type = "01"
  516. balance = card.balance
  517. elif int(card.balance-totalConsume) < 0:
  518. _type = "02"
  519. balance = card.balance - totalConsume
  520. else:
  521. _type = "01"
  522. balance = card.balance - totalConsume
  523. self._card_stop_repeat(
  524. _type = _type,
  525. portStr = portStrHex,
  526. cardCode = cardCode,
  527. balance = balance,
  528. money = float(totalConsume),
  529. virtualCardLeftDays=virtualCardLeftDays,
  530. cardType="02" if virtualCard else "01"
  531. )
  532. self._handle_card_stop(
  533. cardOrderNo = portCache.get("cardOrderNo"),
  534. orderNo = portCache.get("orderNo"),
  535. totalConsume = totalConsume,
  536. chargeTime = chargeTime,
  537. elec = portChargeStatus.get("elec"),
  538. card = card,
  539. portStr = portStr
  540. )
  541. extra = [{u"消费金额": "{}元".format(totalConsume),"余额": "{}元".format(RMB(card.balance))}]
  542. self.notify_user_service_complete(
  543. service_name='充电',
  544. openid=card.managerialOpenId,
  545. port=portStr,
  546. address=self.device.group['address'],
  547. reason=self.event_data.get('reasonDesc'),
  548. finished_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  549. extra=extra)
  550. @powerRecorder(stop=True)
  551. def do_stop(self):
  552. """
  553. 处理充电结束上报
  554. 充电结束上报和刷卡请求结束充电的区别在于:
  555. 充电结束表示主板在该次充电服务已经结束,端口信息已经清空,信息做最后上报
  556. 刷卡请求结束充电,主板端口信息未清空,但是端口服务已经停止,需要请求最后一次数据
  557. 处理流程与 刷卡请求结束充电 基本类似
  558. :return:
  559. """
  560. # 获取原始参数
  561. orderNo = self.event_data.get("orderNo")
  562. orderNoHex = self.event_data.get("orderNoHex")
  563. portStr = self.event_data.get("portStr")
  564. portHex = self.event_data.get("portHex")
  565. voltage = self.event_data.get("voltage")
  566. power = self.event_data.get("power")
  567. elec = self.event_data.get("elec")
  568. chargeTime = self.event_data.get("chargeTime")
  569. reason = self.event_data.get("reason")
  570. portCache = Device.get_port_control_cache(self.device.devNo, portStr)
  571. try:
  572. self._stop_repeat(orderNoHex, portHex)
  573. except Exception as e:
  574. logger.exception("devNo is <{}>, orderNoHex is <{}> port is <{}>, error is {}".format(self.device.devNo, orderNoHex, portHex, e))
  575. # 检查订单号
  576. if portCache.get("orderNo") != orderNo:
  577. logger.info("stop event, orderNo not equal! event = {} portCache = {}".format(self.event_data, portCache))
  578. return
  579. # 将最后一次的充电信息存储
  580. reportRecord = DevicePortReport.create(
  581. devNo = self.device["devNo"],
  582. port = portStr,
  583. orderNo = orderNo,
  584. openId = portCache["openId"],
  585. voltage = voltage,
  586. power = power,
  587. elec = elec,
  588. chargeTime = chargeTime
  589. )
  590. # 推送通知信息组织
  591. extra = [
  592. {u'结束原因': u'{}'.format(reason)},
  593. {u'设备编号': u'{}-{}'.format(self.device.logicalCode, portStr)},
  594. {u'服务地址': u'{}'.format(self.device.group["address"])},
  595. {u'充电时间': u'{}分钟'.format(chargeTime)},
  596. ]
  597. # 订单信息组织
  598. consumeDict = {
  599. "chargeIndex": portStr,
  600. "reason": self.event_data.get("reason"),
  601. 'actualNeedTime': chargeTime,
  602. 'duration': chargeTime,
  603. 'elec': self.event_data.get("elec"),
  604. 'elecFee': self.calc_elec_fee(self.event_data.get("elec")),
  605. }
  606. # 获取计费模式以及消费模式
  607. chargeType = portCache.get("chargeType", self.deviceAdapter.DEFAULT_CHARGE_TYPE)
  608. payAfterUse = portCache.get("payAfterUse", self.deviceAdapter.DEFAULT_PAY_AFTER_USE)
  609. # 计算各个模式下面 到目前为止使用的金钱
  610. if chargeType == "time":
  611. useMoney = self.deviceAdapter._get_time_use_money(reportRecord)
  612. elif chargeType == "elec":
  613. useMoney = self.deviceAdapter._get_elec_use_money(reportRecord)
  614. elif chargeType == "billAsService":
  615. elecCharge, serviceCharge = self.deviceAdapter._get_billAsService_use_money(reportRecord)
  616. useMoney = elecCharge + serviceCharge
  617. extra.extend([
  618. {u'电量费用': u'{}'.format(elecCharge)},
  619. {u'服务费用': u'{}'.format(serviceCharge)},
  620. ])
  621. consumeDict.update({
  622. 'elecCharge': elecCharge,
  623. 'serviceCharge': serviceCharge,
  624. })
  625. else:
  626. useMoney = self.deviceAdapter._get_power_use_money(reportRecord)
  627. # 根据用户使用的时间 以及经销商所设置的最小使用金额等等信息 判定最终所需支付的金额
  628. usedCoins = self._handle_min_consume(useMoney)
  629. consumeDict.update({
  630. DEALER_CONSUMPTION_AGG_KIND.COIN: usedCoins.mongo_amount
  631. })
  632. # 刷卡的单独走
  633. if portCache.get("cardNo"):
  634. card = self.update_card_dealer_and_type(portCache.get("cardNo"))
  635. self._handle_card_stop(
  636. cardOrderNo = portCache.get("cardOrderNo"),
  637. orderNo = portCache.get("orderNo"),
  638. totalConsume = usedCoins,
  639. chargeTime = chargeTime,
  640. elec = self.event_data.get("elec"),
  641. card = card,
  642. portStr = portStr,
  643. reason = self.event_data.get("reason")
  644. )
  645. return
  646. user = MyUser.objects.filter(openId=portCache["openId"], groupId=self.device.groupId).first() # type: MyUser
  647. self._notify_user_finished(user, extra)
  648. # 对金钱的处理 先付费 如果自动退款打开并且存在退款金额
  649. if not payAfterUse:
  650. backCoins = VirtualCoin(portCache["coins"]) - VirtualCoin(usedCoins)
  651. refundProtectTime = int(self.device.get("otherConf", dict()).get("refundProtectTime", self.deviceAdapter.DEFAULT_MIN_CHARGE_TIME))
  652. if int(chargeTime) < int(refundProtectTime):
  653. refund_money(self.device, backCoins, portCache.get("openId"))
  654. self._notify_user_refund(user, backCoins, orderNo)
  655. consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: backCoins.mongo_amount})
  656. elif self.device.is_auto_refund and backCoins > VirtualCoin(0):
  657. refund_money(self.device, backCoins, portCache.get("openId"))
  658. self._notify_user_refund(user, backCoins, orderNo)
  659. consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: backCoins.mongo_amount})
  660. else:
  661. logger.info("device not refund, devNo = {}, refund coins = {}, autoRefund = {}, chargeTime= {}".format(self.device.devNo, backCoins, self.device.is_auto_refund, chargeTime))
  662. # 后付费 修改订单的金额 并对用户的个人账户进行扣款
  663. else:
  664. consumeOrder = ConsumeRecord.objects.get(orderNo=orderNo)
  665. consumeOrder.money = usedCoins.mongo_amount
  666. consumeOrder.coin = usedCoins.mongo_amount
  667. try:
  668. consumeOrder.save()
  669. except Exception as e:
  670. logger.error(e)
  671. # 对于个人账户的金币减去 相应的消费值
  672. try:
  673. user.total_consumed = user.total_consumed + usedCoins
  674. user.balance = user.balance - usedCoins
  675. user.save()
  676. except Exception as e:
  677. logger.error(e)
  678. self._finished_service(portStr, user.openId, consumeDict)
  679. def do_all_report(self):
  680. """
  681. 自定义的指令 将主板的主动上报修改为 模块侧轮询然后组装
  682. """
  683. subChargeList = self.event_data["subChargeList"]
  684. for _sub in subChargeList:
  685. self.__class__(self.deviceAdapter, _sub).do_report()
  686. def _notify_user_refund(self, user, coins, orderNo, refundType=u"金币退款"):
  687. """
  688. 通知用户金币退回
  689. :return:
  690. """
  691. group = Group.get_group(self.device.groupId)
  692. self.notify_user(
  693. managerialOpenId=user.managerialOpenId if user else "",
  694. templateName="refund_coins",
  695. title=u"\\n\\n退款方式:\\t\\t{refundType}\\n\\n退款地址:\\t\\t{group}\\n\\n退款设备:\\t\\t{logicalCode}\\n\\n消费单号:\\t\\t{orderNo}".format(
  696. refundType=refundType,
  697. group=group["address"],
  698. logicalCode=self.device.logicalCode,
  699. orderNo=orderNo
  700. ),
  701. backCount="{:.2f}".format(float(coins)),
  702. finishTime=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  703. )