tasks.py 22 KB

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