changyuan4.py 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import logging
  5. import random
  6. import time
  7. import uuid
  8. import simplejson as json
  9. from bson import ObjectId
  10. from apilib.monetary import Ratio, RMB, VirtualCoin
  11. from apilib.utils_sys import memcache_lock
  12. from apps.web.api.models import APIServiceStartRecord
  13. from apps.web.common.models import TempValues
  14. from apps.web.constant import Const, DEALER_CONSUMPTION_AGG_KIND
  15. from apps.web.core.accounting import Accounting
  16. from apps.web.core.device_define.changyuan import CYCardMixin, CYConstant
  17. from apps.web.device.models import Group, Device
  18. from apps.web.eventer import EventBuilder
  19. from apps.web.eventer.base import FaultEvent, WorkEvent
  20. from apps.web.report.utils import record_consumption_stats
  21. from apps.web.south_intf.platform import handle_and_notify_event_to_north_cy4, handle_and_notify_event_to_north_cy4_new
  22. from apps.web.user.models import RefundMoneyRecord
  23. from apps.web.user.models import ServiceProgress, RechargeRecord, CardRechargeOrder, CardRechargeRecord, Card, MyUser, \
  24. ConsumeRecord
  25. from apps.web.user.transaction_deprecated import refund_money, refund_cash
  26. logger = logging.getLogger(__name__)
  27. class builder(EventBuilder):
  28. def __getEvent__(self, device_event):
  29. event_data = self.deviceAdapter.analyze_event_data(device_event['data'])
  30. if event_data is None or 'cmdCode' not in event_data:
  31. return
  32. if event_data["cmdCode"] in ["D7"]:
  33. return ChargingCY4FaultEventer(self.deviceAdapter, event_data)
  34. if event_data['cmdCode'] in ['D8', 'D9', 'DA', 'D4', 'D3']:
  35. return ChargingCY4WorkEventer(self.deviceAdapter, event_data)
  36. return None
  37. class ChargingCY4FaultEventer(FaultEvent):
  38. def do(self, **args):
  39. # 收到指令之后优先回复
  40. self.deviceAdapter.ack_event()
  41. group = Group.get_group(self.device["groupId"])
  42. warningData = {
  43. "warningStatus": 2,
  44. "warningDesc": "设备温度超限警告",
  45. "warningTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  46. }
  47. # 整机告警
  48. Device.update_dev_warning_cache(self.device.devNo, {"0": warningData})
  49. self.notify_dealer(
  50. templateName = "device_fault",
  51. title = "注意!设备火警预告!",
  52. device = u"{groupNumber}组-{logicalCode}".format(groupNumber = self.device["groupNumber"],
  53. logicalCode = self.device["logicalCode"]),
  54. location = u"{address}-{groupName}".format(address = group["address"], groupName = group["groupName"]),
  55. fault = u"设备当前温度过高,可能发生了火警!",
  56. notifyTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  57. )
  58. class ChargingCY4WorkEventer(CYCardMixin, WorkEvent):
  59. def do(self, **args):
  60. devNo = self.device['devNo']
  61. logger.info('ChangYuan4 charging event detected, devNo=%s,curInfo=%s' % (devNo, self.event_data))
  62. cmdCode = self.event_data['cmdCode']
  63. with memcache_lock(key = "{devNo}.{cmdCode}.event".format(devNo = devNo, cmdCode = cmdCode), expire=10, value = '1') as acquired:
  64. if acquired:
  65. # 收到某系指令之后先回复确认
  66. if self.event_data.get("cmdCode") in ["D3", "D9", "DA"]:
  67. self.deviceAdapter.ack_event()
  68. # 设备状态由主板主动上报,上报之后需要和之前的端口信息做对比,如果之前端口处于工作状态的,而现在出于空闲状态的,需要结束服务
  69. if cmdCode == 'D8':
  70. devCache = Device.get_dev_control_cache(devNo)
  71. portInfo = self.event_data.get("portInfo")
  72. newDevCache = self.compare_port_cache(portInfo, devCache)
  73. self.temperature_check(self.event_data.get("temperature"))
  74. self.main_board_check(portInfo)
  75. newDevCache.update({"temperature": self.event_data.get("temperature")})
  76. Device.update_dev_control_cache(self.device["devNo"], newDevCache)
  77. if "cy4EventApi" in self.dealer.features:
  78. for port, portData in newDevCache.items():
  79. if port.isdigit():
  80. orderNo = portData["consumeType"] + str(uuid.uuid4())
  81. dataDict = {"deviceCode":self.device.logicalCode,
  82. "port":port,
  83. "notifyType": "portInfo",
  84. "status":portData["status"],
  85. "portInfo":{}}
  86. if portData["status"] == Const.DEV_WORK_STATUS_WORKING:
  87. portDict = {
  88. "deviceCode": self.device.logicalCode,
  89. "status": Const.DEV_WORK_STATUS_WORKING,
  90. "power" : portData.get('power',0),
  91. "leftTime": portData.get('leftTime',None),
  92. "startTime":portData["startTime"],
  93. "consumeType":portData["consumeType"],
  94. "orderNo":orderNo,
  95. "isAPI":portData.get("isAPI",False),
  96. "port":port,
  97. "extOrderNo":portData.get("extOrderNo",""),
  98. }
  99. dataDict["portInfo"] = portDict
  100. handle_and_notify_event_to_north_cy4_new(self.device,portDict)
  101. # 开始充电 状态上报 启动方式分为 刷卡 扫码 投币 对于刷卡需要创建相应的消费记录 其余的更新端口状态
  102. elif cmdCode == 'D9':
  103. self.do_event_start()
  104. elif cmdCode == 'DA': # 充电停止
  105. self.do_event_finish()
  106. elif cmdCode == 'D4': # 卡充值
  107. self.do_event_recharge_card()
  108. return # D4充值,直接已经回应了报文,不需要再次回应报文。
  109. elif cmdCode == 'D3': # 卡充值的回执
  110. self.do_event_recharge_card_result()
  111. def compare_port_cache(self, portInfo, devCache):
  112. """
  113. 主板要求 对比 上报上来的端口信息 和 缓存中的端口信息 如果端口的信息由工作变为空闲 ,则处理该端口停止
  114. 同时更新端口缓存信息
  115. :param portInfo: 主板上报的信息
  116. :param devCache: 服务器记录的信息
  117. :return:
  118. """
  119. newDevCache = dict()
  120. for portStr, portData in portInfo.items():
  121. # 首先读取 服务器的缓存信息 如果存在等待D9的标记 则此次上报的数据不对 该端口信息产生任何影响
  122. portCache = devCache.get(portStr) or dict()
  123. if portCache.get("waitD9"):
  124. newDevCache[portStr] = portCache
  125. continue
  126. # 存在剩余时间以及使用时间
  127. if portData.get("leftTime", 0) and portCache.get("needTime", 0):
  128. portData["usedTime"] = portCache.get("needTime") - portData.get("leftTime")
  129. portCache.update(portData)
  130. newDevCache[portStr] = portCache
  131. # 非正常情况 清掉缓存
  132. if portData.get("status") == Const.DEV_WORK_STATUS_IDLE and portCache.get("status") == Const.DEV_WORK_STATUS_WORKING:
  133. Device.update_dev_control_cache(self.device.devNo, {portStr: {'status': Const.DEV_WORK_STATUS_IDLE}})
  134. return newDevCache
  135. def stop_port(self, portStr):
  136. """
  137. 停止端口 状态
  138. :param portStr:
  139. :return:
  140. """
  141. self.deviceAdapter.stop(portStr)
  142. def do_event_start(self):
  143. """
  144. 处理 D9 充电开始指令事件
  145. :return:
  146. """
  147. cardNo = self.event_data["cardNo"]
  148. portStr = self.event_data["port"]
  149. if not self.event_data["result"]:
  150. logger.info("start charging device reply error! devNo is %s, port is %s" % (self.device["devNo"], portStr))
  151. return
  152. if self.event_data["consumeType"] == "card":
  153. card = self.update_card_dealer_and_type(cardNo)
  154. if "cy4EventApi" not in self.dealer.features:
  155. if not self.check_card_can_use(card):
  156. logger.info("[ChargingCY4WorkEventer do_event_start] check card not can use devNo = {}".format(self.device.devNo))
  157. self.notify_invalid_card_to_dealer(self.event_data["cardNo"], card)
  158. return self.deviceAdapter.stop_charging_port(portStr)
  159. # 判断卡是否存在 如果不存在 直接更新缓存信息即可
  160. if not card:
  161. Device.update_dev_control_cache(self.device.devNo, {portStr: self.event_data})
  162. return
  163. # 更新卡的余额
  164. self.update_card_balance(card, RMB(self.event_data["balance"]))
  165. # 记录卡的消费
  166. attachParas = {"chargeIndex": portStr}
  167. servicedInfo = {"cardNo": cardNo, "chargeIndex": portStr}
  168. orderNo, cardOrderNo = self.record_consume_for_card(card, RMB(self.event_data["coins"]), servicedInfo=servicedInfo, attachParas=attachParas)
  169. # 记录当前的卡消费的 progress
  170. fee = RMB(self.event_data["coins"])
  171. ServiceProgress.register_card_service(
  172. self.device,
  173. int(portStr),
  174. card,
  175. {
  176. "orderNo": orderNo,
  177. "money": fee.mongo_amount,
  178. "coin": fee.mongo_amount,
  179. "needTime": self.event_data["needTime"],
  180. "cardOrderNo": cardOrderNo
  181. }
  182. )
  183. # 通知卡消费成功
  184. self.notify_balance_has_consume_for_card(card, fee)
  185. cacheDict = {
  186. "isStart": True,
  187. "status": Const.DEV_WORK_STATUS_WORKING,
  188. "coins": float(fee),
  189. "price": float(fee),
  190. "startTime": datetime.datetime.now().strftime(Const.DATETIME_FMT),
  191. "openId": card.openId,
  192. "needTime": self.event_data.get("needTime"),
  193. "power": self.event_data.get("power"),
  194. "consumeType": "card",
  195. "payInfo": [{
  196. "needTime": self.event_data.get("needTime"),
  197. "coins": float(fee),
  198. "price": float(fee),
  199. "cardId": str(card.id),
  200. "cardNo": self.event_data.get("cardNo")
  201. }]
  202. }
  203. Device.update_dev_control_cache(self.device['devNo'], {portStr: cacheDict}) # 记录该端口累计需要的时间和钱
  204. else:
  205. # dataDict = {
  206. # 'orderNo': orderNo,
  207. # 'port': portStr,
  208. # 'devNo': self.device['devNo'],
  209. # 'logicalCode': self.device['logicalCode'],
  210. # 'money': float(fee),
  211. # 'startTime': datetime.datetime.now().strftime(Const.DATETIME_FMT),
  212. # 'openId': card.openId,
  213. # 'cardNo': cardNo,
  214. # 'needTime': self.event_data.get('needTime'),
  215. # 'consumeType': 'card',
  216. # 'status': 'PENDING',
  217. # }
  218. dataDict = {
  219. "data": self.event_data.get('data'),
  220. "logicalCode":self.device['logicalCode'],
  221. "notifyType":"swipCard"
  222. }
  223. handle_and_notify_event_to_north_cy4_new(self.device, dataDict)
  224. elif self.event_data["consumeType"] == "mobile":
  225. # 如果是扫码启动的设备 可能会存在续充问题 payInfo needTime 需要特殊处理
  226. devCache = Device.get_dev_control_cache(self.device["devNo"])
  227. portCache = devCache.get(portStr, {})
  228. if portCache.get('isAPI', False) is False:
  229. # 没有 等待D9 的标记 说明已经处理过D9了 不需要再次处理
  230. if not portCache.get("waitD9"):
  231. return
  232. # 如果是虚拟卡消费,在service处理消费记录的时候一定会写入到端口缓存中
  233. consumeRcdId = portCache.pop("consumeRcdId", None)
  234. # 获取本阶段的充电时间
  235. needTime = self.get_need_time(portCache, self.event_data.get("needTime"))
  236. payInfo = portCache.get("payInfo")
  237. payInfo[-1].update({
  238. "needTime": needTime,
  239. })
  240. if consumeRcdId:
  241. payInfo[-1].update({"consumeRcdId": consumeRcdId})
  242. portCache.update({
  243. "needTime": portCache.get("needTime", 0) + needTime,
  244. "power": self.event_data["power"],
  245. "waitD9": False
  246. })
  247. Device.update_dev_control_cache(self.device["devNo"], {portStr: portCache})
  248. else:
  249. portCache.update({
  250. "needTime": self.event_data["needTime"] if self.event_data["needTime"] != 0 else 1,
  251. "power": self.event_data["power"],
  252. "waitD9": False
  253. })
  254. Device.update_dev_control_cache(self.device["devNo"], {portStr: portCache})
  255. dataDict = {
  256. 'orderNo': portCache.get('orderNo'),
  257. 'port': portStr,
  258. 'devNo': self.device.get("devNo"),
  259. 'logicalCode': self.device.get('logicalCode'),
  260. 'money': portCache.get('price'),
  261. 'startTime': datetime.datetime.now().strftime(Const.DATETIME_FMT),
  262. 'needTime': int(self.event_data.get('needTime', 0)),
  263. 'consumeType': 'mobile',
  264. 'status': 'PENDING',
  265. 'extOrderNo': portCache.get('extOrderNo')
  266. }
  267. handle_and_notify_event_to_north_cy4(self.device, dataDict)
  268. dataDict.update({"notifyType": "start"})
  269. handle_and_notify_event_to_north_cy4_new(self.device, dataDict)
  270. # 投币的暂时只是更新一下端口缓存
  271. elif self.event_data["consumeType"] == "coin":
  272. nowTime = datetime.datetime.now().strftime(Const.DATETIME_FMT)
  273. Accounting.recordOfflineCoin(
  274. self.device,
  275. int(time.time()),
  276. int(self.event_data["coins"]),
  277. port = portStr)
  278. orderNo = self.record_consume_for_coin(RMB(self.event_data["coins"]))
  279. Device.update_dev_control_cache(
  280. self.device["devNo"],
  281. {
  282. portStr: {
  283. "isStart": True,
  284. "coins": self.event_data["coins"],
  285. "needTime": self.event_data["needTime"],
  286. "power": self.event_data["power"],
  287. "consumeType": self.event_data["consumeType"],
  288. "startTime": datetime.datetime.now().strftime(Const.DATETIME_FMT),
  289. "status": Const.DEV_WORK_STATUS_WORKING,
  290. "consumeOrderNo": orderNo,
  291. "payInfo": [{
  292. "coins": float(self.event_data["coins"]),
  293. "price": float(self.event_data["coins"]),
  294. "needTIme": self.event_data["needTime"],
  295. "consumeOrderNo": orderNo
  296. }]
  297. }
  298. }
  299. )
  300. else:
  301. logger.error("error consumeType %s" % self.event_data["consumeType"])
  302. def do_event_finish(self):
  303. """
  304. 充电结束事件上报
  305. :return:
  306. """
  307. portStr = self.event_data["port"]
  308. leftTime = self.event_data["leftTime"]
  309. devCache = Device.get_dev_control_cache(self.device.devNo)
  310. portCache = devCache.get(portStr, {})
  311. if not portCache:
  312. logger.error("[ChargingCY4WorkEventer do_event_finish] portCache is None, device = {}, port = {}".format(self.device.devNo, portStr))
  313. return
  314. group = self.device.group
  315. dealer = self.device.owner
  316. if not dealer or not group:
  317. logger.error('[ChargingCY4WorkEventer do_event_finish] dealer is not found, dealerId = {}'.format(self.device.ownerId))
  318. return
  319. if "coins" not in portCache:
  320. logger.error("[ChargingCY4WorkEventer do_event_finish] no coins info, device = {}, event data = {}".format(self.device.devNo, self.event_data))
  321. return
  322. nowTime = datetime.datetime.now()
  323. openId = portCache.get("openId", None)
  324. consumeType = portCache.get("consumeType", None)
  325. if consumeType == "mobile":
  326. if portCache.get('isAPI', False) is False:
  327. if not openId:
  328. # 没有OPENID的情况下 仅仅是 清空端口缓存就OK
  329. Device.clear_port_control_cache(self.device.devNo, portStr)
  330. logger.info("[ChargingCY4WorkEventer do_event_finish] mobile consumeType finish but no openId, devNo = {}, port = {}".format(self.device.devNo, portStr))
  331. return
  332. user = MyUser.objects(openId=openId, groupId=self.device.groupId).first()
  333. consumeDict = {
  334. 'chargeIndex': portStr,
  335. 'reason': self.event_data["reason"],
  336. 'actualNeedTime': portCache.get("needTime"),
  337. 'duration': int(portCache.get("needTime", 0)) - int(leftTime),
  338. 'elec': self.event_data["spendElec"],
  339. 'elecFee': self.calc_elec_fee(self.event_data["spendElec"]),
  340. 'leftTime': leftTime,
  341. 'needTime': u'扫码订购%s分钟' % portCache.get("needTime"),
  342. DEALER_CONSUMPTION_AGG_KIND.COIN: VirtualCoin(portCache.get("coins")).mongo_amount
  343. }
  344. # 扫码退费的情况处理
  345. payInfo = portCache.get("payInfo", list())
  346. allNeedTime = portCache.get("needTime", 0)
  347. usedTime = allNeedTime - leftTime
  348. extra = [
  349. {u"订购时长": u"{} 分钟".format(allNeedTime)},
  350. {u"使用时长": u"{} 分钟".format(usedTime)}
  351. ]
  352. # 剩余时间比总订购时间还要长 则证明没有使用
  353. if usedTime <= 0:
  354. usedTime = 0
  355. if usedTime <= CYConstant.REFUND_PRODUCTION_TIME:
  356. usedTime = 0
  357. self.event_data.update({"reason": u"异常结束,如非自己停止,可能是插头松动"})
  358. self.notify_user_service_complete(
  359. service_name=u"充电",
  360. openid=user.managerialOpenId if user else "",
  361. port=self.event_data['port'],
  362. address=group.address,
  363. reason=self.event_data["reason"],
  364. finished_time=nowTime.strftime("%Y-%m-%d %H:%M:%S"),
  365. extra=extra
  366. )
  367. # 如果设备开启了退费并且结束原因在退费原因之列 或者时间不满5分钟
  368. if (self.device.is_auto_refund and self.event_data.get(
  369. "reasonCode") in CYConstant.NEED_REFUND) or usedTime <= CYConstant.REFUND_PRODUCTION_TIME:
  370. # 总的退还
  371. allRefundMoney = VirtualCoin(0)
  372. # 针对每一笔的支付信息不同进行退款
  373. for item in payInfo:
  374. usedTime, refundMoney = self.refund_user(item, usedTime, openId)
  375. allRefundMoney += refundMoney
  376. if allRefundMoney > VirtualCoin(0):
  377. consumeDict.update({
  378. DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: allRefundMoney.mongo_amount,
  379. DEALER_CONSUMPTION_AGG_KIND.COIN: (
  380. VirtualCoin(portCache.get("coins")) - allRefundMoney).mongo_amount
  381. })
  382. refundTitle = u'您使用的%s号端口充电,共付款:%s元,充电预定时间为:%s分钟,剩余时间:%s分钟,给您退款:%s元(退款金额将于1-5个工作日内返还)' % (
  383. portStr, portCache.get("coins"), portCache.get('needTime'), leftTime, allRefundMoney)
  384. #
  385. self.notify_user(user.managerialOpenId if user else '', 'refund_coins', **{
  386. 'title': refundTitle,
  387. 'backCount': u'%s' % allRefundMoney.amount,
  388. 'finishTime': datetime.datetime.strftime(nowTime, "%Y-%m-%d %H:%M:%S")
  389. })
  390. # 没有退费的情况下 需要对经销商进行分账
  391. else:
  392. for item in payInfo:
  393. rechargeRcdId = item.get("rechargeRcdId")
  394. self.do_ledger(rechargeRcdId)
  395. ServiceProgress.update_progress_and_consume_rcd(
  396. self.device["ownerId"],
  397. {
  398. "open_id": openId,
  399. "device_imei": self.device["devNo"],
  400. "port": int(portStr),
  401. "isFinished": False
  402. },
  403. consumeDict
  404. )
  405. else:
  406. try:
  407. asr = APIServiceStartRecord.objects(orderNo=portCache['orderNo']).first()
  408. needTime = asr.needTime
  409. dataDict = {
  410. 'port': portStr,
  411. 'devNo': self.device['devNo'],
  412. 'logicalCode': self.device['logicalCode'],
  413. 'finishReason': self.event_data["reason"],
  414. 'duration': int(needTime) - int(leftTime),
  415. 'spendElec': self.event_data["spendElec"],
  416. 'leftTime': int(leftTime),
  417. 'status': 'FINISHED',
  418. 'consumeType': 'mobile',
  419. 'endTime': datetime.datetime.now().strftime(Const.DATETIME_FMT),
  420. 'extOrderNo': asr.extOrderNo
  421. }
  422. handle_and_notify_event_to_north_cy4(self.device, dataDict)
  423. dataDict.update({"notifyType": "finished"})
  424. handle_and_notify_event_to_north_cy4_new(self.device, dataDict)
  425. except Exception as e:
  426. logger.error('changyuan4 api finish error! e={}'.format(e))
  427. Device.clear_port_control_cache(self.device['devNo'], portStr)
  428. elif consumeType == "card":
  429. cardId = portCache.get("payInfo")[0].get("cardId")
  430. if not cardId:
  431. logger.info("[ChargingCY4WorkEventer do_event_finish] card consumeType finish but no cardId, devNo = {}, port = {}".format(self.device.devNo, portStr))
  432. else:
  433. card = Card.objects.get(id = cardId)
  434. extra = [
  435. {u"刷卡卡号": "{}".format(card.cardNo)},
  436. {u"订购时长": u"{} 分钟".format(portCache.get("needTime", 0))},
  437. {u"使用时长": u"{} 分钟".format(int(portCache.get("needTime", 0)) - int(leftTime))}
  438. ]
  439. self.notify_user_service_complete(
  440. service_name=u"充电",
  441. openid=card.managerialOpenId,
  442. port=self.event_data['port'],
  443. address=group.address,
  444. reason=self.event_data["reason"],
  445. finished_time=nowTime.strftime("%Y-%m-%d %H:%M:%S"),
  446. extra=extra
  447. )
  448. consumeDict = {
  449. 'chargeIndex': portStr,
  450. 'reason': self.event_data["reason"],
  451. 'actualNeedTime': portCache.get("needTime"),
  452. 'duration': int(portCache.get("needTime")) - int(leftTime),
  453. 'elec': self.event_data["spendElec"],
  454. 'elecFee': self.calc_elec_fee(self.event_data["spendElec"]),
  455. 'leftTime': leftTime,
  456. 'needTime': u'刷卡订购%s分钟' % portCache.get("needTime")
  457. }
  458. ServiceProgress.update_progress_and_consume_rcd(
  459. self.device["ownerId"],
  460. {
  461. "open_id": openId,
  462. "device_imei": self.device["devNo"],
  463. "port": int(portStr),
  464. "isFinished": False
  465. },
  466. consumeDict
  467. )
  468. dataDict = {
  469. 'port': portStr,
  470. 'cardNo': card.cardNo,
  471. 'devNo': self.device['devNo'],
  472. 'logicalCode': self.device['logicalCode'],
  473. 'finishReason': self.event_data["reason"],
  474. 'duration': int(portCache.get("needTime")) - int(leftTime),
  475. 'spendElec': self.event_data["spendElec"],
  476. 'leftTime': int(leftTime),
  477. 'status': 'FINISHED',
  478. 'consumeType': 'card',
  479. 'endTime': datetime.datetime.now().strftime(Const.DATETIME_FMT),
  480. }
  481. handle_and_notify_event_to_north_cy4(self.device, dataDict)
  482. dataDict.update({"notifyType": "finished"})
  483. handle_and_notify_event_to_north_cy4_new(self.device, dataDict)
  484. elif consumeType == "coin":
  485. CoinConsumeRcd = ConsumeRecord.objects.get(orderNo = portCache.get('consumeOrderNo')) # type: ConsumeRecord
  486. CoinConsumeRcd.servicedInfo['elec'] = portCache.get("spendElec")
  487. CoinConsumeRcd.servicedInfo['actualNeedTime'] = u'动态功率计算为%s分钟' % str(
  488. int(portCache.get("needTime")) - int(leftTime))
  489. CoinConsumeRcd.servicedInfo['needTime'] = u'投币订购%s分钟' % portCache.get("needTime")
  490. CoinConsumeRcd.servicedInfo['reason'] = self.event_data.get("reason")
  491. CoinConsumeRcd.servicedInfo['chargeIndex'] = portStr
  492. CoinConsumeRcd.finishedTime = datetime.datetime.strftime(nowTime, '%Y-%m-%d %H:%M:%S')
  493. CoinConsumeRcd.save()
  494. valueDict = {
  495. DEALER_CONSUMPTION_AGG_KIND.ELEC: portCache.get("spendElec"),
  496. DEALER_CONSUMPTION_AGG_KIND.ELECFEE: self.calc_elec_fee(portCache.get("spendElec"))
  497. }
  498. status = CoinConsumeRcd.update_agg_info(valueDict)
  499. if status:
  500. record_consumption_stats(CoinConsumeRcd)
  501. else:
  502. logger.error(
  503. '[update_progress_and_consume_rcd] failed to update_agg_info record=%r' % (CoinConsumeRcd,))
  504. else:
  505. logger.error("error consumeType! devNo is %s, port is %s" % (self.device["devNo"], portStr))
  506. # 事件结束之后清空端口的信息 更新设备的服务信息
  507. Device.clear_port_control_cache(self.device["devNo"], portStr)
  508. def do_event_recharge_card(self):
  509. """
  510. 主板发起 卡金额同步指令
  511. 服务器对照 相应卡的 余额 状态 充值记录等等进行相应的操作
  512. :return:
  513. """
  514. cardNo = self.event_data["cardNo"]
  515. cardBalance = self.event_data["balance"]
  516. card = self.update_card_dealer_and_type(cardNo)
  517. if not card:
  518. logger.error("no card exist, can not async card balance")
  519. return
  520. # 更新卡的余额 以设备侧的为准
  521. if RMB(cardBalance) != card.balance:
  522. logger.info('Card<{}> balance<{}> needs to be sync !!!'.format(cardNo, card.balance))
  523. card.balance = RMB(cardBalance)
  524. card = card.save()
  525. # orderNos 表示充值的订单 cardOrderNos 表示卡充值的订单
  526. money, coins, orderNos, cardOrderNos = CardRechargeOrder.get_to_do_list(str(card.id))
  527. # 一旦卡被冻结 立即下发 创建一张为 负数的金额订单 将卡的余额清空
  528. if card.frozen:
  529. group = Group.get_group(card.groupId)
  530. CardRechargeOrder.new_one(
  531. openId=card.openId,
  532. cardId=str(card.id),
  533. cardNo=card.cardNo,
  534. money=RMB(0),
  535. coins=VirtualCoin(0) - VirtualCoin(coins) - VirtualCoin(card.balance),
  536. group=group,
  537. rechargeId=ObjectId(),
  538. rechargeType=u"sendCoin"
  539. )
  540. money, coins, orderNos, cardOrderNos = CardRechargeOrder.get_to_do_list(str(card.id))
  541. # 没有需要同步的订单的情况下 直接退出
  542. if not orderNos:
  543. logger.info('Not card recharge record!, card is <{}>'.format(cardNo))
  544. # self._do_offline_recharge_by_dealer(cardNo, card, cardType)
  545. return
  546. orderNos = [str(item) for item in orderNos]
  547. sid = str(random.randint(0, 0xFFFF))
  548. TempValues.set('%s-%s' % (self.device.devNo, sid), value=json.dumps(orderNos))
  549. TempValues.set('%s-%s-%s' % (self.device.devNo, sid, cardNo), value=json.dumps(cardOrderNos))
  550. TempValues.set('%s-%s' % (self.device.devNo, cardNo), value=str(sid))
  551. # 计算总的需要同步的金额
  552. asyncMoney = RMB(cardBalance) + RMB(coins)
  553. logger.info('ready to recharge card, card is <{}>, money is <{}>'.format(cardNo, coins))
  554. # 已经下发金额后,先将订单的状态改为结束。如果主板上报失败,再将订单状态迁移回来否则就不再变化订单状态了
  555. try:
  556. CardRechargeOrder.update_card_order_has_finished(str(card.id))
  557. except Exception as e:
  558. logger.debug('%s' % e)
  559. else:
  560. self.deviceAdapter.recharge_card(cardNo, asyncMoney)
  561. def do_event_recharge_card_result(self):
  562. """
  563. 同步卡余额 D3指令的应答指令 表示主板已经确认卡的余额已经被更新
  564. :return:
  565. """
  566. result = self.event_data["result"]
  567. if not result:
  568. logger.error("async card balance error, device reply error")
  569. return
  570. answerSid = self.event_data["sid"]
  571. cardNo = self.event_data["cardNo"]
  572. cardBalance = self.event_data["balance"]
  573. sid = TempValues.get("%s-%s" % (self.device["devNo"], cardNo))
  574. if sid != answerSid:
  575. logger.error("answer sid is not equal sid! %s\t%s" % (answerSid, sid))
  576. card = self.update_card_dealer_and_type(cardNo)
  577. if not card:
  578. logger.error("not found card, event is <{}>".format(self.event_data))
  579. return
  580. # 根据sid 以及卡号 查询出此次充值的所有的订单
  581. orderNos = TempValues.get('{}-{}'.format(self.device.devNo, sid))
  582. cardOrderNos = TempValues.get('{}-{}-{}'.format(self.device.devNo, sid, cardNo))
  583. # 下面的都不会更改订单的状态 最多走售后
  584. money, coins = RMB(0), VirtualCoin(0)
  585. # 校验金额是否是相等的
  586. orderNos = [ObjectId(item) for item in orderNos]
  587. rds = RechargeRecord.objects.filter(ownerId=card.dealerId, id__in=orderNos)
  588. for item in rds:
  589. money += item.money
  590. coins += item.coins
  591. # 这个地方就是异常值处理了
  592. if VirtualCoin(coins + card.balance) != VirtualCoin(cardBalance):
  593. logger.error('card pre balance not equal now balance! event is <{}>'.format(self.event_data))
  594. return
  595. # 依次更改卡充值的订单 并创建充值订单
  596. cardOrders = CardRechargeOrder.objects.filter(orderNo__in=cardOrderNos)
  597. for _order in cardOrders: # type: CardRechargeOrder
  598. _order.update_after_recharge_ic_card(
  599. device=self.device,
  600. sendMoney=RMB(_order.coins),
  601. preBalance=card.balance
  602. )
  603. preBalance = card.balance
  604. card.balance = card.balance + _order.coins
  605. # 创建充值记录
  606. CardRechargeRecord.add_record(
  607. card=card,
  608. group=Group.get_group(_order.groupId),
  609. order=_order,
  610. device=self.device
  611. )
  612. # 保存
  613. card.save()
  614. # 完成之后将TempValue 的key value 清空
  615. TempValues.remove('%s-%s' % (self.device['devNo'], sid))
  616. TempValues.remove('%s-%s' % (self.device['devNo'], cardNo))
  617. TempValues.remove("%s-%s-%s" % (self.device["devNo"], sid, cardNo))
  618. def temperature_check(self, temperature):
  619. """
  620. 温度监测
  621. :param temperature
  622. :return:
  623. """
  624. # 黑龙江温度 -19 度 昌原让屏蔽低温告警
  625. if temperature > self.deviceAdapter.MAX_TEMPERATURE:
  626. group = Group.get_group(self.device["groupId"])
  627. # self.notify_dealer(
  628. # 'device_fault',
  629. # **{
  630. # 'title': u'注意!注意!设备温度不正常',
  631. # 'device': u'组号::%s, 二维码编号: %s, 当前温度: %s' % (self.device['groupNumber'], self.device['logicalCode'], temperature),
  632. # 'location': u'组名称:%s, 地址:%s' % (group['groupName'], group['address']),
  633. # 'fault': u"设备温度超限",
  634. # 'notifyTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  635. # }
  636. # )
  637. self.notify_dealer(
  638. templateName = "device_fault",
  639. title = "注意!设备当前温度过高",
  640. device = u"{groupNumber}组-{logicalCode}".format(groupNumber = self.device["groupNumber"],
  641. logicalCode = self.device["logicalCode"]),
  642. location = u"{address}-{groupName}".format(address = group["address"], groupName = group["groupName"]),
  643. fault = u"设备当前温度过高,请小心火警发生!",
  644. notifyTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  645. )
  646. def main_board_check(self, portInfo):
  647. """
  648. 继电器粘连 监测
  649. :param portInfo:
  650. :return:
  651. """
  652. group = Group.get_group(self.device["groupId"])
  653. for portStr, portData in portInfo.items():
  654. if portData.get("status") == Const.DEV_WORK_STATUS_FAULT_RELAY_CONNECT:
  655. # self.notify_dealer(
  656. # 'device_fault',
  657. # **{
  658. # 'title': u'注意!注意!您的设备发生继电器粘连',
  659. # 'device': u'组号::%s, 二维码编号: %s, 端口号: %s' % (self.device['groupNumber'], self.device['logicalCode'], portStr),
  660. # 'location': u'组名称:%s, 地址:%s' % (group['groupName'], group['address']),
  661. # 'fault': u"设备端口发生继电器粘连",
  662. # 'notifyTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  663. # }
  664. # )
  665. self.notify_dealer(
  666. templateName = "device_fault",
  667. title = "注意!设备继电器发生粘连!",
  668. device = u"{groupNumber}组-{logicalCode}-{port}".format(groupNumber = self.device["groupNumber"],
  669. logicalCode = self.device["logicalCode"],
  670. port = portStr),
  671. location = u"{address}-{groupName}".format(address = group["address"],
  672. groupName = group["groupName"]),
  673. fault = u"设备继电器粘连,端口将一直处于供电状态!",
  674. notifyTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  675. )
  676. @staticmethod
  677. def get_need_time(portCache, allLeftTime):
  678. """
  679. 获取此次支付的 needTime 协议中上报的NeedTime的理解:第一次支付获得了300分钟,使用了100分钟,第二次支付又获得了300分钟 然后上传过来的needTime是500分钟
  680. :param portCache: 端口缓存值
  681. :param allLeftTime: 上报过来的needTime
  682. :return:
  683. """
  684. leftTime = portCache.get("leftTime", 0)
  685. needTime = portCache.get("needTime")
  686. # 如果端口缓存中不存在剩余时间 说明是第一次支付
  687. # 绝大多数是这种情况
  688. if not leftTime and not needTime:
  689. return allLeftTime
  690. # 如果存在剩余时间 则可以说明此次获得的消费时间 = 上传的总剩余时间-之前的剩余时间(可能会有误差 已经和主板沟通)
  691. # 充电了一段时间之后的续充
  692. if leftTime and needTime:
  693. return allLeftTime - leftTime
  694. # 如果不存在剩余时间 但是端口缓存中已经存在了总的消费时间 说明是在1-5分钟之内续充 也就是D8指令还没有上来 连续续充 D9, D9
  695. # 连续续充 连续两次D9
  696. if not leftTime and needTime:
  697. return allLeftTime - needTime
  698. # 这种情况应该是不可能发生的,但是还是以防万一,出现的情况可能是 设备启动后,定时上传指令 比 上传充电指令更早到
  699. # D8 先 D9后 通过标记应该已经解决
  700. if leftTime and not needTime:
  701. return allLeftTime - leftTime
  702. def refund_user(self, payInfo, usedTime, openId):
  703. """
  704. 退钱给用户 每一笔退还 如果不是虚拟卡支付 则说明是金币或者现金启动 后分账方式都需要对经销商进行分账
  705. :param payInfo: 单笔支付的信息
  706. :param usedTime: 使用的时间
  707. :param openId:
  708. :return: usedTime 退还此笔消费之后还剩余的使用时间 refundMoney 此笔退款退还的钱(币种可能不同 后续需要统一设计)
  709. """
  710. needTime = payInfo.get("needTime", 0)
  711. rechargeRcdId = payInfo.get("rechargeRcdId")
  712. vCardId = payInfo.get("vCardId")
  713. coins = payInfo.get("coins")
  714. # 进入退款后,还是优先分账 分完帐再说
  715. rechargeRecord = self.do_ledger(rechargeRcdId)
  716. # 接下来 计算退款率
  717. leftTime = needTime - usedTime
  718. # 情况一: 计算出来的剩余时间小于等于0 说明本单使用完毕 无需退款 但有可能下一单还需要退
  719. if leftTime <= 0:
  720. return usedTime - needTime, VirtualCoin(0)
  721. # 情况二:虚拟卡的直接本单不退 由于虚拟卡算1次 所以这一单直接不处理
  722. if vCardId:
  723. return 0, VirtualCoin(0)
  724. # 计算退款率 所需的退款即退款全额 * 退款率
  725. refundRate = Ratio(float(leftTime) / float(needTime))
  726. if rechargeRecord and rechargeRecord.isQuickPay:
  727. # 情况三: 现金退费
  728. refundMoney = refundRate * RMB(rechargeRecord.money)
  729. refund_cash(
  730. rechargeRecord, RMB(refundMoney), VirtualCoin(0),
  731. minus_total_consume = VirtualCoin(rechargeRecord.coins)) # type: RefundMoneyRecord
  732. return 0, refundMoney
  733. else:
  734. # 情况四:金币退费
  735. refundMoney = refundRate * VirtualCoin(coins)
  736. refund_money(self.device, refundMoney, openId)
  737. return 0, refundMoney