| @@ -86,6 +86,7 @@ def decrypt(request): | ||
| 86 | 86 | 'Brand': brand.brand_name if brand else '', | 
| 87 | 87 | 'ModelID': model_pk, | 
| 88 | 88 | 'Model': (model.model_full_name or model.model_name) if model else '', | 
| 89 | + 'DistributorID': distributor_pk, | |
| 89 | 90 | 'SerialNo': sn, | 
| 90 | 91 | } | 
| 91 | 92 | }) | 
| @@ -18,6 +18,7 @@ from page import oauth_views, sale_views | ||
| 18 | 18 | from pay import views as pay_views | 
| 19 | 19 | from photo import views as photo_views | 
| 20 | 20 | from server import server_views | 
| 21 | +from statistic import views as tj_views | |
| 21 | 22 |  | 
| 22 | 23 |  | 
| 23 | 24 | # 帐户相关 | 
| @@ -224,3 +225,7 @@ urlpatterns += [ | ||
| 224 | 225 | urlpatterns += [ | 
| 225 | 226 | url(r'^membercard_extradata$', mini_views.membercard_extradata, name='membercard_extradata'), # | 
| 226 | 227 | ] | 
| 228 | + | |
| 229 | +urlpatterns += [ | |
| 230 | + url(r'^tj$', tj_views.tj_data, name='tj_data'), # 统计数据 | |
| 231 | +] | 
| @@ -62,6 +62,7 @@ INSTALLED_APPS = ( | ||
| 62 | 62 | 'photo', | 
| 63 | 63 | 'product', | 
| 64 | 64 | 'server', | 
| 65 | + 'statistic', | |
| 65 | 66 | 'website', | 
| 66 | 67 | ) | 
| 67 | 68 |  | 
| @@ -12,6 +12,7 @@ from pywe_storage import RedisStorage | ||
| 12 | 12 | from TimeConvert import TimeConvert as tc | 
| 13 | 13 |  | 
| 14 | 14 | from account.models import UserInfo | 
| 15 | +from statistic.models import RegisterStatisticInfo | |
| 15 | 16 | from utils.redis.connect import r | 
| 16 | 17 | from utils.redis.rprofile import set_profile_info | 
| 17 | 18 |  | 
| @@ -49,6 +50,12 @@ def get_userinfo_api(request): | ||
| 49 | 50 | # Set User_id | 
| 50 | 51 | if created: | 
| 51 | 52 | user.user_id = CurtailUUID.uuid(UserInfo, 'user_id') | 
| 53 | + # 注册用户统计 | |
| 54 | + rsi, _ = RegisterStatisticInfo.objects.select_for_update().get_or_create( | |
| 55 | + ymd=tc.local_string(format='%Y%m%d'), | |
| 56 | + ) | |
| 57 | + rsi.num += 1 | |
| 58 | + rsi.save() | |
| 52 | 59 |  | 
| 53 | 60 | # Set User Key's Value | 
| 54 | 61 | user.user_from = UserInfo.MINIAPP_USER | 
| @@ -3,16 +3,19 @@ | ||
| 3 | 3 | from __future__ import division | 
| 4 | 4 |  | 
| 5 | 5 | from django.conf import settings | 
| 6 | +from django.db import transaction | |
| 6 | 7 | from django.shortcuts import render | 
| 7 | 8 | from django_logit import logit | 
| 8 | 9 | from django_response import response | 
| 9 | 10 | from paginator import pagination | 
| 11 | +from TimeConvert import TimeConvert as tc | |
| 10 | 12 |  | 
| 11 | 13 | from account.models import SaleclerkInfo, UserInfo | 
| 12 | 14 | from integral.models import SaleclerkIntegralIncomeExpensesInfo, SaleclerkSubmitLogInfo | 
| 13 | -from mch.models import ModelInfo | |
| 15 | +from mch.models import DistributorInfo, ModelInfo | |
| 14 | 16 | from product.models import ProductModelInfo | 
| 15 | -from utils.error.errno_utils import ProductModelStatusCode, SaleclerkStatusCode | |
| 17 | +from statistic.models import DistributorSaleStatisticInfo, ModelSaleStatisticInfo, SaleStatisticInfo | |
| 18 | +from utils.error.errno_utils import ProductDistributorStatusCode, ProductModelStatusCode, SaleclerkStatusCode | |
| 16 | 19 |  | 
| 17 | 20 |  | 
| 18 | 21 | def clerk_sale_oauth(request): | 
| @@ -34,6 +37,7 @@ def clerk_sale_oauth(request): | ||
| 34 | 37 |  | 
| 35 | 38 |  | 
| 36 | 39 | @logit | 
| 40 | +@transaction.atomic | |
| 37 | 41 | def clerk_sale_submit_api(request): | 
| 38 | 42 |      user_id = request.POST.get('user_id', '') | 
| 39 | 43 |      iv = request.POST.get('iv', '') | 
| @@ -41,6 +45,7 @@ def clerk_sale_submit_api(request): | ||
| 41 | 45 |      lat = float(request.POST.get('lat', 0)) | 
| 42 | 46 |      lon = float(request.POST.get('lon', 0)) | 
| 43 | 47 |      modelID = request.POST.get('ModelID', '') | 
| 48 | +    distributorID = request.POST.get('DistributorID', '') | |
| 44 | 49 |      serialNo = request.POST.get('SerialNo', '') | 
| 45 | 50 |      verifyResult = request.POST.get('verifyResult', '') | 
| 46 | 51 |  | 
| @@ -97,6 +102,11 @@ def clerk_sale_submit_api(request): | ||
| 97 | 102 | except ModelInfo.DoesNotExist: | 
| 98 | 103 | return response(ProductModelStatusCode.MODEL_NOT_FOUND) | 
| 99 | 104 |  | 
| 105 | + try: | |
| 106 | + distributor = DistributorInfo.objects.get(pk=distributorID) | |
| 107 | + except DistributorInfo.DoesNotExist: | |
| 108 | + return response(ProductDistributorStatusCode.DISTRIBUTOR_NOT_FOUND) | |
| 109 | + | |
| 100 | 110 | integral = model.integral | 
| 101 | 111 |  | 
| 102 | 112 | clerk.integral += integral | 
| @@ -121,6 +131,34 @@ def clerk_sale_submit_api(request): | ||
| 121 | 131 | test_user=clerk.test_user, | 
| 122 | 132 | ) | 
| 123 | 133 |  | 
| 134 | + if not sci: | |
| 135 | + ymd = tc.local_string(format='%Y%m%d') | |
| 136 | + | |
| 137 | + # 销量统计 | |
| 138 | + ssi, _ = SaleStatisticInfo.objects.select_for_update().get_or_create( | |
| 139 | + ymd=ymd, | |
| 140 | + ) | |
| 141 | + ssi.num += 1 | |
| 142 | + ssi.save() | |
| 143 | + | |
| 144 | + # 型号销量统计 | |
| 145 | + mssi, _ = ModelSaleStatisticInfo.objects.select_for_update().get_or_create( | |
| 146 | + model_id=modelID, | |
| 147 | + ymd=ymd, | |
| 148 | + ) | |
| 149 | + mssi.model_name = model.model_name | |
| 150 | + mssi.num += 1 | |
| 151 | + mssi.save() | |
| 152 | + | |
| 153 | + # 经销商销量统计 | |
| 154 | + dssi, _ = DistributorSaleStatisticInfo.objects.select_for_update().get_or_create( | |
| 155 | + distributor_id=distributorID, | |
| 156 | + ymd=ymd, | |
| 157 | + ) | |
| 158 | + dssi.distributor_name = distributor.distributor_name | |
| 159 | + dssi.num += 1 | |
| 160 | + dssi.save() | |
| 161 | + | |
| 124 | 162 |      return response(200, data={ | 
| 125 | 163 | 'integral': integral, | 
| 126 | 164 | 'total_integral': clerk.integral, | 
| @@ -0,0 +1,7 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | +from __future__ import unicode_literals | |
| 3 | + | |
| 4 | +from django.contrib import admin | |
| 5 | + | |
| 6 | + | |
| 7 | +# Register your models here. | 
| @@ -0,0 +1,8 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | +from __future__ import unicode_literals | |
| 3 | + | |
| 4 | +from django.apps import AppConfig | |
| 5 | + | |
| 6 | + | |
| 7 | +class StatisticConfig(AppConfig): | |
| 8 | + name = 'statistic' | 
| @@ -0,0 +1,80 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | +# Generated by Django 1.11.11 on 2018-04-22 17:45 | |
| 3 | +from __future__ import unicode_literals | |
| 4 | + | |
| 5 | +from django.db import migrations, models | |
| 6 | + | |
| 7 | + | |
| 8 | +class Migration(migrations.Migration): | |
| 9 | + | |
| 10 | + initial = True | |
| 11 | + | |
| 12 | + dependencies = [ | |
| 13 | + ] | |
| 14 | + | |
| 15 | + operations = [ | |
| 16 | + migrations.CreateModel( | |
| 17 | + name='DistributorSaleStatisticInfo', | |
| 18 | + fields=[ | |
| 19 | +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
| 20 | +                ('status', models.BooleanField(db_index=True, default=True, help_text='Status', verbose_name='status')), | |
| 21 | +                ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')), | |
| 22 | +                ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')), | |
| 23 | +                ('distributor_id', models.CharField(db_index=True, help_text='\u7ecf\u9500\u5546\u552f\u4e00\u6807\u8bc6', max_length=32, verbose_name='distributor_id')), | |
| 24 | +                ('distributor_name', models.CharField(blank=True, help_text='\u7ecf\u9500\u5546\u540d\u79f0', max_length=255, null=True, verbose_name='distributor_name')), | |
| 25 | +                ('ymd', models.CharField(blank=True, db_index=True, help_text='\u5e74\u6708\u65e5', max_length=8, null=True, verbose_name='ymd')), | |
| 26 | +                ('num', models.IntegerField(default=0, help_text='\u6570\u91cf', verbose_name='num')), | |
| 27 | + ], | |
| 28 | +            options={ | |
| 29 | + 'verbose_name': '\u7ecf\u9500\u5546\u9500\u91cf\u7edf\u8ba1', | |
| 30 | + 'verbose_name_plural': '\u7ecf\u9500\u5546\u9500\u91cf\u7edf\u8ba1', | |
| 31 | + }, | |
| 32 | + ), | |
| 33 | + migrations.CreateModel( | |
| 34 | + name='ModelSaleStatisticInfo', | |
| 35 | + fields=[ | |
| 36 | +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
| 37 | +                ('status', models.BooleanField(db_index=True, default=True, help_text='Status', verbose_name='status')), | |
| 38 | +                ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')), | |
| 39 | +                ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')), | |
| 40 | +                ('model_id', models.CharField(db_index=True, help_text='\u578b\u53f7\u552f\u4e00\u6807\u8bc6', max_length=32, verbose_name='model_id')), | |
| 41 | +                ('model_name', models.CharField(blank=True, help_text='\u578b\u53f7\u540d\u79f0', max_length=255, null=True, verbose_name='model_name')), | |
| 42 | +                ('ymd', models.CharField(blank=True, db_index=True, help_text='\u5e74\u6708\u65e5', max_length=8, null=True, verbose_name='ymd')), | |
| 43 | +                ('num', models.IntegerField(default=0, help_text='\u6570\u91cf', verbose_name='num')), | |
| 44 | + ], | |
| 45 | +            options={ | |
| 46 | + 'verbose_name': '\u578b\u53f7\u9500\u91cf\u7edf\u8ba1', | |
| 47 | + 'verbose_name_plural': '\u578b\u53f7\u9500\u91cf\u7edf\u8ba1', | |
| 48 | + }, | |
| 49 | + ), | |
| 50 | + migrations.CreateModel( | |
| 51 | + name='RegisterStatisticInfo', | |
| 52 | + fields=[ | |
| 53 | +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
| 54 | +                ('status', models.BooleanField(db_index=True, default=True, help_text='Status', verbose_name='status')), | |
| 55 | +                ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')), | |
| 56 | +                ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')), | |
| 57 | +                ('ymd', models.CharField(blank=True, db_index=True, help_text='\u5e74\u6708\u65e5', max_length=8, null=True, verbose_name='ymd')), | |
| 58 | +                ('num', models.IntegerField(default=0, help_text='\u6570\u91cf', verbose_name='num')), | |
| 59 | + ], | |
| 60 | +            options={ | |
| 61 | + 'verbose_name': '\u6ce8\u518c\u7528\u6237\u7edf\u8ba1', | |
| 62 | + 'verbose_name_plural': '\u6ce8\u518c\u7528\u6237\u7edf\u8ba1', | |
| 63 | + }, | |
| 64 | + ), | |
| 65 | + migrations.CreateModel( | |
| 66 | + name='SaleStatisticInfo', | |
| 67 | + fields=[ | |
| 68 | +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
| 69 | +                ('status', models.BooleanField(db_index=True, default=True, help_text='Status', verbose_name='status')), | |
| 70 | +                ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')), | |
| 71 | +                ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')), | |
| 72 | +                ('ymd', models.CharField(blank=True, db_index=True, help_text='\u5e74\u6708\u65e5', max_length=8, null=True, verbose_name='ymd')), | |
| 73 | +                ('num', models.IntegerField(default=0, help_text='\u6570\u91cf', verbose_name='num')), | |
| 74 | + ], | |
| 75 | +            options={ | |
| 76 | + 'verbose_name': '\u9500\u91cf\u7edf\u8ba1', | |
| 77 | + 'verbose_name_plural': '\u9500\u91cf\u7edf\u8ba1', | |
| 78 | + }, | |
| 79 | + ), | |
| 80 | + ] | 
| @@ -0,0 +1,89 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | + | |
| 3 | +from django.db import models | |
| 4 | +from django.utils.translation import ugettext_lazy as _ | |
| 5 | +from django_models_ext import BaseModelMixin | |
| 6 | + | |
| 7 | + | |
| 8 | +class RegisterStatisticInfo(BaseModelMixin): | |
| 9 | + ymd = models.CharField(_(u'ymd'), max_length=8, blank=True, null=True, help_text=u'年月日', db_index=True) # 例:20171208, tc.local_string(format='%Y%m%d') | |
| 10 | + num = models.IntegerField(_(u'num'), default=0, help_text=u'数量') | |
| 11 | + | |
| 12 | + class Meta: | |
| 13 | + verbose_name = _(u'注册用户统计') | |
| 14 | + verbose_name_plural = _(u'注册用户统计') | |
| 15 | + | |
| 16 | + def __unicode__(self): | |
| 17 | + return unicode(self.pk) | |
| 18 | + | |
| 19 | + @property | |
| 20 | + def data(self): | |
| 21 | +        return { | |
| 22 | + 'ymd': self.ymd, | |
| 23 | + 'num': self.num, | |
| 24 | + } | |
| 25 | + | |
| 26 | + | |
| 27 | +class SaleStatisticInfo(BaseModelMixin): | |
| 28 | + ymd = models.CharField(_(u'ymd'), max_length=8, blank=True, null=True, help_text=u'年月日', db_index=True) # 例:20171208, tc.local_string(format='%Y%m%d') | |
| 29 | + num = models.IntegerField(_(u'num'), default=0, help_text=u'数量') | |
| 30 | + | |
| 31 | + class Meta: | |
| 32 | + verbose_name = _(u'销量统计') | |
| 33 | + verbose_name_plural = _(u'销量统计') | |
| 34 | + | |
| 35 | + def __unicode__(self): | |
| 36 | + return unicode(self.pk) | |
| 37 | + | |
| 38 | + @property | |
| 39 | + def data(self): | |
| 40 | +        return { | |
| 41 | + 'ymd': self.ymd, | |
| 42 | + 'num': self.num, | |
| 43 | + } | |
| 44 | + | |
| 45 | + | |
| 46 | +class ModelSaleStatisticInfo(BaseModelMixin): | |
| 47 | + model_id = models.CharField(_(u'model_id'), max_length=32, help_text=u'型号唯一标识', db_index=True) | |
| 48 | + model_name = models.CharField(_(u'model_name'), max_length=255, blank=True, null=True, help_text=u'型号名称') | |
| 49 | + ymd = models.CharField(_(u'ymd'), max_length=8, blank=True, null=True, help_text=u'年月日', db_index=True) # 例:20171208, tc.local_string(format='%Y%m%d') | |
| 50 | + num = models.IntegerField(_(u'num'), default=0, help_text=u'数量') | |
| 51 | + | |
| 52 | + class Meta: | |
| 53 | + verbose_name = _(u'型号销量统计') | |
| 54 | + verbose_name_plural = _(u'型号销量统计') | |
| 55 | + | |
| 56 | + def __unicode__(self): | |
| 57 | + return unicode(self.pk) | |
| 58 | + | |
| 59 | + @property | |
| 60 | + def data(self): | |
| 61 | +        return { | |
| 62 | + 'model_id': self.model_id, | |
| 63 | + 'model_name': self.model_name, | |
| 64 | + 'ymd': self.ymd, | |
| 65 | + 'num': self.num, | |
| 66 | + } | |
| 67 | + | |
| 68 | + | |
| 69 | +class DistributorSaleStatisticInfo(BaseModelMixin): | |
| 70 | + distributor_id = models.CharField(_(u'distributor_id'), max_length=32, help_text=u'经销商唯一标识', db_index=True) | |
| 71 | + distributor_name = models.CharField(_(u'distributor_name'), max_length=255, blank=True, null=True, help_text=u'经销商名称') | |
| 72 | + ymd = models.CharField(_(u'ymd'), max_length=8, blank=True, null=True, help_text=u'年月日', db_index=True) # 例:20171208, tc.local_string(format='%Y%m%d') | |
| 73 | + num = models.IntegerField(_(u'num'), default=0, help_text=u'数量') | |
| 74 | + | |
| 75 | + class Meta: | |
| 76 | + verbose_name = _(u'经销商销量统计') | |
| 77 | + verbose_name_plural = _(u'经销商销量统计') | |
| 78 | + | |
| 79 | + def __unicode__(self): | |
| 80 | + return unicode(self.pk) | |
| 81 | + | |
| 82 | + @property | |
| 83 | + def data(self): | |
| 84 | +        return { | |
| 85 | + 'distributor_id': self.distributor_id, | |
| 86 | + 'distributor_name': self.distributor_name, | |
| 87 | + 'ymd': self.ymd, | |
| 88 | + 'num': self.num, | |
| 89 | + } | 
| @@ -0,0 +1,7 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | +from __future__ import unicode_literals | |
| 3 | + | |
| 4 | +from django.test import TestCase | |
| 5 | + | |
| 6 | + | |
| 7 | +# Create your tests here. | 
| @@ -0,0 +1,50 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | + | |
| 3 | +from django_logit import logit | |
| 4 | +from django_response import response | |
| 5 | +from TimeConvert import TimeConvert as tc | |
| 6 | + | |
| 7 | +from statistic.models import (DistributorSaleStatisticInfo, ModelSaleStatisticInfo, RegisterStatisticInfo, | |
| 8 | + SaleStatisticInfo) | |
| 9 | + | |
| 10 | + | |
| 11 | +@logit | |
| 12 | +def tj_data(request): | |
| 13 | + ymd = tc.local_string(format='%Y%m%d') | |
| 14 | + | |
| 15 | + # 注册用户统计 & 今日注册用户 | |
| 16 | + try: | |
| 17 | + register_num = RegisterStatisticInfo.objects.get(ymd=ymd).num | |
| 18 | + except RegisterStatisticInfo.DoesNotExist: | |
| 19 | + register_num = 0 | |
| 20 | + | |
| 21 | + # 注册用户数趋势 | |
| 22 | +    register_trends = RegisterStatisticInfo.objects.filter(status=True).order_by('-pk')[:30] | |
| 23 | + register_trends = [r.data for r in register_trends] | |
| 24 | + | |
| 25 | + # 销量统计 & 今日销量 | |
| 26 | + try: | |
| 27 | + sale_num = SaleStatisticInfo.objects.get(ymd=ymd).num | |
| 28 | + except RegisterStatisticInfo.DoesNotExist: | |
| 29 | + sale_num = 0 | |
| 30 | + | |
| 31 | + # 商品销量趋势 | |
| 32 | +    sale_trends = SaleStatisticInfo.objects.filter(status=True).order_by('-pk')[:30] | |
| 33 | + sale_trends = [s.data for s in sale_trends] | |
| 34 | + | |
| 35 | + # 型号销量统计 & 热销商品榜 | |
| 36 | +    model_sales = ModelSaleStatisticInfo.objects.filter(status=True).order_by('-num')[:3] | |
| 37 | + model_sales = [m.data for m in model_sales] | |
| 38 | + | |
| 39 | + # 经销商销量统计 & 经销商榜 | |
| 40 | +    distributor_sales = DistributorSaleStatisticInfo.objects.filter(status=True).order_by('-num')[:3] | |
| 41 | + distributor_sales = [d.data for d in distributor_sales] | |
| 42 | + | |
| 43 | +    return response(200, 'Get TJ Data Success', u'获取统计数据成功', { | |
| 44 | + 'register_num': register_num, | |
| 45 | + 'register_trends': register_trends, | |
| 46 | + 'sale_num': sale_num, | |
| 47 | + 'sale_trends': sale_trends, | |
| 48 | + 'model_sales': model_sales, | |
| 49 | + 'distributor_sales': distributor_sales, | |
| 50 | + }) | 
| @@ -25,6 +25,11 @@ class ProductModelStatusCode(BaseStatusCode): | ||
| 25 | 25 | MODEL_NOT_FOUND = StatusCodeField(501001, 'Model Not Found', description=u'型号不存在') | 
| 26 | 26 |  | 
| 27 | 27 |  | 
| 28 | +class ProductDistributorStatusCode(BaseStatusCode): | |
| 29 | + """ 经销商相关错误码 5011xx """ | |
| 30 | + DISTRIBUTOR_NOT_FOUND = StatusCodeField(501101, 'Distributor Not Found', description=u'经销商不存在') | |
| 31 | + | |
| 32 | + | |
| 28 | 33 | class ProductStatusCode(BaseStatusCode): | 
| 29 | 34 | """ 产品相关错误码 5020xx """ | 
| 30 | 35 | PRODUCT_NOT_FOUND = StatusCodeField(502001, 'Product Not Found', description=u'产品不存在') |