service.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import logging
  5. import time
  6. from mongoengine import DoesNotExist
  7. from apilib.monetary import VirtualCoin, RMB
  8. from apps.web.constant import Const, DEALER_CONSUMPTION_AGG_KIND
  9. from apps.web.common.transaction import UserConsumeSubType
  10. from apps.web.core.accounting import Accounting
  11. from apps.web.core.exceptions import ServiceException
  12. from apps.web.dealer.models import Dealer
  13. from apps.web.device.models import Group, Device, DeviceDict
  14. from apps.web.report.utils import record_consumption_stats
  15. from .device import BtPulseDevice, BtChargingSocket, BtSoftSwitch, BtTimeSwitchIR, BtClothesTree
  16. from apps.web.user.models import ConsumeRecord, Redpack
  17. from apps.web.user.utils import RedpackBuilder
  18. logger = logging.getLogger(__name__)
  19. class ActionBtDeviceBuilder(object):
  20. @staticmethod
  21. def create(code, dev):
  22. if code == Const.DEVICE_TYPE_CODE_B_PULSE or code == Const.DEVICE_TYPE_CODE_B_QUICK_CHARGE_TWO:
  23. return BtPulseDevice(dev)
  24. elif code == Const.DEVICE_TYPE_CODE_B_CHARGING:
  25. return BtChargingSocket(dev)
  26. elif code == Const.DEVICE_TYPE_CODE_B_TIME_SWITCH_GPIO:
  27. return BtSoftSwitch(dev)
  28. elif code == Const.DEVICE_TYPE_CODE_B_TIME_SWITCH_IR:
  29. return BtTimeSwitchIR(dev)
  30. elif code == Const.DEVICE_TYPE_CODE_B_CLOTHES_TREE:
  31. return BtClothesTree(dev)
  32. else:
  33. return None
  34. class StartAction(object):
  35. def __init__(self, smartBox, user, packageId, attachParas):
  36. self._smartBox = smartBox
  37. self._user = user
  38. self._packageId = packageId
  39. self._result = {}
  40. self._attachParas = attachParas
  41. self._order = None
  42. super(StartAction, self).__init__()
  43. @property
  44. def device(self):
  45. # type:()->DeviceDict
  46. return self._smartBox
  47. def handle_verify_service(self):
  48. group = Group.get_group(self._smartBox['groupId'])
  49. # 校验业务配置
  50. if 'washConfig' not in self._smartBox:
  51. raise ServiceException({'result': 2, 'description': u'未配置该业务,请联系经销商(1002)'})
  52. # 校验套餐ID的有效性
  53. package = self._smartBox['washConfig'].get(self._packageId)
  54. if package is None:
  55. raise ServiceException({'result': 2, 'description': u'该套餐不存在,请联系经销商'})
  56. # 检验套餐设置的投币数
  57. if 'coins' not in package:
  58. raise ServiceException({'result': 2, 'description': u'套餐未设置投币数,请联系经销商'})
  59. # 校验经销商有效性
  60. dealer = Dealer.get_dealer(group['ownerId'])
  61. if dealer is None:
  62. raise ServiceException({'result': 2, 'description': u'该地址所属经销商无效,请联系服务商'})
  63. self._attachParas['package'] = self._smartBox['washConfig'].get(self._packageId)
  64. def handle_deducting_fee(self):
  65. self._order.update(status = 'Deducting')
  66. def create_consume_record(self, **kwargs):
  67. try:
  68. group = Group.get_group(self._smartBox['groupId'])
  69. isFree = group.get('isFree', False)
  70. if isFree:
  71. pay_money = RMB(0)
  72. else:
  73. pay_money = RMB(self._attachParas['package']['price'])
  74. pulse = int(self._attachParas['package']['coins'])
  75. group = Group.get_group(self._smartBox['groupId'])
  76. address = group['address']
  77. group_number = self._smartBox['groupNumber']
  78. new_record = {
  79. 'orderNo': ConsumeRecord.make_no(self.device.logicalCode, UserConsumeSubType.NETPAY),
  80. 'openId': self._user.openId,
  81. 'coin': VirtualCoin(pulse),
  82. 'money': pay_money,
  83. 'devNo': self._smartBox['devNo'],
  84. 'ownerId': self._smartBox['ownerId'],
  85. 'logicalCode': self._smartBox['logicalCode'],
  86. 'groupId': self._smartBox['groupId'],
  87. 'address': address,
  88. 'groupNumber': group_number,
  89. 'groupName': group['groupName'],
  90. 'devTypeCode': self.device.devTypeCode,
  91. 'devTypeName': self.device.devTypeName,
  92. 'status': ConsumeRecord.Status.CREATED,
  93. 'isNormal': False,
  94. 'attachParas': self._attachParas,
  95. 'package': self._attachParas['package']
  96. }
  97. self._order = ConsumeRecord(**new_record)
  98. self._order.save()
  99. logger.debug('%s created.' % repr(self._order))
  100. return self._order
  101. except Exception, e:
  102. logger.exception(e)
  103. raise ServiceException({'result': 0, 'description': u'记录消费失败'})
  104. def handle_encode_cmd(self):
  105. try:
  106. actionBox = ActionBtDeviceBuilder.create(self._smartBox['code'], self._smartBox)
  107. self._result.update(
  108. {'devNo': self._smartBox['devNo'],
  109. 'cmdId': 1,
  110. 'order': {
  111. 'id': str(self._order.id),
  112. 'orderNo': self._order.orderNo,
  113. 'money': self._order.money,
  114. 'coin': self._order.coin
  115. },
  116. 'command': actionBox.encode_cmd(cmdId = 1,
  117. **{'seqNo': int(time.time()),
  118. 'attachParas': self._attachParas})})
  119. except ServiceException, e:
  120. logger.exception('failed to start the device(devNo=%s)' % (self._smartBox['devNo'],))
  121. raise e
  122. def get_result(self):
  123. return self._result
  124. class FreeStartAction(StartAction):
  125. def __init__(self, smartBox, openId, packageId, attachParas):
  126. super(FreeStartAction, self).__init__(smartBox, openId, packageId, attachParas)
  127. class PaymentStartAction(StartAction):
  128. def __init__(self, smartBox, user, packageId, attachParas):
  129. super(PaymentStartAction, self).__init__(smartBox, user, packageId, attachParas)
  130. def handle_verify_service(self):
  131. try:
  132. StartAction.handle_verify_service(self)
  133. except ServiceException as e:
  134. raise e
  135. if Redpack.can_use(dealer=self.device.owner, devTypeCode=self.device.devTypeCode):
  136. package = self._attachParas['package']
  137. balance = self._user.calc_currency_balance(self.device.owner, self.device.group)
  138. # 正常套餐 红包 + 余额 如果他们能支撑套餐 直接启动
  139. redpack = Redpack.auto_suit_with_coins({'openId': self._user.openId}, balance, package)
  140. if redpack:
  141. self._attachParas.update({'redpackId': redpack['id']})
  142. return
  143. pay_count = VirtualCoin(self._attachParas['package']['coins'])
  144. errCode, errMsg = self._user.verify_payment_for_bt(self.device, pay_count)
  145. if errCode != 1:
  146. raise ServiceException({'result': errCode, 'description': errMsg})
  147. def handle_deducting_fee(self):
  148. if self._order.redpackId:
  149. RedpackBuilder.bt_user_pay_coin_with_redpack(self._order)
  150. else:
  151. pay_count = VirtualCoin(self._attachParas['package']['coins'])
  152. errCode, errMsg = self._user.deduct_fee_for_bt(self.device, pay_count)
  153. if errCode != 1:
  154. raise ServiceException({'result': errCode, 'description': errMsg})
  155. self._order.update(status = 'Deducting')
  156. class FinishAction(object):
  157. def __init__(self, smartBox, user, packageId):
  158. self._smartBox = smartBox
  159. self._user = user
  160. self._packageId = packageId
  161. self._order = None
  162. self._result = None
  163. self._service_info = {}
  164. super(FinishAction, self).__init__()
  165. def is_free(self):
  166. return False
  167. def handle_refund(self):
  168. pass
  169. def get_consume_order(self, orderId):
  170. try:
  171. self._order = ConsumeRecord.objects.get(ownerId = self._smartBox['ownerId'],
  172. id = str(orderId)) # type: ConsumeRecord
  173. start_time = long(time.time())
  174. duration = 0
  175. try:
  176. package = self._order.attachParas['package']
  177. if 'unit' in package:
  178. if package['unit'] == u'分钟':
  179. duration = long(package['time'])
  180. elif package['unit'] == u'小时':
  181. duration = long(package['time']) * 60.0
  182. elif package['unit'] == u'天':
  183. duration = long(package['time']) * 24.0 * 60.0
  184. elif package['unit'] == u'包':
  185. duration = long(package['time'])
  186. else:
  187. duration = long(package['time'])
  188. except Exception:
  189. pass
  190. finished_time = long(start_time + duration * 60.0)
  191. self._service_info = {
  192. 'startTime': start_time,
  193. 'finishedTime': finished_time,
  194. 'totalTime': duration
  195. }
  196. except DoesNotExist:
  197. raise ServiceException({'result': 0, 'description': u'消费订单不存在'})
  198. if (datetime.datetime.now() - self._order.dateTimeAdded).total_seconds() > 300:
  199. raise ServiceException({'result': 0, 'description': u'消费订单已失效'})
  200. def update_account_record(self):
  201. pass
  202. def update_consume_status(self, status):
  203. try:
  204. self._order.status = status
  205. if status == 'Charged':
  206. self._order.isNormal = True
  207. status = self._order.update_agg_info({
  208. DEALER_CONSUMPTION_AGG_KIND.DURATION: self._service_info['totalTime'],
  209. DEALER_CONSUMPTION_AGG_KIND.COIN: self._order.coin
  210. })
  211. if status:
  212. record_consumption_stats(self._order)
  213. else:
  214. logger.error('[handle_consume_record]failed to update_agg_info record=%r' % (self._order,))
  215. self._order.save()
  216. except Exception:
  217. raise ServiceException({'result': 0, 'description': u'更新消费订单失败'})
  218. def handle_service_progress(self):
  219. try:
  220. if self._service_info['totalTime'] > 0:
  221. Device.update_dev_control_cache(
  222. devNo = self._smartBox['devNo'],
  223. newValue = {
  224. 'openId': self._user.openId,
  225. 'status': Const.DEV_WORK_STATUS_WORKING,
  226. 'totalTime': self._service_info['totalTime'] * 60,
  227. 'startTime': self._service_info['startTime'],
  228. 'finishedTime': self._service_info['finishedTime']
  229. })
  230. except Exception, e:
  231. logger.exception(e)
  232. class FreeFinishAction(FinishAction):
  233. def __init__(self, smartBox, user, packageId):
  234. super(FreeFinishAction, self).__init__(smartBox, user, packageId)
  235. def is_free(self):
  236. return True
  237. class PaymentFinishAction(FinishAction):
  238. def __init__(self, smartBox, user, packageId):
  239. super(PaymentFinishAction, self).__init__(smartBox, user, packageId)
  240. def handle_refund(self):
  241. if self._order.status != 'Deducting':
  242. return
  243. pay_count = VirtualCoin(self._order.coin)
  244. errCode, errMsg = self._user.refund(pay_count)
  245. if errCode != 1:
  246. raise ServiceException({'result': errCode, 'description': errMsg})
  247. def update_account_record(self):
  248. try:
  249. updated = self._user.update(inc__total_consumed = self._order.coin)
  250. if not updated:
  251. logger.error(u'记录累计消费失败. openId = %s' % self._user.openId)
  252. Accounting.recordNetPayCoinCount(self._smartBox['devNo'])
  253. except Exception, e:
  254. logger.exception(e)
  255. raise ServiceException({'result': 0, 'description': u'记录消费失败'})
  256. class StartService(object):
  257. def __init__(self, user, dev, packageId, attachParas):
  258. self._smartBox = dev
  259. self._user = user
  260. self._packageId = packageId
  261. self._attachParas = attachParas
  262. self._order = None
  263. super(StartService, self).__init__()
  264. def __get_service(self):
  265. group = Group.get_group(self._smartBox['groupId'])
  266. isFree = group.get('isFree', False)
  267. return FreeStartAction(self._smartBox, self._user, self._packageId, self._attachParas) if isFree \
  268. else PaymentStartAction(self._smartBox, self._user, self._packageId, self._attachParas)
  269. def start(self):
  270. result = {'result': 1, 'description': None, 'payload': {}}
  271. try:
  272. service = self.__get_service()
  273. service.handle_verify_service()
  274. service.create_consume_record()
  275. service.handle_encode_cmd()
  276. service.handle_deducting_fee()
  277. result['payload'] = service.get_result()
  278. except ServiceException, e:
  279. logger.exception(e)
  280. result = e.result
  281. return result
  282. class FinishService(object):
  283. def __init__(self, user, dev, cmdId, orderId, packageId, errCode):
  284. self._smartBox = dev
  285. self._user = user
  286. self._cmdId = cmdId
  287. self._orderId = orderId
  288. self._errCode = errCode
  289. self._packageId = packageId
  290. super(FinishService, self).__init__()
  291. def __get_service(self):
  292. group = Group.get_group(self._smartBox['groupId'])
  293. isFree = group.get('isFree', False)
  294. return FreeFinishAction(self._smartBox, self._user, self._packageId) if isFree \
  295. else PaymentFinishAction(self._smartBox, self._user, self._packageId)
  296. def start(self):
  297. result = {'result': 1, 'description': None, 'payload': {}}
  298. try:
  299. service = self.__get_service()
  300. service.get_consume_order(self._orderId)
  301. if self._errCode == 1:
  302. service.handle_service_progress()
  303. service.update_consume_status('Charged')
  304. service.update_account_record()
  305. else:
  306. service.handle_refund()
  307. service.update_consume_status('Refund')
  308. except ServiceException, e:
  309. result = e.result
  310. return result