kopia lustrzana https://github.com/longclawshop/longclaw
				
				
				
			Fixes #19
							rodzic
							
								
									8fb63a71cb
								
							
						
					
					
						commit
						bf643d5606
					
				
										
											Plik binarny nie jest wyświetlany.
										
									
								
							| 
		 Po Szerokość: | Wysokość: | Rozmiar: 113 KiB  | 
| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					from longclaw.longclawsettings.models import LongclawSettings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def currency(request):
 | 
				
			||||||
 | 
					    settings = LongclawSettings.for_site(request.site)
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        'currency_html_code': settings.currency_html_code,
 | 
				
			||||||
 | 
					        'currency': settings.currency
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,11 @@
 | 
				
			||||||
'''
 | 
					'''
 | 
				
			||||||
Various stats/analysis calculations
 | 
					Various stats/analysis calculations
 | 
				
			||||||
'''
 | 
					'''
 | 
				
			||||||
 | 
					import itertools
 | 
				
			||||||
import calendar
 | 
					import calendar
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
from django.db.models import Q
 | 
					from django.db.models import Q, Sum, F
 | 
				
			||||||
from longclaw.longclaworders.models import Order
 | 
					from longclaw.longclaworders.models import Order, OrderItem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def current_month():
 | 
					def current_month():
 | 
				
			||||||
| 
						 | 
					@ -24,3 +25,24 @@ def sales_for_time_period(from_date, to_date):
 | 
				
			||||||
    ).exclude(status=Order.CANCELLED)
 | 
					    ).exclude(status=Order.CANCELLED)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return sales
 | 
					    return sales
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def daily_sales(from_date, to_date):
 | 
				
			||||||
 | 
					    sales = sales_for_time_period(from_date, to_date)
 | 
				
			||||||
 | 
					    grouped = itertools.groupby(sales, lambda order: order.payment_date.strftime("%Y-%m-%d"))
 | 
				
			||||||
 | 
					    return grouped
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def sales_by_product(from_date, to_date):
 | 
				
			||||||
 | 
					    sales = OrderItem.objects.filter(
 | 
				
			||||||
 | 
					        Q(order__payment_date__lte=to_date) & Q(order__payment_date__gte=from_date)
 | 
				
			||||||
 | 
					    ).exclude(
 | 
				
			||||||
 | 
					        order__status=Order.CANCELLED
 | 
				
			||||||
 | 
					    ).annotate(
 | 
				
			||||||
 | 
					        title=F('product__product__title')
 | 
				
			||||||
 | 
					    ).values(
 | 
				
			||||||
 | 
					        'title'
 | 
				
			||||||
 | 
					    ).annotate(
 | 
				
			||||||
 | 
					        quantity=Sum('quantity')
 | 
				
			||||||
 | 
					    ).order_by('-quantity')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return sales
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +0,0 @@
 | 
				
			||||||
<li class="icon icon-doc-empty-inverse">
 | 
					 | 
				
			||||||
<a href="{{ url }}">
 | 
					 | 
				
			||||||
  <span>{{ total }}</span> {{ text }}
 | 
					 | 
				
			||||||
</a>
 | 
					 | 
				
			||||||
</li>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,97 @@
 | 
				
			||||||
 | 
					{% load i18n wagtailadmin_tags %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<section class="panel nice-padding">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <div class="col6">
 | 
				
			||||||
 | 
					    <canvas id="daily-sales" width="100" height="75"></canvas>
 | 
				
			||||||
 | 
					    <br>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <div class="col6">
 | 
				
			||||||
 | 
					    <canvas id="popular-products" width="100" height="75"></canvas>
 | 
				
			||||||
 | 
					    <br>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.bundle.min.js"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					  var ctx = document.getElementById("daily-sales");
 | 
				
			||||||
 | 
					  var data = {
 | 
				
			||||||
 | 
					    labels: {{ labels|safe }},
 | 
				
			||||||
 | 
					  datasets: [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      data: {{ daily_income }}
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  var dailySalesChart = new Chart(ctx, {
 | 
				
			||||||
 | 
					    type: 'line',
 | 
				
			||||||
 | 
					    data: data,
 | 
				
			||||||
 | 
					    options: {
 | 
				
			||||||
 | 
					      title: {
 | 
				
			||||||
 | 
					        display: true,
 | 
				
			||||||
 | 
					        text: 'Revenue this month'
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      legend: {
 | 
				
			||||||
 | 
					        display: false
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      scales: {
 | 
				
			||||||
 | 
					        xAxes: [{
 | 
				
			||||||
 | 
					              scaleLabel: {
 | 
				
			||||||
 | 
					                  display: true,
 | 
				
			||||||
 | 
					                  labelString: 'Date'
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }],
 | 
				
			||||||
 | 
					        yAxes: [{
 | 
				
			||||||
 | 
					          scaleLabel: {
 | 
				
			||||||
 | 
					              display: true,
 | 
				
			||||||
 | 
					              labelString: 'Revenue ({{currency}})'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var ctx_products = document.getElementById("popular-products");
 | 
				
			||||||
 | 
					  var data = {
 | 
				
			||||||
 | 
					    labels: {{ product_labels|safe }},
 | 
				
			||||||
 | 
					    datasets: [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        data: {{ sales_volume }}
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var popProductsChart = new Chart(ctx_products, {
 | 
				
			||||||
 | 
					    type: 'bar',
 | 
				
			||||||
 | 
					    data: data,
 | 
				
			||||||
 | 
					    options: {
 | 
				
			||||||
 | 
					      title: {
 | 
				
			||||||
 | 
					        display: true,
 | 
				
			||||||
 | 
					        text: 'Popular products this month'
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      scales: {
 | 
				
			||||||
 | 
					            yAxes: [{
 | 
				
			||||||
 | 
					                ticks: {
 | 
				
			||||||
 | 
					                    beginAtZero:true
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                scaleLabel: {
 | 
				
			||||||
 | 
					                  display: true,
 | 
				
			||||||
 | 
					                  labelString: 'No. Sales'
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }],
 | 
				
			||||||
 | 
					            xAxes: [{
 | 
				
			||||||
 | 
					              scaleLabel: {
 | 
				
			||||||
 | 
					                  display: true,
 | 
				
			||||||
 | 
					                  labelString: 'Product Title'
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }]
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      legend: {
 | 
				
			||||||
 | 
					        display: false
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					<li class="icon {{ icon }}">
 | 
				
			||||||
 | 
					<a href="{{ url }}">
 | 
				
			||||||
 | 
					  <span>{{ total|safe }}</span> {{ text }}
 | 
				
			||||||
 | 
					</a>
 | 
				
			||||||
 | 
					</li>
 | 
				
			||||||
| 
						 | 
					@ -1,47 +1,94 @@
 | 
				
			||||||
 | 
					import datetime
 | 
				
			||||||
from wagtail.wagtailcore import hooks
 | 
					from wagtail.wagtailcore import hooks
 | 
				
			||||||
from wagtail.wagtailadmin.site_summary import SummaryItem
 | 
					from wagtail.wagtailadmin.site_summary import SummaryItem
 | 
				
			||||||
from longclaw.longclaworders.models import Order
 | 
					from longclaw.longclaworders.models import Order
 | 
				
			||||||
from longclaw.longclawproducts.models import Product
 | 
					from longclaw.longclawproducts.models import Product
 | 
				
			||||||
from longclaw.longclawstats import stats
 | 
					from longclaw.longclawstats import stats
 | 
				
			||||||
 | 
					from longclaw.longclawsettings.models import LongclawSettings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class OutstandingOrdersSummary(SummaryItem):
 | 
					
 | 
				
			||||||
 | 
					class LongclawSummaryItem(SummaryItem):
 | 
				
			||||||
 | 
					    order = 10
 | 
				
			||||||
 | 
					    template = 'longclawstats/summary_item.html'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_context(self):
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'total': 0,
 | 
				
			||||||
 | 
					            'text': '',
 | 
				
			||||||
 | 
					            'url': '',
 | 
				
			||||||
 | 
					            'icon': 'icon-doc-empty-inverse'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class OutstandingOrders(LongclawSummaryItem):
 | 
				
			||||||
    order = 10
 | 
					    order = 10
 | 
				
			||||||
    template = 'longclawhome/summary_item.html'
 | 
					 | 
				
			||||||
    def get_context(self):
 | 
					    def get_context(self):
 | 
				
			||||||
        orders = Order.objects.filter(status=Order.SUBMITTED)
 | 
					        orders = Order.objects.filter(status=Order.SUBMITTED)
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'total': orders.count(),
 | 
					            'total': orders.count(),
 | 
				
			||||||
            'text': 'Outstanding Orders',
 | 
					            'text': 'Outstanding Orders',
 | 
				
			||||||
            'url': '/admin/longclaworders/order/'
 | 
					            'url': '/admin/longclaworders/order/',
 | 
				
			||||||
 | 
					            'icon': 'icon-warning'
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ProductCount(SummaryItem):
 | 
					class ProductCount(LongclawSummaryItem):
 | 
				
			||||||
    order = 20
 | 
					    order = 20
 | 
				
			||||||
    template = 'longclawhome/summary_item.html'
 | 
					 | 
				
			||||||
    def get_context(self):
 | 
					    def get_context(self):
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'total': Product.objects.all().count(),
 | 
					            'total': Product.objects.all().count(),
 | 
				
			||||||
            'text': 'Products',
 | 
					            'text': 'Products',
 | 
				
			||||||
            'url': ''
 | 
					            'url': '',
 | 
				
			||||||
 | 
					            'icon': 'icon-list-ul'
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MonthlySales(SummaryItem):
 | 
					class MonthlySales(LongclawSummaryItem):
 | 
				
			||||||
    order = 30
 | 
					    order = 30
 | 
				
			||||||
    template = 'longclawhome/summary_item.html'
 | 
					 | 
				
			||||||
    def get_context(self):
 | 
					    def get_context(self):
 | 
				
			||||||
 | 
					        settings = LongclawSettings.for_site(self.request.site)
 | 
				
			||||||
        sales = stats.sales_for_time_period(*stats.current_month())
 | 
					        sales = stats.sales_for_time_period(*stats.current_month())
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'total': sum(order.total for order in sales),
 | 
					            'total': "{}{}".format(settings.currency_html_code,
 | 
				
			||||||
            'text': 'Sales this month',
 | 
					                                   sum(order.total for order in sales)),
 | 
				
			||||||
            'url': '/admin/longclaworders/order/'
 | 
					            'text': 'In sales this month',
 | 
				
			||||||
 | 
					            'url': '/admin/longclaworders/order/',
 | 
				
			||||||
 | 
					            'icon': 'icon-tick'
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LongclawStatsPanel(SummaryItem):
 | 
				
			||||||
 | 
					    order = 110
 | 
				
			||||||
 | 
					    template = 'longclawstats/stats_panel.html'
 | 
				
			||||||
 | 
					    def get_context(self):
 | 
				
			||||||
 | 
					        month_start, month_end = stats.current_month()
 | 
				
			||||||
 | 
					        daily_sales = stats.daily_sales(month_start, month_end)
 | 
				
			||||||
 | 
					        labels = [(month_start + datetime.timedelta(days=x)).strftime('%Y-%m-%d')
 | 
				
			||||||
 | 
					                  for x in range(0, datetime.datetime.now().day)]
 | 
				
			||||||
 | 
					        daily_income = [0] * len(labels)
 | 
				
			||||||
 | 
					        for k, order_group in daily_sales:
 | 
				
			||||||
 | 
					            i = labels.index(k)
 | 
				
			||||||
 | 
					            daily_income[i] = float(sum(order.total for order in order_group))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        popular_products = stats.sales_by_product(month_start, month_end)[:5]
 | 
				
			||||||
 | 
					        print(popular_products)
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            "daily_income": daily_income,
 | 
				
			||||||
 | 
					            "labels": labels,
 | 
				
			||||||
 | 
					            "product_labels": list(popular_products.values_list('title', flat=True)),
 | 
				
			||||||
 | 
					            "sales_volume": list(popular_products.values_list('quantity', flat=True))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@hooks.register('construct_homepage_summary_items')
 | 
					@hooks.register('construct_homepage_summary_items')
 | 
				
			||||||
def add_longclaw_summary_items(request, items):
 | 
					def add_longclaw_summary_items(request, items):
 | 
				
			||||||
    for item in items:
 | 
					
 | 
				
			||||||
        items.remove(item)
 | 
					    # We are going to replace everything with our own items
 | 
				
			||||||
 | 
					    items[:] = []
 | 
				
			||||||
    items.extend([
 | 
					    items.extend([
 | 
				
			||||||
        OutstandingOrdersSummary(request),
 | 
					        OutstandingOrders(request),
 | 
				
			||||||
        ProductCount(request),
 | 
					        ProductCount(request),
 | 
				
			||||||
        MonthlySales(request)
 | 
					        MonthlySales(request)
 | 
				
			||||||
    ])
 | 
					    ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@hooks.register('construct_homepage_panels')
 | 
				
			||||||
 | 
					def add_stats_panel(request, panels):
 | 
				
			||||||
 | 
					    return panels.append(LongclawStatsPanel(request))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -93,6 +93,7 @@ TEMPLATES = [
 | 
				
			||||||
                'django.template.context_processors.request',
 | 
					                'django.template.context_processors.request',
 | 
				
			||||||
                'django.contrib.auth.context_processors.auth',
 | 
					                'django.contrib.auth.context_processors.auth',
 | 
				
			||||||
                'django.contrib.messages.context_processors.messages',
 | 
					                'django.contrib.messages.context_processors.messages',
 | 
				
			||||||
 | 
					                'longclaw.longclawsettings.context_processors.currency',
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Ładowanie…
	
		Reference in New Issue