models.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import logging
  5. from bson.decimal128 import Decimal128
  6. from bson.objectid import ObjectId
  7. from cytoolz import get_in
  8. from mongoengine import DictField, StringField, DateTimeField, ObjectIdField, ListField
  9. from typing import Union, TYPE_CHECKING, Optional
  10. from apilib.monetary import RMB
  11. from apilib.quantity import Quantity
  12. from apps.web.constant import Const, DEALER_CONSUMPTION_AGG_KIND, MONTH_DATE_KEY
  13. from apps.web.core.accounting import Accounting
  14. from apps.web.core.db import Searchable
  15. from apps.web.dealer.define import DEALER_INCOME_SOURCE
  16. logger = logging.getLogger(__name__)
  17. if TYPE_CHECKING:
  18. from apps.web.user.models import ConsumeRecord
  19. from apps.web.dealer.proxy import DealerIncomeProxy
  20. class DevReport(Searchable):
  21. rpt = DictField(verbose_name = "报表数据")
  22. devNo = StringField(verbose_name = "设备编号", default = "")
  23. type = StringField(verbose_name = "报表类型", default = "")
  24. date = StringField(verbose_name = "日期", default = "")
  25. dateTimeAdded = DateTimeField(verbose_name = "时间", default = datetime.datetime.now)
  26. _shard_key = ('devNo', 'type', 'date')
  27. _origin_meta = {
  28. 'collection': 'DevReport',
  29. 'db_alias': 'report'
  30. }
  31. meta = _origin_meta
  32. @staticmethod
  33. def get_rpt(devNos, startDate, endDate):
  34. todayTime = datetime.datetime.now()
  35. today = todayTime.strftime("%Y-%m-%d")
  36. todayRpt = {}
  37. if startDate <= today <= endDate:
  38. todayRpt = Accounting.getDevIncome(devNos, todayTime)
  39. if startDate == endDate:
  40. return todayRpt
  41. devDict = {}
  42. filters = {
  43. 'devNo': {'$in': devNos}, 'type': 'day'
  44. }
  45. from apps.web.common.proxy import DevReportModelProxy
  46. rpts = DevReportModelProxy.get_data_list(startTime = startDate, endTime = endDate, **filters)
  47. for item in rpts: # type: DevReport
  48. if item.date == today:
  49. continue
  50. if item.devNo in devDict:
  51. devDict[item.devNo]['lineCoins'] += item.rpt.get('lineCoins', 0)
  52. devDict[item.devNo]['count'] += item.rpt.get('count', 0)
  53. else:
  54. devDict[item.devNo] = {'lineCoins': item.rpt.get('lineCoins', 0), 'count': item.rpt.get('count', 0)}
  55. result = {}
  56. for devNo in devNos:
  57. if devNo in devDict:
  58. result[devNo] = devDict[devNo]
  59. else:
  60. result[devNo] = {'lineCoins': 0, 'count': 0}
  61. if devNo in todayRpt:
  62. result[devNo]['lineCoins'] += todayRpt[devNo]['lineCoins']
  63. result[devNo]['count'] += todayRpt[devNo]['count']
  64. return result
  65. class GroupReport(Searchable):
  66. rpt = DictField(verbose_name = "报表数据")
  67. groupId = StringField(verbose_name = "地址ID")
  68. date = StringField(verbose_name = "日期", default = "")
  69. type = StringField(verbose_name = "报表类型", default = "")
  70. dateTimeAdded = DateTimeField(verbose_name = "时间", default = datetime.datetime.now)
  71. _shard_key = ('groupId', 'type', 'date')
  72. _origin_meta = {
  73. 'collection': 'GroupReport',
  74. 'db_alias': 'report'
  75. }
  76. meta = _origin_meta
  77. @staticmethod
  78. def get_rpt(groupIds, startDate, endDate):
  79. todayTime = datetime.datetime.now()
  80. today = todayTime.strftime("%Y-%m-%d")
  81. todayRpt = {}
  82. if startDate <= today <= endDate:
  83. todayRpt = Accounting.getGroupIncome(groupIds, todayTime)
  84. if startDate == endDate:
  85. return todayRpt
  86. groupDict = {}
  87. filters = {
  88. 'groupId': {'$in': groupIds}, 'type': 'day'
  89. }
  90. from apps.web.common.proxy import GroupReportModelProxy
  91. rpts = GroupReportModelProxy.get_data_list(startTime = startDate, endTime = endDate, **filters)
  92. for item in rpts: # type: GroupReport
  93. if item.date == today:
  94. continue
  95. temp = groupDict.get(item.groupId, dict())
  96. l = temp.get('lineCoins', 0)
  97. c = temp.get('count', 0)
  98. l += item.rpt.get('lineCoins', 0)
  99. c += item.rpt.get('count', 0)
  100. temp.update({'lineCoins': l, 'count': c})
  101. groupDict[item.groupId] = temp
  102. result = {}
  103. for groupId in groupIds:
  104. if groupId in groupDict:
  105. result[groupId] = groupDict[groupId]
  106. else:
  107. result[groupId] = {'lineCoins': 0, 'count': 0}
  108. if groupId in todayRpt:
  109. result[groupId]['lineCoins'] += todayRpt[groupId]['lineCoins']
  110. result[groupId]['count'] += todayRpt[groupId]['count']
  111. return result
  112. class DealerReport(Searchable):
  113. rpt = DictField(verbose_name = "报表数据")
  114. ownerId = StringField(verbose_name = "用户ID")
  115. date = StringField(verbose_name = "日期", default = "")
  116. type = StringField(verbose_name = "报表类型", default = "")
  117. dateTimeAdded = DateTimeField(verbose_name = "时间", default = datetime.datetime.now)
  118. _shard_key = ('ownerId', 'type', 'date')
  119. _origin_meta = {
  120. 'collection': 'DealerReport',
  121. 'db_alias': 'report'
  122. }
  123. meta = _origin_meta
  124. @staticmethod
  125. def prepare_group_pipeline(featureMatch = None):
  126. # type:(dict) -> list
  127. """
  128. 创建mongo查询的聚合管道
  129. :param featureMatch: 特征查询条件 {"dealerId": ObjectId()}
  130. :return: pipeline
  131. """
  132. pipeline = list()
  133. if featureMatch:
  134. pipeline.append({"$match": featureMatch})
  135. dateConvertPro = {"$project": {
  136. "date": {"$dateFromString": {"dateString": "$date"}},
  137. "rpt": 1
  138. }}
  139. pipeline.append(dateConvertPro) # 通过project将 date 的数据类型改变
  140. dateUnpackPro = {"$project": {
  141. "month": {"$month": "$date"},
  142. "year": {"$year": "$date"},
  143. "rpt": 1
  144. }}
  145. pipeline.append(dateUnpackPro) # 通过project 获取日期里面的年、月 为聚合做准备
  146. return pipeline
  147. @staticmethod
  148. def month_group_key():
  149. groupKey = {
  150. "_id": {"year": "$year", "month": "$month"}
  151. }
  152. return groupKey
  153. @staticmethod
  154. def get_rpts(ownerId, startDate, endDate):
  155. todayTime = datetime.datetime.now()
  156. today = todayTime.strftime("%Y-%m-%d")
  157. result = []
  158. if startDate <= today <= endDate:
  159. todayRpt = Accounting.getOwnerIncome(ownerId, todayTime)
  160. todayRpt.update({'date': today})
  161. result.append(todayRpt)
  162. if startDate == endDate:
  163. return [todayRpt]
  164. filters = {
  165. 'ownerId': ownerId, 'type': 'day'
  166. }
  167. from apps.web.common.proxy import DealerReportModelProxy
  168. rpts = DealerReportModelProxy.get_data_list(startTime = startDate, endTime = endDate, **filters)
  169. for item in rpts: # type: DealerReport
  170. if item.date == today:
  171. continue
  172. result.append({
  173. 'date': item.date,
  174. 'count': item.rpt.get('count', 0),
  175. 'lineCoins': item.rpt.get('lineCoins', 0)
  176. })
  177. return result
  178. #: #####
  179. #: stats
  180. #: #####
  181. ###: default dicts
  182. # `default_agg_map` example
  183. # {'totalIncome': 100
  184. # 'income': { 'recharge': 50, 'ad': 50 },
  185. # 'consumption: { 'elec': 10 }
  186. # }
  187. default_agg_map = {
  188. 'totalIncome': RMB('0.0').mongo_amount,
  189. 'income': {k: RMB('0.0').mongo_amount for k in DEALER_INCOME_SOURCE.choices()},
  190. 'consumption': {k: Quantity('0.0').mongo_amount for k in DEALER_CONSUMPTION_AGG_KIND.choices()}
  191. }
  192. # `default_hour_stats` example
  193. # {
  194. # '1':
  195. # {
  196. # 'totalIncome': 100
  197. # 'income': { 'recharge': 50, 'ad': 50 },
  198. # 'consumption: { 'elec': 10 }
  199. # }
  200. # ...
  201. # }
  202. #
  203. default_hour_stats = {str(hour): default_agg_map for hour in range(24)}
  204. # `default_minute_stats` example
  205. # { '1:10':
  206. # {
  207. # 'totalIncome': 100
  208. # 'income': { 'recharge': 50, 'ad': 50 },
  209. # 'consumption: { 'elec': 10 }
  210. # }
  211. # ...
  212. # }
  213. default_minute_stats = {'%d:%d' % (hour, minute): default_agg_map for hour in range(24) for minute in range(60)}
  214. # `default_month_stats` example
  215. # {'daily.5'
  216. # {
  217. # 'totalIncome': 100
  218. # 'income': { 'recharge': 50, 'ad': 50 },
  219. # 'consumption: { 'elec': 10 }
  220. # }
  221. # ...
  222. # }
  223. default_month_stats = {'daily.%d' % d: default_agg_map for d in range(1, 32)}
  224. ############
  225. ## Daily ###
  226. ############
  227. class DailyStat(Searchable):
  228. date = StringField()
  229. origin = DictField()
  230. daily = DictField(default=default_agg_map)
  231. hourly = DictField(default = default_hour_stats)
  232. minute = DictField(default = default_minute_stats)
  233. meta = {
  234. 'abstract': True
  235. }
  236. def get(self, key, default = None):
  237. try:
  238. value = self.__getitem__(key)
  239. except KeyError:
  240. return default
  241. return value
  242. @classmethod
  243. def is_allowed(cls, allowed):
  244. supportKey = cls.__name__.replace(DailyStat.__name__, "").lower()
  245. return bool(allowed.get(supportKey, False))
  246. @classmethod
  247. def check_already_record(cls, record): # type:(Optional[ConsumeRecord, DealerIncomeProxy]) -> bool
  248. """
  249. 对于 查询每个记录是否已经存在 实际判断的方式就是 origin里面有没有record id
  250. 但是对于该模型建立的索引是date + 特征id 所以是先走索引降低数据查询量 在查询是否id 存在于origin
  251. :param record:
  252. :return:
  253. """
  254. raise NotImplementedError("not realize!")
  255. @classmethod
  256. def update_statistic_data(cls, record): # type:(Optional[ConsumeRecord, DealerIncomeProxy]) -> bool
  257. """
  258. 更新统计数据
  259. 根据 record 的记录统计不同的数据
  260. 对于设备 消费即消费 收益即收益
  261. 对于组 消费及消费 收益需要记录当时每个经销商的分成(1条 里面记录map)
  262. 对于经销商 消费每个经销商都需要记录一份 收益根据每个经销商根据map 记录自己分得的那一份(n个经销商n条 每条记录自己的)
  263. :param record:
  264. :return:
  265. """
  266. raise NotImplementedError("not realize")
  267. @classmethod
  268. def update_statistic(cls, query, updateOrInsertData): # type:(dict, dict) -> True
  269. try:
  270. result = cls.objects.filter(__raw__ = query).update(upsert = True, **updateOrInsertData)
  271. except Exception as e:
  272. logger.error(e)
  273. result = False
  274. if not result:
  275. logger.info('update failed, model={}, query={}, params={}'.format(cls, query, updateOrInsertData))
  276. return result
  277. @staticmethod
  278. def prepare_group_pipeline(featureMatch = None, project = ('daily'), needTime = True, hasDay = False):
  279. # type:(dict, tuple, bool, bool) -> list
  280. """
  281. 创建mongo查询的聚合管道
  282. :param featureMatch: 特征查询条件 {"dealerId": ObjectId()}
  283. :return: pipeline
  284. """
  285. pipeline = list()
  286. if featureMatch:
  287. pipeline.append({"$match": featureMatch})
  288. _include = {field: 1 for field in project}
  289. if needTime:
  290. dateConvertPro = {"$project": {
  291. "date": {"$dateFromString": {"dateString": "$date"}}
  292. }}
  293. dateConvertPro['$project'].update(_include)
  294. pipeline.append(dateConvertPro) # 通过project将 date 的数据类型改变
  295. dateUnpackPro = {"$project": {
  296. "month": {"$month": "$date"},
  297. "year": {"$year": "$date"}
  298. }}
  299. dateUnpackPro['$project'].update(_include)
  300. if hasDay:
  301. dateUnpackPro['$project'].update({"day": {"$dayOfMonth": "$date"}})
  302. pipeline.append(dateUnpackPro) # 通过project 获取日期里面的年、月 为聚合做准备
  303. else:
  304. pipeline.append({'$project': _include})
  305. return pipeline
  306. @staticmethod
  307. def month_group_key():
  308. groupKey = {
  309. "_id": {"year": "$year", "month": "$month"}
  310. }
  311. return groupKey
  312. @staticmethod
  313. def day_group_key():
  314. groupKey = {
  315. "_id": {"year": "$year", "month": "$month", "day": "$day"}
  316. }
  317. return groupKey
  318. class DeviceDailyStat(DailyStat):
  319. logicalCode = StringField()
  320. _shard_key = ('logicalCode', 'dateTimeAdded')
  321. _origin_meta = {
  322. 'collection': 'device_daily_stats',
  323. 'indexes': [
  324. {'fields': ['logicalCode', 'date'], 'unique': True},
  325. ],
  326. 'db_alias': 'report',
  327. }
  328. meta = _origin_meta
  329. def __repr__(self):
  330. return '<DeviceDailyStat logicalCode=%s date=%s>' % (self.logicalCode, self.date)
  331. @classmethod
  332. def check_already_record(cls, record): # type:(Optional[ConsumeRecord, DealerIncomeProxy]) -> bool
  333. logicalCode = record.logicalCode
  334. query = {
  335. "logicalCode": logicalCode,
  336. "date": record.dateTimeAdded.strftime("%Y-%m-%d"),
  337. "origin.{}".format(record.statistic_type): record.id
  338. }
  339. return bool(cls.objects(__raw__ = query).count())
  340. @classmethod
  341. def update_statistic_data(cls, record): # type:(Optional[ConsumeRecord, DealerIncomeProxy]) -> bool
  342. logicalCode = record.logicalCode
  343. findQuery = {
  344. "logicalCode": logicalCode,
  345. "date": record.dateTimeAdded.strftime("%Y-%m-%d"),
  346. }
  347. updateOrInsertData = record.get_statistic_update_info()
  348. return cls.update_statistic(findQuery, updateOrInsertData)
  349. class GroupDailyStat(DailyStat):
  350. groupId = ObjectIdField()
  351. devices = ListField(ObjectIdField())
  352. _shard_key = ('groupId', 'dateTimeAdded')
  353. _origin_meta = {
  354. 'collection': 'group_daily_stats',
  355. 'indexes': [
  356. {'fields': ['groupId', 'date'], 'unique': True},
  357. ],
  358. 'db_alias': 'report'
  359. }
  360. meta = _origin_meta
  361. def __repr__(self):
  362. return '<GroupDailyStat groupId=%s date=%s>' % (self.groupId, self.date)
  363. @classmethod
  364. def get_today_recharge_count(cls, groupIds):
  365. today = datetime.datetime.now().strftime(Const.DATE_FMT)
  366. count, total = cls.objects(groupId__in = groupIds, date = today).sum_and_count("daily.totalIncomeCount")
  367. return total
  368. @classmethod
  369. def check_already_record(cls, record): # type:(Optional[ConsumeRecord, DealerIncomeProxy]) -> bool
  370. groupId = ObjectId(record.groupId)
  371. query = {
  372. "groupId": groupId,
  373. "date": record.dateTimeAdded.strftime("%Y-%m-%d"),
  374. "origin.{}".format(record.statistic_type): record.id
  375. }
  376. return bool(cls.objects(__raw__ = query).count())
  377. @classmethod
  378. def update_statistic_data(cls, record): # type:(Optional[ConsumeRecord, DealerIncomeProxy]) -> bool
  379. groupId = ObjectId(record.groupId)
  380. findQuery = {
  381. "groupId": groupId,
  382. "date": record.dateTimeAdded.strftime("%Y-%m-%d"),
  383. }
  384. updateOrInsertData = record.get_statistic_update_info()
  385. # 如果组统计的是 收益类型 需要为 收益进行Map的收益分成记录 模型如下
  386. """
  387. {
  388. "daily": {
  389. "consumption": {},
  390. "income": {},
  391. "incomeMap":{
  392. "5fdadcc418e358a57aa8f59a": RMB(1),
  393. "5fdadcc418e358a57aa8f59a": RMB(3),
  394. ...
  395. }
  396. },
  397. }
  398. """
  399. if record.statistic_type == "income":
  400. for _dealerId, _income in record.actualAmountMap.items():
  401. updateOrInsertData['inc__daily__incomeMap__{}'.format(str(_dealerId))] = RMB(_income).mongo_amount
  402. return cls.update_statistic(findQuery, updateOrInsertData)
  403. class DealerDailyStat(DailyStat):
  404. dealerId = ObjectIdField()
  405. groups = ListField(ObjectIdField())
  406. _shard_key = ('dealerId', 'dateTimeAdded')
  407. _origin_meta = {
  408. 'collection': 'dealer_daily_stats',
  409. 'indexes': [
  410. {'fields': ['dealerId', 'date'], 'unique': True},
  411. ],
  412. 'db_alias': 'report'
  413. }
  414. meta = _origin_meta
  415. @classmethod
  416. def get_numerical_value(cls, dealerId, date, keys):
  417. # type:(ObjectId, str, list)->Union[Decimal128, int, float]
  418. return get_in(keys, cls.objects(dealerId = dealerId, date = date).head(default = cls).daily, default = 0)
  419. @classmethod
  420. def get_recharge(cls, dealerId, date):
  421. # type:(ObjectId, str)->RMB
  422. return RMB(cls.get_numerical_value(dealerId = dealerId, date = date,
  423. keys = ['income', DEALER_INCOME_SOURCE.RECHARGE]))
  424. @classmethod
  425. def get_recharge_card(cls, dealerId, date):
  426. # type:(ObjectId, str)->RMB
  427. return RMB(cls.get_numerical_value(dealerId = dealerId, date = date,
  428. keys = ['income', DEALER_INCOME_SOURCE.RECHARGE_CARD]))
  429. @classmethod
  430. def get_recharge_virtual_card(cls, dealerId, date):
  431. # type:(ObjectId, str)->RMB
  432. return RMB(cls.get_numerical_value(dealerId = dealerId, date = date,
  433. keys = ['income', DEALER_INCOME_SOURCE.RECHARGE_VIRTUAL_CARD]))
  434. @classmethod
  435. def get_redpack(cls, dealerId, date):
  436. # type:(ObjectId, str)->RMB
  437. return RMB(cls.get_numerical_value(dealerId = dealerId, date = date,
  438. keys = ['income', DEALER_INCOME_SOURCE.REDPACK]))
  439. @classmethod
  440. def get_refund_cash(cls, dealerId, date):
  441. return RMB(cls.get_numerical_value(dealerId=dealerId, date=date,
  442. keys = ["income", DEALER_INCOME_SOURCE.REFUND_CASH]))
  443. @classmethod
  444. def get_today_recharge(cls, dealerId):
  445. # type:(ObjectId)->RMB
  446. today = datetime.datetime.now().strftime(Const.DATE_FMT)
  447. return cls.get_recharge(dealerId = dealerId, date = today)
  448. @classmethod
  449. def get_today_income(cls, dealerId):
  450. # type:(ObjectId)->RMB
  451. today = datetime.datetime.now().strftime(Const.DATE_FMT)
  452. return cls.get_recharge(dealerId, today) + \
  453. cls.get_recharge_card(dealerId, today) + \
  454. cls.get_recharge_virtual_card(dealerId, today) + \
  455. cls.get_redpack(dealerId, today)
  456. @classmethod
  457. def get_today_refund_cash(cls, dealerId):
  458. # type:(ObjectId)->RMB
  459. """
  460. 获取今日退款金额
  461. :param dealerId:
  462. :return:
  463. """
  464. today = datetime.datetime.now().strftime(Const.DATE_FMT)
  465. return cls.get_refund_cash(dealerId, today)
  466. @classmethod
  467. def get_today(cls, dealerId):
  468. # type:(ObjectId)->dict
  469. today = datetime.datetime.now().strftime(Const.DATE_FMT)
  470. return cls.objects(dealerId = dealerId, date = today).head(default = cls).daily
  471. @classmethod
  472. def get_someday_income(cls, dealerId, somedayTime):
  473. # type:(ObjectId)->RMB
  474. someday = somedayTime.strftime(Const.DATE_FMT)
  475. return cls.get_recharge(dealerId, someday) + cls.get_recharge_card(dealerId,
  476. someday) + cls.get_recharge_virtual_card(
  477. dealerId, someday)
  478. @staticmethod
  479. def get_someday_income_and_consume_count(cls, dealerId, somedayTime):
  480. somdayDate = somedayTime.strftime(Const.DATE_FMT)
  481. rcd = cls.get_collection().find({'dealerId': ObjectId(dealerId), 'date': somdayDate}, {'daily': 1})
  482. if len(rcd) == 0:
  483. return {'consumeTotalCount': 0, 'incomeTotalCount': 0}
  484. dayly = rcd[0].get('daily', {})
  485. return {
  486. 'consumeTotalCount': dayly.get('consumption', {}).get('totalCount', 0),
  487. 'incomeTotalCount': dayly.get('income', {}).get('totalCount', 0)
  488. }
  489. @classmethod
  490. def check_already_record(cls, record): # type:(Optional[ConsumeRecord, DealerIncomeProxy]) -> bool
  491. dealerIds = record.dealerIds
  492. query = {
  493. "dealerId": {"$in": dealerIds},
  494. "date": record.dateTimeAdded.strftime("%Y-%m-%d"),
  495. "origin.{}".format(record.statistic_type): record.id
  496. }
  497. return bool(cls.objects(__raw__ = query).count())
  498. @classmethod
  499. def update_statistic_data(cls, record): # type:(Optional[ConsumeRecord, DealerIncomeProxy]) -> bool
  500. dealerIds = record.dealerIds
  501. findQuery = {
  502. "date": record.dateTimeAdded.strftime("%Y-%m-%d"),
  503. }
  504. # 经销商的需要循环处理
  505. for _dealerId in dealerIds:
  506. findQuery.update({"dealerId": ObjectId(_dealerId)})
  507. if record.statistic_type == "income":
  508. amount = record.actualAmountMap[str(_dealerId)]
  509. else:
  510. amount = record.coin
  511. updateOrInsertData = record.get_statistic_update_info(amount = amount)
  512. cls.update_statistic(findQuery, updateOrInsertData)
  513. @staticmethod
  514. def dealer_group_key():
  515. return {
  516. '_id': '$dealerId'
  517. }
  518. def __repr__(self):
  519. return '<DealerDailyStat dealerId=%s date=%s>' % (self.dealerId, self.date)
  520. ############
  521. ## Month ###
  522. ##########
  523. class MonthlyStat(Searchable):
  524. date = StringField()
  525. origin = DictField()
  526. daily = DictField(default = default_month_stats)
  527. monthly = DictField(default = default_agg_map)
  528. meta = {
  529. 'abstract': True
  530. }
  531. class DeviceMonthlyStat(MonthlyStat):
  532. logicalCode = StringField()
  533. _shard_key = ('logicalCode', 'dateTimeAdded')
  534. _origin_meta = {
  535. 'collection': 'device_monthly_stats',
  536. 'indexes': [
  537. {'fields': ['logicalCode', 'date'], 'unique': True},
  538. ],
  539. 'db_alias': 'report'
  540. }
  541. meta = _origin_meta
  542. def __repr__(self):
  543. return '<DeviceMonthlyStat logicalCode=%s date=%s>' % (self.logicalCode, self.date)
  544. class GroupMonthlyStat(MonthlyStat):
  545. groupId = ObjectIdField()
  546. devices = ListField(ObjectIdField())
  547. _shard_key = ('groupId', 'dateTimeAdded')
  548. _origin_meta = {
  549. 'collection': 'group_monthly_stats',
  550. 'indexes': [
  551. {'fields': ['groupId', 'date'], 'unique': True},
  552. ],
  553. 'db_alias': 'report'
  554. }
  555. meta = _origin_meta
  556. def __repr__(self):
  557. return '<GroupMonthlyStat groupId=%s date=%s>' % (self.groupId, self.date)
  558. class DealerMonthlyStat(MonthlyStat):
  559. dealerId = ObjectIdField()
  560. groups = ListField(ObjectIdField())
  561. _shard_key = ('dealerId', 'dateTimeAdded')
  562. _origin_meta = {
  563. 'collection': 'dealer_monthly_stats',
  564. 'indexes': [
  565. {'fields': ['dealerId', 'date'], 'unique': True},
  566. ],
  567. 'db_alias': 'report'
  568. }
  569. meta = _origin_meta
  570. @classmethod
  571. def get_numerical_value(cls, dealerId, date, keys):
  572. # type:(ObjectId, str, list)->Union[Decimal128, int, float]
  573. assert isinstance(dealerId, ObjectId), 'dealerId has to be a ObjectId'
  574. return get_in(keys, cls.objects(dealerId = dealerId, date = date).head(default = cls).monthly, default = 0)
  575. @classmethod
  576. def get_recharge(cls, dealerId, date):
  577. # type:(ObjectId, str)->RMB
  578. return RMB(cls.get_numerical_value(dealerId = dealerId, date = date,
  579. keys = ['income', DEALER_INCOME_SOURCE.RECHARGE]))
  580. @classmethod
  581. def get_recharge_card(cls, dealerId, date):
  582. # type:(ObjectId, str)->RMB
  583. return RMB(cls.get_numerical_value(dealerId = dealerId, date = date,
  584. keys = ['income', DEALER_INCOME_SOURCE.RECHARGE_CARD]))
  585. @classmethod
  586. def get_recharge_virtual_card(cls, dealerId, date):
  587. # type:(ObjectId, str)->RMB
  588. return RMB(cls.get_numerical_value(dealerId = dealerId, date = date,
  589. keys = ['income', DEALER_INCOME_SOURCE.RECHARGE_VIRTUAL_CARD]))
  590. @classmethod
  591. def get_this_month_income(cls, dealerId):
  592. # type:(ObjectId)->RMB
  593. now = datetime.datetime.now()
  594. date = MONTH_DATE_KEY.format(year = now.year, month = now.month)
  595. return cls.get_recharge(dealerId = dealerId, date = date) + cls.get_recharge_card(dealerId = dealerId,
  596. date = date) + cls.get_recharge_virtual_card(
  597. dealerId = dealerId, date = date)
  598. @classmethod
  599. def get_someday_income_and_consume_count(cls, dealerId, somedayTime):
  600. date = MONTH_DATE_KEY.format(year = somedayTime.year, month = somedayTime.month)
  601. rcds = cls.get_collection().find({'dealerId': ObjectId(dealerId), 'date': date}, {'monthly': 1})
  602. if len(rcds) == 0:
  603. return {'consumeTotalCount': 0, 'incomeTotalCount': 0}
  604. monthly = rcds[0].get('monthly')
  605. return {'consumeTotalCount': monthly.get('cosumption', {}).get('totalCount'),
  606. 'incomeTotalCount': monthly.get('income', {}).get('totalCount')}
  607. def __repr__(self):
  608. return '<DealerMonthlyStat dealerId=%s date=%s>' % (self.dealerId, self.date)