diff --git a/billard/admin.py b/billard/admin.py index 2caca6d..062d815 100644 --- a/billard/admin.py +++ b/billard/admin.py @@ -57,4 +57,8 @@ class DeskAdmin(admin.ModelAdmin): @admin.register(Accounting) class AccountingAdmin(admin.ModelAdmin): - list_display = ('desk', 'time_from', 'time_to', 'prize', 'billed') + list_display = ('desk', 'time_from', 'time_to', 'prize', 'billed', 'account_user', 'account_tst') + list_filter = ('desk__client__location', 'account_user', 'account_tst', 'billed') + + def has_add_permission(self, request): + return False diff --git a/billard/migrations/0001_initial.py b/billard/migrations/0001_initial.py index 143db55..6a2e816 100644 --- a/billard/migrations/0001_initial.py +++ b/billard/migrations/0001_initial.py @@ -10,6 +10,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('sessions', '0001_initial'), ] operations = [ diff --git a/billard/migrations/0021_accounting_account_user.py b/billard/migrations/0021_accounting_account_user.py new file mode 100644 index 0000000..a36ff21 --- /dev/null +++ b/billard/migrations/0021_accounting_account_user.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-04-26 10:17 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('billard', '0020_auto_20170410_1853'), + ] + + operations = [ + migrations.AddField( + model_name='accounting', + name='account_user', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Abr. Benutzer'), + ), + ] diff --git a/billard/migrations/0022_auto_20170427_0835.py b/billard/migrations/0022_auto_20170427_0835.py new file mode 100644 index 0000000..96e686e --- /dev/null +++ b/billard/migrations/0022_auto_20170427_0835.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-04-27 08:35 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('billard', '0021_accounting_account_user'), + ] + + operations = [ + migrations.AlterField( + model_name='client', + name='location', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='clients', to='billard.Location', verbose_name='Standort'), + ), + ] diff --git a/billard/migrations/0023_accounting_account_tst.py b/billard/migrations/0023_accounting_account_tst.py new file mode 100644 index 0000000..7fe5c71 --- /dev/null +++ b/billard/migrations/0023_accounting_account_tst.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-04-29 11:09 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('billard', '0022_auto_20170427_0835'), + ] + + operations = [ + migrations.AddField( + model_name='accounting', + name='account_tst', + field=models.DateTimeField(blank=True, null=True, verbose_name='Abr. TST'), + ), + ] diff --git a/billard/models.py b/billard/models.py index 33aedfe..65d6875 100644 --- a/billard/models.py +++ b/billard/models.py @@ -2,8 +2,6 @@ import uuid import logging from django.db import models from django.contrib.auth.models import User -from datetime import datetime, timezone -from billard import utils from django.contrib.auth.models import User from django.db.models.signals import post_save from django.dispatch import receiver @@ -50,7 +48,7 @@ class Location(models.Model): class Client(models.Model): uuid = models.UUIDField(unique=True, default=uuid.uuid4, verbose_name="Identifier") - location = models.ForeignKey(Location, verbose_name="Standort") + location = models.ForeignKey(Location, related_name="clients", verbose_name="Standort") report_user = models.ForeignKey(User, blank=True, null=True, verbose_name="Reporting Benutzer", related_name='reporting_clients') def __str__(self): @@ -70,7 +68,6 @@ class Desk(models.Model): prize_hh = models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True, verbose_name="Preis Happy Hour") - def __str__(self): return '{}, {}'.format(self.client.uuid, self.name) @@ -88,6 +85,8 @@ class Accounting(models.Model): reporter_uuid = models.UUIDField(blank=True, null=True, verbose_name='Reporter UUID') prize_normal = models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True, verbose_name="Preis Normalzeit") prize_hh = models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True, verbose_name="Preis Happy Hour") + account_user = models.CharField(blank=True, null=True, max_length=128, verbose_name="Abr. Benutzer") + account_tst = models.DateTimeField(blank=True, null=True, verbose_name="Abr. TST") def __str__(self): return '{}: {} -> {}, {}, {}'.format(self.desk, self.time_from, self.time_to, self.prize, self.billed) diff --git a/billard/templates/billard/accounting.html b/billard/templates/billard/accounting.html index 112beae..207b502 100644 --- a/billard/templates/billard/accounting.html +++ b/billard/templates/billard/accounting.html @@ -1,24 +1,14 @@ {% extends 'billard/base.html' %} {% load display_client %} -{% block title %}Accounting Data{% endblock %} +{% block title %}Abrechnung{% endblock %} {% block content %} -{% if not locations|length_is:"1" %} -
-{% csrf_token %} -
- -
-
-{% endif %} + {{ pk }} + @@ -38,7 +28,7 @@ {% endfor %}
Start-Datum:
-
+ {% csrf_token %} diff --git a/billard/templates/billard/accountmodal.html b/billard/templates/billard/accountmodal.html index 9d0b008..a38dedd 100644 --- a/billard/templates/billard/accountmodal.html +++ b/billard/templates/billard/accountmodal.html @@ -22,7 +22,7 @@ diff --git a/billard/templates/billard/base.html b/billard/templates/billard/base.html index 07d5217..cba7394 100644 --- a/billard/templates/billard/base.html +++ b/billard/templates/billard/base.html @@ -5,7 +5,7 @@ - {% block title %}TITLE SETZEN{% endblock %} + carom - {% block title %}TITLE SETZEN{% endblock %} {% block header %} @@ -28,10 +28,7 @@ diff --git a/billard/templates/billard/index.html b/billard/templates/billard/index.html index 398718e..2ca45f8 100644 --- a/billard/templates/billard/index.html +++ b/billard/templates/billard/index.html @@ -16,12 +16,17 @@
{% endif %} + + + +
{% include 'billard/index_ajax.html' %}
{% endblock %} + {% block js %} +{% endblock %} \ No newline at end of file diff --git a/billard/templates/billard/location_detail_ajax.html b/billard/templates/billard/location_detail_ajax.html new file mode 100644 index 0000000..cef9821 --- /dev/null +++ b/billard/templates/billard/location_detail_ajax.html @@ -0,0 +1,12 @@ +{% load display_client %} +{% if location.clients.all %} +{% for cli in location.clients.all %} +{% for i in "12345678" %} + {{ cli|display_client:i }} +{% endfor %} +{% endfor %} +{% else %} +
+
Keine Tische angelegt!
+
+{% endif %} diff --git a/billard/templates/billard/location_index.html b/billard/templates/billard/location_index.html new file mode 100644 index 0000000..cfad5af --- /dev/null +++ b/billard/templates/billard/location_index.html @@ -0,0 +1,37 @@ +{% extends 'billard/base.html' %} + +{% block title %}Standortliste{% endblock %} + +{% block content %} + +{% if location_list %} +

Bitte Standort auswählen:

+ + + + + + + +{% if perms.billard.change_accounting %} + +{% endif %} + +{% for loc in location_list %} + + + + + + +{% if perms.billard.change_accounting %} + +{% endif %} + +{% endfor %} +
CodeNameStrassePlzOrtAccounting
{{ loc.code|default_if_none:"" }}{{ loc.name|default_if_none:"" }}{{ loc.street|default_if_none:"" }}{{ loc.plz|default_if_none:"" }}{{ loc.city|default_if_none:"" }}Abrechnen
+{% else %} +

Keine Standorte Zugeordnet.

+{% endif %} + +{% endblock %} diff --git a/billard/urls.py b/billard/urls.py index c61e51c..79d842e 100644 --- a/billard/urls.py +++ b/billard/urls.py @@ -1,16 +1,28 @@ from django.conf.urls import url, include +from django.contrib.auth.decorators import login_required from rest_framework import routers from billard import views router = routers.DefaultRouter() router.register(r'location_data', views.LocationDataViewSet) +app_name = 'billard' urlpatterns = [ - url(r'^$', views.index, name='carom_index'), - url(r'^(?P[0-9]+)/$', views.LocationDataDetailView.as_view(), name='detail'), + # ex. /billard/ + url(r'^$', login_required(views.LocationIndexView.as_view()), name='location_index'), + # ex. /billard/1/ + url(r'^(?P[0-9]+)/$', login_required(views.LocationDetailView.as_view()), name='location_detail'), + # ex. /billard/1/accounting/ + url(r'^(?P[0-9]+)/accounting/$', views.AccountingView.as_view(), name='accounting_detail'), + # ex. /billard/1/accounting/confirm + url(r'^(?P[0-9]+)/accounting/confirm/$', views.accounting_confirm, name='accounting_detail_confirm'), + # ex. /billard/1/account_modal/ + url(r'^(?P[0-9]+)/account_modal/$', views.account_modal_view, name='account_modal'), + # ex. /billard/1/account_modal/confirm/ + url(r'^(?P[0-9]+)/account_modal/(?P[0-9]+)/confirm/$', views.account_modal_confirm_view, + name='account_modal_confirm'), + # ex. /billard/api/v1/ (rest api) url(r'api/v1/', include(router.urls)), - url(r'process_locationdata', views.process_locationdata, name='process_locationdata'), - url(r'accounting', views.accounting, name='accounting'), - url(r'accountmodal$', views.accountmodalview, name='accountmodal'), - url(r'accoutmodal/confirm/(?P[0-9]+)$', views.accountmodalconfirmview, name="accountmodalconfirm") + # ex. /billard/process_location_data/ + url(r'^process_location_data/$', views.process_location_data, name='process_location_data'), ] diff --git a/billard/views.py b/billard/views.py index d9b091e..087fef1 100644 --- a/billard/views.py +++ b/billard/views.py @@ -1,13 +1,96 @@ +import ast + from billard.serializers import LocationDataSerializer from billard.models import LocationData, Location, Client, Accounting from billard.tasks import process_location_data from rest_framework import viewsets from django.shortcuts import render, redirect from django.views import generic -from django.views.generic.detail import DetailView from django.contrib.auth.decorators import login_required, permission_required -from django.db.models import Min, Sum +from django.db.models import Sum from django.http import HttpResponse +from django.utils.decorators import method_decorator +from django.utils import timezone + + +class LocationIndexView(generic.ListView): + template_name = 'billard/location_index.html' + context_object_name = 'location_list' + + def get_queryset(self): + """Return the last five published questions.""" + return Location.objects.filter(users__id=self.request.user.id).order_by('code') + + +class LocationDetailView(generic.DetailView): + model = Location + template_name = 'billard/location_detail.html' + + def dispatch(self, request, *args, **kwargs): + if request.is_ajax(): + context = { + 'location': self.get_object(), + } + return render(request, template_name='billard/location_detail_ajax.html', context=context) + result = super(LocationDetailView, self).dispatch(request, *args, **kwargs) + result.context_data['pk'] = self.kwargs['pk'] + return result + + +@method_decorator(login_required, name='dispatch') +class AccountingView(generic.ListView): + template_name = 'billard/accounting.html' + context_object_name = 'accounting' + + def get_queryset(self): + return Accounting.objects.filter(billed=False).exclude(time_to__isnull=True).order_by('time_from') + + def dispatch(self, request, *args, **kwargs): + result = super(AccountingView, self).dispatch(request, *args, **kwargs) + acc_sum = self.get_queryset().aggregate(Sum('prize')) + if acc_sum['prize__sum'] is None: + result.context_data['acc_sum'] = 0 + else: + result.context_data['acc_sum'] = acc_sum['prize__sum'] + result.context_data['acc_ids'] = [acc.id for acc in self.get_queryset().all()] + return result + + +@login_required +@permission_required('billard.change_accounting') +def accounting_confirm(request, pk): + if request.method == 'POST': + if 'accountings' in request.POST: + acc_ids = ast.literal_eval(request.POST['accountings']) + if len(acc_ids) > 0: + Accounting.objects.filter(id__in=acc_ids).update(billed=True) + Accounting.objects.filter(id__in=acc_ids).update(account_user=request.user.username) + Accounting.objects.filter(id__in=acc_ids).update(account_tst=timezone.now()) + resp = redirect('billard:accounting_detail', pk=pk) + return resp + + +@login_required +def account_modal_view(request, loc_pk): + try: + uuids = Client.objects.filter(report_user=request.user).values_list('uuid') + account = Accounting.objects.filter(reporter_uuid__in=uuids).first + # TODO: support multiple account objects + except Client.DoesNotExist: + account = None + context = { + 'account': account, + 'loc_pk': loc_pk, + } + return render(request, 'billard/accountmodal.html', context=context) + + +@login_required +def account_modal_confirm_view(request, loc_pk, pk): + account = Accounting.objects.get(pk=pk) + account.reporter_uuid = None + account.save() + return redirect('billard:location_detail', pk=loc_pk) class LocationDataViewSet(viewsets.ModelViewSet): @@ -15,133 +98,6 @@ class LocationDataViewSet(viewsets.ModelViewSet): serializer_class = LocationDataSerializer -class IndexView(generic.ListView): - model = LocationData - - def get_template_names(self): - if self.request.is_ajax(): - return ('billard/locationdata_list_ajax.html',) - return super().get_template_names() - - -class LocationDataDetailView(DetailView): - model = LocationData - - def get_template_names(self): - if self.request.is_ajax(): - return ('billard/locationdata_detail_ajax.html',) - return super().get_template_names() - - -@login_required -def accountmodalview(request): - try: - uuids = Client.objects.filter(report_user=request.user).values_list('uuid') - account = Accounting.objects.filter(reporter_uuid__in=uuids).first - #TODO: support multiple account objects - except Client.DoesNotExist: - account = None - context = { - 'account': account - } - return render(request, 'billard/accountmodal.html', context=context) - - -@login_required -def accountmodalconfirmview(request, pk): - account = Accounting.objects.get(pk=pk) - account.reporter_uuid = None - account.save() - return redirect('carom_index') - - -@login_required -@permission_required('billard.change_accounting') -def accounting(request): - if request.method == 'GET': - template = 'billard/accounting.html' - loc = None - min_loc = Location.objects.filter(users__id=request.user.id).aggregate(Min('id'))['id__min'] - if 'loc' in request.GET: - loc = request.GET['loc'] - if not Location.objects.filter(users__id=request.user.id).filter(id=loc).exists(): - resp = redirect('accounting') - if min_loc is not None: - resp['Location'] += '?loc={}'.format(str(min_loc)) - request.session['loc'] = str(min_loc) - return resp - else: - return render(request, accounting) - if loc is None: - loc = min_loc - locations = Location.objects.filter(users__id=request.user.id).order_by('code') - acc = Accounting.objects.filter(billed=False).exclude(time_to__isnull=True).\ - filter(desk__client__location_id=loc).order_by('-time_from') - acc_sum = acc.aggregate(Sum('prize')) - acc_ids = list() - for a in acc: - acc_ids.append(a.id) - - context = { - 'location_id': int(loc), - 'locations': locations, - 'accounting': acc, - 'acc_ids': ','.join(str(e) for e in acc_ids), - } - if acc_sum['prize__sum'] is None: - context['acc_sum'] = 0 - else: - context['acc_sum'] = acc_sum['prize__sum'] - return render(request, template_name=template, context=context) - if request.method == 'POST': - loc = request.POST['location-selector'] - if 'accountings' in request.POST: - acc_ids = request.POST['accountings'].split(',') - Accounting.objects.filter(id__in=acc_ids).update(billed=True) - request.session['loc'] = str(loc) - resp = redirect('accounting') - resp['Location'] += '?loc={}'.format(str(loc)) - return resp - - -@login_required -def index(request): - if request.method == 'GET': - template = 'billard/index.html' - loc = None - if request.is_ajax(): - template = 'billard/index_ajax.html' - loc = request.session.get('loc') - min_loc = Location.objects.filter(users__id=request.user.id).aggregate(Min('id'))['id__min'] - if 'loc' in request.GET: - loc = request.GET['loc'] - if not Location.objects.filter(users__id=request.user.id).filter(id=loc).exists(): - resp = redirect('carom_index') - if min_loc is not None: - resp['Location'] += '?loc={}'.format(str(min_loc)) - request.session['loc'] = str(min_loc) - return resp - else: - return render(request, template) - if loc is None: - loc = min_loc - locations = Location.objects.filter(users__id=request.user.id).order_by('code') - clients = Client.objects.filter(location_id=loc).order_by('id') - context = { - 'range': range(1, 9), - 'locations': locations, - 'clients': clients, - 'location_id': int(loc), - } - return render(request, template, context=context) - if request.method == 'POST': - loc = request.POST['location-selector'] - request.session['loc'] = str(loc) - resp = redirect('carom_index') - resp['Location'] += '?loc={}'.format(str(loc)) - return resp - - -def process_locationdata(request): +def process_location_data(request): process_location_data() return HttpResponse('DONE') diff --git a/caromserver/settings.py b/caromserver/settings.py index f966cca..d6ee8c9 100644 --- a/caromserver/settings.py +++ b/caromserver/settings.py @@ -135,8 +135,8 @@ REST_FRAMEWORK = { CRISPY_TEMPLATE_PACK = 'bootstrap3' LOGIN_URL = 'login' LOGOUT_URL = 'logout' -LOGIN_REDIRECT_URL = 'carom_index' -LOGOUT_REDIRECT_URL = 'carom_index' +LOGIN_REDIRECT_URL = 'billard:location_index' +LOGOUT_REDIRECT_URL = 'billard:location_index' # CELERY STUFF BROKER_URL = 'redis://localhost:6379'