guangfa.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import logging
  5. import time
  6. from decimal import Decimal
  7. from mongoengine import DoesNotExist
  8. from apilib.monetary import RMB
  9. from apilib.utils_datetime import to_datetime
  10. from apps.web.agent.models import Agent
  11. from apps.web.constant import Const, DEALER_CONSUMPTION_AGG_KIND
  12. from apps.web.core.device_define.jndz import CMD_CODE, SWIPE_CARD_PARAM_OP, SWIPE_CARD_RES
  13. from apps.web.core.accounting import Accounting
  14. from apps.web.core.exceptions import ServiceException
  15. from apps.web.device.models import Device, Group
  16. from apps.web.eventer.base import WorkEvent, FaultEvent
  17. from apps.web.eventer import EventBuilder
  18. from apps.web.report.utils import record_consumption_stats
  19. from apps.web.south_intf.platform import notify_event_to_north
  20. from apps.web.south_intf.zhejiang_fire import send_event_to_zhejiang
  21. from apps.web.user.models import VCardConsumeRecord, ServiceProgress, CardRechargeOrder, MyUser, UserVirtualCard, Card, \
  22. ConsumeRecord
  23. from apps.web.eventer.errors import EventerError, InvalidOption, NoCommandHandlerAvailable
  24. from apps.web.user.transaction_deprecated import refund_money
  25. logger = logging.getLogger(__name__)
  26. class builder(EventBuilder):
  27. def __getEvent__(self, device_event):
  28. event_data = self.deviceAdapter.analyze_event_data(device_event)
  29. if event_data is None or 'cmdCode' not in event_data:
  30. return
  31. if 'duration' in device_event:
  32. event_data.update({'duration': device_event['duration']})
  33. if event_data['cmdCode'] in [CMD_CODE.SWIPE_CARD_10,
  34. CMD_CODE.DEVICE_SUBMIT_CHARGE_FINISHED_06,
  35. CMD_CODE.DEVICE_SUBMIT_CHARGE_FINISHED_v2_16,
  36. CMD_CODE.DEVICE_SUBMIT_OFFLINE_COINS_20]:
  37. return ChargingGUANGFAWorkEvent(self.deviceAdapter, event_data)
  38. if event_data['cmdCode'] == CMD_CODE.DEVICE_SUBMIT_DEVICE_FAULT_0A:
  39. return FaultEvent(self.deviceAdapter, event_data)
  40. class JNDZEventerFailure(EventerError):
  41. pass
  42. class ChargingGUANGFAWorkEvent(WorkEvent):
  43. def find_busy_ports(self, card):
  44. result = []
  45. ctrInfo = Device.get_dev_control_cache(self.device['devNo'])
  46. for port, value in ctrInfo.items():
  47. if not str(port).isdigit():
  48. continue
  49. if value.get('cardId', '') == str(card.id) and value.get('status', '') == Const.DEV_WORK_STATUS_WORKING:
  50. result.append(port)
  51. return result
  52. def stop_ports(self, ports):
  53. for port in ports:
  54. self.deviceAdapter.stop_charging_port(port)
  55. def do(self, **args):
  56. devNo = self.device['devNo']
  57. logger.info('guangfa charging event detected, devNo=%s,curInfo=%s' % (devNo, self.event_data))
  58. cmdCode = self.event_data['cmdCode']
  59. #: 刷卡消费的,分为扣费和退费两种
  60. if cmdCode == CMD_CODE.SWIPE_CARD_10:
  61. cardNo = self.event_data['cardNo']
  62. preFee = RMB(self.event_data['preFee'])
  63. #: 操作符,是充值还是减少 <- (00, 01)
  64. oper = self.event_data['oper']
  65. card = self.update_card_dealer_and_type(cardNo)
  66. #: 经销商限制ID卡, 如果满足直接return
  67. if not card:
  68. return
  69. self.event_data['openId'] = card.openId
  70. self.event_data['cardId'] = str(card.id)
  71. self.event_data['startTime'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  72. Device.update_dev_control_cache(devNo, self.event_data)
  73. #: 首先检查订单,并进行充值
  74. #: 用户首先在手机客户端充值,需要这个时刻刷卡上报事件将充值的订单同步上来
  75. card_recharge_order = CardRechargeOrder.get_last_to_do_one(str(card.id))
  76. result = self.recharge_id_card(card = card,
  77. rechargeType = 'append',
  78. order = card_recharge_order)
  79. card.reload()
  80. logger.info('guangfa cmdNo(10) - cardNo=%s, openId=%s, result=%s, preFee=%s, curinfo=%s' % (
  81. cardNo, card.openId, result, preFee, self.event_data))
  82. virtual_card = card.bound_virtual_card
  83. # 如果虚拟卡已经绑定,需要检查下今天是否可用,如果可用,有限使用虚拟卡
  84. vCardCanUse = False
  85. package = {'coins': float(preFee), 'unit': u'分钟', 'time': 180}
  86. if virtual_card is not None and card.openId is not None:
  87. devObj = Device.objects.get(devNo = devNo)
  88. cardMin = devObj.otherConf.get('cardMin', None)
  89. if cardMin is not None:
  90. package = {'coins': float(preFee), 'unit': u'分钟', 'time': int(cardMin)}
  91. vCardCanUse = virtual_card.can_use_today(package)
  92. #: 扣费
  93. if oper == SWIPE_CARD_PARAM_OP.DECR_00:
  94. # #光法的特殊需求,如果开始是通过刷卡启动设备的,这时再次刷卡,直接把刷卡启动的端口关闭掉,然后不回复任何数据
  95. # ports = self.find_busy_ports(card)
  96. # if ports:
  97. # self.stop_ports(ports)
  98. # return
  99. if card.openId == '' or card.frozen:
  100. res = SWIPE_CARD_RES.INVALID_CARD_02
  101. leftBalance = RMB(0)
  102. return self.response_use_card(res, leftBalance)
  103. # 如果虚拟卡可用,卡的费用不要扣掉,仅仅做记录,但是虚拟卡的额度需要扣掉
  104. elif vCardCanUse:
  105. # 记录卡消费记录以及消费记录
  106. orderNo, cardOrderNo = self.record_consume_for_card(card, money = RMB(0.0), desc = u'使用绑定的虚拟卡')
  107. # 记录当前服务的progress.没有上报端口号,所以先用-1标记,表示端口未知。端口等消费的时候会报上来
  108. ServiceProgress.register_card_service(self.device, -1, card,
  109. {
  110. 'orderNo': orderNo,
  111. 'money': self.event_data['preFee'],
  112. 'coin': self.event_data['preFee'], 'needTime': 0,
  113. 'cardOrderNo': cardOrderNo
  114. })
  115. self.consume_money_for_card(card, money = RMB(0.0))
  116. group = Group.get_group(self.device['groupId'])
  117. consumeRcd = virtual_card.consume(openId = card.openId, group = group, dev = self.device,
  118. package = package,
  119. attachParas = {}, nickname = card.cardName)
  120. if consumeRcd is None: # 如果额度没有扣除成功,就用卡
  121. pass
  122. else:
  123. self.response_use_card(SWIPE_CARD_RES.SUCCESS_00, leftBalance = 0)
  124. # 通知微信,已经扣费
  125. self.notify_balance_has_consume_for_card(card, preFee, desc = u'使用绑定的虚拟卡')
  126. return
  127. elif result['balance'] < preFee:
  128. res = SWIPE_CARD_RES.BALANCE_NOT_ENOUGH_01
  129. leftBalance = result['balance']
  130. return self.response_use_card(res, leftBalance)
  131. else:
  132. # 记录卡消费记录以及消费记录
  133. orderNo, cardOrderNo = self.record_consume_for_card(card, preFee)
  134. # 记录当前服务的progress.没有上报端口号,所以先用-1标记,表示端口未知。端口等消费的时候会报上来
  135. ServiceProgress.register_card_service(self.device, -1, card,
  136. {
  137. 'orderNo': orderNo,
  138. 'money': self.event_data['preFee'],
  139. 'coin': self.event_data['preFee'], 'needTime': 0,
  140. 'cardOrderNo': cardOrderNo
  141. })
  142. res = SWIPE_CARD_RES.SUCCESS_00
  143. leftBalance = result['balance']
  144. self.consume_money_for_card(card, preFee)
  145. leftBalance -= preFee
  146. self.response_use_card(res, leftBalance)
  147. # 通知微信,已经扣费
  148. self.notify_balance_has_consume_for_card(card, preFee)
  149. # 退费.卡的退费比较特殊:如果需要按照电量进行扣费退费,需要在设备管理后台,设置成按电量方式计费。然后把卡的余额回收关闭掉。
  150. # 充电结束后,上报事件,然后把钱退到卡里。如果是按照时间计费(霍柏的特殊),完全由设备决定,设备告诉我退费,我就退。针对霍柏
  151. # 绑定虚拟卡的情况下,按照时间退费,需要直接取消掉余额回收,靠结束事件触发结束
  152. elif oper == SWIPE_CARD_PARAM_OP.INCR_01:
  153. if virtual_card is not None:
  154. pass
  155. else:
  156. res = SWIPE_CARD_RES.SUCCESS_00
  157. self.response_use_card(res, card.balance + preFee)
  158. self.refund_money_for_card(preFee, str(card.id))
  159. # 通知微信,已经退费
  160. self.notify_user(card.managerialOpenId, 'refund_coins', **{
  161. 'title': u'退币完成!您的卡号是%s,卡别名:%s' % (card.cardNo, card.nickName),
  162. 'backCount': u'金币:%s' % preFee,
  163. 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  164. })
  165. # 还需要把最近刷卡使用的消费记录中,补充退费的金额,便于报表汇总数据
  166. self.update_consume_record_refund_money_for_card(preFee, card)
  167. else:
  168. raise InvalidOption('oper has be to 00 or 01, %s was given' % (oper,))
  169. #: 结束的命令
  170. elif cmdCode in [CMD_CODE.DEVICE_SUBMIT_CHARGE_FINISHED_06, CMD_CODE.DEVICE_SUBMIT_CHARGE_FINISHED_v2_16]:
  171. lineInfo = Device.update_port_control_cache(devNo, self.event_data)
  172. group = Group.get_group(self.device['groupId'])
  173. if 'coins' not in lineInfo:
  174. return
  175. if 'duration' in self.event_data and self.event_data['duration'] > 0:
  176. usedTime = self.event_data['duration']
  177. else:
  178. if (not lineInfo) or ('startTime' not in lineInfo):
  179. return
  180. startTime = to_datetime(lineInfo['startTime'])
  181. nowTime = datetime.datetime.now()
  182. if startTime > nowTime: # 如果web服务器时间和事件监控服务器时间不一致,导致开始时间比事件时间还大
  183. usedTime = 0
  184. else:
  185. usedTime = int(round(((nowTime - startTime).total_seconds() / 60.0)))
  186. cardId = lineInfo.get('cardId', '')
  187. vCardId = lineInfo.get('vCardId', None)
  188. money = RMB(lineInfo['coins'])
  189. price = lineInfo.get('price', 0.0)
  190. leftTime = self.event_data['leftTime']
  191. billingType = lineInfo.get('billingType', 'time')
  192. consumeType = lineInfo.get('consumeType', '')
  193. backCoins, backPrice = RMB(0.0), RMB(0.0)
  194. try:
  195. #: 刷卡或者万一缓存重启了,出现needElec为空的,作为1000度电吧,就会一定使用设备上报的电量
  196. if lineInfo.get('elec', 0.0) < lineInfo.get('needElec', 0.0):
  197. spendElec = round(lineInfo['needElec'] - lineInfo['elec'], 2)
  198. else:
  199. spendElec = 0.0
  200. if leftTime == 65535:
  201. actualNeedTime = 0
  202. backCoins = money
  203. backPrice = price
  204. usedTime = 0
  205. spendElec = 0.0
  206. else:
  207. actualNeedTime = usedTime + leftTime
  208. leftTimeStr = leftTime
  209. if billingType == 'time':
  210. needTime = lineInfo['needTime']
  211. backCoins = money * (float(leftTime) / float(actualNeedTime))
  212. else:
  213. needElec, elec = Decimal(lineInfo.get('needElec', 1000)), Decimal(str(lineInfo.get('elec')))
  214. ratio = (needElec - elec) / needElec
  215. backCoins = RMB(money.amount - money.amount * ratio)
  216. refundProtectionTime = int(self.device.get_other_conf_item('refundProtectionTime', 5))
  217. if (usedTime < refundProtectionTime) and backCoins != 0:
  218. backCoins = money
  219. if backCoins > money:
  220. backCoins = money
  221. backPrice = round(price * float(backCoins) / float(money), 2)
  222. #: 扫码的方式
  223. if cardId == '' and vCardId is None and consumeType != 'coin':
  224. openId = lineInfo['openId']
  225. billingType = lineInfo.get('billingType', 'time')
  226. if billingType == 'time':
  227. leftTimeStr = leftTime if leftTime != 65535 else lineInfo['needTime']
  228. title = u'%s 按动态功率计算总时间为:%s分钟,剩余时间:%s分钟' % (
  229. self.event_data['reason'],
  230. actualNeedTime,
  231. leftTimeStr)
  232. else:
  233. title = u'%s %s元购买%s度电量,实际使用电量为:%s度' % (
  234. self.event_data['reason'],
  235. money, lineInfo.get('needElec', 1000),
  236. spendElec
  237. )
  238. # 通知充电完成
  239. user = MyUser.objects(openId = openId, groupId = self.device['groupId']).first()
  240. self.notify_user(user.managerialOpenId, 'service_complete',
  241. **{
  242. 'title': title,
  243. 'service': u'充电服务(设备编号:%s,端口:%s,地址:%s)' % (
  244. self.device['logicalCode'], self.event_data['port'], group['address']),
  245. 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  246. 'remark': u'谢谢您的支持'
  247. })
  248. consumeDict = {'chargeIndex': lineInfo['port'],
  249. 'reason': lineInfo['reason'],
  250. 'elec': spendElec, # 记录以及花费的电量
  251. 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime,
  252. 'duration': usedTime,
  253. 'elecFee': self.calc_elec_fee(spendElec)}
  254. if billingType == 'time':
  255. leftTimeStr = leftTime if leftTime != 65535 else lineInfo['needTime']
  256. consumeDict.update({'leftTime': leftTimeStr, 'needTime': u'扫码订购%s分钟' % lineInfo['needTime']})
  257. else:
  258. consumeDict.update({'needElec': lineInfo['needElec']})
  259. # 如果需要退款,计算退款数据.
  260. if not self.device.is_auto_refund:
  261. ServiceProgress.update_progress_and_consume_rcd(
  262. self.device['ownerId'],
  263. {'open_id': openId, 'device_imei': self.device['devNo'],
  264. 'port': lineInfo['port'], 'isFinished': False}, consumeDict
  265. )
  266. else:
  267. # 扫码退钱, 退到个人账号
  268. refund_money(self.device, backCoins, lineInfo['openId'])
  269. consumeDict.update({'refundedMoney': str(backCoins)})
  270. ServiceProgress.update_progress_and_consume_rcd(
  271. self.device['ownerId'],
  272. {'open_id': openId, 'device_imei': self.device['devNo'],
  273. 'port': lineInfo['port'], 'isFinished': False}, consumeDict)
  274. if billingType == 'time':
  275. leftTimeStr = leftTime if leftTime != 65535 else lineInfo['needTime']
  276. desc = u'您使用的%s号端口充电,共付款:%s元,充电预定时间为:%s分钟,剩余时间:%s分钟,给您退款:%s元' % (
  277. lineInfo['port'], money, lineInfo['needTime'], leftTimeStr, backCoins)
  278. else:
  279. desc = u'您使用的%s号端口充电,共付款:%s元,充电预定电量为:%s度,使用:%s度,给您退款:%s元' % (
  280. lineInfo['port'], money, lineInfo.get('needElec', 1), spendElec, backCoins)
  281. self.notify_user(user.managerialOpenId if user else '', 'refund_coins', **{
  282. 'title': desc,
  283. 'backCount': u'金币:%s' % backCoins,
  284. 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  285. })
  286. #: 使用的是虚拟卡
  287. elif vCardId is not None:
  288. billingType = lineInfo.get('billingType', 'time')
  289. if billingType == 'time':
  290. leftTimeStr = leftTime if leftTime != 65535 else lineInfo['needTime']
  291. title = u'%s 按动态功率计算总时间为:%s分钟,剩余时间:%s分钟' % (
  292. self.event_data['reason'],
  293. actualNeedTime,
  294. leftTimeStr)
  295. else:
  296. title = u'%s %s元购买%s度电量,实际使用电量为:%s度' % (
  297. self.event_data['reason'],
  298. money, lineInfo.get('needElec', 1000),
  299. spendElec
  300. )
  301. # 通知充电完成
  302. try:
  303. vCard = UserVirtualCard.objects.get(id = vCardId)
  304. except DoesNotExist:
  305. logger.info('can not find the vCard id = %s' % vCardId)
  306. return
  307. self.notify_user(self.get_managerialOpenId_by_openId(lineInfo['openId']), 'service_complete',
  308. **{
  309. 'title': title,
  310. 'service': u'充电服务(设备编号:%s,端口:%s,地址:%s)' % (
  311. self.device['logicalCode'], self.event_data['port'], group['address']),
  312. 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  313. 'remark': u'谢谢您的支持'
  314. })
  315. consumeDict = {'chargeIndex': lineInfo['port'],
  316. 'reason': lineInfo['reason'],
  317. 'elec': spendElec, # 记录以及花费的电量
  318. 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime,
  319. 'duration': usedTime,
  320. 'elecFee': self.calc_elec_fee(spendElec)}
  321. ServiceProgress.update_progress_and_consume_rcd(
  322. self.device['ownerId'],
  323. {'open_id': lineInfo['openId'], 'device_imei': self.device['devNo'],
  324. 'port': lineInfo['port'], 'isFinished': False}, consumeDict
  325. )
  326. consumeRcdId = lineInfo.get('consumeRcdId', None)
  327. if consumeRcdId is None:
  328. logger.info('can not find consume rcd id')
  329. return
  330. try:
  331. vCardConsumeRcd = VCardConsumeRecord.objects.get(id = consumeRcdId)
  332. except DoesNotExist, e:
  333. logger.info('can not find the consume rcd id = %s' % consumeRcdId)
  334. return
  335. vCard.refund_quota(vCardConsumeRcd, usedTime, spendElec, backCoins.mongo_amount)
  336. #: 刷的实体卡
  337. elif cardId != '':
  338. card = Card.objects.get(id = cardId)
  339. agent = Agent.objects.get(id = card.agentId)
  340. virtual_card = card.bound_virtual_card
  341. consumeDict = {'chargeIndex': lineInfo['port'],
  342. 'reason': lineInfo['reason'],
  343. 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime,
  344. 'duration': usedTime}
  345. if billingType == 'time': # 刷卡,如果按照时间的,不予退费(霍柏家的特殊,需要退费)
  346. self.notify_user(card.managerialOpenId if card else '', 'service_complete',
  347. **{
  348. 'title': u'%s 卡号:%s(%s),按动态功率计算总时间为:%s分钟,剩余时间:%s分钟' % (
  349. self.event_data['reason'],
  350. card.cardNo,
  351. card.cardName,
  352. actualNeedTime,
  353. leftTimeStr),
  354. 'service': u'充电服务(设备编号:%s,端口:%s 地址:%s)' % (
  355. self.device['logicalCode'], self.event_data['port'],
  356. group['address']),
  357. 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  358. 'remark': u'谢谢您的支持'
  359. })
  360. if 'huopo_card_time_refund' in agent.features and virtual_card is None:
  361. ServiceProgress.update_progress_and_consume_rcd(
  362. self.device['ownerId'],
  363. {'cardId': cardId, 'device_imei': self.device['devNo'],
  364. 'port': lineInfo['port'], 'isFinished': False},
  365. {'chargeIndex': lineInfo['port'], 'leftTime': leftTimeStr,
  366. 'needTime': u'刷卡订购%s分钟' % needTime if virtual_card is None else u'绑定虚拟卡订购%s分钟' % needTime,
  367. 'reason': lineInfo['reason'],
  368. 'elec': spendElec, 'duration': usedTime,
  369. 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime,
  370. 'elecFee': self.calc_elec_fee(spendElec),
  371. 'refundedMoney': str(backCoins)
  372. }
  373. )
  374. else:
  375. ServiceProgress.update_progress_and_consume_rcd(
  376. self.device['ownerId'],
  377. {'cardId': cardId, 'device_imei': self.device['devNo'],
  378. 'port': lineInfo['port'], 'isFinished': False},
  379. {'chargeIndex': lineInfo['port'], 'leftTime': leftTimeStr,
  380. 'needTime': u'刷卡订购%s分钟' % needTime if virtual_card is None else u'绑定虚拟卡订购%s分钟' % needTime,
  381. 'reason': lineInfo['reason'],
  382. 'elec': spendElec, 'duration': usedTime,
  383. 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime,
  384. 'elecFee': self.calc_elec_fee(spendElec)})
  385. if 'huopo_card_time_refund' in agent.features:
  386. if virtual_card is None:
  387. self.refund_money_for_card(backCoins, str(card.id))
  388. else:
  389. consumeRcdId = lineInfo.get('consumeRcdId', None)
  390. if consumeRcdId is None:
  391. logger.info('can not find consume rcd id')
  392. return
  393. try:
  394. vCardConsumeRcd = VCardConsumeRecord.objects.filter(
  395. cardId = str(virtual_card.id)).first()
  396. except DoesNotExist, e:
  397. logger.info('can not find the consume rcd id = %s' % consumeRcdId)
  398. return
  399. virtual_card.refund_quota(vCardConsumeRcd, usedTime, spendElec, backCoins.mongo_amount)
  400. desc = u'您使用的%s号端口充电,共付款:%s元,充电预定时间为:%s分钟,使用:%s分钟,给您退款:%s元' % (
  401. lineInfo['port'], money, needTime, usedTime, backCoins)
  402. self.notify_user(card.managerialOpenId if card else '', 'refund_coins', **{
  403. 'title': desc,
  404. 'backCount': u'金币:%s' % backCoins,
  405. 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  406. })
  407. return
  408. self.notify_user(card.managerialOpenId if card else '', 'service_complete',
  409. **{
  410. 'title': u'%s 卡号:%s(%s),%s元购买%s度电量,实际使用电量为:%s度' % (
  411. self.event_data['reason'],
  412. card.cardNo,
  413. card.cardName,
  414. money,
  415. lineInfo['needElec'],
  416. spendElec),
  417. 'service': u'充电服务(设备编号:%s,端口:%s 地址:%s)' % (
  418. self.device['logicalCode'], self.event_data['port'],
  419. group['address']),
  420. 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  421. 'remark': u'谢谢您的支持'
  422. })
  423. consumeDict = {'chargeIndex': lineInfo['port'], 'leftTime': leftTimeStr,
  424. 'reason': lineInfo['reason'],
  425. 'elec': spendElec, 'duration': usedTime, 'needElec': lineInfo['needElec'],
  426. 'elecFee': self.calc_elec_fee(spendElec)}
  427. # 如果需要退款,计算退款数据.
  428. if not self.device.is_auto_refund:
  429. ServiceProgress.update_progress_and_consume_rcd(
  430. self.device['ownerId'],
  431. {'cardId': cardId, 'device_imei': self.device['devNo'],
  432. 'port': lineInfo['port'], 'isFinished': False}, consumeDict
  433. )
  434. return
  435. # 按照电量的退费,由结束事件来搞定
  436. if virtual_card is None:
  437. self.refund_money_for_card(backCoins, str(card.id))
  438. consumeDict.update({'refundedMoney': str(backCoins)})
  439. else:
  440. consumeRcdId = lineInfo.get('consumeRcdId', None)
  441. if consumeRcdId is None:
  442. logger.info('can not find consume rcd id')
  443. return
  444. try:
  445. vCardConsumeRcd = VCardConsumeRecord.objects.filter(cardId = str(virtual_card.id)).first()
  446. except DoesNotExist, e:
  447. logger.info('can not find the consume rcd id = %s' % consumeRcdId)
  448. return
  449. virtual_card.refund_quota(vCardConsumeRcd, usedTime, spendElec, backCoins.mongo_amount)
  450. ServiceProgress.update_progress_and_consume_rcd(
  451. self.device['ownerId'],
  452. {'open_id': lineInfo['openId'], 'device_imei': self.device['devNo'],
  453. 'port': lineInfo['port'], 'isFinished': False}, consumeDict)
  454. desc = u'您使用的%s号端口充电,共付款:%s元,充电预定电量为:%s度,使用:%s度,给您退款:%s元' % (
  455. lineInfo['port'], money, lineInfo.get('needElec', 0.0), spendElec, backCoins)
  456. self.notify_user(card.managerialOpenId if card else '', 'refund_coins', **{
  457. 'title': desc,
  458. 'backCount': u'金币:%s' % backCoins,
  459. 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  460. })
  461. #: 消费类型为金币,则
  462. elif consumeType == 'coin':
  463. CoinConsumeRcd = ConsumeRecord.objects.get(orderNo = lineInfo['consumeOrderNo']) # type: ConsumeRecord
  464. CoinConsumeRcd.servicedInfo['elec'] = spendElec
  465. CoinConsumeRcd.servicedInfo['actualNeedTime'] = u'动态功率计算为%s分钟' % actualNeedTime
  466. CoinConsumeRcd.servicedInfo['needTime'] = u'投币订购%s分钟' % needTime
  467. CoinConsumeRcd.servicedInfo['reason'] = lineInfo['reason']
  468. CoinConsumeRcd.servicedInfo['chargeIndex'] = lineInfo['port']
  469. CoinConsumeRcd.finishedTime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  470. CoinConsumeRcd.save()
  471. valueDict = {
  472. DEALER_CONSUMPTION_AGG_KIND.ELEC: spendElec,
  473. DEALER_CONSUMPTION_AGG_KIND.ELECFEE: self.calc_elec_fee(spendElec)
  474. }
  475. status = CoinConsumeRcd.update_agg_info(valueDict)
  476. if status:
  477. record_consumption_stats(CoinConsumeRcd)
  478. else:
  479. logger.error(
  480. '[update_progress_and_consume_rcd] failed to update_agg_info record=%r' % (CoinConsumeRcd,))
  481. except Exception as e:
  482. logger.exception('deal with jingneng devNo=%s event e=%s' % (devNo, e))
  483. finally:
  484. Device.clear_port_control_cache(devNo, str(self.event_data['port']))
  485. dataDict = {'backMoney': backPrice, 'backCoins': str(backCoins.mongo_amount)}
  486. if lineInfo.has_key('orderNo'):
  487. dataDict.update({'orderNo': lineInfo['orderNo']})
  488. notify_event_to_north(self.dealer, self.device, level = Const.EVENT_NORMAL,
  489. desc = self.event_data['reason'], dataDict = dataDict)
  490. send_event_to_zhejiang(self.dealer, self.device, self.event_data)
  491. #: 启动了端口,主要记录下投币数据
  492. elif cmdCode == CMD_CODE.DEVICE_SUBMIT_OFFLINE_COINS_20:
  493. consumeType = self.event_data['consumeType']
  494. port = self.event_data['port']
  495. ctrInfo = Device.get_dev_control_cache(self.device['devNo'])
  496. lastPortInfo = ctrInfo.get(str(port), None)
  497. if consumeType == 'coin':
  498. self.event_data.update({'startTime': datetime.datetime.now().strftime(Const.DATETIME_FMT)})
  499. # 记录该端口累计需要的时间和钱,cardId
  500. if lastPortInfo is None:
  501. Accounting.recordOfflineCoin(self.device,
  502. int(time.time()),
  503. int(self.event_data['coins']))
  504. self.event_data.update({'needElec': self.event_data['elec']})
  505. self.event_data.update(
  506. {'consumeOrderNo': self.record_consume_for_coin(RMB(self.event_data['coins']))})
  507. Device.update_port_control_cache(self.device['devNo'], self.event_data)
  508. else:
  509. if lastPortInfo.has_key('needElec'):
  510. lastPortInfo['needElec'] += self.event_data['elec']
  511. else:
  512. lastPortInfo['needElec'] = self.event_data['elec']
  513. orderNo = self.record_consume_for_coin(RMB(self.event_data['coins']))
  514. lastPortInfo['consumeOrderNo'] = orderNo
  515. Device.update_port_control_cache(self.device['devNo'], lastPortInfo)
  516. elif consumeType == 'card':
  517. port = self.event_data['port']
  518. consumeDict = {'chargeIndex': port, 'elec': self.event_data['elec'],
  519. 'money': self.event_data['coins'], 'needTime': u'刷卡订购%s分钟' % self.event_data['needTime']}
  520. queryDict = {'device_imei': self.device['devNo'],
  521. 'port': -1, 'isFinished': False,
  522. 'cardId': {'$ne': ''}, 'start_time': {'$gte': int(time.time()) - 3600}}
  523. progressDict = {'port': port}
  524. ServiceProgress.update_progress_and_consume_rcd(ownerId = self.device['ownerId'], queryDict = queryDict,
  525. consumeDict = consumeDict, updateConsume = True,
  526. progressDict = progressDict)
  527. # 找出对应的卡的ID记录到端口内存数据
  528. queryDict.update(progressDict)
  529. rcds = ServiceProgress.get_collection().find(queryDict, {'cardId': 1, 'open_id': 1},
  530. sort = [('start_time', 1)])
  531. if rcds.count() == 0:
  532. return
  533. rcd = rcds[0]
  534. dev = Device.objects.get(devNo = self.device['devNo'])
  535. billingType = dev.otherConf.get('billingType', 'time')
  536. self.event_data.update({'billingType': billingType})
  537. self.event_data.update({'cardId': rcd['cardId']})
  538. self.event_data.update({'openId': rcd['open_id']})
  539. cInfo = Device.get_dev_control_cache(devNo)
  540. lastPortInfo = cInfo.get(str(port), {})
  541. # 钱需要累计
  542. lastCoins = lastPortInfo.get('coins', 0.0)
  543. self.event_data.update({'coins': self.event_data['coins'] + lastCoins})
  544. # 电量需要累加
  545. lastNeedElec = lastPortInfo.get('needElec', 0.0)
  546. self.event_data.update({'needElec': self.event_data['elec'] + lastNeedElec})
  547. # 时间需要累加
  548. lastNeedTime = lastPortInfo.get('needTime', 0.0)
  549. self.event_data.update({'needTime': self.event_data['needTime'] + lastNeedTime})
  550. self.event_data.update({'startTime': datetime.datetime.now().strftime(Const.DATETIME_FMT)})
  551. self.event_data.update({'isStart': True, 'status': Const.DEV_WORK_STATUS_WORKING})
  552. #: 记录该端口累计需要的时间和钱,cardId
  553. Device.update_port_control_cache(self.device['devNo'], self.event_data)
  554. elif consumeType == 'server':
  555. port = self.event_data['port']
  556. #: 记录该端口累计需要的时间和钱
  557. Device.update_port_control_cache(self.device['devNo'], self.event_data)
  558. consumeDict = {'chargeIndex': self.event_data['port'], 'elec': self.event_data['elec'],
  559. 'needTime': u'订购%s分钟' % self.event_data['needTime']}
  560. queryDict = {'device_imei': self.device['devNo'],
  561. 'port': port, 'isFinished': False,
  562. 'start_time': {'$gte': int(time.time()) - 3600}}
  563. progressDict = {'port': self.event_data['port']}
  564. ServiceProgress.update_progress_and_consume_rcd(ownerId = self.device['ownerId'],
  565. queryDict = queryDict, consumeDict = consumeDict,
  566. updateConsume = True, progressDict = progressDict)
  567. else:
  568. raise NoCommandHandlerAvailable(
  569. '[JNDZ]] no command handler for cmd %s, curDevInfo=%s' % (cmdCode, self.event_data))
  570. def response_use_card(self, res, leftBalance):
  571. # 启动失败需要把金币还回去
  572. try:
  573. self.deviceAdapter.response_use_card(res, leftBalance)
  574. except ServiceException as e:
  575. logger.exception(e)
  576. raise JNDZEventerFailure('failed to connect to device')