jinque.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import json
  5. import logging
  6. import time
  7. from arrow import Arrow
  8. from django.conf import settings
  9. from typing import TYPE_CHECKING
  10. from apilib.monetary import RMB, VirtualCoin
  11. from apilib.utils_string import make_title_from_dict
  12. from apps import serviceCache
  13. from apps.web.constant import Const, APP_TYPE, DEALER_CONSUMPTION_AGG_KIND, DeviceCmdCode
  14. from apps.web.core.exceptions import ServiceException
  15. from apps.web.core.networking import MessageSender
  16. from apps.web.device.models import Device
  17. from apps.web.eventer.base import FaultEvent, WorkEvent, ComNetPayAckEvent, AckEventProcessorIntf, IdStartAckEvent
  18. from apps.web.eventer import EventBuilder
  19. from apps.web.helpers import get_wechat_auth_bridge
  20. from apps.web.user.models import VCardConsumeRecord, CardRechargeOrder, MyUser, ConsumeRecord, \
  21. RechargeRecord, Card
  22. if TYPE_CHECKING:
  23. from apps.web.device.models import DeviceDict
  24. logger = logging.getLogger(__name__)
  25. class builder(EventBuilder):
  26. def __getEvent__(self, device_event):
  27. # 订单机制事件
  28. if 'order_id' in device_event:
  29. if device_event['order_type'] == 'com_start':
  30. return MyComNetPayAckEvent(self.deviceAdapter, device_event)
  31. if device_event['order_type'] == 'ic_recharge':
  32. pass
  33. if device_event['order_type'] == 'ic_start':
  34. pass
  35. if device_event['order_type'] == 'id_start':
  36. return OnlineCardStartAckEvent(self.deviceAdapter, device_event)
  37. if device_event['order_type'] == 'card_refund':
  38. pass
  39. else:
  40. if 'event_type' in device_event:
  41. if device_event['event_type'] == 'card':
  42. return CardEvent(self.deviceAdapter, device_event)
  43. if device_event['event_type'] == 'fault':
  44. return JinQueFaultEvent(self.deviceAdapter, device_event)
  45. else:
  46. return None
  47. if 'sCmd' in device_event:
  48. return GeneralEvent(self.deviceAdapter, device_event)
  49. class CardEvent(WorkEvent):
  50. NORMAL = 'normal' # 正常卡
  51. ILLEGAL = 'illegal' # 非法卡
  52. def do(self):
  53. sCmd = self.event_data['sCmd']
  54. if sCmd == 2508:
  55. self._do_get_balance()
  56. else:
  57. pass
  58. def _do_get_balance(self):
  59. card_no = self.event_data['card_no']
  60. cardNo = str(int(card_no, 16))
  61. logger.info('[_do_get_balance] receive cardNo = {}'.format(cardNo))
  62. card = self.update_card_dealer_and_type(cardNo) # type: Card
  63. data = {'funCode': 'card', 'status': 'illegal', 'balance': 0, 'cardNo': cardNo, 'card_no': card_no} # 默认为非法卡
  64. dealer = self.device.owner
  65. # 无主卡或者是卡被冻结
  66. if not card or not card.openId or card.frozen or not dealer:
  67. logger.info('[_do_get_balance] receive cardNo = {}, card invalid!'.format(cardNo))
  68. data.update({'status': self.ILLEGAL})
  69. return self.send_mqtt(data=data)
  70. if self.card_is_busy():
  71. data.update({'status': self.ILLEGAL})
  72. return self.send_mqtt(data=data)
  73. # 是否存在没有到账的余额 进行充值
  74. card_recharge_order = CardRechargeOrder.get_last_to_do_one(str(card.id))
  75. self.recharge_id_card(
  76. card=card,
  77. rechargeType='append',
  78. order=card_recharge_order
  79. )
  80. card.reload()
  81. # ongoingList = getattr(card, 'ongoingList', []) # 有冻结未结束的订单
  82. #
  83. # # 先不给做一张卡只能开启一单的限制
  84. # if ongoingList:
  85. # logger.info(
  86. # '[_do_get_balance] receive cardNo = {}, card balance = {} card wroking!'.format(cardNo, card.balance))
  87. # data.update({'status': self.ILLEGAL})
  88. # return self.send_mqtt(data=data)
  89. onceIdcardFee = RMB(self.device.otherConf.get('onceIdcardFee', 5))
  90. balance = RMB(card.balance)
  91. amount = min(card.balance, onceIdcardFee)
  92. data.update({
  93. 'balance': int(balance * 100), # 单位: 分
  94. 'amount': int(amount * 100), # 单位: 分
  95. 'status': self.NORMAL,
  96. 'attach_paras': {},
  97. })
  98. if self.send_mqtt(data=data):
  99. self.set_card_busy()
  100. def send_mqtt(self, data=None, cmd=DeviceCmdCode.OPERATE_DEV_SYNC, otherData=None):
  101. """
  102. 发送mqtt 指令默认210 返回data
  103. """
  104. result = MessageSender.send(self.device, cmd,
  105. data)
  106. if 'rst' in result and result['rst'] != 0:
  107. if result['rst'] == -1:
  108. raise ServiceException(
  109. {'result': 2, 'description': u'该设备正在玩命找网络,请您稍候再试', 'rst': -1})
  110. elif result['rst'] == 1:
  111. raise ServiceException(
  112. {'result': 2, 'description': u'该设备忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能', 'rst': 1})
  113. else:
  114. if cmd in [DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, DeviceCmdCode.PASSTHROUGH_OPERATE_DEV_NO_RESPONSE]:
  115. return
  116. return result.get('data', 'ok')
  117. def card_is_busy(self):
  118. card_no = self.event_data['card_no']
  119. key = 'jinque_{}_{}'.format(self.device.ownerId, card_no)
  120. if serviceCache.get(key):
  121. return True
  122. else:
  123. return False
  124. def set_card_busy(self):
  125. card_no = self.event_data['card_no']
  126. key = 'jinque_{}_{}'.format(self.device.ownerId, card_no)
  127. return serviceCache.set(key, '1', 60 * 3)
  128. class GeneralEvent(WorkEvent):
  129. SUCCESS_01 = '01'
  130. BALANCE_NOT_ENOUGH_02 = '02'
  131. INVALID_CARD_03 = '03'
  132. FREEZE_CARD_04 = '04'
  133. def do(self):
  134. if self.event_data['sCmd'] == 2503:
  135. self._do_update_status()
  136. elif 1513 < self.event_data['sCmd'] < 1523:
  137. self._do_update_power()
  138. elif self.event_data['sCmd'] == 2504:
  139. self._do_update_total_elec()
  140. def _do_get_balance(self):
  141. cardNo = str(int(self.event_data.get('card_no'), 16))
  142. logger.info('receive cardNo:{}'.format(cardNo))
  143. card = self.update_card_dealer_and_type(cardNo)
  144. mqtt_data = {
  145. 'funCode': self.event_data.get('cmdCode')
  146. }
  147. if not card or not card.openId:
  148. data = self.event_data.get('session')
  149. data += '36'
  150. data += self.INVALID_CARD_03
  151. data += self.event_data.get('card_no')
  152. data += self.event_data.get('fee')
  153. data += '{:0>4X}'.format(0)
  154. logger.info('no this card! card_no_hex<{}>, cardNo<{}>'.format(self.event_data.get('card_no'), cardNo))
  155. elif card.frozen:
  156. data = self.event_data.get('session')
  157. data += '36'
  158. data += self.FREEZE_CARD_04
  159. data += self.event_data.get('card_no')
  160. data += self.event_data.get('fee')
  161. data += '{:0>4X}'.format(0)
  162. logger.info('card is frozen card_no_hex<{}>, cardNo<{}>'.format(self.event_data.get('card_no'), cardNo))
  163. else:
  164. # 是否存在没有到账的余额 进行充值
  165. card_recharge_order = CardRechargeOrder.get_last_to_do_one(str(card.id))
  166. self.recharge_id_card(
  167. card=card,
  168. rechargeType='append',
  169. order=card_recharge_order
  170. )
  171. card.reload()
  172. fee = RMB(int(self.event_data.get('fee'), 16)) * 0.1
  173. if card.balance >= RMB(fee):
  174. data = self.event_data.get('session')
  175. data += '36'
  176. data += self.SUCCESS_01
  177. data += self.event_data.get('card_no')
  178. data += self.event_data.get('fee')
  179. data += '{:0>4X}'.format(int((card.balance - fee) * 10))
  180. else:
  181. data = self.event_data.get('session')
  182. data += '36'
  183. data += self.BALANCE_NOT_ENOUGH_02
  184. data += self.event_data.get('card_no')
  185. data += self.event_data.get('fee')
  186. data += '{:0>4X}'.format(int((card.balance - fee) * 10))
  187. mqtt_data['data'] = data
  188. self.send_mqtt(data=mqtt_data, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  189. def send_mqtt(self, data=None, cmd=DeviceCmdCode.OPERATE_DEV_SYNC):
  190. """
  191. 发送mqtt 指令默认210 返回data
  192. """
  193. result = MessageSender.send(self.device, cmd,
  194. data)
  195. if 'rst' in result and result['rst'] != 0:
  196. if result['rst'] == -1:
  197. raise ServiceException(
  198. {'result': 2, 'description': u'该设备正在玩命找网络,请您稍候再试', 'rst': -1})
  199. elif result['rst'] == 1:
  200. raise ServiceException(
  201. {'result': 2, 'description': u'该设备忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能', 'rst': 1})
  202. else:
  203. if cmd in [DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, DeviceCmdCode.PASSTHROUGH_OPERATE_DEV_NO_RESPONSE]:
  204. return
  205. return result.get('data', 'ok')
  206. def _do_update_status(self):
  207. STATUS_MAP = {
  208. 'idle': 0,
  209. 'busy': 1
  210. }
  211. status = self.event_data.get('status', {})
  212. ts = self.event_data.get('ts', 0)
  213. now = time.time()
  214. if now - ts < 10:
  215. ctrInfo = Device.get_dev_control_cache(self.device.devNo)
  216. for strPort, status in status.items():
  217. if strPort in ctrInfo:
  218. ctrInfo[strPort].update({'status': STATUS_MAP.get(status, 1)})
  219. else:
  220. ctrInfo[strPort] = {'status': status}
  221. Device.update_dev_control_cache(self.device.devNo, ctrInfo)
  222. def _do_update_power(self):
  223. powers = self.event_data.get('powers', {})
  224. ts = self.event_data.get('ts', 0)
  225. now = time.time()
  226. if now - ts < 10:
  227. ctrInfo = Device.get_dev_control_cache(self.device.devNo)
  228. for strPort, power in powers.items():
  229. if power > 0:
  230. if strPort in ctrInfo:
  231. ctrInfo[strPort].update({'power': power})
  232. else:
  233. ctrInfo[strPort] = {'power': power}
  234. Device.update_dev_control_cache(self.device.devNo, ctrInfo)
  235. def _do_update_total_elec(self):
  236. total_elec = round(self.event_data.get('total_elec', 0) * 0.01, 2)
  237. ts = self.event_data.get('ts', 0)
  238. now = time.time()
  239. if now - ts < 10:
  240. Device.get_collection().update_one(
  241. filter={'devNo': self.device.devNo},
  242. update={'$set': {'otherConf.total_elec': total_elec}}
  243. )
  244. class JinQueFaultEvent(FaultEvent):
  245. def do(self, **args):
  246. # 将告警的消息打入相应的缓存
  247. port = self.event_data.get('port')
  248. code = self.event_data.get('code')
  249. now = time.time()
  250. ts = self.event_data.get('ts', 0)
  251. if now - ts < 10:
  252. # 0 表示整机
  253. if not port:
  254. port = str(0)
  255. else:
  256. port = str(port)
  257. if code == 0x02:
  258. statusInfo = '用户正在使用'
  259. return
  260. elif code == 0x06:
  261. statusInfo = '未设置电价'
  262. elif code == 0x11:
  263. statusInfo = '没有检测到充电器'
  264. elif code == 0x12:
  265. statusInfo = '输出控制有故障'
  266. else:
  267. return
  268. warningData = {
  269. 'warningStatus': 2,
  270. 'warningDesc': statusInfo,
  271. 'warningTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  272. }
  273. Device.update_dev_warning_cache(self.device.devNo, {port: warningData})
  274. super(JinQueFaultEvent, self).do()
  275. class StartAckEventPreProcessor(AckEventProcessorIntf):
  276. def analysis_reason(self, reason, fault_code=None):
  277. FINISHED_CHARGE_REASON_MAP = {
  278. 0x00: u'充满自停',
  279. 0x01: u'订购的时间已用完',
  280. 0x04: u'超负荷断电',
  281. 0x10: u'订购的金额已用完',
  282. 0x11: u'未检测到充电器,或充电器脱落',
  283. # 服务器定义的停止事件
  284. 0xF1: u'用户远程停止',
  285. 0xF2: u'管理员远程停止',
  286. 0xF3: u'检测到充电已停止, 系统结单',
  287. }
  288. return FINISHED_CHARGE_REASON_MAP.get(reason, '充电结束')
  289. def pre_processing(self, device, event_data):
  290. # type:(DeviceDict, dict)->dict
  291. source = json.dumps(event_data, indent=4)
  292. event_data['source'] = source
  293. if 'duration' in event_data:
  294. duration = event_data.pop('duration', 0)
  295. event_data['duration'] = round((duration + 59) / 60.0, 1)
  296. if 'fts' in event_data and 'sts' in event_data:
  297. duration = event_data['fts'] - event_data['sts']
  298. event_data['duration'] = round((duration + 59) / 60.0, 1)
  299. if 'elec' in event_data:
  300. event_data['elec'] = round(event_data['elec'] / 3600000, 2)
  301. if 'amount' in event_data:
  302. pass
  303. # 刷卡启动. 需要建单, 需要支付金额
  304. if 'cardNo' in event_data:
  305. event_data['fee'] = event_data['amount'] * 0.01
  306. if 'status' in event_data and event_data['status'] == 'finished':
  307. event_data['reasonDesc'] = self.analysis_reason(event_data.get('reason'))
  308. if 'left' in event_data:
  309. pass
  310. return event_data
  311. class MyComNetPayAckEvent(ComNetPayAckEvent):
  312. def __init__(self, smartBox, event_data):
  313. super(MyComNetPayAckEvent, self).__init__(smartBox, event_data, StartAckEventPreProcessor())
  314. def post_before_start(self, order=None):
  315. # 记录处理的源数据报文
  316. uart_source = getattr(order, 'uart_source', [])
  317. uart_source.append({
  318. 'rece_running': self.event_data.get('source'),
  319. 'time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  320. })
  321. order.uart_source = uart_source
  322. order.save()
  323. def post_after_start(self, order=None):
  324. pass
  325. def post_before_finish(self, order=None):
  326. # 记录处理的源数据报文
  327. uart_source = getattr(order, 'uart_source', [])
  328. uart_source.append({
  329. 'rece_finished': self.event_data.get('source'),
  330. 'time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  331. })
  332. order.uart_source = uart_source
  333. order.save()
  334. def post_after_finish(self, order=None):
  335. pass
  336. def merge_order(self, master_order, sub_orders):
  337. # type:(ConsumeRecord, list)->dict
  338. start_time = Arrow.fromdatetime(master_order.startTime, tzinfo=settings.TIME_ZONE)
  339. portDict = {
  340. 'coins': str(master_order.package['coins']),
  341. 'money': str(master_order.package['price']),
  342. 'start_time': start_time.format(Const.DATETIME_FMT),
  343. 'estimatedTs': int(start_time.timestamp + 3600 * 12),
  344. 'consumeType': 'mobile'
  345. }
  346. return portDict
  347. def do_finished_event(self, master_order, sub_orders, merge_order_info):
  348. # type: (ConsumeRecord, [ConsumeRecord], dict)->None
  349. self._do_finished(master_order, sub_orders, merge_order_info)
  350. def insert_vCard_consume_record(self, vCard, order, success, consumeTotal, consumeDay):
  351. try:
  352. if success and consumeDay['count'] > 0:
  353. record = VCardConsumeRecord(
  354. orderNo=VCardConsumeRecord.make_no(order.logicalCode),
  355. openId=order.openId,
  356. nickname=order.nickname,
  357. cardId=str(vCard.id),
  358. dealerId=vCard.dealerId,
  359. devNo=order.devNo,
  360. devTypeCode = order.devTypeCode,
  361. devTypeName = order.dev_type_name,
  362. logicalCode=order.logicalCode,
  363. groupId=order.groupId,
  364. address=order.address,
  365. groupNumber=order.groupNumber,
  366. groupName=order.groupName,
  367. attachParas=order.attachParas,
  368. consumeData=consumeTotal,
  369. consumeDayData=consumeDay
  370. )
  371. record.save()
  372. except Exception, e:
  373. logger.exception(e)
  374. def _do_finished(self, order, sub_orders, merge_order_info):
  375. # type: (ConsumeRecord, list, dict)->None
  376. duration, left, amount = self.event_data.get('duration', 0), self.event_data.get('left', VirtualCoin(
  377. 0)), self.event_data.get('amount', VirtualCoin(0))
  378. coins = VirtualCoin(merge_order_info['coins'])
  379. # 做一个保护 left 不能超过 amount
  380. left = min(amount, left)
  381. auto_refund = self.device.is_auto_refund
  382. if amount == 0:
  383. refundRatio = 0
  384. else:
  385. if auto_refund:
  386. refundRatio = left * 1.0 / amount
  387. else:
  388. refundRatio = 0
  389. backCoins = coins * refundRatio
  390. logger.debug('{} auto refund enable switch is {}, coins={} refundRatio={}'.format(
  391. repr(self.device), auto_refund, coins, refundRatio))
  392. extra = []
  393. extra.append({u'使用详情': u'{}分钟(端口: {} )'.format(duration, order.used_port)})
  394. user = order.user # type: MyUser
  395. if order.paymentInfo['via'] == 'free':
  396. extra.append({u'消费金额': u'当前设备免费使用'})
  397. elif order.paymentInfo['via'] in ['netPay', 'coins', 'cash', 'coin']:
  398. if backCoins > VirtualCoin(0):
  399. extra.append({u'消费明细': u'支付{}元,退费{}元'.format(coins, backCoins)})
  400. else:
  401. extra.append({u'消费明细': u'支付{}元'.format(coins)})
  402. order_processing_list = [order] + sub_orders
  403. for _order in order_processing_list[::-1]:
  404. consumeDict = {
  405. 'reason': self.event_data.get('reasonDesc', None),
  406. 'chargeIndex': str(order.used_port)
  407. }
  408. need_back_coins, need_consume_coins, backCoins = self._calc_refund_info(backCoins, _order.coin)
  409. refundCash = 'refundRMB_device_event' in self.device.owner.features
  410. rechargeRcdId = _order.attachParas.get('linkedRechargeRecordId', '')
  411. if rechargeRcdId != '':
  412. rechargeRcd = RechargeRecord.objects.filter(id=rechargeRcdId, isQuickPay=True).first()
  413. else:
  414. rechargeRcd = None
  415. if refundCash and rechargeRcd: # 退现金特征 + 有充值订单
  416. # 退现金部分
  417. user.clear_frozen_balance(str(_order.id), _order.paymentInfo['deduct'],
  418. back_coins=VirtualCoin(0),
  419. consume_coins=VirtualCoin(_order.coin))
  420. refundRMB = rechargeRcd.money * refundRatio
  421. self.refund_net_pay(user, {'rechargeRcdId': rechargeRcdId, 'openId': user.openId},
  422. refundRMB, VirtualCoin(0), consumeDict, True)
  423. else:
  424. # 退金币部分
  425. user.clear_frozen_balance(str(_order.id), _order.paymentInfo['deduct'],
  426. back_coins=need_back_coins,
  427. consume_coins=need_consume_coins)
  428. consumeDict.update({
  429. DEALER_CONSUMPTION_AGG_KIND.COIN: _order.coin.mongo_amount,
  430. DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: need_back_coins.mongo_amount,
  431. })
  432. if _order.orderNo == order.orderNo:
  433. consumeDict.update({
  434. DEALER_CONSUMPTION_AGG_KIND.DURATION: duration,
  435. # DEALER_CONSUMPTION_AGG_KIND.ELEC: elec,
  436. # DEALER_CONSUMPTION_AGG_KIND.ELECFEE: self.deviceAdapter.calc_elec_fee(elec),
  437. })
  438. _order.update_service_info(consumeDict)
  439. else:
  440. logger.error('not net pay rather user virtual card pay. something is wrong.')
  441. return
  442. auth_bridge = get_wechat_auth_bridge(source=self.device,
  443. app_type=APP_TYPE.WECHAT_USER_MANAGER)
  444. self.notify_user_service_complete(
  445. service_name='充电',
  446. openid=user.get_bound_pay_openid(auth_bridge.bound_openid_key),
  447. port='',
  448. address=order.address,
  449. reason=self.event_data.get('reasonDesc'),
  450. finished_time=order.finishedTime.strftime('%Y-%m-%d %H:%M:%S'),
  451. extra=extra)
  452. # 更新一次缓存
  453. self.deviceAdapter.async_update_portinfo_from_dev()
  454. def _calc_refund_info(self, backCoins, orderCoin):
  455. if backCoins >= orderCoin:
  456. need_back_coins = orderCoin
  457. need_consume_coins = VirtualCoin(0)
  458. backCoins -= orderCoin
  459. else:
  460. need_back_coins = backCoins
  461. need_consume_coins = orderCoin - need_back_coins
  462. backCoins = VirtualCoin(0)
  463. return need_back_coins, need_consume_coins, backCoins
  464. class CardStartAckEventPreProcessor(AckEventProcessorIntf):
  465. def analysis_reason(self, reason, fault_code=None):
  466. pass
  467. def pre_processing(self, device, event_data):
  468. # type:(DeviceDict, dict)->dict
  469. pass
  470. class OnlineCardStartAckEvent(IdStartAckEvent):
  471. def __init__(self, smartBox, event_data):
  472. super(OnlineCardStartAckEvent, self).__init__(smartBox, event_data, StartAckEventPreProcessor())
  473. def clear_card_busy(self):
  474. card_no = self.event_data['card_no']
  475. key = 'jinque_{}_{}'.format(self.device.ownerId, card_no)
  476. return serviceCache.delete(key)
  477. def post_before_start(self, order=None):
  478. self.clear_card_busy()
  479. # 记录处理的源数据报文
  480. uart_source = getattr(order, 'uart_source', [])
  481. uart_source.append({
  482. 'rece_running': self.event_data.get('source'),
  483. 'time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  484. })
  485. order.uart_source = uart_source
  486. order.save()
  487. def post_after_start(self, order=None):
  488. self.card.reload()
  489. # 通知用户,已经扣费
  490. title = make_title_from_dict([
  491. {u"设备地址": u"{}".format(self.device.group.address)},
  492. {u"设备编号": u"{}-{}".format(self.device["logicalCode"], order.used_port)},
  493. {u"实体卡": u"{}--No:{}".format(self.card.cardName or self.card.nickName, self.card.cardNo)},
  494. {u"本次消费": u"{} 元".format(order.coin)},
  495. {u"卡余额": u"{} 元".format(self.card.balance)},
  496. ])
  497. start_time_stamp = self.event_data.get("sts")
  498. start_time = datetime.datetime.fromtimestamp(start_time_stamp)
  499. self.notify_user(
  500. self.card.managerialOpenId,
  501. 'dev_start',
  502. **{
  503. 'title': title,
  504. 'things': u'刷卡消费',
  505. 'remark': u'感谢您的支持!',
  506. 'time': start_time.strftime(Const.DATETIME_FMT)
  507. }
  508. )
  509. def post_before_finish(self, order=None):
  510. self.clear_card_busy()
  511. # 记录处理的源数据报文
  512. uart_source = getattr(order, 'uart_source', [])
  513. uart_source.append({
  514. 'rece_finished': self.event_data.get('source'),
  515. 'time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  516. })
  517. order.uart_source = uart_source
  518. order.save()
  519. def post_after_finish(self, order=None):
  520. pass
  521. def merge_order(self, master_order, sub_orders):
  522. # type:(ConsumeRecord, list)->dict
  523. start_time = Arrow.fromdatetime(master_order.startTime, tzinfo=settings.TIME_ZONE)
  524. portDict = {
  525. 'coins': str(master_order.coin),
  526. 'money': str(master_order.money),
  527. 'start_time': start_time.format(Const.DATETIME_FMT),
  528. 'estimatedTs': int(start_time.timestamp + 3600 * 12),
  529. 'consumeType': 'card'
  530. }
  531. return portDict
  532. def _do_finished(self, order, merge_order_info):
  533. # type: (ConsumeRecord, dict)->None
  534. duration, left, amount = self.event_data.get('duration', 0), self.event_data.get('left', VirtualCoin(
  535. 0)), self.event_data.get('amount', VirtualCoin(0))
  536. coins = VirtualCoin(merge_order_info['coins'])
  537. # 做一个保护 left 不能超过 amount
  538. left = min(amount, left)
  539. auto_refund = self.device.is_auto_refund
  540. if amount == 0:
  541. refundRatio = 0
  542. else:
  543. if auto_refund:
  544. refundRatio = left * 1.0 / amount
  545. else:
  546. refundRatio = 0
  547. _BACK = backCoins = coins * refundRatio
  548. logger.debug('{} auto refund enable switch is {}, coins={} refundRatio={}'.format(
  549. repr(self.device), auto_refund, coins, refundRatio))
  550. # 分批塞入订单信息
  551. master_info = {
  552. 'order_id': self.event_data['order_id'],
  553. 'fee': order.coin,
  554. }
  555. order_processing_list = [master_info]
  556. if 'sub' in self.event_data:
  557. order_processing_list += self.event_data['sub']
  558. # 订单服务信息与退款处理
  559. for _info in order_processing_list[::-1]:
  560. consumeDict = {
  561. 'reason': self.event_data.get('reasonDesc', None),
  562. }
  563. _order = ConsumeRecord.objects.filter(devNo=self.device.devNo, startKey=_info['order_id']).first()
  564. if not _order:
  565. continue
  566. consumeDict.update({
  567. DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: _order.coin,
  568. })
  569. # 全退
  570. if backCoins >= VirtualCoin(_order.coin):
  571. consumeDict.update({
  572. DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: _order.coin.mongo_amount,
  573. DEALER_CONSUMPTION_AGG_KIND.REFUND_CARD: _order.coin.mongo_amount,
  574. })
  575. self.card.clear_frozen_balance(str(_order.id), _order.coin)
  576. self.record_refund_money_for_card(_order.coin, str(self.card.id), orderNo=order.orderNo)
  577. backCoins -= _order.coin
  578. else: # 部分退
  579. consumeDict.update({
  580. DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: _order.coin.mongo_amount,
  581. DEALER_CONSUMPTION_AGG_KIND.REFUND_CARD: backCoins.mongo_amount,
  582. })
  583. self.card.clear_frozen_balance(str(_order.id), backCoins)
  584. self.record_refund_money_for_card(backCoins, str(self.card.id), orderNo=order.orderNo)
  585. backCoins = VirtualCoin(0)
  586. if _order.orderNo == order.orderNo:
  587. consumeDict.update({
  588. DEALER_CONSUMPTION_AGG_KIND.DURATION: duration,
  589. # DEALER_CONSUMPTION_AGG_KIND.ELEC: elec,
  590. # DEALER_CONSUMPTION_AGG_KIND.ELECFEE: self.deviceAdapter.calc_elec_fee(elec),
  591. })
  592. _order.update_service_info(consumeDict)
  593. self.card.reload()
  594. extra = []
  595. extra.append({u'在线卡片': '{}--No:{}'.format(self.card.cardName, self.card.cardNo)})
  596. extra.append({u'使用详情': u'{}分钟(端口: {} )'.format(duration, order.used_port)})
  597. if _BACK > VirtualCoin(0):
  598. extra.append({u'消费明细': '支付{}元,退费{}元'.format(coins, _BACK)})
  599. else:
  600. extra.append({u'消费明细': '支付{}元'.format(coins)})
  601. extra.append({u'卡片余额': '{}元'.format(self.card.balance)})
  602. self.notify_user_service_complete(
  603. service_name='充电',
  604. openid=self.card.managerialOpenId,
  605. port='',
  606. address=order.address,
  607. reason=self.event_data.get('reasonDesc'),
  608. finished_time=order.finishedTime.strftime('%Y-%m-%d %H:%M:%S'),
  609. extra=extra)
  610. # 更新一次缓存
  611. self.deviceAdapter.async_update_portinfo_from_dev()
  612. def do_finished_event(self, order, merge_order_info):
  613. # type:(ConsumeRecord, dict)->None
  614. self._do_finished(order, merge_order_info)
  615. def checkout_order(self, order):
  616. # 在线卡 执行扣费
  617. fee = VirtualCoin(order.coin)
  618. self.card.freeze_balance(transaction_id=str(order.id), fee=fee)
  619. def _calc_refund_info(self, backCoins, orderCoin):
  620. if backCoins >= orderCoin:
  621. need_back_coins = orderCoin
  622. need_consume_coins = VirtualCoin(0)
  623. backCoins -= orderCoin
  624. else:
  625. need_back_coins = backCoins
  626. need_consume_coins = orderCoin - need_back_coins
  627. backCoins = VirtualCoin(0)
  628. return need_back_coins, need_consume_coins, backCoins