tasks.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. # -*- coding: utf-8 -*-
  2. #!/usr/bin/env python
  3. import datetime
  4. import os
  5. from collections import OrderedDict
  6. import pandas
  7. import xlrd
  8. from celery.utils.log import get_task_logger
  9. from django.conf import settings
  10. from xlrd import xldate_as_tuple
  11. from apilib.monetary import sum_rmb
  12. from apilib.utils_datetime import to_datetime
  13. from apps.thirdparties.dingding import DingDingRobot
  14. from apps.web.agent.define import AgentConst
  15. from apps.web.agent.models import Agent
  16. from apps.web.constant import Const, ComplaintResponseTemplate
  17. from apps.web.core.payment import WithdrawGateway
  18. from apps.web.core.utils import generate_excel_report
  19. from apps.web.dealer.define import DealerConst
  20. from apps.web.dealer.models import Dealer, DealerRechargeRecord
  21. from apps.web.device.models import SIMCard, Device
  22. from apps.web.user.models import RechargeRecord, ConsumeRecord
  23. logger = get_task_logger(__name__)
  24. def import_simcard_excel_to_db(excelFile, source):
  25. book = xlrd.open_workbook(excelFile)
  26. try:
  27. sheet = book.sheet_by_name("Sheet")
  28. except Exception, e:
  29. logger.info('open excel file error=%s' % e)
  30. return None
  31. nrows = sheet.nrows
  32. allCount = 0
  33. needNotUpdated, devUpdated = 0, 0
  34. for i in range(1, nrows):
  35. row = sheet.row_values(i)
  36. try:
  37. if source == 'qiben':
  38. expireTime = to_datetime(row[2], time_format = "%Y-%m-%d")
  39. elif source == 'hezhou':
  40. expireTime = datetime.datetime(*xldate_as_tuple(row[2], 0))
  41. elif source == 'jieyang':
  42. expireTime = to_datetime(row[2], time_format = "%Y-%m-%d")
  43. else:
  44. logger.info('source type is error')
  45. return None
  46. iccid = row[0]
  47. SIMCard.get_collection().update_one({'iccid': iccid}, {
  48. '$set': {'iccid': iccid, 'imsi': row[1], 'supplier': source, 'expireTime': expireTime}}, upsert = True)
  49. allCount += 1
  50. if allCount % 300 == 0:
  51. logger.info('import simcard into simcard table finishe num is %s' % allCount)
  52. except Exception, e:
  53. logger.error('import simcard some exception = %s' % e)
  54. continue
  55. try:
  56. # 直接查询device的数据,检查是否需要更新
  57. devObj = Device.objects(iccid = iccid).only('simExpireDate', 'simStatus').get()
  58. except Exception, e:
  59. continue
  60. # 如果过期日期比数据库中小20天(最差也会充值一个月,所以20天够用了),认为卡商并没有更新此卡的数据,就不需要更新,直接pass掉。
  61. if (devObj.simExpireDate is not None) and (expireTime - devObj.simExpireDate).days < 20:
  62. needNotUpdated += 1
  63. logger.info('this dev iccid =%s need not be updated,sim \
  64. card of device expireTime=%s,sim card expireTime=%s' %
  65. (iccid, expireTime.strftime(Const.DATETIME_FMT),
  66. devObj.simExpireDate.strftime(Const.DATETIME_FMT)))
  67. continue
  68. try:
  69. devObj.simExpireDate = expireTime
  70. devObj.simStatus = 'updated'
  71. devObj.save()
  72. devUpdated += 1
  73. except Exception, e:
  74. logger.info('save dev obj iccid =%s error=%s' % (iccid, e))
  75. def export_simcard_excel_from_db(filepath, queryDict):
  76. logger.info('start export_simcard_excel_from_db,filepath=%s,query=%s' % (filepath, queryDict))
  77. startTimeStr = queryDict.get('startTimeStr', None)
  78. endTimeStr = queryDict.get('endTimeStr', None)
  79. searchKey = queryDict.get('searchKey', '')
  80. searchType = queryDict.get('searchType', '')
  81. simCardSource = queryDict.get('simCardSource', '')
  82. startTime = datetime.datetime.strptime(startTimeStr + ' 00:00:00', "%Y-%m-%d %H:%M:%S")
  83. endTime = datetime.datetime.strptime(endTimeStr + ' 23:59:59', "%Y-%m-%d %H:%M:%S")
  84. queryArr = ['expireTime__gte=startTime', 'expireTime__lte=endTime']
  85. if searchType == 'IMSI' and searchKey != '':
  86. queryArr.append('imsi=searchKey')
  87. elif searchType == 'ICCID' and searchKey != '':
  88. queryArr.append('iccid=searchKey')
  89. else:
  90. pass
  91. if simCardSource != '':
  92. queryArr.append('supplier=searchKey')
  93. query = ','.join(queryArr)
  94. ss = eval('SIMCard.objects({})'.format(query))
  95. records = []
  96. for _ in ss:
  97. records.append(
  98. OrderedDict([
  99. (u'imsi', _.imsi),
  100. (u'iccid', _.iccid),
  101. (u'卡商', _.supplier),
  102. (u'过期时间', _.expireTime.strftime(Const.DATETIME_FMT)),
  103. ])
  104. )
  105. generate_excel_report(filepath, records)
  106. return
  107. def export_device_excel_from_db(filepath, queryDict):
  108. logger.info('start export_device_excel_from_db,filepath=%s,query=%s' % (filepath, queryDict))
  109. logicalCodeList = queryDict.get('logicalCodeList', None)
  110. ds = Device.objects(logicalCode__in=logicalCodeList)
  111. if ds.count() == 0:
  112. logger.error('devices are not found')
  113. return
  114. dds = [_.logicalCode for _ in ds]
  115. records = []
  116. for _ in dds:
  117. try:
  118. d = Device.objects(logicalCode=_).first()
  119. try:
  120. dd = Device.get_dev(d.devNo)
  121. except Exception as e:
  122. continue
  123. # 不存在的设备跳过
  124. if d is None:
  125. continue
  126. # 1.检测是否在线
  127. if dd.online != 0:
  128. onlineStatus = 1
  129. else:
  130. onlineStatus = 0
  131. # 2.检测最近离线
  132. if dd.offTime != 0:
  133. lastOfflineTime = datetime.datetime.fromtimestamp(int(str(dd.offTime)[0:10])).strftime("%Y-%m-%d")
  134. else:
  135. lastOfflineTime = 0
  136. # 3.检测流量卡充值时间
  137. simRechargeRcd = DealerRechargeRecord.objects(
  138. __raw__={'dealerId': d.ownerId, 'status': 'Paid', 'name': {'$regex': str(_)}}).order_by('-id').first()
  139. if simRechargeRcd is not None:
  140. simRechargeTime = simRechargeRcd.finishedTime.strftime("%Y-%m-%d")
  141. else:
  142. simRechargeTime = 0
  143. try:
  144. # 4.检测设备过期时间
  145. if d.simExpireDate is not None:
  146. simExpireTime = d.simExpireDate.strftime("%Y-%m-%d")
  147. elif d.expireDate is not None:
  148. simExpireTime = d.expireDate.strftime("%Y-%m-%d")
  149. else:
  150. simExpireTime = 'None'
  151. except Exception as e:
  152. continue
  153. records.append(
  154. OrderedDict([
  155. (u'设备编号', _),
  156. (u'最近离线时间', lastOfflineTime),
  157. (u'流量卡过期时间', simExpireTime),
  158. (u'最后充值时间', simRechargeTime),
  159. (u'是否在线', onlineStatus),
  160. ])
  161. )
  162. except Exception as e:
  163. print e
  164. generate_excel_report(filepath, records)
  165. return
  166. class BaseSum(object):
  167. def __init__(self, fieldMap, filters=None, checkKey=True):
  168. if filters is None:
  169. filters = dict()
  170. self._fieldMap = fieldMap
  171. self._filters = filters
  172. self._check = checkKey
  173. def __repr__(self):
  174. return self.__class__.__name__
  175. def sub_balance(self, obj, incomeType, no_check = False):
  176. balance_dict = getattr(obj, self._fieldMap.get(incomeType))
  177. balanceList = list()
  178. frozenBalanceList = list()
  179. for key, value in balance_dict.iteritems():
  180. if not WithdrawGateway.is_ledger(key):
  181. continue
  182. balanceList.append(self.get_balance(key, value, no_check))
  183. return sum_rmb(balanceList), sum_rmb(frozenBalanceList)
  184. def get_balance(self, key, value, no_check = False):
  185. if no_check or not self._check:
  186. return value.balance
  187. else:
  188. return value.balance if settings.MY_PRIMARY_AGENT_ID in key.split("-") else 0
  189. def get_frozen_balance(self, key, value, no_check):
  190. if no_check or not self._check:
  191. return value.frozenBalance
  192. else:
  193. return value.frozenBalance if settings.MY_PRIMARY_AGENT_ID in key.split("-") else 0
  194. def get_model_objects(self):
  195. return list()
  196. def get_sum_data(self):
  197. return list()
  198. class AgentSum(BaseSum):
  199. def get_model_objects(self):
  200. return Agent.objects.filter(**self._filters)
  201. def get_sum_data(self):
  202. dataList = list()
  203. total = float(0)
  204. totalFrozen = float(0)
  205. for agent in self.get_model_objects():
  206. adBalance, adFrozenBalance = self.sub_balance(agent, "ad", True)
  207. withdrawBalance, withdrawFrozenBalance = self.sub_balance(agent, "withdraw")
  208. deviceBalance, deviceFrozenBalance = self.sub_balance(agent, "device")
  209. trafficBalance, trafficFrozenBalance = self.sub_balance(agent, "traffic", True)
  210. totalBalance = adBalance + withdrawBalance + deviceBalance + trafficBalance
  211. totalFrozenBalance = adFrozenBalance + withdrawFrozenBalance + deviceFrozenBalance + trafficFrozenBalance
  212. total += float(totalBalance)
  213. totalFrozen += float(totalFrozenBalance)
  214. dataList.append(OrderedDict([
  215. (u"代理商", str(agent.nickname)),
  216. (u"联系方式", str(agent.username)),
  217. (u"广告余额", float(adBalance)),
  218. (u"广告余额(冻结)", float(adFrozenBalance)),
  219. (u"提现余额", float(withdrawBalance)),
  220. (u"提现余额(冻结)", float(withdrawFrozenBalance)),
  221. (u"设备余额", float(deviceBalance)),
  222. (u"设备余额(冻结)", float(deviceFrozenBalance)),
  223. (u"交通余额", float(trafficBalance)),
  224. (u"交通余额(冻结)", float(trafficFrozenBalance)),
  225. (u"余额", float(totalBalance)),
  226. (u"余额(冻结)", float(totalFrozenBalance)),
  227. ("", ""),
  228. (u"总余额", float(totalBalance + totalFrozenBalance))
  229. ]))
  230. dataList.insert(0, OrderedDict([]))
  231. dataList.insert(0, OrderedDict([
  232. (u"代理商", "总统计"),
  233. (u"联系方式", "没排除自己的"),
  234. (u"广告余额", ""),
  235. (u"广告余额(冻结)", ""),
  236. (u"提现余额", ""),
  237. (u"提现余额(冻结)", ""),
  238. (u"设备余额", ""),
  239. (u"设备余额(冻结)", ""),
  240. (u"交通余额", ""),
  241. (u"交通余额(冻结)", ""),
  242. (u"余额", float(total)),
  243. (u"余额(冻结)", float(totalFrozen)),
  244. ("", ""),
  245. (u"总余额", float(total+totalFrozen))
  246. ]))
  247. return dataList
  248. class DealerSum(BaseSum):
  249. def get_model_objects(self):
  250. return Dealer.objects.filter(**self._filters)
  251. def get_sum_data(self):
  252. dataList = list()
  253. total = float(0)
  254. totalFrozen = float(0)
  255. for dealer in self.get_model_objects():
  256. deviceBalance, deviceFrozenBalance = self.sub_balance(dealer, "device")
  257. adBalance, adFrozenBalance = self.sub_balance(dealer, "ad", True)
  258. totalBalance = deviceBalance + adBalance
  259. totalFrozenBalance = deviceFrozenBalance + adFrozenBalance
  260. total += float(totalBalance)
  261. totalFrozen += float(totalFrozenBalance)
  262. dataList.append(OrderedDict([
  263. (u"经销商", str(dealer.nickname)),
  264. (u"联系方式", str(dealer.username)),
  265. (u"设备余额", float(deviceBalance)),
  266. (u"设备余额(冻结)", float(deviceFrozenBalance)),
  267. (u"广告余额", float(adBalance)),
  268. (u"广告余额(冻结)", float(adFrozenBalance)),
  269. (u"余额", float(totalBalance)),
  270. (u"余额(冻结)", float(totalFrozenBalance)),
  271. (u"", ""),
  272. (u"总余额", float(totalBalance))
  273. ]))
  274. dataList.insert(0, OrderedDict([]))
  275. dataList.insert(0, OrderedDict([
  276. (u"经销商", "总统计"),
  277. (u"联系方式", "没排除自己的"),
  278. (u"设备余额", ""),
  279. (u"设备余额(冻结)", ""),
  280. (u"广告余额", ""),
  281. (u"广告余额(冻结)", ""),
  282. (u"余额", float(total)),
  283. (u"余额(冻结)", float(totalFrozen)),
  284. (u"", ""),
  285. (u"总余额", float(total+totalFrozen))
  286. ]))
  287. return dataList
  288. class SumContext(object):
  289. def __init__(self, strategy=None):
  290. self._strategy = strategy
  291. @property
  292. def strategy(self):
  293. return self._strategy
  294. @strategy.setter
  295. def strategy(self, strategy):
  296. if isinstance(strategy, BaseSum):
  297. self._strategy = strategy
  298. def sum_balance(self):
  299. return self.strategy.get_sum_data()
  300. class Writer(object):
  301. def __init__(self, path):
  302. if not os.path.exists(path):
  303. raise Exception("path is not exists!")
  304. filePath = os.path.join(path, str(datetime.date.today()))
  305. fileName = "{}.xlsx".format(filePath)
  306. self._writer = pandas.ExcelWriter(fileName)
  307. self._context = SumContext()
  308. def __enter__(self):
  309. return self
  310. def __exit__(self, exc_type, exc_val, exc_tb):
  311. if exc_val:
  312. logger.error(exc_val)
  313. else:
  314. self._writer.save()
  315. @property
  316. def context(self):
  317. return self._context
  318. @property
  319. def write_data(self):
  320. return self.context.sum_balance()
  321. def write(self, sheetName=None):
  322. if sheetName is None:
  323. sheetName = str(self.context.strategy)
  324. dataForm = pandas.DataFrame(self.write_data)
  325. dataForm.to_excel(self._writer, sheet_name=sheetName, index=False)
  326. def sum_customer(path=None):
  327. if path is None:
  328. path = "/var/www/uploaded/sumIncome"
  329. with Writer(path=path) as w:
  330. w.context.strategy = AgentSum(fieldMap=AgentConst.MAP_TYPE_TO_FIELD, checkKey=True)
  331. w.write()
  332. w.context.strategy = DealerSum(fieldMap=DealerConst.MAP_TYPE_TO_FIELD, checkKey=True)
  333. w.write()
  334. def handle_customer_complaints_yesterday():
  335. # def submitAndFeedback(complaintUtil, mchid, origin_mchid, complaintId, responseContent):
  336. # trueMchid = origin_mchid if mchid == '' else mchid
  337. # submitResult = complaintUtil.complaint_response(
  338. # complaint_id = complaintId, complainted_mchid = trueMchid, response_content = responseContent)
  339. # if submitResult != 204:
  340. # logger.error('handle_customer_complaints, submit error, c_id=%s' % complaintId)
  341. # else:
  342. # complaintUtil.complaint_complete(complaint_id = complaintId, complainted_mchid = trueMchid)
  343. # logger.info('done! for handle_customer_complaints %s' % complaintId)
  344. if settings.SKIP_HANDLE_WECHAT_COMPLAINT_TASK:
  345. logger.debug('SKIP_HANDLE_WECHAT_COMPLAINT_TASK is True.')
  346. return
  347. channels = [item for item in WechatChannelApp.objects().all()]
  348. crt = ComplaintResponseTemplate()
  349. for channel in channels: # type: WechatChannelApp
  350. loop = 0
  351. pageSize = 30
  352. while True:
  353. wechatComplaintUtil = MyWechatClientV3(channel).complaint # type: Complaint
  354. yesterday = (datetime.date.today() + datetime.timedelta(days = -1)).strftime("%Y-%m-%d")
  355. threeDaysAgo = (datetime.date.today() + datetime.timedelta(days = -20)).strftime("%Y-%m-%d")
  356. result = wechatComplaintUtil.complaint_list_query(
  357. begin_date = threeDaysAgo, end_date = yesterday, offset = loop * pageSize, limit = pageSize)
  358. if 'data' not in result:
  359. logger.warning('handle_customer_complaints, no data in query result.')
  360. break
  361. for _ in [w for w in result['data'] if w['complaint_state'] == u'PENDING']:
  362. mchid = _.get('complainted_mchid', '')
  363. userComplaintTimes = _.get('user_complaint_times', 1)
  364. try:
  365. complaintOrderInfo = _.get('complaint_order_info', [])
  366. complaintId = _['complaint_id']
  367. if not complaintOrderInfo:
  368. logger.error('handle_customer_complaints, no complaintOrderInfo')
  369. continue
  370. transactionId = complaintOrderInfo[0].get('transaction_id', '')
  371. if transactionId == '':
  372. logger.error('handle_customer_complaints, no transaction_id')
  373. continue
  374. rechargeRecord = RechargeRecord.objects(__raw__ = {
  375. '$or': [
  376. {
  377. 'wxOrderNo': transactionId
  378. },
  379. {
  380. 'transactionId': transactionId
  381. }
  382. ]}).first()
  383. if rechargeRecord is None:
  384. outTradeNo = complaintOrderInfo[0].get('out_trade_no', '')
  385. logger.error('handle_customer_complaints, no rechargeRecord, id=%s' % transactionId)
  386. responseContent = crt.NO_RECHARGE_RECORD_CONTENT
  387. trueMchid = channel.mchid if mchid == '' else mchid
  388. wechatComplaintUtil.complaint_response(complaint_id=complaintId, complainted_mchid=trueMchid,
  389. response_content=responseContent)
  390. time.sleep(1)
  391. wechatComplaintUtil.complaint_complete(complaint_id=complaintId, complainted_mchid=trueMchid)
  392. DingDingRobot().send_msg(msg = u'[客诉异常] 投诉单关联的商户订单号不存在, outTradeNo=%s' % outTradeNo)
  393. continue
  394. dealer = Dealer.objects(id = rechargeRecord.ownerId).first()
  395. if dealer is None:
  396. logger.error('handle_customer_complaints, no dealer, ref_id=%s' % str(rechargeRecord.id))
  397. continue
  398. startKey = rechargeRecord.attachParas.get('startKey', '')
  399. refundedType = 0
  400. consumeRecord = None
  401. refundedMoney = 0
  402. refundedCash = 0
  403. if startKey != '':
  404. consumeRecord = ConsumeRecord.objects(startKey = startKey).first()
  405. if consumeRecord is not None:
  406. if consumeRecord.isNormal is True:
  407. refundedMoney = consumeRecord.servicedInfo.get('refundedMoney', 0.0) # 金币
  408. if str(refundedMoney) == str(consumeRecord.coin):
  409. refundedType = 1
  410. refundedCash = consumeRecord.servicedInfo.get('refundedCash', 0.0) # 现金
  411. if str(refundedCash) == str(consumeRecord.money):
  412. refundedType = 2
  413. else:
  414. refundedType = 3
  415. device = Device.objects(logicalCode = rechargeRecord.logicalCode).first()
  416. if device is None:
  417. logger.error('handle_customer_complaints, no device, ref_id=%s' % str(rechargeRecord.id))
  418. deviceTypeStr = u'设备'
  419. else:
  420. deviceTypeStr = device.devTypeName
  421. if userComplaintTimes == 1:
  422. if refundedType == 1:
  423. responseContent = crt.REFUND_RESPONSE_CONTENT_1.format(deviceTypeStr,
  424. rechargeRecord.logicalCode,
  425. consumeRecord.finishedTime.strftime(
  426. '%Y-%m-%d %H:%M:%S'),
  427. str(refundedMoney))
  428. elif refundedType == 2:
  429. responseContent = crt.REFUND_RESPONSE_CONTENT_2.format(deviceTypeStr,
  430. rechargeRecord.logicalCode,
  431. consumeRecord.finishedTime.strftime(
  432. '%Y-%m-%d %H:%M:%S'),
  433. str(refundedCash))
  434. elif refundedType == 3:
  435. responseContent = crt.REFUND_RESPONSE_CONTENT_3.format(deviceTypeStr,
  436. rechargeRecord.logicalCode)
  437. else:
  438. responseContent = crt.COMMON_RESPONSE_CONTENT.format(
  439. rechargeRecord.dateTimeAdded.strftime('%Y-%m-%d %H:%M:%S'), rechargeRecord.logicalCode,
  440. deviceTypeStr, str(rechargeRecord.money), rechargeRecord.orderNo, dealer.service_phone)
  441. elif userComplaintTimes == 2:
  442. responseContent = crt.COMMON_RESPONSE_CONTENT_2.format(dealer.service_phone)
  443. elif userComplaintTimes == 3:
  444. responseContent = crt.REAL_PROBLEM_RESPONSE_CONTENT.format(dealer.service_phone)
  445. else:
  446. responseContent = crt.ERROR_RESPONSE_CONTENT
  447. trueMchid = channel.mchid if mchid == '' else mchid
  448. wechatComplaintUtil.complaint_response(complaint_id=complaintId, complainted_mchid=trueMchid, response_content=responseContent)
  449. time.sleep(1)
  450. wechatComplaintUtil.complaint_complete(complaint_id=complaintId, complainted_mchid=trueMchid)
  451. except Exception as e:
  452. logger.error('error=%s, error_mchid=%s' % (e, mchid))
  453. continue
  454. if len(result['data']) < pageSize:
  455. break
  456. loop = loop + 1