weifuleCommon.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import time
  5. from apilib.monetary import RMB
  6. from apps.web.common.proxy import ClientConsumeModelProxy
  7. from apps.web.constant import Const, MQTT_TIMEOUT, DeviceCmdCode, CONSUMETYPE
  8. from apps.web.core.adapter.base import SmartBox
  9. from apps.web.core.adapter.policy_common import PolicyPackageInit
  10. from apps.web.core.exceptions import ServiceException
  11. from apps.web.core.networking import MessageSender
  12. from apps.web.device.models import Device
  13. from apps.web.user.constant2 import StartDeviceType
  14. from apps.web.user.models import ConsumeRecord, MyUser
  15. class PolicyBase(PolicyPackageInit, SmartBox):
  16. @property
  17. def look_at_money(self):
  18. return self.device.devTypeFeatures.get('look_at_money', False)
  19. def send_mqtt(self, data, cmd=DeviceCmdCode.OPERATE_DEV_SYNC, timeout=10):
  20. """
  21. 发送mqtt 指令默认210 返回data
  22. """
  23. payload = {'IMEI': self.device.devNo, 'data': data}
  24. result = MessageSender.send(
  25. self.device, cmd,
  26. payload=payload, timeout=timeout
  27. )
  28. self.check_feedback_result(result)
  29. if cmd in [DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, DeviceCmdCode.PASSTHROUGH_OPERATE_DEV_NO_RESPONSE]:
  30. return
  31. if result.get('data') == '00':
  32. raise ServiceException({'result': 2, 'description': u'设备操作失败.请重试'})
  33. else:
  34. return result
  35. def check_dev_status(self, attachParas=None):
  36. port = int(attachParas['chargeIndex'])
  37. openId = attachParas['openId']
  38. port_stat = self.get_port_info(port)
  39. status = port_stat.get('status')
  40. last_openId = port_stat.get('openId')
  41. if status == 'busy':
  42. if openId and self.device.devType.get('payableWhileBusy'):
  43. if openId != last_openId:
  44. raise ServiceException({'result': 2, 'description': u'当前充电的设备不是您的设备哦!无法进行续充'})
  45. else:
  46. if 'cardNo' in port_stat:
  47. raise ServiceException({'result': 2, 'description': u'刷卡启动的订单,无法扫码续充哦'})
  48. else:
  49. raise ServiceException({'result': 2, 'description': u'当前端口处于充电状态, 请确认是否为您选择的充电端口哦!'})
  50. def get_port_info(self, port):
  51. data = {'fun_code': 2, 'port': int(port)}
  52. devInfo = self.send_mqtt(data)
  53. portInfo = devInfo.get('data', {})
  54. result = {'status': portInfo['status']}
  55. exec_orders = portInfo.get('orders', []) or portInfo.get('exec_orders', [])
  56. _wait = []
  57. for exec_order in exec_orders:
  58. if exec_order['status'] == 'running':
  59. policy = exec_order.get('policy', {})
  60. result['openId'] = policy['open_id']
  61. result['billingMethod'] = policy['billingMethod']
  62. result['policyType'] = policy['type']
  63. result['voltage'] = round(portInfo.get('volt', 0), 2)
  64. result['power'] = round(portInfo.get('watt', 0), 2)
  65. result['ampere'] = round((portInfo.get('ampr', 0) * 0.001), 2)
  66. result['usedTime'] = round(exec_order.get('time', 0) / 60.0, 1)
  67. result['usedElec'] = round(exec_order.get('elec', 0) * 0.000001, 4)
  68. if 'exec_time' in exec_order:
  69. result['startTime'] = datetime.datetime.fromtimestamp(int(exec_order['exec_time'])).strftime(
  70. '%m-%d %H:%M:%S')
  71. if 'execute_time' in exec_order:
  72. result['startTime'] = datetime.datetime.fromtimestamp(int(exec_order['execute_time'])).strftime(
  73. '%m-%d %H:%M:%S')
  74. if 'card_no' in result:
  75. result['cardNo'] = result['card_no']
  76. if exec_order['status'] == 'waiting':
  77. _one = {}
  78. _wait.append(_one)
  79. if _wait:
  80. result['waittingOrder'] = _wait
  81. return result
  82. def _check_package(self, package, isTemp=False):
  83. """
  84. 获取设备启动的发送数据 根据设备的当前模式以及套餐获取
  85. :param package:
  86. :return:
  87. """
  88. # 针对套餐特性的修改 套餐的特性在校验通过后 不应该再到设备处理一次 有可能的话 check_package 应该放到verify来处理 先对package加工 然后verify
  89. # TODO 一旦verify通过之后 package 任何特性都不应该被中途改变 直至订单结束
  90. policyType = package['policyType']
  91. billingMethod = package['billingMethod']
  92. if billingMethod == 'postpaid':
  93. money = 0.0
  94. over_money_stop = False
  95. auto_refund = False
  96. if self.look_at_money:
  97. open_id = package.pop('open_id', None)
  98. user = MyUser.objects.filter(openId=open_id).first()
  99. if user:
  100. over_money_stop = True
  101. balance = user.calc_currency_balance(self.device.owner, self.device.group)
  102. money = round(float(balance), 2)
  103. else:
  104. money = package['price'] or 0.0
  105. over_money_stop = True
  106. auto_refund = package.get('auto_refund', False)
  107. rv = {
  108. 'policyType': policyType,
  109. 'billingMethod': billingMethod,
  110. 'money': money,
  111. 'over_money_stop': over_money_stop,
  112. 'auto_refund': auto_refund,
  113. }
  114. auto_stop = package.get('autoStop', True) # 时间模式自动停止取决于套餐里面的自动停止开关, 电量模式一定是自动停止(充满后无法再冲入)
  115. if isTemp:
  116. if policyType == 'time':
  117. rv.update({
  118. 'needTime': '临时套餐',
  119. 'auto_stop': auto_stop,
  120. 'over_money_stop': True
  121. })
  122. else:
  123. rv.update({
  124. 'needElec': '临时套餐',
  125. 'auto_stop': auto_stop,
  126. 'over_money_stop': True
  127. })
  128. else:
  129. if policyType == 'time':
  130. # 自定义套餐
  131. if 'customValue' in package:
  132. needTime = '自定义{}分钟'.format(package['customValue'])
  133. _write_value = float(package['customValue'])
  134. # 充满自停套餐
  135. elif 'fullValue' in package:
  136. needTime = '充满自停'
  137. _write_value = float(package['fullValue'])
  138. auto_stop = True
  139. else:
  140. unit = package.get('unit')
  141. if unit == '分钟':
  142. needTime = '{:.1f}分钟'.format(float(package.get('time') or 0))
  143. _write_value = float(package.get('time') or 0)
  144. elif unit == '小时':
  145. needTime = '{:.1f}分钟'.format(float(package.get('time') or 0) * 60)
  146. _write_value = float(package.get('time') or 0) * 60
  147. else:
  148. raise ServiceException('当前模式为时间计费模式, 套餐单位配置错误')
  149. rv.update({
  150. 'needTime': needTime,
  151. 'auto_stop': auto_stop,
  152. '_write_value': _write_value,
  153. })
  154. elif policyType == 'elec':
  155. rv.update({'auto_stop': True})
  156. if 'customValue' in package:
  157. needElec = '自定义{}'.format(package['customValue'])
  158. _write_value = float(package['customValue'])
  159. elif 'fullValue' in package:
  160. needElec = '充满自停'
  161. _write_value = float(package['fullValue'])
  162. else:
  163. needElec = '{:.3f}'.format(float(package.get('time') or 0))
  164. _write_value = float(package.get('time', 0))
  165. rv.update({
  166. 'needElec': needElec,
  167. '_write_value': _write_value,
  168. })
  169. return rv
  170. def generate_apps_rules(self, package, isTemp=False):
  171. _package = self._check_package(package, isTemp)
  172. _policyTemp = self.device.policyTemp.get('forApps', self.INIT_POLICY['forApps'])
  173. policyType = _package.get('policyType')
  174. if policyType == 'time':
  175. # 格式化规则
  176. prices = sorted(_policyTemp.get('rule', {}).get('prices', []), key=lambda _: _['power'])
  177. # 单位元转换为分
  178. for item in prices:
  179. item['value'] = int(float(item.pop('price')) * 100)
  180. item['power'] = int(item['power'])
  181. rule = {
  182. 'prices': prices
  183. }
  184. # 临时套餐取最大12个小时填进去
  185. if isTemp:
  186. rule.update({'time': 12 * 60 * 60})
  187. else:
  188. rule.update({'time': _package['_write_value'] * 60})
  189. elif policyType == 'elec':
  190. price = RMB.yuan_to_fen(_policyTemp.get('rule', {}).get('price', 0))
  191. rule = {
  192. 'elec': _package['needElec'] * 1000000,
  193. 'price': price
  194. }
  195. # 临时套餐取最大取10度填进去
  196. if isTemp:
  197. rule.update({'elec': 5 * 1000000})
  198. else:
  199. rule.update({'elec': _package['_write_value'] * 1000000})
  200. else:
  201. raise ServiceException({'result': 2, 'description': u'设备尚未配置该套餐,请联系经销商配置该计费套餐'})
  202. # 适配100298 mode 固定为price
  203. policy = {
  204. 'type': policyType,
  205. 'mode': 'price',
  206. 'auto_stop': _package['auto_stop'],
  207. 'auto_refund': _package['auto_refund'],
  208. 'money': int(float(_package['money']) * 100),
  209. 'over_money_stop': _package['over_money_stop'],
  210. 'rule': rule,
  211. 'billingMethod': _package['billingMethod']
  212. }
  213. l_power = _package["l_power"] if "l_power" in _package else None
  214. l_power and policy.update({"l_power": l_power})
  215. return policy
  216. def _prepare_package_for_card(self, attachParas):
  217. is_free = self.device.group.is_free
  218. package = {
  219. "packageId": attachParas['packageId'],
  220. "name": attachParas["name"],
  221. "policyType": attachParas['policyType'],
  222. "isPostpaid": attachParas["billingMethod"] == "postpaid",
  223. "price": attachParas["price"].amount,
  224. "autoRefund": attachParas["autoRefund"],
  225. "autoStop": attachParas["autoStop"],
  226. "rules": attachParas["rules"],
  227. "isFree": is_free,
  228. "refundProtectTime": self.device.otherConf.get('refundProtectionTime', 5)
  229. }
  230. return package
  231. def prepare_package(self, packageId, attachParas, startType=StartDeviceType.ON_LIEN):
  232. """
  233. 准备套餐模型
  234. """
  235. # 卡启动的套餐
  236. if startType == StartDeviceType.CARD:
  237. return self._prepare_package_for_card(attachParas)
  238. # 获取基础的套餐模型 名称等等
  239. package = super(PolicyBase, self).prepare_package(packageId, attachParas)
  240. # 获取套餐的计量方式 时间/电量
  241. policyType = self.device.policyTemp.get('forApps', {}).get('policyType', 'time')
  242. # 获取套餐的计费规则
  243. _policyTemp = self.device.policyTemp.get('forApps', self.INIT_POLICY['forApps'])
  244. if policyType == "time":
  245. rules = sorted(_policyTemp.get('rule', {}).get('prices', []), key=lambda _: _['power'])
  246. else:
  247. rules = _policyTemp.get('rule', {}).get('price', 0)
  248. # 是否套餐免费
  249. is_free = self.device.group.is_free
  250. # 自定义套餐的时间 是客户自行设置
  251. if packageId == "customPackage":
  252. if policyType == "time":
  253. if package["unit"] == u"分钟":
  254. need = int(attachParas["consumeValue"])
  255. else:
  256. need = int(float(attachParas["consumeValue"]) * 60)
  257. else:
  258. need = float(attachParas["consumeValue"])
  259. # 其余的 直接是package 里面的time数值
  260. else:
  261. if policyType == "time":
  262. if package["unit"] == u"分钟":
  263. need = int(package["time"]) or 720
  264. else:
  265. need = int(float(package["time"]) * 60) or 720
  266. else:
  267. need = float(package["time"]) or 10
  268. # 获取两种套餐的基本最大值
  269. if policyType == "time":
  270. if need > 720:
  271. raise ServiceException({"result": 2, "description": u"套餐时间最大为720分钟,请重新输入套餐值或联系经销商处理"})
  272. if need < 10:
  273. raise ServiceException({"result": 2, "description": u"套餐时间最小为10分钟,请重新输入套餐值或联系经销商处理"})
  274. else:
  275. if need > 10:
  276. raise ServiceException({"result": 2, "description": u"套餐电量最大为10度,请重新输入套餐值或联系经销商处理"})
  277. if need <= 0:
  278. raise ServiceException({"result": 2, "description": u"套餐电量不得设置小于0度,请重新输入套餐值或联系经销商处理"})
  279. package = {
  280. "packageId": packageId,
  281. "name": package["name"],
  282. "policyType": policyType,
  283. "isPostpaid": package.get("billingMethod") == "postpaid",
  284. "price": RMB(package["price"]).amount,
  285. "minAfterStartCoins": RMB(package["minAfterStartCoins"]).amount,
  286. "minFee": RMB(package["minFee"]).amount,
  287. "autoRefund": package["autoRefund"],
  288. "autoStop": package["autoStop"],
  289. "time": need,
  290. "unit": u"分钟" if policyType == "time" else u"度",
  291. "sn": package["sn"],
  292. "rules": rules,
  293. "isFree": is_free,
  294. "refundProtectTime": self.device['otherConf'].get('refundProtectionTime', 5)
  295. }
  296. return package
  297. def prepare_serviced_info(self, package, attach_paras): # type:(dict, dict)->dict
  298. isTemp = attach_paras.get('isTempPackage', False)
  299. _package = self._check_package(package, isTemp)
  300. rv = {
  301. 'chargeIndex': attach_paras['chargeIndex'],
  302. 'policyTemp': self.device.policyTemp.get('forApps', {}).get('rule'),
  303. 'billingMethod': _package['billingMethod'],
  304. }
  305. if _package['policyType'] == 'time':
  306. rv.update({
  307. 'policyType': _package['policyType'],
  308. 'needTime': _package['needTime'],
  309. })
  310. elif _package['policyType'] == 'elec':
  311. rv.update({
  312. 'policyType': _package['policyType'],
  313. 'needElec': _package['needElec'],
  314. })
  315. return rv
  316. def start_device(self, package, openId, attachParas):
  317. if attachParas is None:
  318. raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路、电池类型信息'})
  319. if 'chargeIndex' not in attachParas:
  320. raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路'})
  321. port = int(attachParas['chargeIndex'])
  322. if attachParas.get('onPoints'):
  323. orderNo = ConsumeRecord.make_no() + self.device.owner.username
  324. else:
  325. orderNo = attachParas.get('orderNo')
  326. isTemp = attachParas.get('isTempPackage', False)
  327. # 兼容远程上分, lua不能有null
  328. package.update({'open_id': openId or self.device.ownerId})
  329. policy = self.generate_apps_rules(package, isTemp)
  330. policy.update({'open_id': openId or self.device.ownerId, })
  331. data = {
  332. 'fun_code': 7,
  333. 'port': port,
  334. 'order_id': orderNo,
  335. 'policy': policy,
  336. }
  337. devInfo = self.send_mqtt(data, timeout=MQTT_TIMEOUT.START_DEVICE)
  338. ctrInfo = Device.get_dev_control_cache(self.device.devNo)
  339. lineInfo = ctrInfo.get(str(port), {})
  340. if not lineInfo or lineInfo.get('status') != Const.DEV_WORK_STATUS_WORKING:
  341. lineInfo = {
  342. 'port': str(port),
  343. 'status': Const.DEV_WORK_STATUS_WORKING,
  344. 'order_type': 'apps_start',
  345. 'startTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  346. 'orderNo': orderNo,
  347. }
  348. Device.update_port_control_cache(self._device['devNo'], lineInfo)
  349. devInfo['finished_time'] = int(time.time()) + 3600 * 12
  350. return devInfo
  351. def check_feedback_result(self, result):
  352. if 'rst' not in result:
  353. raise ServiceException({'result': 2, 'description': u'报文异常'})
  354. if result['rst'] == -1:
  355. raise ServiceException({'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'})
  356. if result['rst'] == 1:
  357. raise ServiceException({'result': 2, 'description': u'串口通讯失败,您稍候再试,或者联系客服'})
  358. def start(self, packageId, openId=None, attachParas={}):
  359. washConfig = self.device.get("washConfig", dict())
  360. package = washConfig.get(packageId)
  361. if not package:
  362. raise ServiceException({"result": 2, "description": u"未找到套餐,请联系经销商!"}) # new
  363. if attachParas.get('onPoints'):
  364. package = self.prepare_package(packageId, attachParas)
  365. attachParas.update({'openId': openId})
  366. self.check_dev_status(attachParas)
  367. if self.device.bill_as_service_feature.support and self.device.bill_as_service_feature.on:
  368. return self.bill_as_service_start_device(package, openId, attachParas)
  369. else:
  370. return self.start_device(package, openId, attachParas)
  371. def check_order_state(self, openId):
  372. """
  373. 通过 openId 以及设备来鉴别 订单
  374. :param openId:
  375. :return:
  376. """
  377. dealerId = self.device.ownerId
  378. return ClientConsumeModelProxy.get_not_finished_record(ownerId=dealerId, openId=openId,
  379. devTypeCode=self._device["devType"]["code"],
  380. package__billingMethod=CONSUMETYPE.POSTPAID)
  381. def force_stop_order(self, order, **kwargs):
  382. order.update(isNormal=False, errorDesc='经销商强制结束订单')
  383. @staticmethod
  384. def _get_order_policy(order):
  385. """
  386. TODO 具体是否契合原版本 需要验证
  387. """
  388. package = order.package
  389. policy = {
  390. "type": package.category,
  391. "money": int(float(order.price) * 100),
  392. "auto_stop": package.autoStop,
  393. "over_money_stop": True if not package.isPostpaid else False
  394. }
  395. # 时间模式
  396. if package.category == "time":
  397. prices = [
  398. {
  399. "price": int(float(_["price"]) * 100),
  400. "power": int(_["power"])
  401. }
  402. for _ in package.rules
  403. ]
  404. rules = {
  405. "prices": prices,
  406. "time": package.time * 60 or 12 * 60 * 60
  407. }
  408. # 电量模式
  409. else:
  410. price = int(package.rules * 100)
  411. rules = {
  412. "price": price,
  413. "elec": package.elec * 1000000 or 5 * 1000000
  414. }
  415. policy.update({
  416. "rule": rules,
  417. })
  418. return policy
  419. def start_device_realiable(self, order): # type:(ConsumeRecord)->dict
  420. port = order.port
  421. orderNo = order.orderNo
  422. policy = self._get_order_policy(order)
  423. policy.update({
  424. 'open_id': order.openId or self.device.ownerId,
  425. "billingMethod": "postpaid" if order.isPostPaid else "prepaid"
  426. })
  427. data = {
  428. 'fun_code': 7,
  429. 'port': port,
  430. 'order_id': orderNo,
  431. 'policy': policy,
  432. }
  433. devInfo = self.send_mqtt(data, timeout=MQTT_TIMEOUT.START_DEVICE)
  434. devInfo['deviceStartTime'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  435. return devInfo