123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579 |
- # coding=utf-8
- # noinspection PyUnresolvedReferences
- import datetime
- import logging
- from collections import defaultdict
- from typing import TYPE_CHECKING, Optional
- from UserDict import UserDict
- from contextlib2 import AbstractContextManager
- from apilib.monetary import RMB
- from apps.web.core.exceptions import ServiceException
- from apps.web.dealer.proxy import DealerGroupStats
- from apps.web.device.models import Device
- from apps.web.exceptions import UnifiedConsumeOrderError, UserServerException
- from apps.web.user.constant2 import StartDeviceType, ConsumeOrderServiceItem
- from apps.web.user.models import MyUser, ConsumeRecord, OrderPackage, Card
- if TYPE_CHECKING:
- from apps.web.device.models import DeviceDict, GroupDict
- logger = logging.getLogger(__name__)
- class OrderContext(object, UserDict):
-
- def __init__(self):
- super(OrderContext, self).__init__()
- @property
- def client(self):
- # noinspection PyUnresolvedReferences
- return self._client
- @property
- def terminal(self): # type:() -> Optional[DeviceDict, GroupDict]
- # noinspection PyUnresolvedReferences
- return self._device or self._group
- @property
- def user(self):
- # noinspection PyUnresolvedReferences
- return self._user
- @property
- def goods(self): # type:() -> Goods
- # noinspection PyUnresolvedReferences
- return self._goods
- class OrderContextManager(AbstractContextManager):
- def __init__(self, **kwargs):
- self._source = kwargs
- def __enter__(self):
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- super(OrderContextManager, self).__exit__(exc_type, exc_val, exc_tb)
- def get_starter(self):
- """检查启动实体"""
- userInfo = self._source.get("userInfo")
- if not userInfo:
- raise UnifiedConsumeOrderError(u"用户异常【下单参数异常】,请重新扫码下单(00000)")
- user = userInfo.pop("user", MyUser.objects.filter(openId=userInfo["openId"], groupId=userInfo["groupId"]).first()) # type: MyUser
- return user
- def get_device(self):
- """检查启动设备"""
- devInfo = self._source.get("devInfo")
- if not devInfo:
- raise UnifiedConsumeOrderError(u"设备异常【下单参数异常】,请重新扫码下单(10000)")
- device = Device.get_dev_by_l(devInfo.get("logicalCode")) # type: DeviceDict
- if not device:
- raise UnifiedConsumeOrderError(u"设备异常,请重新扫码下单(10001)")
- if not device.online:
- raise UnifiedConsumeOrderError(u"设备异常【离线】,请重新扫码下单(10002)")
- if not device.is_registered:
- raise UnifiedConsumeOrderError(u"设备异常【未注册】,请重新扫码下单(10003)")
- if device.is_fault:
- raise UnifiedConsumeOrderError(u"设备异常【故障】,请重新扫码下单(10004)")
- return device
- def get_start_context(self, user, device):
- """检查启动其余环境"""
- startInfo = self._source.get("startInfo")
- context = StartParamContext()
- packageId, startType, port \
- = startInfo["packageId"], startInfo["startType"], startInfo["port"]
- if startType not in [StartDeviceType.ON_LIEN, StartDeviceType.CARD]:
- raise UnifiedConsumeOrderError(u"启动参数异常【方式异常】,请重新扫码下单(20001)")
- context.startType = startType
- try:
- package = device.deviceAdapter.prepare_package(packageId, startInfo, startType)
- except ServiceException as se:
- raise UnifiedConsumeOrderError(se.result["description"])
- if not package:
- raise UnifiedConsumeOrderError(u"启动参数异常【套餐异常】,请重新扫码下单(20000)")
- context.package = package
- context.port = port
- return context
- def buildOrder(self):
- user = self.get_starter()
- device = self.get_device()
- context = self.get_start_context(user, device)
- order = ConsumeRecord.new_one(
- orderNo=ConsumeRecord.make_no(),
- user=user,
- device=device,
- context=context
- )
- logger.debug("[{} build order] order = {} has been create".format(self.__class__.__name__, order))
- return order
- class UnifiedConsumeOrderManager(OrderContextManager):
- def __exit__(self, exc_type, exc_val, exc_tb):
- if exc_type == UnifiedConsumeOrderError or exc_type is None:
- return False
- else:
- logger.exception(exc_tb)
- raise UnifiedConsumeOrderError(u"下单异常(00000)")
- def get_starter(self):
- userInfo = self._source.get("userInfo")
- if not userInfo:
- raise UnifiedConsumeOrderError(u"用户异常【下单参数异常】,请重新扫码下单(00000)")
- user = userInfo.pop("user", MyUser.objects.filter(openId=userInfo["openId"], groupId=userInfo["groupId"]).first()) # type: MyUser
- if not user:
- raise UnifiedConsumeOrderError(u"用户异常【未找到用户】,请重新扫码下单(00001)")
- if not user.isNormal:
- raise UnifiedConsumeOrderError(u"用户异常,请重新扫码下单(00002)")
- # 检验用户是否有未完成订单 主要是没有付费的后付费的
- notCompleteOrder = get_user_not_complete_order(user)
- if notCompleteOrder:
- raise UnifiedConsumeOrderError(u"当前存在尚未完成订单【{}】,请前往查看".format(notCompleteOrder.orderNo))
- return user
- def get_start_context(self, user, device): # type:(MyUser, DeviceDict) -> StartParamContext
- context = super(UnifiedConsumeOrderManager, self).get_start_context(user, device)
- canUse, msg = device.is_port_can_use(context.port)
- if not canUse:
- raise UnifiedConsumeOrderError(u"启动设备失败【{}】".format(msg))
- return context
- class UnifiedCardConsumeOrderManager(OrderContextManager):
- def get_starter(self):
- userInfo = self._source.get("userInfo")
- card = Card.objects.filter(id=userInfo['cardId']).first() # type: Card
- if not card:
- raise UnifiedConsumeOrderError(u"卡异常【未找到卡】,请重新下单(00001)")
- if card.frozen:
- raise UnifiedConsumeOrderError(u"用户异常【冻结】,请重新下单(00002)")
- # 检验用户是否有未完成订单 主要是没有付费的后付费的
- notCompleteOrder = get_user_not_complete_order(card)
- if notCompleteOrder:
- raise UnifiedConsumeOrderError(u"当前存在尚未完成订单【{}】,请前往查看".format(notCompleteOrder.orderNo))
- return card
- def get_start_context(self, user, device): # type:(MyUser, DeviceDict) -> StartParamContext
- context = super(UnifiedCardConsumeOrderManager, self).get_start_context(user, device)
- context.cardId = str(user.id)
- context.sequence = self._source["startInfo"]["sequenceNo"]
- return context
- class StartParamContext(object):
- def __init__(self):
- self._port = None
- self._startType = None
- self._package = None
- self._cardId = None
- self._sequence = None
- @property
- def port(self):
- return self._port or 0
- @port.setter
- def port(self, value):
- self._port = int(value)
- @property
- def startType(self):
- return self._startType
- @startType.setter
- def startType(self, value):
- self._startType = value
- @property
- def package(self): # type:() -> OrderPackage
- return self._package
- @package.setter
- def package(self, value):
- self._package = OrderPackage(value)
- @property
- def cardId(self):
- return self._cardId
- @cardId.setter
- def cardId(self, value):
- self._cardId = value
- @property
- def sequence(self):
- return self._sequence
- @sequence.setter
- def sequence(self, value):
- self._sequence = value
- class ConsumeOrderStateEngine(object):
- """
- 订单状态引擎 驱使订单状态的改变
- """
- def __init__(self, order): # type: (ConsumeRecord) -> None
- self._order = order
- def to_wait_confirm(self):
- """
- 订单 待确认状态
- """
- assert self._order.status == self._order.Status.CREATED
- self._order.update(status=self._order.Status.WAIT_CONF)
- # 开启订单的超时检查
- check_consume_order_timeout(self._order)
- def to_running(self, result): # type:(dict) -> None
- """
- 订单 已经启动 此时的状态一定切换
- """
- assert self._order.status in [
- self._order.Status.CREATED,
- self._order.Status.WAIT_CONF,
- self._order.Status.UNKNOWN,
- self._order.Status.TIMEOUT
- ]
- # 更新订单的启动时间
- serviceInfo = self._order.service
- if "deviceStartTime" in result:
- serviceInfo.deviceStartTime = result["deviceStartTime"]
- else:
- serviceInfo.deviceStartTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
- self._order.update(
- status=self._order.Status.RUNNING,
- serviceInfo=serviceInfo
- )
- try:
- PublishOrderInfoToUser.order_executing(self._order.user, self._order.device, self._order.reload())
- except Exception as e:
- logger.exception(e)
- def to_end(self, result): # type:(dict) -> None
- """
- 订单运行结束 由事件驱动
- """
- assert self._order.status == self._order.Status.RUNNING
- # 更新订单的结束事件
- serviceInfo = self._order.service.copy()
- for kind in result:
- if kind not in ConsumeOrderServiceItem.choices():
- continue
- serviceInfo.update({kind: self._trans_value(result[kind])})
- self._order.update(
- status=self._order.Status.END,
- serviceInfo=serviceInfo
- )
- try:
- PublishOrderInfoToUser.order_finished(self._order.user, self._order.device, self._order.reload())
- except Exception as e:
- logger.exception("[PublishOrderInfoToUser] error = {}".format(e))
- def to_wait_pay(self):
- """
- 后付费订单的临时状态
- """
- assert self._order.status == self._order.Status.END
- self._order.update(status=self._order.Status.WAIT_PAY)
- def to_finished(self):
- """
- 订单完全结束 设备运行结束才能够结单 同时结单前需要检查支付信息是否已经完成
- """
- assert self._order.status in [self._order.Status.END, self._order.Status.WAIT_PAY]
- # 没有支付过的订单 无法切换到订单的结束状态
- if not self._order.isPaid:
- logger.warning("[ConsumeOrderStateEngine] to finished error, order is not paid!")
- return
- self._order.update(
- status=self._order.Status.FINISHED
- )
- state = DealerGroupStats.update_group_stats(
- group=self._order.group,
- order=self._order
- )
- if state:
- self._order.link_state(state)
- def to_failure(self, desc=""):
- """
- 订单失败 等同于订单结束
- """
- self._order.update(
- status=self._order.Status.FAILURE,
- description=desc
- )
- def to_timeout(self, desc=""):
- """
- 订单执行超时
- """
- self._order.update(
- status=self._order.Status.TIMEOUT,
- description=desc
- )
- def to_unknown(self, desc=""):
- """
- 订单状态未知 不进行任何的操作
- """
- self._order.update(
- status=self._order.Status.UNKNOWN,
- description=desc
- )
- @staticmethod
- def _trans_value(value):
- if isinstance(value, RMB):
- return value.mongo_amount
- return value
- class PublishOrderInfoToUser(object):
- @staticmethod
- def order_executing(user, device, order):
- device.deviceAdapter.notify_service_start_to_user(order, user)
- @staticmethod
- def order_finished(user, device, order):
- device.deviceAdapter.notify_service_end_to_user(order)
- def get_user_not_complete_order(user, owner=None):
- """
- 获取用户未完成的订单 (没有付钱的)
- """
- # 首先查找状态还在进行中的订单
- query = ConsumeRecord.objects.filter(
- openId=user.openId,
- status__in=[ConsumeRecord.Status.RUNNING, ConsumeRecord.Status.END, ConsumeRecord.Status.WAIT_PAY]
- )
- if owner:
- query = query.filter(ownerId=str(owner.id))
- if isinstance(user, Card):
- query.filter(cardId=str(user.id))
- # 判断订单是否已经被支付
- curOrder = None
- for order in query: # type: ConsumeRecord
- if order.isPaid:
- continue
- curOrder = order
- break
- return curOrder
- def generate_net_payment(order): # type:(ConsumeRecord) -> dict
- """
- 生成订单的用户支付扣款相关信息
- """
- needPayMoney = order.price
- user = order.user
- share_group_ids = order.owner.get_currency_group_ids(order.group)
- chargeBalanceField = user.__class__.chargeBalance.name
- bestowBalanceField = user.__class__.bestowBalance.name
- # ONLY虽然会加快查询的速度,但是好像也会忽略相应的默认值 需要有一个补漏的措施
- currency_users = list(user.__class__.objects.filter(
- openId=user.openId,
- groupId__in=share_group_ids
- ).only(
- chargeBalanceField,
- bestowBalanceField,
- ))
- # 始终保证从当前地址开始扣除
- currency_users.insert(0, user)
- # 添加默认参数 这个地方也可以将deduct单项作为一个object实例化 这样就避免键值不统一
- deduct = defaultdict(lambda: {
- user.__class__.id.name: "",
- chargeBalanceField: RMB(0).mongo_amount,
- bestowBalanceField: RMB(0).mongo_amount
- })
- for _user in currency_users: # type: MyUser
- minPay = min(_user.chargeBalance or RMB(0), needPayMoney)
- deduct[_user.id].update({
- user.__class__.id.name: str(_user.id),
- chargeBalanceField: minPay.mongo_amount
- })
- needPayMoney -= minPay
- if needPayMoney == RMB(0):
- break
- # 充值余额走完 还是没有支付完 就走赠送余额
- else:
- for __user in currency_users:
- minPay = min(__user.bestowBalance or RMB(0), needPayMoney)
- deduct[__user.id].update({
- bestowBalanceField: minPay.mongo_amount
- })
- needPayMoney -= minPay
- if needPayMoney == RMB(0):
- break
- else:
- raise UserServerException(u"用户支付异常")
- return {
- "via": "user",
- "itemId": str(user.id),
- "deduct_list": deduct.values()
- }
- def generate_card_payment(order): # type:(ConsumeRecord) -> dict
- needPayMoney = order.price
- card = order.card # type: Card
- chargeBalanceField = card.__class__.chargeBalance.name
- bestowBalanceField = card.__class__.bestowBalance.name
- deduct = defaultdict(lambda: {
- card.__class__.id.name: str(card.id),
- chargeBalanceField: RMB(0).mongo_amount,
- bestowBalanceField: RMB(0).mongo_amount
- })
- # 首先使用充值余额
- minPay = min(card.chargeBalance or RMB(0), needPayMoney)
- deduct[str(card.id)].update({
- chargeBalanceField: minPay.mongo_amount
- })
- needPayMoney -= minPay
- # 充值余额不足的 剩余的由赠送余额支付
- if needPayMoney > RMB(0):
- minPay = min(card.bestowBalance or RMB(0), needPayMoney)
- deduct[str(card.id)].update({
- bestowBalanceField: minPay.mongo_amount
- })
- needPayMoney -= minPay
- if needPayMoney > RMB(0):
- raise UserServerException(u"卡支付异常")
- return {
- "via": "card",
- "itemId": str(card.id),
- "deduct_list": deduct.values()
- }
- def generate_refund(order, refundMoney): # type: (ConsumeRecord, RMB) -> dict
- payment = order.payment
- refund = [
- {
- "id": _["id"],
- "chargeBalance": RMB(0).mongo_amount,
- "bestowBalance": RMB(0).mongo_amount
- }
- for _ in payment.deduct_list
- ]
- # 首先退还的是赠送余额
- for _index, _deduct in enumerate(payment.deduct_list):
- minRefund = min(RMB(_deduct["bestowBalance"]), refundMoney)
- refund[_index].update({
- "bestowBalance": minRefund.mongo_amount,
- })
- refundMoney -= minRefund
- if refundMoney == RMB(0):
- break
- else:
- for __index, __deduct in enumerate(payment.deduct_list):
- minRefund = min(RMB(__deduct["chargeBalance"]), refundMoney)
- refund[__index].update({
- "chargeBalance": minRefund.mongo_amount
- })
- refundMoney -= minRefund
- if refundMoney == RMB(0):
- break
- else:
- raise UserServerException(u"退款异常")
- return {
- "vid": order.payment.via,
- "itemId": order.payment["itemId"],
- "deduct_list": refund
- }
- def check_consume_order_timeout(order):
- """
- 检测消费订单是否超时
- """
- # TODO 建立延时任务 如果超过一定时间 直接将订单置为失败
- def check_consume_order_pay_timeout(order):
- """
- 检查消费订单的支付是否超时
- """
- # TODO 建立延时任务 如果订单支付超时 直接切换成为正常结束状态
- def notify_user(managerialOpenId, dealerId, templateName, **kwargs):
- try:
- if not managerialOpenId or not dealerId:
- logger.error('managerialOpenId is null')
- return
- from taskmanager.mediator import task_caller
- task_caller('send_msg_to_user_via_wechat', openId = managerialOpenId, dealerId = dealerId, templateName = templateName, **kwargs)
- except Exception as e:
- logger.exception(e)
- def get_paginate(data, pageSize, pageIndex): # type:(list, int, int) -> int
- if len(data) == pageSize*pageIndex:
- return pageSize * pageIndex + 1
- return (pageIndex-1) * pageSize + len(data)
|