aoqiang.py 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import logging
  4. from decimal import Decimal
  5. import datetime
  6. import time
  7. from mongoengine import DoesNotExist
  8. from apilib.monetary import Ratio, RMB, VirtualCoin
  9. from apilib.utils_datetime import to_datetime
  10. from apilib.utils_string import make_title_from_dict
  11. from apps.web.agent.models import Agent
  12. from apps.web.constant import Const, DEALER_CONSUMPTION_AGG_KIND
  13. from apps.web.core.accounting import Accounting
  14. from apps.web.core.device_define.jndz import CMD_CODE, SWIPE_CARD_PARAM_OP, SWIPE_CARD_RES
  15. from apps.web.core.exceptions import ServiceException
  16. from apps.web.dealer.models import Dealer
  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.eventer.errors import InvalidOption, NoCommandHandlerAvailable
  21. from apps.web.report.utils import record_consumption_stats
  22. from apps.web.south_intf.platform import notify_event_to_north
  23. from apps.web.south_intf.zhejiang_fire import send_event_to_zhejiang
  24. from apps.web.user.models import VCardConsumeRecord, CardRechargeOrder, ServiceProgress, MyUser, \
  25. UserVirtualCard, Card, ConsumeRecord
  26. from apps.web.api.models import APIStartDeviceRecord
  27. from apps.web.user.transaction_deprecated import refund_money
  28. from apps.web.south_intf.zhongtian import report_zhongtian_service_complete, report_zhongtian_refund
  29. logger = logging.getLogger(__name__)
  30. class builder(EventBuilder):
  31. def __getEvent__(self, device_event):
  32. event_data = self.deviceAdapter.analyze_event_data(device_event['data'])
  33. if event_data is None:
  34. return None
  35. if not event_data.has_key('cmdCode'):
  36. return
  37. if 'duration' in device_event:
  38. event_data.update({'duration': device_event['duration']})
  39. if event_data['cmdCode'] in [ CMD_CODE.SWIPE_CARD_10,
  40. CMD_CODE.DEVICE_SUBMIT_CHARGE_FINISHED_06,
  41. CMD_CODE.DEVICE_SUBMIT_CHARGE_FINISHED_v2_16,
  42. CMD_CODE.DEVICE_SUBMIT_OFFLINE_COINS_20 ]:
  43. return ChargingJNDZWorkEvent(self.deviceAdapter, event_data)
  44. if event_data['cmdCode'] in [
  45. CMD_CODE.DEVICE_SUBMIT_DEVICE_FAULT_0A,
  46. CMD_CODE.DEVICE_FAULT_TEMPERATURE,
  47. CMD_CODE.DEVICE_FAULT_POWER,
  48. CMD_CODE.DEVICE_FAULT_SMOKE,
  49. CMD_CODE.DEVICE_ELEC,
  50. ]:
  51. return JNDZEventerFailure(self.deviceAdapter, event_data)
  52. class JNDZEventerFailure(FaultEvent):
  53. def do(self, **args):
  54. cmdCode = self.event_data.get("cmdCode")
  55. faultType=self.event_data.get("FaultCode")
  56. desc=self.event_data.get("statusInfo", "")
  57. # 保证原有的故障处理逻辑不变
  58. if cmdCode == CMD_CODE.DEVICE_SUBMIT_DEVICE_FAULT_0A:
  59. super(JNDZEventerFailure, self).do()
  60. group = Group.get_group(self.device.groupId)
  61. titleList = [
  62. {u"告警名称": desc},
  63. {u"地址名称": group["groupName"]}
  64. ]
  65. title = make_title_from_dict(titleList)
  66. # 接下来的都是整机告警,这个地方需要通知到经销商
  67. # TODO zjl 需要知道 这个告警标志位是否有具体含义
  68. self.notify_dealer(
  69. "device_fault",
  70. title=title,
  71. device=u" 号设备".format(self.device.logicalCode),
  72. faultType=desc,
  73. notifyTime=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  74. fault=desc
  75. )
  76. # 记录错误故障
  77. self.record(
  78. faultCode=cmdCode,
  79. description=desc,
  80. title=faultType,
  81. detail=faultType,
  82. )
  83. class ChargingJNDZWorkEvent(WorkEvent):
  84. def time_ratio_pricing(self, value, leftTime, actualNeedTime):
  85. return value * Ratio(leftTime) * Ratio( 1 / float(actualNeedTime))
  86. def get_backCoins(self, coins, leftTime, actualNeedTime):
  87. return self.time_ratio_pricing(value=coins, leftTime=leftTime, actualNeedTime=actualNeedTime)
  88. def get_backMoney(self, money, leftTime, actualNeedTime):
  89. return self.time_ratio_pricing(value=money, leftTime=leftTime, actualNeedTime=actualNeedTime)
  90. def do(self, **args):
  91. devNo = self.device['devNo']
  92. logger.info('JingNengDianZi charging event detected, devNo=%s,curInfo=%s' % (devNo, self.event_data))
  93. cmdCode = self.event_data['cmdCode']
  94. #: 刷卡消费的,分为扣费和退费两种
  95. if cmdCode == CMD_CODE.SWIPE_CARD_10:
  96. cardNo = self.event_data['cardNo']
  97. preFee = RMB(self.event_data['preFee'])
  98. #: 操作符,是充值还是减少 <- (00, 01)
  99. oper = self.event_data['oper']
  100. card = self.update_card_dealer_and_type(cardNo)
  101. #: 经销商限制ID卡, 如果满足直接return
  102. if not card:
  103. return
  104. self.event_data['openId'] = card.openId
  105. self.event_data['cardId'] = str(card.id)
  106. self.event_data['startTime'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  107. Device.update_dev_control_cache(devNo, self.event_data)
  108. #: 首先检查订单,并进行充值
  109. #: 用户首先在手机客户端充值,需要这个时刻刷卡上报事件将充值的订单同步上来
  110. card_recharge_order = CardRechargeOrder.get_last_to_do_one(str(card.id))
  111. result = self.recharge_id_card(card = card,
  112. rechargeType = 'append',
  113. order = card_recharge_order)
  114. card.reload()
  115. logger.info('JingNengDianZi cmdNo(10) - cardNo=%s, openId=%s, result=%s, preFee=%s, curinfo=%s' % (cardNo, card.openId, result, preFee, self.event_data))
  116. # TODO 这个地方为了同时满足劲能电子和霍珀的需求 先使用特性 vCardNeedBind 后续需要统一规则
  117. try:
  118. dealer = Dealer.get_dealer(card.dealerId)
  119. agent = Agent.objects.get(id=dealer.get("agentId"))
  120. features = agent.features
  121. except Exception as e:
  122. features = list()
  123. if "vCardNeedBind" in features:
  124. virtual_card = card.bound_virtual_card
  125. else:
  126. virtual_card = card.related_virtual_card
  127. #如果虚拟卡已经绑定,需要检查下今天是否可用,如果可用,有限使用虚拟卡
  128. vCardCanUse = False
  129. package = {'coins':float(preFee),'unit':u'分钟','time':180}
  130. if virtual_card is not None and card.openId is not None:
  131. devObj = Device.objects.get(devNo = devNo)
  132. cardMin = devObj.otherConf.get('cardMin',None)
  133. if cardMin is not None:
  134. package = {'coins':float(preFee),'unit':u'分钟','time':int(cardMin)}
  135. vCardCanUse = virtual_card.can_use_today(package)
  136. #: 扣费
  137. if oper == SWIPE_CARD_PARAM_OP.DECR_00:
  138. if card.openId == '' or card.frozen:
  139. res = SWIPE_CARD_RES.INVALID_CARD_02
  140. leftBalance = RMB(0)
  141. return self.response_use_card(res, leftBalance)
  142. #如果虚拟卡可用,卡的费用不要扣掉,仅仅做记录,但是虚拟卡的额度需要扣掉
  143. elif vCardCanUse:
  144. # 记录卡消费记录以及消费记录
  145. orderNo, cardOrderNo = self.record_consume_for_card(card, money=RMB(0.0),desc = u'使用绑定的虚拟卡')
  146. group = Group.get_group(self.device['groupId'])
  147. consumeRcd = virtual_card.consume(openId=card.openId, group=group, dev=self.device,
  148. package=package,
  149. attachParas={}, nickname=card.cardName)
  150. # 记录当前服务的progress.没有上报端口号,所以先用-1标记,表示端口未知。端口等消费的时候会报上来
  151. ServiceProgress.register_card_service(self.device, -1, card,
  152. {
  153. 'orderNo': orderNo,
  154. 'money': self.event_data['preFee'],
  155. 'coin': self.event_data['preFee'], 'needTime': 0,
  156. 'cardOrderNo': cardOrderNo,
  157. 'consumeRcdId': str(consumeRcd.id)
  158. })
  159. self.consume_money_for_card(card, money=RMB(0.0))
  160. if consumeRcd is None:#如果额度没有扣除成功,就用卡
  161. pass
  162. else:
  163. self.response_use_card(SWIPE_CARD_RES.SUCCESS_00, leftBalance=0)
  164. # 通知微信,已经扣费
  165. self.notify_balance_has_consume_for_card(card, preFee,desc=u'使用绑定的虚拟卡')
  166. return
  167. elif result['balance'] < preFee:
  168. res = SWIPE_CARD_RES.BALANCE_NOT_ENOUGH_01
  169. leftBalance = result['balance']
  170. return self.response_use_card(res, leftBalance)
  171. else:
  172. # 记录卡消费记录以及消费记录
  173. orderNo, cardOrderNo = self.record_consume_for_card(card, preFee)
  174. # 记录当前服务的progress.没有上报端口号,所以先用-1标记,表示端口未知。端口等消费的时候会报上来
  175. ServiceProgress.register_card_service(self.device, -1, card,
  176. {
  177. 'orderNo': orderNo,
  178. 'money': self.event_data['preFee'],
  179. 'coin': self.event_data['preFee'], 'needTime': 0,
  180. 'cardOrderNo': cardOrderNo
  181. })
  182. res = SWIPE_CARD_RES.SUCCESS_00
  183. leftBalance = result['balance']
  184. self.consume_money_for_card(card, preFee)
  185. leftBalance -= preFee
  186. self.response_use_card(res, leftBalance)
  187. # 通知微信,已经扣费
  188. self.notify_balance_has_consume_for_card(card, preFee)
  189. # 退费.卡的退费比较特殊:如果需要按照电量进行扣费退费,需要在设备管理后台,设置成按电量方式计费。然后把卡的余额回收关闭掉。
  190. #充电结束后,上报事件,然后把钱退到卡里。如果是按照时间计费(霍柏的特殊),完全由设备决定,设备告诉我退费,我就退。针对霍柏
  191. #绑定虚拟卡的情况下,按照时间退费,需要直接取消掉余额回收,靠结束事件触发结束
  192. elif oper == SWIPE_CARD_PARAM_OP.INCR_01:
  193. if virtual_card is not None:
  194. pass
  195. else:
  196. dev = Device.objects.get(devNo = self.device['devNo'])
  197. billingType = dev.otherConf.get('billingType', 'time')
  198. if billingType != 'time':
  199. logger.debug('dev<{}> billing type is elec. not to card refund.')
  200. return
  201. res = SWIPE_CARD_RES.SUCCESS_00
  202. self.response_use_card(res, card.balance + preFee)
  203. self.refund_money_for_card(preFee, str(card.id))
  204. # 通知微信,已经退费
  205. self.notify_user(card.managerialOpenId, 'refund_coins', **{
  206. 'title': u'退币完成!您的卡号是%s,卡别名:%s' % (card.cardNo, card.nickName),
  207. 'backCount': u'金币:%s' % preFee,
  208. 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  209. })
  210. else:
  211. raise InvalidOption('oper has be to 00 or 01, %s was given' % (oper,))
  212. #: 结束的命令
  213. elif cmdCode in [CMD_CODE.DEVICE_SUBMIT_CHARGE_FINISHED_06, CMD_CODE.DEVICE_SUBMIT_CHARGE_FINISHED_v2_16]:
  214. lineInfo = Device.update_port_control_cache(devNo, self.event_data)
  215. group = Group.get_group(self.device['groupId'])
  216. dealer = Dealer.objects(id=group['ownerId']).first()
  217. if not dealer:
  218. logger.error('dealer is not found, dealerId=%s' % group['ownerId'])
  219. return
  220. agent = Agent.objects(id=dealer.agentId).first()
  221. if not agent:
  222. logger.error('agent is not found, agentId=%s' % dealer.agentId)
  223. return
  224. if not lineInfo.has_key('coins'):
  225. return
  226. if self.event_data.has_key('duration') and self.event_data['duration'] > 0:
  227. usedTime = self.event_data['duration']
  228. else:
  229. if (not lineInfo) or (not lineInfo.has_key('startTime')):
  230. return
  231. startTime = to_datetime(lineInfo['startTime'])
  232. nowTime = datetime.datetime.now()
  233. if startTime > nowTime:#如果web服务器时间和事件监控服务器时间不一致,导致开始时间比事件时间还大
  234. usedTime = 0
  235. else:
  236. usedTime = int(round(((nowTime - startTime).total_seconds() / 60.0)))
  237. cardId = lineInfo.get('cardId', '')
  238. vCardId = lineInfo.get('vCardId', None)
  239. money = RMB(lineInfo['coins'])
  240. price = lineInfo.get('price', 0.0)
  241. leftTime = self.event_data['leftTime']
  242. billingType = lineInfo.get('billingType', 'time')
  243. consumeType = lineInfo.get('consumeType', '')
  244. refundProtection = lineInfo.get('refundProtection', 0)
  245. refundProtectionTime = lineInfo.get('refundProtectionTime', 5)
  246. backCoins, backPrice = RMB(0.0), RMB(0.0)
  247. # todo 初始化, 防止变量before assignment
  248. needTime = 0
  249. leftTimeStr = ''
  250. try:
  251. #: 刷卡或者万一缓存重启了,出现needElec为空的,作为1000度电吧,就会一定使用设备上报的电量
  252. if lineInfo.get('elec', 0.0) < lineInfo.get('needElec', 0.0):
  253. spendElec = round(lineInfo['needElec'] - lineInfo['elec'], 2)
  254. else:
  255. spendElec = 0.0
  256. if leftTime == 65535:
  257. actualNeedTime = 0
  258. backCoins = money
  259. backPrice = price
  260. usedTime = 0
  261. spendElec = 0.0
  262. else:
  263. actualNeedTime = usedTime + leftTime
  264. leftTimeStr = leftTime
  265. if 'alt_tech_refund_mode' in agent.features and billingType == 'time':
  266. needTime = lineInfo['needTime']
  267. # 剩余时间不满 60 按照 0 算, 不满 120 按照 60 算...
  268. calcleftTime = (int(leftTime) // 60) * 60
  269. backCoins = money * (float(calcleftTime) / float(actualNeedTime))
  270. elif billingType == 'time':
  271. needTime = lineInfo['needTime']
  272. backCoins = money * (float(leftTime) / float(actualNeedTime))
  273. else:
  274. needElec, elec = Decimal(lineInfo.get('needElec', 1000)), Decimal(str(lineInfo.get('elec')))
  275. ratio = (needElec - elec) / needElec
  276. backCoins = RMB(money.amount - money.amount * ratio)
  277. # refundProtection 开关, 控制 refundProtectionTime 内全额退款
  278. if (refundProtection == 1 and usedTime < refundProtectionTime) and backCoins != 0:
  279. backCoins = money
  280. if backCoins > money:
  281. backCoins = money
  282. backPrice = round(price * float(backCoins) / float(money), 2)
  283. #: 扫码的方式
  284. if cardId == '' and vCardId is None and consumeType != 'coin':
  285. #: 这里需要考虑API调用的和普通使用场景
  286. if 'extOrderNo' in lineInfo:
  287. record = APIStartDeviceRecord.get_api_record(self.device['logicalCode'], lineInfo['extOrderNo'])
  288. if not record:
  289. logger.debug('cannot find api start device record')
  290. return
  291. if record.postActionTriggered:
  292. logger.debug('api({}) post action has done.'.format(lineInfo['extOrderNo']))
  293. return
  294. # 中天的结束状态匹配
  295. reasonCode = self.event_data['reasonCode']
  296. if reasonCode == '0B':
  297. reasonCode = '03'
  298. elif reasonCode == '03':
  299. reasonCode = '04'
  300. else:
  301. pass
  302. # 中天空载需要这样写
  303. if leftTime == 65535:
  304. leftTime = lineInfo['needTime']
  305. report_zhongtian_service_complete(
  306. event_code = '16',
  307. record=record,
  308. orderNo=lineInfo['extOrderNo'],
  309. deviceCode=self.device['logicalCode'],
  310. groupName=group['groupName'],
  311. address=group['address'],
  312. actualNeedTime=lineInfo['needTime'],
  313. leftTime=leftTime,
  314. finishedState=reasonCode
  315. )
  316. record.update(servicedInfo={'spendElec': str(spendElec), 'backCoins': '0'})
  317. if self.device.is_auto_refund:
  318. coins = VirtualCoin(lineInfo['coins'])
  319. money = RMB(lineInfo['price'])
  320. backCoins = self.get_backCoins(coins=coins, leftTime=leftTime,
  321. actualNeedTime=lineInfo['needTime'])
  322. backMoney = self.get_backMoney(money=money, leftTime=leftTime,
  323. actualNeedTime=lineInfo['needTime'])
  324. report_zhongtian_refund(
  325. eventCode = '16',
  326. record=record,
  327. orderNo=lineInfo['extOrderNo'],
  328. deviceCode=self.device['logicalCode'],
  329. groupName=group['groupName'],
  330. address=group['address'],
  331. backMoney=str(backMoney),
  332. backCoins=str(backCoins),
  333. actualNeedTime=lineInfo['needTime'],
  334. leftTime=leftTime,
  335. finishedState=reasonCode
  336. )
  337. record.update(servicedInfo={'spendElec': str(spendElec), 'backCoins': str(backCoins)})
  338. else:
  339. openId = lineInfo['openId']
  340. billingType = lineInfo.get('billingType', 'time')
  341. if billingType == 'time':
  342. leftTimeStr = leftTime if leftTime != 65535 else lineInfo['needTime']
  343. title = u"\\n\\n结束原因:\\t\\t{reason}\\n\\n设备编号:\\t\\t{logicalCode}-{port}\\n\\n设备地址:\\t\\t{address}\\n\\n消费时间:\\t\\t动态功率计算{actualNeedTime}分钟\\n\\n剩余时间:\\t\\t{leftTimeStr}分钟".format(
  344. reason=self.event_data["reason"],
  345. logicalCode=self.device["logicalCode"],
  346. port=self.event_data["port"],
  347. address=group["address"],
  348. actualNeedTime=actualNeedTime,
  349. leftTimeStr=leftTimeStr
  350. )
  351. else:
  352. title = u"\\n\\n结束原因:\\t\\t{reason}\\n\\n设备编号:\\t\\t{logicalCode}-{port}\\n\\n设备地址:\\t\\t{address}\\n\\n购买电量:\\t\\t{needElec}\\n\\n消耗电量:\\t\\t{spendElec}".format(
  353. reason=self.event_data["reason"],
  354. logicalCode=self.device["logicalCode"],
  355. port=self.event_data["port"],
  356. address=group["address"],
  357. needElec=lineInfo.get("needElec", 1000),
  358. spendElec=spendElec)
  359. # 通知充电完成
  360. user = MyUser.objects(openId=openId, groupId=self.device['groupId']).first()
  361. self.notify_user(
  362. managerialOpenId=user.managerialOpenId if user else "",
  363. templateName="service_complete",
  364. title=title,
  365. service=u"充电服务",
  366. finishTime=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  367. remark = u'谢谢您的支持')
  368. consumeDict = {'chargeIndex': lineInfo['port'],
  369. 'reason': lineInfo['reason'],
  370. 'actualNeedTime':u'动态功率计算为%s分钟' % actualNeedTime,
  371. 'duration':usedTime}
  372. if billingType == 'time':
  373. leftTimeStr = leftTime if leftTime != 65535 else lineInfo['needTime']
  374. consumeDict.update({'leftTime': leftTimeStr, 'needTime': u'扫码订购%s分钟' % lineInfo['needTime']})
  375. consumeDict.update({'elec': spendElec})
  376. else:
  377. consumeDict.update({'needElec': lineInfo['needElec']})
  378. consumeDict.update({'elec':spendElec})
  379. consumeDict.update({'elecFee':self.calc_elec_fee(spendElec)})
  380. # 如果需要退款,计算退款数据.
  381. if not self.device.is_auto_refund:
  382. consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.COIN: money.mongo_amount})
  383. ServiceProgress.update_progress_and_consume_rcd(
  384. self.device['ownerId'],
  385. {'open_id': openId, 'device_imei': self.device['devNo'],
  386. 'port': lineInfo['port'], 'isFinished': False}, consumeDict
  387. )
  388. else:
  389. if group.get('isFree', False) is True:
  390. backCoins = RMB('0.0')
  391. desc = u'免费充电。'
  392. else:
  393. desc = u''
  394. # 扫码退钱, 退到个人账号
  395. refund_money(self.device, backCoins, lineInfo['openId'])
  396. consumeDict.update({
  397. DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: backCoins.mongo_amount,
  398. DEALER_CONSUMPTION_AGG_KIND.COIN: (money - backCoins).mongo_amount
  399. })
  400. ServiceProgress.update_progress_and_consume_rcd(
  401. self.device['ownerId'],
  402. {'open_id': openId, 'device_imei': self.device['devNo'],
  403. 'port': lineInfo['port'], 'isFinished': False}, consumeDict)
  404. if billingType == 'time':
  405. leftTimeStr = leftTime if leftTime != 65535 else lineInfo['needTime']
  406. desc += u'您使用的%s号端口充电,共付款:%s元,充电预定时间为:%s分钟,剩余时间:%s分钟,给您退款:%s元' % (
  407. lineInfo['port'], money, lineInfo['needTime'], leftTimeStr, backCoins)
  408. else:
  409. desc += u'您使用的%s号端口充电,共付款:%s元,充电预定电量为:%s度,使用:%s度,给您退款:%s元' % (
  410. lineInfo['port'], money, lineInfo.get('needElec', 1), spendElec, backCoins)
  411. self.notify_user(user.managerialOpenId if user else '', 'refund_coins', **{
  412. 'title': desc,
  413. 'backCount': u'金币:%s' % backCoins,
  414. 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  415. })
  416. #: 使用的是虚拟卡
  417. elif vCardId is not None:
  418. billingType = lineInfo.get('billingType', 'time')
  419. if billingType == 'time':
  420. leftTimeStr = leftTime if leftTime != 65535 else lineInfo['needTime']
  421. title = u"\\n\\n结束原因:\\t\\t{reason}\\n\\n设备编号:\\t\\t{logicalCode}-{port}\\n\\n设备地址:\\t\\t{address}\\n\\n消费时间:\\t\\t动态功率计算{actualNeedTime}分钟\\n\\n剩余时间:\\t\\t{leftTimeStr}分钟".format(
  422. reason=self.event_data["reason"],
  423. logicalCode=self.device["logicalCode"],
  424. port=self.event_data["port"],
  425. address=group["address"],
  426. actualNeedTime=actualNeedTime,
  427. leftTimeStr=leftTimeStr
  428. )
  429. else:
  430. title = u"\\n\\n结束原因:\\t\\t{reason}\\n\\n设备编号:\\t\\t{logicalCode}-{port}\\n\\n设备地址:\\t\\t{address}\\n\\n购买电量:\\t\\t{needElec}\\n\\n消耗电量:\\t\\t{spendElec}".format(
  431. reason=self.event_data["reason"],
  432. logicalCode=self.device["logicalCode"],
  433. port=self.event_data["port"],
  434. address=group["address"],
  435. needElec=lineInfo.get("needElec", 1000),
  436. spendElec=spendElec
  437. )
  438. # 通知充电完成
  439. try:
  440. vCard = UserVirtualCard.objects.get(id=vCardId)
  441. except DoesNotExist:
  442. logger.info('can not find the vCard id = %s' % vCardId)
  443. return
  444. self.notify_user(
  445. managerialOpenId=self.get_managerialOpenId_by_openId(lineInfo["openId"]),
  446. templateName="service_complete",
  447. title=title,
  448. service=u"充电服务",
  449. finishTime=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  450. remark = u'谢谢您的支持'
  451. )
  452. consumeDict = {'chargeIndex': lineInfo['port'],
  453. 'reason': lineInfo['reason'],
  454. 'actualNeedTime':u'动态功率计算为%s分钟' % actualNeedTime,
  455. 'duration':usedTime}
  456. consumeDict.update({'elec': spendElec})
  457. if billingType != 'time':
  458. consumeDict.update({'elec':spendElec})
  459. consumeDict.update({'elecFee':self.calc_elec_fee(spendElec)})
  460. ServiceProgress.update_progress_and_consume_rcd(
  461. self.device['ownerId'],
  462. {'open_id': lineInfo['openId'], 'device_imei': self.device['devNo'],
  463. 'port': lineInfo['port'], 'isFinished': False}, consumeDict
  464. )
  465. consumeRcdId = lineInfo.get('consumeRcdId', None)
  466. if consumeRcdId is None:
  467. logger.info('can not find consume rcd id')
  468. return
  469. # 尝试进行虚拟卡退费
  470. try:
  471. vCardConsumeRcd = VCardConsumeRecord.objects.get(id=consumeRcdId)
  472. except DoesNotExist, e:
  473. logger.info('can not find the consume rcd id = %s' % consumeRcdId)
  474. else:
  475. vCard.refund_quota(vCardConsumeRcd, usedTime, spendElec, backCoins.mongo_amount)
  476. #: 刷的实体卡
  477. elif cardId != '':
  478. card = Card.objects.get(id=cardId)
  479. agent = Agent.objects.get(id=card.agentId)
  480. virtual_card = card.bound_virtual_card
  481. consumeDict = {'chargeIndex': lineInfo['port'],
  482. 'reason': lineInfo['reason'],
  483. 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime,
  484. 'duration': usedTime}
  485. consumeDict.update({'elec': spendElec})
  486. if billingType == 'time':
  487. self.notify_user(
  488. managerialOpenId=card.managerialOpenId,
  489. templateName="service_complete",
  490. title=u"\\n\\n结束原因:\\t\\t{reason}\\n\\n设备编号:\\t\\t{logicalCode}-{port}\\n\\n设备地址:\\t\\t{address}\\n\\n消费卡号:\\t\\t{cardNo}\\n\\n持卡姓名:\\t\\t{cardName}\\n\\n消费时间:\\t\\t动态功率计算{actualNeedTime}分钟\\n\\n剩余时间:\\t\\t{leftTimeStr}分钟".format(
  491. reason=self.event_data["reason"],
  492. logicalCode=self.device["logicalCode"],
  493. port=self.event_data["port"],
  494. address=group["address"],
  495. cardNo=card.cardNo,
  496. cardName=card.cardName,
  497. actualNeedTime=actualNeedTime,
  498. leftTimeStr=leftTimeStr
  499. ),
  500. service=u"充电服务",
  501. finishTime=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  502. remark = u'谢谢您的支持')
  503. if 'huopo_card_time_refund' in agent.features and virtual_card is None:
  504. consumeDict.update({
  505. DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: (money - backCoins).mongo_amount,
  506. DEALER_CONSUMPTION_AGG_KIND.REFUND_CARD: backCoins.mongo_amount
  507. })
  508. ServiceProgress.update_progress_and_consume_rcd(
  509. self.device['ownerId'],
  510. {'cardId': cardId, 'device_imei': self.device['devNo'],
  511. 'port': lineInfo['port'], 'isFinished': False},
  512. {'chargeIndex': lineInfo['port'], 'leftTime': leftTimeStr,
  513. 'needTime': u'刷卡订购%s分钟' % needTime if virtual_card is None else u'绑定虚拟卡订购%s分钟' % needTime,
  514. 'reason': lineInfo['reason'],
  515. 'elec': spendElec, 'duration': usedTime,
  516. 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime,
  517. 'elecFee':self.calc_elec_fee(spendElec),
  518. 'refundedMoney': str(backCoins)
  519. })
  520. else:
  521. ServiceProgress.update_progress_and_consume_rcd(
  522. self.device['ownerId'],
  523. {'cardId': cardId, 'device_imei': self.device['devNo'],
  524. 'port': lineInfo['port'], 'isFinished': False},
  525. {'chargeIndex': lineInfo['port'], 'leftTime': leftTimeStr,
  526. 'needTime': u'刷卡订购%s分钟' % needTime if virtual_card is None else u'绑定虚拟卡订购%s分钟' % needTime,
  527. 'reason': lineInfo['reason'],
  528. 'elec': spendElec, 'duration': usedTime,
  529. 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime,
  530. 'elecFee':self.calc_elec_fee(spendElec)})
  531. if 'huopo_card_time_refund' in agent.features:
  532. if virtual_card is None:
  533. self.refund_money_for_card(backCoins, str(card.id))
  534. desc = u'您使用的%s号端口充电,共付款:%s元,充电预定时间为:%s分钟,使用:%s分钟,给您退款:%s元' % (
  535. lineInfo['port'], money, needTime, usedTime, backCoins)
  536. self.notify_user(card.managerialOpenId if card else '', 'refund_coins', **{
  537. 'title': desc,
  538. 'backCount': u'金币:%s' % backCoins,
  539. 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')})
  540. else:
  541. consumeRcdId = lineInfo.get('consumeRcdId', None)
  542. if consumeRcdId is None:
  543. logger.info('can not find consume rcd id')
  544. return
  545. # 尝试进行虚拟卡退费
  546. try:
  547. vCardConsumeRcd = VCardConsumeRecord.objects.get(id=consumeRcdId)
  548. except DoesNotExist, e:
  549. logger.info('can not find the consume rcd id = %s' % consumeRcdId)
  550. else:
  551. virtual_card.refund_quota(vCardConsumeRcd, usedTime, spendElec, backCoins.mongo_amount)
  552. # 非霍珀特性
  553. else:
  554. consumeRcdId = lineInfo.get('consumeRcdId', None)
  555. # 不是虚拟卡启动的直接结束掉
  556. if consumeRcdId is None:
  557. logger.info('can not find consume rcd id')
  558. return
  559. # 尝试进行虚拟卡退费
  560. try:
  561. vCardConsumeRcd = VCardConsumeRecord.objects.get(id=consumeRcdId)
  562. except DoesNotExist, e:
  563. logger.info('can not find the consume rcd id = %s' % consumeRcdId)
  564. else:
  565. virtual_card.refund_quota(vCardConsumeRcd, usedTime, spendElec, backCoins.mongo_amount)
  566. return
  567. else:
  568. self.notify_user(
  569. managerialOpenId=card.managerialOpenId,
  570. templateName="service_complete",
  571. title=u"\\n\\n结束原因:\\t\\t{reason}\\n\\n设备编号:\\t\\t{logicalCode}-{port}\\n\\n设备地址:\\t\\t{address}\\n\\n消费卡号:\\t\\t{cardNo}\\n\\n持卡姓名:\\t\\t{cardName}\\n\\n购买电量:\\t\\t{needElec}\\n\\n消耗电量:\\t\\t{spendElec}".format(
  572. reason=self.event_data["reason"],
  573. logicalCode=self.device["logicalCode"],
  574. port=self.event_data["port"],
  575. address=group["address"],
  576. cardNo=card.cardNo,
  577. cardName=card.cardName,
  578. needElec=lineInfo.get("needElec"),
  579. spendElec=spendElec
  580. ),
  581. service=u"充电服务",
  582. finishTime=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  583. remark = u'谢谢您的支持')
  584. consumeDict = {'chargeIndex': lineInfo['port'], 'leftTime': leftTimeStr,
  585. 'reason': lineInfo['reason'],
  586. 'elec': spendElec, 'duration': usedTime, 'needElec': lineInfo['needElec']}
  587. consumeDict.update({'elecFee':self.calc_elec_fee(spendElec)})
  588. if not self.device.is_auto_refund:
  589. ServiceProgress.update_progress_and_consume_rcd(
  590. self.device['ownerId'],
  591. {'cardId': cardId, 'device_imei': self.device['devNo'],
  592. 'port': lineInfo['port'], 'isFinished': False}, consumeDict
  593. )
  594. else:
  595. if virtual_card is None: # 扫码退钱, 退到个人卡号
  596. self.refund_money_for_card(backCoins, str(card.id))
  597. consumeDict.update({
  598. DEALER_CONSUMPTION_AGG_KIND.REFUND_CARD: backCoins.mongo_amount,
  599. DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: (money - backCoins).mongo_amount
  600. })
  601. desc = u'您使用的%s号端口充电,共付款:%s元,充电预定电量为:%s度,使用:%s度,给您退款:%s元' % (
  602. lineInfo['port'], money, lineInfo.get('needElec', 0.0), spendElec, backCoins)
  603. self.notify_user(card.managerialOpenId if card else '', 'refund_coins', **{
  604. 'title': desc,
  605. 'backCount': u'金币:%s' % backCoins,
  606. 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  607. })
  608. else:
  609. consumeRcdId = lineInfo.get('consumeRcdId', None)
  610. if consumeRcdId is None:
  611. logger.info('can not find consume rcd id')
  612. return
  613. # 尝试进行虚拟卡退费
  614. try:
  615. vCardConsumeRcd = VCardConsumeRecord.objects.get(id=consumeRcdId)
  616. except DoesNotExist, e:
  617. logger.info('can not find the consume rcd id = %s' % consumeRcdId)
  618. else:
  619. virtual_card.refund_quota(vCardConsumeRcd, usedTime, spendElec, backCoins.mongo_amount)
  620. ServiceProgress.update_progress_and_consume_rcd(
  621. self.device['ownerId'],
  622. {'open_id': lineInfo['openId'], 'device_imei': self.device['devNo'],
  623. 'port': lineInfo['port'], 'isFinished': False}, consumeDict)
  624. #: 消费类型为金币,则
  625. elif consumeType == 'coin':
  626. CoinConsumeRcd = ConsumeRecord.objects.get(
  627. orderNo = lineInfo['consumeOrderNo']) # type: ConsumeRecord
  628. CoinConsumeRcd.servicedInfo['elec'] = spendElec
  629. CoinConsumeRcd.servicedInfo['actualNeedTime'] = u'动态功率计算为%s分钟' % actualNeedTime
  630. CoinConsumeRcd.servicedInfo['needTime'] = u'投币订购%s分钟' % needTime
  631. CoinConsumeRcd.servicedInfo['reason'] = lineInfo['reason']
  632. CoinConsumeRcd.servicedInfo['chargeIndex'] = lineInfo['port']
  633. CoinConsumeRcd.finishedTime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  634. CoinConsumeRcd.save()
  635. valueDict = {
  636. DEALER_CONSUMPTION_AGG_KIND.ELEC: spendElec,
  637. DEALER_CONSUMPTION_AGG_KIND.ELECFEE: self.calc_elec_fee(spendElec)
  638. }
  639. status = CoinConsumeRcd.update_agg_info(valueDict)
  640. if status:
  641. record_consumption_stats(CoinConsumeRcd)
  642. else:
  643. logger.error(
  644. '[update_progress_and_consume_rcd] failed to update_agg_info record=%r' % (CoinConsumeRcd,))
  645. except Exception as e:
  646. logger.exception('deal with jingneng devNo=%s event e=%s' % (devNo, e))
  647. finally:
  648. Device.clear_port_control_cache(devNo, str(self.event_data['port']))
  649. dataDict = {'backMoney': backPrice, 'backCoins': str(backCoins.mongo_amount)}
  650. if lineInfo.has_key('orderNo'):
  651. dataDict.update({'orderNo': lineInfo['orderNo']})
  652. notify_event_to_north(self.dealer, self.device, level = Const.EVENT_NORMAL,
  653. desc = self.event_data['reason'], dataDict=dataDict)
  654. send_event_to_zhejiang(self.dealer, self.device, self.event_data)
  655. #: 启动了端口,主要记录下投币数据
  656. elif cmdCode == CMD_CODE.DEVICE_SUBMIT_OFFLINE_COINS_20:
  657. consumeType = self.event_data['consumeType']
  658. if consumeType == 'coin':
  659. self.event_data.update({'startTime': datetime.datetime.now().strftime(Const.DATETIME_FMT)})
  660. # 记录该端口累计需要的时间和钱,cardId
  661. Device.update_port_control_cache(self.device['devNo'], self.event_data)
  662. Accounting.recordOfflineCoin(self.device,
  663. int(time.time()),
  664. int(self.event_data['coins']),
  665. port = self.event_data.get('port', None))
  666. self.event_data.update({'needElec': self.event_data['elec']})
  667. self.event_data.update({'consumeOrderNo': self.record_consume_for_coin(RMB(self.event_data['coins']))})
  668. Device.update_port_control_cache(self.device['devNo'], self.event_data)
  669. elif consumeType == 'card':
  670. port = self.event_data['port']
  671. consumeDict = {'chargeIndex': port, 'elec': self.event_data['elec'],
  672. 'money': self.event_data['coins'], 'needTime': u'刷卡订购%s分钟' % self.event_data['needTime']}
  673. queryDict = {'device_imei': self.device['devNo'],
  674. 'port': -1, 'isFinished': False,
  675. 'cardId': {'$ne': ''}, 'start_time': {'$gte': int(time.time()) - 3600}}
  676. progressDict = {'port': port}
  677. ServiceProgress.update_progress_and_consume_rcd(ownerId=self.device['ownerId'], queryDict=queryDict,
  678. consumeDict=consumeDict, updateConsume=True,
  679. progressDict=progressDict)
  680. # 找出对应的卡的ID记录到端口内存数据
  681. queryDict.update(progressDict)
  682. rcds = ServiceProgress.get_collection().find(queryDict, {'cardId': 1, 'open_id': 1, "consumeOrder": 1},
  683. sort=[('start_time', -1)])
  684. if rcds.count() == 0:
  685. return
  686. rcd = rcds[0]
  687. dev = Device.objects.get(devNo=self.device['devNo'])
  688. billingType = dev.otherConf.get('billingType', 'time')
  689. # 刷卡虚拟卡启动的时候,将consumeRcd 写入到缓存 退费的时候使用
  690. consumeRcdId = rcd.get("consumeOrder", dict()).get("consumeRcdId")
  691. if consumeRcdId:
  692. self.event_data.update({"consumeRcdId": consumeRcdId})
  693. self.event_data.update({'billingType': billingType})
  694. self.event_data.update({'cardId': rcd['cardId']})
  695. self.event_data.update({'openId': rcd['open_id']})
  696. cInfo = Device.get_dev_control_cache(devNo)
  697. lastPortInfo = cInfo.get(str(port), {})
  698. # 钱需要累计
  699. lastCoins = lastPortInfo.get('coins', 0.0)
  700. self.event_data.update({'coins': self.event_data['coins'] + lastCoins})
  701. # 电量需要累加
  702. lastNeedElec = lastPortInfo.get('needElec', 0.0)
  703. self.event_data.update({'needElec': self.event_data['elec'] + lastNeedElec})
  704. # 时间需要累加
  705. lastNeedTime = lastPortInfo.get('needTime', 0.0)
  706. self.event_data.update({'needTime': self.event_data['needTime'] + lastNeedTime})
  707. self.event_data.update({'startTime': datetime.datetime.now().strftime(Const.DATETIME_FMT)})
  708. self.event_data.update({'isStart': True, 'status': Const.DEV_WORK_STATUS_WORKING})
  709. #: 记录该端口累计需要的时间和钱,cardId
  710. Device.update_port_control_cache(self.device['devNo'], self.event_data)
  711. elif consumeType == 'server':
  712. port = self.event_data['port']
  713. #: 记录该端口累计需要的时间和钱
  714. Device.update_port_control_cache(self.device['devNo'], self.event_data)
  715. consumeDict = {'chargeIndex': self.event_data['port'], 'elec': self.event_data['elec'],
  716. 'needTime': u'订购%s分钟' % self.event_data['needTime']}
  717. queryDict = {'device_imei': self.device['devNo'],
  718. 'port': port, 'isFinished': False,
  719. 'start_time': {'$gte': int(time.time()) - 3600}}
  720. progressDict = {'port': self.event_data['port']}
  721. ServiceProgress.update_progress_and_consume_rcd(ownerId=self.device['ownerId'],
  722. queryDict=queryDict, consumeDict=consumeDict,
  723. updateConsume=True, progressDict=progressDict)
  724. # todo 功率过低告警还没做完
  725. # openId = self.event_data['openId']
  726. # if openId is not None:
  727. # user = MyUser.objects(openId=openId, groupId = self.device['groupId']).first()
  728. #
  729. # report_to_user_low_power_wechat.delay(self.notify_low_power_to_user(user, 1, 20), 15)
  730. else:
  731. raise NoCommandHandlerAvailable('[JNDZ]] no command handler for cmd %s, curDevInfo=%s' % (cmdCode, self.event_data))
  732. def response_use_card(self, res, leftBalance):
  733. # 启动失败需要把金币还回去
  734. try:
  735. self.deviceAdapter.response_use_card(res, leftBalance)
  736. except ServiceException as e:
  737. logger.exception(e)
  738. raise JNDZEventerFailure('failed to connect to device')
  739. # todo 功率过低告警还没做完
  740. # def notify_low_power_to_user(self, user, port, power):
  741. # self.notify_user(user.managerialOpenId if user else '', 'device_fault', **{
  742. # 'title': u'设备充电功率过小预警',
  743. # 'device': u'二维码编号:%s, 端口:%s' % (self.device['logicalCode'], port),
  744. # 'location': u'您的电动车充电所在地',
  745. # 'fault': u'端口功率过小, 当前功率检测: %s 瓦, 可能会影响正常充电。' % power,
  746. # 'notifyTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  747. # })