123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492 |
- # coding=utf-8
- import datetime
- import logging
- from arrow import Arrow
- from django.conf import settings
- from typing import TYPE_CHECKING, Iterable
- from apilib.monetary import VirtualCoin, RMB
- from apps.web.common.models import TempValues
- from apps.web.constant import START_DEVICE_STATUS, DEALER_CONSUMPTION_AGG_KIND, Const
- from apps.web.core.device_define.kehang import BillingType, REASON_MAP, CARD_STATUS, CARD_OPT
- from apps.web.device.models import Device, Group
- from apps.web.eventer import EventBuilder
- from apps.web.eventer.base import ComNetPayAckEvent, IdStartAckEvent, AckEventProcessorIntf, WorkEvent
- from apps.web.user.models import Card, ServiceProgress, CardRechargeOrder, CardRechargeRecord
- from apps.web.user.utils import clear_frozen_user_balance
- from apps.web.utils import set_start_key_status
- if TYPE_CHECKING:
- from apps.web.user.models import ConsumeRecord
- logger = logging.getLogger(__name__)
- class builder(EventBuilder):
- def __getEvent__(self, device_event):
- if 'order_id' in device_event:
- if device_event["order_type"] == "com_start":
- return MyComNetPayAckEvent(self.deviceAdapter, device_event)
- elif device_event["order_type"] == "id_start":
- return IDPayAckEvent(self.deviceAdapter, device_event)
- event_data = self.deviceAdapter.analyze_event_data(device_event['data'])
- if event_data is None or 'cmdCode' not in event_data:
- return None
- if event_data["cmdCode"] in ["22", "11", "12", "16"]:
- return KeHangWorkEvent(self.deviceAdapter, event_data)
- class KeHangWorkEvent(WorkEvent):
- def _do_query_card(self):
- logger.info("device <{}> do query id card balance, event = {}".format(self.device.devNo, self.event_data))
- cardNo = self.event_data["cardNo"]
- card = self.update_card_dealer_and_type(cardNo) # type: Card
- # 非法卡 (经销商绑卡会有 openId = any......)
- if not card or not card.openId:
- logger.info("device <{}> receive query card, error card = <{}>".format(self.device.devNo, cardNo))
- return self.deviceAdapter._response_card(int(cardNo), CARD_STATUS.ILLEGAL, float(VirtualCoin(0)), openId="")
- # 冻结卡
- if card.frozen:
- logger.info("device <{}> receive query card, frozen card = <{}>".format(self.device.devNo, cardNo))
- return self.deviceAdapter._response_card(int(cardNo), CARD_STATUS.FROZEN, float(VirtualCoin(card.balance)) * 10, openId=str(card.id))
- # 正常的卡余额回复 不需要管余额是否足够 交由模块判断
- logger.info("device <{}> receive query card success, card = <{}>, balance = <{}>".format(self.device.devNo, cardNo, card.balance))
- return self.deviceAdapter._response_card(int(cardNo), CARD_STATUS.SUCCESS, float(VirtualCoin(card.balance)) * 10, openId=str(card.id))
- def _do_ic_card(self):
- logger.info("device <{}> do card start event, event = {}".format(self.device.devNo, self.event_data))
- cardNo = self.event_data["cardNo"]
- card = self.update_card_dealer_and_type(cardNo, "IC")
- # 卡不存在的地方 直接退出
- if not card:
- return
- # 卡存在的情况下 分为退费 和 扣费两种
- if self.event_data["opt"] == CARD_OPT.DEDUCT:
- orderNo, cardOrderNo = self.record_consume_for_card(card, RMB(self.event_data["cost"]), desc=u"离线卡启动")
- consumeDict = {
- "orderNo": "orderNo",
- "cardOrderNo": cardOrderNo
- }
- ServiceProgress.register_card_service(
- self.device,
- int(self.event_data["port"]),
- card,
- consumeDict
- )
- Device.update_dev_control_cache(self.device.devNo, {str(self.event_data["port"]): {"status": Const.DEV_WORK_STATUS_WORKING}})
- else:
- self.record_refund_money_for_card(RMB(self.event_data["cost"]), str(card.id))
- Device.clear_port_control_cache(self.device.devNo, str(self.event_data["port"]))
- def _do_ic_card_sync_balance(self):
- logger.info("device <{}> do ic card sync balance, event = {}".format(self.device.devNo, self.event_data))
- cardNo = self.event_data["cardNo"]
- cardType = self.event_data["type"]
- card = self.update_card_dealer_and_type(cardNo, "IC")
- if not card:
- logger.info("device <{}> do ic card <{}> sync balance, card not find".format(self.device.devNo, cardNo))
- return
- money, coins, orderNos, cardOrderNos = CardRechargeOrder.get_to_do_list(str(card.id))
- if not orderNos:
- logger.info("device <{}> do ic card <{}> sync balance, charge orders not find".format(self.device.devNo, cardNo))
- return
- # 订单号保存到缓存里
- TempValues.set('%s-%s' % (self.device.devNo, cardNo), value=cardOrderNos)
- asyncMoney = (RMB(self.event_data["balance"]) + RMB(coins)) * 10
- try:
- CardRechargeOrder.update_card_order_has_finished(str(card.id))
- except Exception as e:
- logger.exception('%s' % e)
- else:
- self.deviceAdapter._response_sync_card_balance(cardNo, asyncMoney)
- def _do_ic_card_sync_response(self):
- logger.info("device <{}> do ic card sync balance response, event = {}".format(self.device.devNo, self.event_data))
- cardNo = self.event_data["cardNo"]
- card = self.update_card_dealer_and_type(cardNo, "IC")
- balance = self.event_data["balance"]
- success = self.event_data["success"]
- if not success:
- logger.info("device <{}> do ic card sync balance response not success, event = {}".format(self.device.devNo, self.event_data))
- return
- cardOrderNos = TempValues.get('%s-%s' % (self.device.devNo, cardNo))
- cardOrders = CardRechargeOrder.objects.filter(orderNo__in=cardOrderNos)
- for _order in cardOrders: # type: CardRechargeOrder
- _order.update_after_recharge_ic_card(
- device=self.device,
- sendMoney=RMB(_order.coins),
- preBalance=card.balance
- )
- preBalance = card.balance
- card.balance = card.balance + _order.coins
- # 创建充值记录
- CardRechargeRecord.add_record(
- card=card,
- group=Group.get_group(_order.groupId),
- order=_order,
- device=self.device
- )
- self.update_card_balance(card, RMB(balance))
- TempValues.remove('%s-%s' % (self.device.devNo, cardNo))
- def do(self, **args):
- if self.event_data["cmdCode"] == "22":
- return self._do_query_card()
- if self.event_data["cmdCode"] == "11":
- return self._do_ic_card()
- if self.event_data["cmdCode"] == "12":
- return self._do_ic_card_sync_balance()
- if self.event_data["cmdCode"] == "16":
- return self._do_ic_card_sync_response()
- class MyComNetPayAckEvent(ComNetPayAckEvent):
- def do_running_order(self, order, result): # type: (ConsumeRecord, dict) -> None
- """
- 处理运行订单
- :param order: 用户服务器订单
- :param result: device_event 设备侧订单
- :return:
- """
- # 订单消息已经被回复过
- if order.status in ["running", "finished"]:
- logger.debug('order<{}> no need to deal. this has done.'.format(repr(order)))
- return
- # 启动设备的时候 设备实际启动成功 但是订单串口超时 不知道订单的明确状态 后面启动时间又重新上报
- if order.status == "unknown":
- errorDesc = u"设备信号恢复,订单正常运行"
- logger.info("order <{}> timeout to running")
- # 正常运行的订单
- else:
- errorDesc = u""
- if 'master' in result:
- order.association = {
- 'master': result['master']
- }
- order.servicedInfo.update({'masterOrderNo': result['master']})
- order.errorDesc = errorDesc
- order.isNormal = True
- order.status = 'running'
- order.startTime = datetime.datetime.fromtimestamp(result['sts'])
- order.save()
- set_start_key_status(start_key=order.startKey, state=START_DEVICE_STATUS.FINISHED, order_id=str(order.id))
- def do_finished_order(self, order, result): # type: (ConsumeRecord, dict) -> None
- """
- 处理结束运行订单
- :param order: 用户服务器订单
- :param result: device_event 设备侧订单
- :return:
- """
- portCache = Device.get_port_control_cache(self.device.devNo, str(order.used_port))
- self.event_data["portCache"] = portCache
- # 子单归并主单
- if 'sub' in result:
- order.association = {
- 'sub': [item['order_id'] for item in self.event_data['sub']]
- }
- order.servicedInfo.update(
- {'subOrderNo': '{}'.format(', '.join([item['order_id'] for item in self.event_data['sub']]))})
- # 主单归并自身
- elif 'master' in result:
- order.association = {
- 'master': result['master']
- }
- order.servicedInfo.update({'masterOrderNo': result['master']})
- # 此时理论上服务器订单状态有三种可能(finished在上层已经被排除)
- # 正常的状态 相当于订单由运行状态 即将切换为finished状态
- if order.status == "running":
- order.isNormal = True
- order.status = "finished"
- order.errorDesc = u""
- order.finishedTime = datetime.datetime.fromtimestamp(result['fts'])
- # 非正常状态 相当于订单最开始串口超时 然后直接变为结束
- elif order.status == "unknown":
- order.isNormal = True
- order.status = "finished"
- order.errorDesc = u"设备信号恢复,订单正常结束(0001)"
- order.startTime = datetime.datetime.fromtimestamp(result['sts'])
- order.finishedTime = datetime.datetime.fromtimestamp(result['fts'])
- # 正常状态 相当于订单启动失败或者是中间running单没有上来
- elif order.status == "created":
- order.isNormal = True
- order.status = "finished"
- order.errorDesc = u"设备信号恢复,订单正常结束(0002)"
- order.startTime = datetime.datetime.fromtimestamp(result['sts'])
- order.finishedTime = datetime.datetime.fromtimestamp(result['fts'])
- else:
- logger.warning('order<{}> status = <{}> to finished. no deal with'.format(repr(order), order.status))
- order.save()
- set_start_key_status(start_key=order.startKey, state=START_DEVICE_STATUS.FINISHED, order_id=str(order.id))
- def do_finished_event(self, order, sub_orders, merge_order_info): # type:(ConsumeRecord, list, dict) -> None
- """
- 订单的状态已经完成 进一步事件 扣费等等
- :param order: 处理完毕的订单(主订单)
- :param sub_orders: 子订单
- :param merge_order_info: 合并单的信息
- :return:
- """
- order.reload()
- # 解析event的参数 这个left是转换后的时间
- """
- 比如1元240分钟,充电后变为120分钟,一分钟后119,然后,过一会进入下一档,80分钟,这时候如果停止,我会把80分钟转换为230多分钟给你
- """
- left = self.event_data["left"]
- # 获取端口的缓存
- portCache = self.event_data["portCache"]
- need = portCache["needValue"]
- coins = portCache["coins"]
- billingType = portCache["billingType"]
- # need 和 left 单位始终相同 所以不会造成退费金额出错
- backCoins = VirtualCoin(coins) * ((left * 1.0) / need)
- if not self.device.is_auto_refund:
- backCoins = VirtualCoin(0)
- else:
- backCoins = min(VirtualCoin(coins), backCoins)
- # 算电量 算时间
- if billingType == BillingType.TIME:
- duration = need - left
- spendElec = 0
- extra = [
- {u"本次订购时长": "{}分钟".format(need)},
- {u"本次实际使用时长": "{}分钟".format(need-left)},
- {u"消费金额": "{}(金币)".format((VirtualCoin(coins) - VirtualCoin(backCoins)).amount)}
- ]
- else:
- # 电量模式下 时间有可能不太准 考虑断电的情况
- duration = (self.event_data["fts"] - self.event_data["sts"]) / 60
- spendElec = (need - left) / 100.0
- extra = [
- {u"本次订购电量": "{} 度".format(need/100.0)},
- {u"消费金额": "{}(金币)".format((VirtualCoin(coins) - VirtualCoin(backCoins)).amount)}
- ]
- clear_frozen_user_balance(self.device, order, duration, spendElec=spendElec, backCoins=backCoins, user=order.user)
- # 组织消费信息
- consumeDict = {
- "reason": self._get_finish_reason(),
- "billingType": u"时间计费" if billingType == BillingType.TIME else u"电量计费",
- "leftTime": left,
- DEALER_CONSUMPTION_AGG_KIND.DURATION: duration,
- DEALER_CONSUMPTION_AGG_KIND.SPEND_MONEY: (VirtualCoin(coins) - VirtualCoin(backCoins)).mongo_amount,
- DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: str(backCoins)
- }
- spendElec and consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.ELEC: spendElec})
- spendElec and consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.ELECFEE: self.calc_elec_fee(spendElec)})
- order.update_service_info(consumeDict)
- self.notify_user_service_complete(
- service_name='充电',
- openid=order.user.managerialOpenId,
- port=str(order.used_port),
- address=order.address,
- reason=consumeDict["reason"],
- finished_time=order.finishedTime.strftime('%Y-%m-%d %H:%M:%S'),
- extra=extra
- )
- def merge_order(self, master_order, sub_orders): # type:(ConsumeRecord, list)->dict
- """
- 主板暂时不支持续充 如果续充的话 时间不准
- :param master_order:
- :param sub_orders:
- :return:
- """
- start_time = Arrow.fromdatetime(master_order.startTime, tzinfo=settings.TIME_ZONE)
- need = self.event_data["charge_param"]
- billingType = self.event_data.get("billing_type", BillingType.TIME)
- coins = master_order.package["coins"]
- price = master_order.package["price"]
- portCache = {
- "openId": master_order.openId,
- "consumeType": "mobile",
- "needKind": "needTime" if billingType == BillingType.TIME else "needElec",
- "estimatedTs": int(start_time.timestamp + 720 * 60 * 60),
- "needValue": need,
- "billingType": billingType
- }
- for _sub in sub_orders:
- coins += _sub.package["coins"]
- price += _sub.package["price"]
- portCache["coins"] = coins
- portCache["price"] = price
- if billingType == BillingType.TIME:
- master_order.update_service_info({'needTime': need})
- else:
- master_order.update_service_info({'needElec': need})
- return portCache
- def _get_finish_reason(self):
- if "reason" in self.event_data:
- return REASON_MAP.get(self.event_data["reason"], u"未知原因")
- return u"未知原因"
- class IDPayProcessor(AckEventProcessorIntf):
- def pre_processing(self, device, event_data):
- event_data["cardNo"] = str(event_data.pop("card", ""))
- event_data["fee"] = event_data["cost"] / 10.0
- if "sub" in event_data and isinstance(event_data["sub"], list):
- new_sub = list()
- for _item in event_data["sub"]:
- _item["cardNo"] = _item.pop("card")
- _item["fee"] = _item["cost"] / 10.0
- new_sub.append(_item)
- event_data["sub"] = new_sub
- return event_data
- class IDPayAckEvent(IdStartAckEvent):
- """
- 端口
- 查询卡
- """
- def __init__(self, smartBox, event_data, pre_processor=None):
- super(IDPayAckEvent, self).__init__(smartBox, event_data, IDPayProcessor())
- def _get_finish_reason(self):
- if "reason" in self.event_data:
- return REASON_MAP.get(self.event_data["reason"], u"未知原因")
- return u"未知原因"
- def post_after_start(self, order=None):
- pass
- def post_after_finish(self, order=None):
- pass
- def checkout_order(self, order):
- fee = VirtualCoin(order.coin)
- self.card.freeze_balance(transaction_id=str(order.id), fee=fee)
- def do_finished_event(self, order, merge_order_info): # type:(ConsumeRecord, dict)->None
- left = self.event_data["left"]
- billingType = self.event_data["billing_type"]
- if self.device.is_auto_refund:
- backCoins = min(VirtualCoin(self.event_data["refund"]), VirtualCoin(merge_order_info["coins"]))
- else:
- backCoins = VirtualCoin(0)
- consumeCoins = VirtualCoin(merge_order_info["coins"]) - backCoins
- self.card.clear_frozen_balance(str(order.id), backCoins)
- self.card.reload()
- extra = [
- {u"消费金额": "{}(金币)".format(consumeCoins.amount)},
- {u"卡内余额": "{} (金币)".format(self.card.balance.mongo_amount)}
- ]
- consumeDict = {
- 'reason': self._get_finish_reason(),
- 'chargeIndex': str(order.used_port),
- 'cardNo': self.event_data['cardNo'],
- "left": left,
- "billingType": billingType
- }
- if backCoins > VirtualCoin(0):
- self.record_refund_money_for_card(backCoins, str(self.card.id), orderNo=order.orderNo)
- consumeDict.update({
- DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: backCoins.mongo_amount
- })
- order.update_service_info(consumeDict)
- self.notify_user_service_complete(
- service_name='充电',
- openid=self.card.managerialOpenId,
- port=str(order.used_port),
- address=order.address,
- reason=self.event_data.get('reasonDesc'),
- finished_time=order.finishedTime.strftime('%Y-%m-%d %H:%M:%S'),
- extra=extra
- )
- def merge_order(self, master_order, sub_orders): # type:(ConsumeRecord, Iterable[ConsumeRecord])->dict
- """
- 刷卡的退费是由主板决定的 金币以及钱只是为了展示
- """
- start_time = Arrow.fromdatetime(master_order.startTime, tzinfo=settings.TIME_ZONE)
- billingType = self.event_data.get("billing_type", BillingType.TIME)
- coins = master_order.coin
- price = master_order.money
- portCache = {
- "openId": master_order.openId,
- "consumeType": "card",
- "estimatedTs": int(start_time.timestamp + 720 * 60 * 60),
- "billingType": billingType
- }
- for _sub in sub_orders:
- coins += _sub.coin
- price += _sub.money
- portCache["coins"] = str(coins)
- portCache["price"] = str(price)
- return portCache
|