Merge branch 'release/0.2.0'

This commit is contained in:
Robert Einsle 2017-04-29 11:24:28 +02:00
commit 68003388b7
16 changed files with 269 additions and 164 deletions

View File

@ -57,4 +57,8 @@ class DeskAdmin(admin.ModelAdmin):
@admin.register(Accounting) @admin.register(Accounting)
class AccountingAdmin(admin.ModelAdmin): 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

View File

@ -10,6 +10,7 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('sessions', '0001_initial'),
] ]
operations = [ operations = [

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -2,8 +2,6 @@ import uuid
import logging import logging
from django.db import models from django.db import models
from django.contrib.auth.models import User 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.contrib.auth.models import User
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
@ -50,7 +48,7 @@ class Location(models.Model):
class Client(models.Model): class Client(models.Model):
uuid = models.UUIDField(unique=True, default=uuid.uuid4, verbose_name="Identifier") 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') report_user = models.ForeignKey(User, blank=True, null=True, verbose_name="Reporting Benutzer", related_name='reporting_clients')
def __str__(self): 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, prize_hh = models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True,
verbose_name="Preis Happy Hour") verbose_name="Preis Happy Hour")
def __str__(self): def __str__(self):
return '{}, {}'.format(self.client.uuid, self.name) 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') 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_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") 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): def __str__(self):
return '{}: {} -> {}, {}, {}'.format(self.desk, self.time_from, self.time_to, self.prize, self.billed) return '{}: {} -> {}, {}, {}'.format(self.desk, self.time_from, self.time_to, self.prize, self.billed)

View File

@ -1,24 +1,14 @@
{% extends 'billard/base.html' %} {% extends 'billard/base.html' %}
{% load display_client %} {% load display_client %}
{% block title %}Accounting Data{% endblock %} {% block title %}Abrechnung{% endblock %}
{% block content %} {% block content %}
{% if not locations|length_is:"1" %}
<form action="accounting" method="post" id="location-form">
{% csrf_token %}
<div id="location-selector" class="alert">
<select class="form-control" form="location-form" name="location-selector" id="location-select">
{% for loc in locations %}
<option value="{{ loc.id }}"{% if loc.id == location_id %} selected{% endif %}>{{ loc.code }} - {{ loc.name }}</option>
{% endfor %}
</select>
</div>
</form>
{% endif %}
<div class="alert alert-success" role="alert">Gesamt-Summe: {{ acc_sum }}</div> <div class="alert alert-success" role="alert">Gesamt-Summe: {{ acc_sum }}</div>
{{ pk }}
<table class="table"> <table class="table">
<tr> <tr>
<th>Start-Datum:</th> <th>Start-Datum:</th>
@ -38,7 +28,7 @@
{% endfor %} {% endfor %}
</table> </table>
<form action="accounting" method="post" id="accounting"> <form action="confirm/" method="post" id="accounting">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="location-selector" value="{{ location_id }}"> <input type="hidden" name="location-selector" value="{{ location_id }}">
<input type="hidden" name="accountings" value="{{ acc_ids }}"> <input type="hidden" name="accountings" value="{{ acc_ids }}">

View File

@ -22,7 +22,7 @@
</table> </table>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a class="btn btn-default btn-primary" href="{% url 'accountmodalconfirm' account.pk %}">Schliessen</a> <a class="btn btn-default btn-primary" href="{% url 'billard:account_modal_confirm' pk=account.id loc_pk=loc_pk %}">Schliessen</a>
</div> </div>
</div><!-- /.modal-content --> </div><!-- /.modal-content -->
</div><!-- /.modal-dialog --> </div><!-- /.modal-dialog -->

View File

@ -5,7 +5,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}TITLE SETZEN{% endblock %}</title> <title>carom - {% block title %}TITLE SETZEN{% endblock %}</title>
<meta name="description" content=""> <meta name="description" content="">
<meta name="author" content=""> <meta name="author" content="">
{% block header %} {% block header %}
@ -28,10 +28,7 @@
</div> </div>
<div id="navbar" class="collapse navbar-collapse"> <div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li class="{% ifequal request.path '/billard/' %}active{% endifequal %}"><a href="{% url "carom_index" %}">Tische</a></li> <li class="{% ifequal request.path '/billard/' %}active{% endifequal %}"><a href="{% url "billard:location_index" %}">Standorte</a></li>
{% if perms.billard.change_accounting %}
<li class="{% ifequal request.path '/billard/accounting' %}active{% endifequal %}"><a href="{% url "accounting" %}">Abrechnung</a></li>
{% endif %}
{% if user.is_superuser %} {% if user.is_superuser %}
<li class="{% ifequal request.path '/logout/' %}active{% endifequal %}"><a href="/admin/">Administration</a></li> <li class="{% ifequal request.path '/logout/' %}active{% endifequal %}"><a href="/admin/">Administration</a></li>
{% endif %} {% endif %}
@ -47,7 +44,7 @@
{% endif %} {% endif %}
</ul> </ul>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li><a href="#">c@0.0.1</a></li> <li><a href="#">rel. c@0.2.0</a></li>
</ul> </ul>
</div><!--/.nav-collapse --> </div><!--/.nav-collapse -->
</div> </div>

View File

@ -16,12 +16,17 @@
</div> </div>
</form> </form>
{% endif %} {% endif %}
<div id="desk_data"> <div id="desk_data">
{% include 'billard/index_ajax.html' %} {% include 'billard/index_ajax.html' %}
</div> </div>
<div id="modal-wrapper"> <div id="modal-wrapper">
</div> </div>
{% endblock %} {% endblock %}
{% block js %} {% block js %}
<script type="text/javascript"> <script type="text/javascript">
var interval; var interval;

View File

@ -0,0 +1,31 @@
{% extends 'billard/base.html' %}
{% block title %}Standort{% endblock %}
{% block content %}
<div id="desk_data">
{% include 'billard/location_detail_ajax.html' %}
</div>
<div id="modal-wrapper">
</div>
{% endblock %}
{% block js %}
<script type="text/javascript">
var interval;
$(document).ready(function() {
$.ajaxSetup({ cache: false });
interval = window.setInterval(refresh_page, 1000);
});
function refresh_page() {
$('#desk_data').load('#');
$('#modal-wrapper').load('{% url 'billard:account_modal' loc_pk=pk %}', function() {
if ( $('#accountsmodal').length ) {
window.clearInterval(interval);
$('#accountsmodal').modal('show');
}
});
}
</script>
{% endblock %}

View File

@ -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 %}
<div class="col-md-12">
<div class="col-md-12 alert alert-danger">Keine Tische angelegt!</div>
</div>
{% endif %}

View File

@ -0,0 +1,37 @@
{% extends 'billard/base.html' %}
{% block title %}Standortliste{% endblock %}
{% block content %}
{% if location_list %}
<h2>Bitte Standort auswählen:</h2>
<table class="table table-hover">
<tr>
<th>Code</th>
<th>Name</th>
<th>Strasse</th>
<th>Plz</th>
<th>Ort</th>
{% if perms.billard.change_accounting %}
<th>Accounting</th>
{% endif %}
</tr>
{% for loc in location_list %}
<tr>
<td><a href="{% url 'billard:location_detail' loc.id %}" class="btn btn-primary">{{ loc.code|default_if_none:"" }}</a></td>
<td>{{ loc.name|default_if_none:"" }}</td>
<td>{{ loc.street|default_if_none:"" }}</td>
<td>{{ loc.plz|default_if_none:"" }}</td>
<td>{{ loc.city|default_if_none:"" }}</td>
{% if perms.billard.change_accounting %}
<td><a href="{% url 'billard:accounting_detail' loc.id %}" class="btn btn-primary">Abrechnen</a></td>
{% endif %}
</tr>
{% endfor %}
</table>
{% else %}
<p>Keine Standorte Zugeordnet.</p>
{% endif %}
{% endblock %}

View File

@ -1,16 +1,28 @@
from django.conf.urls import url, include from django.conf.urls import url, include
from django.contrib.auth.decorators import login_required
from rest_framework import routers from rest_framework import routers
from billard import views from billard import views
router = routers.DefaultRouter() router = routers.DefaultRouter()
router.register(r'location_data', views.LocationDataViewSet) router.register(r'location_data', views.LocationDataViewSet)
app_name = 'billard'
urlpatterns = [ urlpatterns = [
url(r'^$', views.index, name='carom_index'), # ex. /billard/
url(r'^(?P<pk>[0-9]+)/$', views.LocationDataDetailView.as_view(), name='detail'), url(r'^$', login_required(views.LocationIndexView.as_view()), name='location_index'),
# ex. /billard/1/
url(r'^(?P<pk>[0-9]+)/$', login_required(views.LocationDetailView.as_view()), name='location_detail'),
# ex. /billard/1/accounting/
url(r'^(?P<pk>[0-9]+)/accounting/$', views.AccountingView.as_view(), name='accounting_detail'),
# ex. /billard/1/accounting/confirm
url(r'^(?P<pk>[0-9]+)/accounting/confirm/$', views.accounting_confirm, name='accounting_detail_confirm'),
# ex. /billard/1/account_modal/
url(r'^(?P<loc_pk>[0-9]+)/account_modal/$', views.account_modal_view, name='account_modal'),
# ex. /billard/1/account_modal/confirm/
url(r'^(?P<loc_pk>[0-9]+)/account_modal/(?P<pk>[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'api/v1/', include(router.urls)),
url(r'process_locationdata', views.process_locationdata, name='process_locationdata'), # ex. /billard/process_location_data/
url(r'accounting', views.accounting, name='accounting'), url(r'^process_location_data/$', views.process_location_data, name='process_location_data'),
url(r'accountmodal$', views.accountmodalview, name='accountmodal'),
url(r'accoutmodal/confirm/(?P<pk>[0-9]+)$', views.accountmodalconfirmview, name="accountmodalconfirm")
] ]

View File

@ -1,13 +1,96 @@
import ast
from billard.serializers import LocationDataSerializer from billard.serializers import LocationDataSerializer
from billard.models import LocationData, Location, Client, Accounting from billard.models import LocationData, Location, Client, Accounting
from billard.tasks import process_location_data from billard.tasks import process_location_data
from rest_framework import viewsets from rest_framework import viewsets
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.views import generic from django.views import generic
from django.views.generic.detail import DetailView
from django.contrib.auth.decorators import login_required, permission_required 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.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): class LocationDataViewSet(viewsets.ModelViewSet):
@ -15,133 +98,6 @@ class LocationDataViewSet(viewsets.ModelViewSet):
serializer_class = LocationDataSerializer serializer_class = LocationDataSerializer
class IndexView(generic.ListView): def process_location_data(request):
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):
process_location_data() process_location_data()
return HttpResponse('DONE') return HttpResponse('DONE')

View File

@ -135,8 +135,8 @@ REST_FRAMEWORK = {
CRISPY_TEMPLATE_PACK = 'bootstrap3' CRISPY_TEMPLATE_PACK = 'bootstrap3'
LOGIN_URL = 'login' LOGIN_URL = 'login'
LOGOUT_URL = 'logout' LOGOUT_URL = 'logout'
LOGIN_REDIRECT_URL = 'carom_index' LOGIN_REDIRECT_URL = 'billard:location_index'
LOGOUT_REDIRECT_URL = 'carom_index' LOGOUT_REDIRECT_URL = 'billard:location_index'
# CELERY STUFF # CELERY STUFF
BROKER_URL = 'redis://localhost:6379' BROKER_URL = 'redis://localhost:6379'