utils2.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. # coding=utf-8
  2. # noinspection PyUnresolvedReferences
  3. import datetime
  4. import logging
  5. from collections import defaultdict
  6. from typing import TYPE_CHECKING, Optional
  7. from UserDict import UserDict
  8. from contextlib2 import AbstractContextManager
  9. from apilib.monetary import RMB
  10. from apps.web.core.exceptions import ServiceException
  11. from apps.web.dealer.proxy import DealerGroupStats
  12. from apps.web.device.models import Device
  13. from apps.web.exceptions import UnifiedConsumeOrderError, UserServerException
  14. from apps.web.user.constant2 import StartDeviceType, ConsumeOrderServiceItem
  15. from apps.web.user.models import MyUser, ConsumeRecord, OrderPackage, Card
  16. if TYPE_CHECKING:
  17. from apps.web.device.models import DeviceDict, GroupDict
  18. logger = logging.getLogger(__name__)
  19. class OrderContext(object, UserDict):
  20. def __init__(self):
  21. super(OrderContext, self).__init__()
  22. @property
  23. def client(self):
  24. # noinspection PyUnresolvedReferences
  25. return self._client
  26. @property
  27. def terminal(self): # type:() -> Optional[DeviceDict, GroupDict]
  28. # noinspection PyUnresolvedReferences
  29. return self._device or self._group
  30. @property
  31. def user(self):
  32. # noinspection PyUnresolvedReferences
  33. return self._user
  34. @property
  35. def goods(self): # type:() -> Goods
  36. # noinspection PyUnresolvedReferences
  37. return self._goods
  38. class OrderContextManager(AbstractContextManager):
  39. def __init__(self, **kwargs):
  40. self._source = kwargs
  41. def __enter__(self):
  42. return self
  43. def __exit__(self, exc_type, exc_val, exc_tb):
  44. super(OrderContextManager, self).__exit__(exc_type, exc_val, exc_tb)
  45. def get_starter(self):
  46. """检查启动实体"""
  47. userInfo = self._source.get("userInfo")
  48. if not userInfo:
  49. raise UnifiedConsumeOrderError(u"用户异常【下单参数异常】,请重新扫码下单(00000)")
  50. user = userInfo.pop("user", MyUser.objects.filter(openId=userInfo["openId"], groupId=userInfo["groupId"]).first()) # type: MyUser
  51. return user
  52. def get_device(self):
  53. """检查启动设备"""
  54. devInfo = self._source.get("devInfo")
  55. if not devInfo:
  56. raise UnifiedConsumeOrderError(u"设备异常【下单参数异常】,请重新扫码下单(10000)")
  57. device = Device.get_dev_by_l(devInfo.get("logicalCode")) # type: DeviceDict
  58. if not device:
  59. raise UnifiedConsumeOrderError(u"设备异常,请重新扫码下单(10001)")
  60. if not device.online:
  61. raise UnifiedConsumeOrderError(u"设备异常【离线】,请重新扫码下单(10002)")
  62. if not device.is_registered:
  63. raise UnifiedConsumeOrderError(u"设备异常【未注册】,请重新扫码下单(10003)")
  64. if device.is_fault:
  65. raise UnifiedConsumeOrderError(u"设备异常【故障】,请重新扫码下单(10004)")
  66. return device
  67. def get_start_context(self, user, device):
  68. """检查启动其余环境"""
  69. startInfo = self._source.get("startInfo")
  70. context = StartParamContext()
  71. packageId, startType, port \
  72. = startInfo["packageId"], startInfo["startType"], startInfo["port"]
  73. if startType not in [StartDeviceType.ON_LIEN, StartDeviceType.CARD]:
  74. raise UnifiedConsumeOrderError(u"启动参数异常【方式异常】,请重新扫码下单(20001)")
  75. context.startType = startType
  76. try:
  77. package = device.deviceAdapter.prepare_package(packageId, startInfo, startType)
  78. except ServiceException as se:
  79. raise UnifiedConsumeOrderError(se.result["description"])
  80. if not package:
  81. raise UnifiedConsumeOrderError(u"启动参数异常【套餐异常】,请重新扫码下单(20000)")
  82. context.package = package
  83. context.port = port
  84. return context
  85. def buildOrder(self):
  86. user = self.get_starter()
  87. device = self.get_device()
  88. context = self.get_start_context(user, device)
  89. order = ConsumeRecord.new_one(
  90. orderNo=ConsumeRecord.make_no(),
  91. user=user,
  92. device=device,
  93. context=context
  94. )
  95. logger.debug("[{} build order] order = {} has been create".format(self.__class__.__name__, order))
  96. return order
  97. class UnifiedConsumeOrderManager(OrderContextManager):
  98. def __exit__(self, exc_type, exc_val, exc_tb):
  99. if exc_type == UnifiedConsumeOrderError or exc_type is None:
  100. return False
  101. else:
  102. logger.exception(exc_tb)
  103. raise UnifiedConsumeOrderError(u"下单异常(00000)")
  104. def get_starter(self):
  105. userInfo = self._source.get("userInfo")
  106. if not userInfo:
  107. raise UnifiedConsumeOrderError(u"用户异常【下单参数异常】,请重新扫码下单(00000)")
  108. user = userInfo.pop("user", MyUser.objects.filter(openId=userInfo["openId"], groupId=userInfo["groupId"]).first()) # type: MyUser
  109. if not user:
  110. raise UnifiedConsumeOrderError(u"用户异常【未找到用户】,请重新扫码下单(00001)")
  111. if not user.isNormal:
  112. raise UnifiedConsumeOrderError(u"用户异常,请重新扫码下单(00002)")
  113. # 检验用户是否有未完成订单 主要是没有付费的后付费的
  114. notCompleteOrder = get_user_not_complete_order(user)
  115. if notCompleteOrder:
  116. raise UnifiedConsumeOrderError(u"当前存在尚未完成订单【{}】,请前往查看".format(notCompleteOrder.orderNo))
  117. return user
  118. def get_start_context(self, user, device): # type:(MyUser, DeviceDict) -> StartParamContext
  119. context = super(UnifiedConsumeOrderManager, self).get_start_context(user, device)
  120. canUse, msg = device.is_port_can_use(context.port)
  121. if not canUse:
  122. raise UnifiedConsumeOrderError(u"启动设备失败【{}】".format(msg))
  123. return context
  124. class UnifiedCardConsumeOrderManager(OrderContextManager):
  125. def get_starter(self):
  126. userInfo = self._source.get("userInfo")
  127. card = Card.objects.filter(id=userInfo['cardId']).first() # type: Card
  128. if not card:
  129. raise UnifiedConsumeOrderError(u"卡异常【未找到卡】,请重新下单(00001)")
  130. if card.frozen:
  131. raise UnifiedConsumeOrderError(u"用户异常【冻结】,请重新下单(00002)")
  132. # 检验用户是否有未完成订单 主要是没有付费的后付费的
  133. notCompleteOrder = get_user_not_complete_order(card)
  134. if notCompleteOrder:
  135. raise UnifiedConsumeOrderError(u"当前存在尚未完成订单【{}】,请前往查看".format(notCompleteOrder.orderNo))
  136. return card
  137. def get_start_context(self, user, device): # type:(MyUser, DeviceDict) -> StartParamContext
  138. context = super(UnifiedCardConsumeOrderManager, self).get_start_context(user, device)
  139. context.cardId = str(user.id)
  140. context.sequence = self._source["startInfo"]["sequenceNo"]
  141. return context
  142. class StartParamContext(object):
  143. def __init__(self):
  144. self._port = None
  145. self._startType = None
  146. self._package = None
  147. self._cardId = None
  148. self._sequence = None
  149. @property
  150. def port(self):
  151. return self._port or 0
  152. @port.setter
  153. def port(self, value):
  154. self._port = int(value)
  155. @property
  156. def startType(self):
  157. return self._startType
  158. @startType.setter
  159. def startType(self, value):
  160. self._startType = value
  161. @property
  162. def package(self): # type:() -> OrderPackage
  163. return self._package
  164. @package.setter
  165. def package(self, value):
  166. self._package = OrderPackage(value)
  167. @property
  168. def cardId(self):
  169. return self._cardId
  170. @cardId.setter
  171. def cardId(self, value):
  172. self._cardId = value
  173. @property
  174. def sequence(self):
  175. return self._sequence
  176. @sequence.setter
  177. def sequence(self, value):
  178. self._sequence = value
  179. class ConsumeOrderStateEngine(object):
  180. """
  181. 订单状态引擎 驱使订单状态的改变
  182. """
  183. def __init__(self, order): # type: (ConsumeRecord) -> None
  184. self._order = order
  185. def to_wait_confirm(self):
  186. """
  187. 订单 待确认状态
  188. """
  189. assert self._order.status == self._order.Status.CREATED
  190. self._order.update(status=self._order.Status.WAIT_CONF)
  191. # 开启订单的超时检查
  192. check_consume_order_timeout(self._order)
  193. def to_running(self, result): # type:(dict) -> None
  194. """
  195. 订单 已经启动 此时的状态一定切换
  196. """
  197. assert self._order.status in [
  198. self._order.Status.CREATED,
  199. self._order.Status.WAIT_CONF,
  200. self._order.Status.UNKNOWN,
  201. self._order.Status.TIMEOUT
  202. ]
  203. # 更新订单的启动时间
  204. serviceInfo = self._order.service
  205. if "deviceStartTime" in result:
  206. serviceInfo.deviceStartTime = result["deviceStartTime"]
  207. else:
  208. serviceInfo.deviceStartTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  209. self._order.update(
  210. status=self._order.Status.RUNNING,
  211. serviceInfo=serviceInfo
  212. )
  213. try:
  214. PublishOrderInfoToUser.order_executing(self._order.user, self._order.device, self._order.reload())
  215. except Exception as e:
  216. logger.exception(e)
  217. def to_end(self, result): # type:(dict) -> None
  218. """
  219. 订单运行结束 由事件驱动
  220. """
  221. assert self._order.status == self._order.Status.RUNNING
  222. # 更新订单的结束事件
  223. serviceInfo = self._order.service.copy()
  224. for kind in result:
  225. if kind not in ConsumeOrderServiceItem.choices():
  226. continue
  227. serviceInfo.update({kind: self._trans_value(result[kind])})
  228. self._order.update(
  229. status=self._order.Status.END,
  230. serviceInfo=serviceInfo
  231. )
  232. try:
  233. PublishOrderInfoToUser.order_finished(self._order.user, self._order.device, self._order.reload())
  234. except Exception as e:
  235. logger.exception("[PublishOrderInfoToUser] error = {}".format(e))
  236. def to_wait_pay(self):
  237. """
  238. 后付费订单的临时状态
  239. """
  240. assert self._order.status == self._order.Status.END
  241. self._order.update(status=self._order.Status.WAIT_PAY)
  242. def to_finished(self):
  243. """
  244. 订单完全结束 设备运行结束才能够结单 同时结单前需要检查支付信息是否已经完成
  245. """
  246. assert self._order.status in [self._order.Status.END, self._order.Status.WAIT_PAY]
  247. # 没有支付过的订单 无法切换到订单的结束状态
  248. if not self._order.isPaid:
  249. logger.warning("[ConsumeOrderStateEngine] to finished error, order is not paid!")
  250. return
  251. self._order.update(
  252. status=self._order.Status.FINISHED
  253. )
  254. state = DealerGroupStats.update_group_stats(
  255. group=self._order.group,
  256. order=self._order
  257. )
  258. if state:
  259. self._order.link_state(state)
  260. def to_failure(self, desc=""):
  261. """
  262. 订单失败 等同于订单结束
  263. """
  264. self._order.update(
  265. status=self._order.Status.FAILURE,
  266. description=desc
  267. )
  268. def to_timeout(self, desc=""):
  269. """
  270. 订单执行超时
  271. """
  272. self._order.update(
  273. status=self._order.Status.TIMEOUT,
  274. description=desc
  275. )
  276. def to_unknown(self, desc=""):
  277. """
  278. 订单状态未知 不进行任何的操作
  279. """
  280. self._order.update(
  281. status=self._order.Status.UNKNOWN,
  282. description=desc
  283. )
  284. @staticmethod
  285. def _trans_value(value):
  286. if isinstance(value, RMB):
  287. return value.mongo_amount
  288. return value
  289. class PublishOrderInfoToUser(object):
  290. @staticmethod
  291. def order_executing(user, device, order):
  292. device.deviceAdapter.notify_service_start_to_user(order, user)
  293. @staticmethod
  294. def order_finished(user, device, order):
  295. device.deviceAdapter.notify_service_end_to_user(order)
  296. def get_user_not_complete_order(user, owner=None):
  297. """
  298. 获取用户未完成的订单 (没有付钱的)
  299. """
  300. # 首先查找状态还在进行中的订单
  301. query = ConsumeRecord.objects.filter(
  302. openId=user.openId,
  303. status__in=[ConsumeRecord.Status.RUNNING, ConsumeRecord.Status.END, ConsumeRecord.Status.WAIT_PAY]
  304. )
  305. if owner:
  306. query = query.filter(ownerId=str(owner.id))
  307. if isinstance(user, Card):
  308. query.filter(cardId=str(user.id))
  309. # 判断订单是否已经被支付
  310. curOrder = None
  311. for order in query: # type: ConsumeRecord
  312. if order.isPaid:
  313. continue
  314. curOrder = order
  315. break
  316. return curOrder
  317. def generate_net_payment(order): # type:(ConsumeRecord) -> dict
  318. """
  319. 生成订单的用户支付扣款相关信息
  320. """
  321. needPayMoney = order.price
  322. user = order.user
  323. share_group_ids = order.owner.get_currency_group_ids(order.group)
  324. chargeBalanceField = user.__class__.chargeBalance.name
  325. bestowBalanceField = user.__class__.bestowBalance.name
  326. # ONLY虽然会加快查询的速度,但是好像也会忽略相应的默认值 需要有一个补漏的措施
  327. currency_users = list(user.__class__.objects.filter(
  328. openId=user.openId,
  329. groupId__in=share_group_ids
  330. ).only(
  331. chargeBalanceField,
  332. bestowBalanceField,
  333. ))
  334. # 始终保证从当前地址开始扣除
  335. currency_users.insert(0, user)
  336. # 添加默认参数 这个地方也可以将deduct单项作为一个object实例化 这样就避免键值不统一
  337. deduct = defaultdict(lambda: {
  338. user.__class__.id.name: "",
  339. chargeBalanceField: RMB(0).mongo_amount,
  340. bestowBalanceField: RMB(0).mongo_amount
  341. })
  342. for _user in currency_users: # type: MyUser
  343. minPay = min(_user.chargeBalance or RMB(0), needPayMoney)
  344. deduct[_user.id].update({
  345. user.__class__.id.name: str(_user.id),
  346. chargeBalanceField: minPay.mongo_amount
  347. })
  348. needPayMoney -= minPay
  349. if needPayMoney == RMB(0):
  350. break
  351. # 充值余额走完 还是没有支付完 就走赠送余额
  352. else:
  353. for __user in currency_users:
  354. minPay = min(__user.bestowBalance or RMB(0), needPayMoney)
  355. deduct[__user.id].update({
  356. bestowBalanceField: minPay.mongo_amount
  357. })
  358. needPayMoney -= minPay
  359. if needPayMoney == RMB(0):
  360. break
  361. else:
  362. raise UserServerException(u"用户支付异常")
  363. return {
  364. "via": "user",
  365. "itemId": str(user.id),
  366. "deduct_list": deduct.values()
  367. }
  368. def generate_card_payment(order): # type:(ConsumeRecord) -> dict
  369. needPayMoney = order.price
  370. card = order.card # type: Card
  371. chargeBalanceField = card.__class__.chargeBalance.name
  372. bestowBalanceField = card.__class__.bestowBalance.name
  373. deduct = defaultdict(lambda: {
  374. card.__class__.id.name: str(card.id),
  375. chargeBalanceField: RMB(0).mongo_amount,
  376. bestowBalanceField: RMB(0).mongo_amount
  377. })
  378. # 首先使用充值余额
  379. minPay = min(card.chargeBalance or RMB(0), needPayMoney)
  380. deduct[str(card.id)].update({
  381. chargeBalanceField: minPay.mongo_amount
  382. })
  383. needPayMoney -= minPay
  384. # 充值余额不足的 剩余的由赠送余额支付
  385. if needPayMoney > RMB(0):
  386. minPay = min(card.bestowBalance or RMB(0), needPayMoney)
  387. deduct[str(card.id)].update({
  388. bestowBalanceField: minPay.mongo_amount
  389. })
  390. needPayMoney -= minPay
  391. if needPayMoney > RMB(0):
  392. raise UserServerException(u"卡支付异常")
  393. return {
  394. "via": "card",
  395. "itemId": str(card.id),
  396. "deduct_list": deduct.values()
  397. }
  398. def generate_refund(order, refundMoney): # type: (ConsumeRecord, RMB) -> dict
  399. payment = order.payment
  400. refund = [
  401. {
  402. "id": _["id"],
  403. "chargeBalance": RMB(0).mongo_amount,
  404. "bestowBalance": RMB(0).mongo_amount
  405. }
  406. for _ in payment.deduct_list
  407. ]
  408. # 首先退还的是赠送余额
  409. for _index, _deduct in enumerate(payment.deduct_list):
  410. minRefund = min(RMB(_deduct["bestowBalance"]), refundMoney)
  411. refund[_index].update({
  412. "bestowBalance": minRefund.mongo_amount,
  413. })
  414. refundMoney -= minRefund
  415. if refundMoney == RMB(0):
  416. break
  417. else:
  418. for __index, __deduct in enumerate(payment.deduct_list):
  419. minRefund = min(RMB(__deduct["chargeBalance"]), refundMoney)
  420. refund[__index].update({
  421. "chargeBalance": minRefund.mongo_amount
  422. })
  423. refundMoney -= minRefund
  424. if refundMoney == RMB(0):
  425. break
  426. else:
  427. raise UserServerException(u"退款异常")
  428. return {
  429. "vid": order.payment.via,
  430. "itemId": order.payment["itemId"],
  431. "deduct_list": refund
  432. }
  433. def check_consume_order_timeout(order):
  434. """
  435. 检测消费订单是否超时
  436. """
  437. # TODO 建立延时任务 如果超过一定时间 直接将订单置为失败
  438. def check_consume_order_pay_timeout(order):
  439. """
  440. 检查消费订单的支付是否超时
  441. """
  442. # TODO 建立延时任务 如果订单支付超时 直接切换成为正常结束状态
  443. def notify_user(managerialOpenId, dealerId, templateName, **kwargs):
  444. try:
  445. if not managerialOpenId or not dealerId:
  446. logger.error('managerialOpenId is null')
  447. return
  448. from taskmanager.mediator import task_caller
  449. task_caller('send_msg_to_user_via_wechat', openId = managerialOpenId, dealerId = dealerId, templateName = templateName, **kwargs)
  450. except Exception as e:
  451. logger.exception(e)
  452. def get_paginate(data, pageSize, pageIndex): # type:(list, int, int) -> int
  453. if len(data) == pageSize*pageIndex:
  454. return pageSize * pageIndex + 1
  455. return (pageIndex-1) * pageSize + len(data)