views.py 47.9 KB
Newer Older
Skia's avatar
Skia committed
1
from django.shortcuts import render
Skia's avatar
Skia committed
2
from django.core.exceptions import PermissionDenied
Skia's avatar
Skia committed
3
from django.views.generic import ListView, DetailView, RedirectView, TemplateView
Skia's avatar
Skia committed
4
from django.views.generic.edit import UpdateView, CreateView, DeleteView, ProcessFormView, FormMixin
Skia's avatar
Skia committed
5
6
from django.forms.models import modelform_factory
from django.forms import CheckboxSelectMultiple
Skia's avatar
Skia committed
7
from django.core.urlresolvers import reverse_lazy, reverse
8
from django.core.exceptions import PermissionDenied
Skia's avatar
Skia committed
9
from django.http import HttpResponseRedirect, HttpResponse
Skia's avatar
Skia committed
10
from django.utils import timezone
Skia's avatar
Skia committed
11
from django import forms
Skia's avatar
Skia committed
12
from django.utils.translation import ugettext_lazy as _
13
from django.conf import settings
Skia's avatar
Skia committed
14
from django.db import DataError, transaction
Skia's avatar
Skia committed
15

Skia's avatar
Skia committed
16
import re
Skia's avatar
Skia committed
17
18
import pytz
from datetime import date, timedelta, datetime
19
20
from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField
from ajax_select import make_ajax_form, make_ajax_field
Skia's avatar
Skia committed
21

22
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin, TabedViewMixin
23
from core.views.forms import SelectUser, LoginForm, SelectDate, SelectDateTime
24
from core.models import User
Skia's avatar
Skia committed
25
from subscription.models import Subscriber, Subscription
Skia's avatar
Skia committed
26
from subscription.views import get_subscriber
Skia's avatar
Skia committed
27
from counter.models import Counter, Customer, Product, Selling, Refilling, ProductType, CashRegisterSummary, CashRegisterSummaryItem, Eticket
Skia's avatar
Skia committed
28
from accounting.models import CurrencyField
Skia's avatar
Skia committed
29

Skia's avatar
Skia committed
30
class GetUserForm(forms.Form):
31
32
33
34
35
36
37
    """
    The Form class aims at providing a valid user_id field in its cleaned data, in order to pass it to some view,
    reverse function, or any other use.

    The Form implements a nice JS widget allowing the user to type a customer account id, or search the database with
    some nickname, first name, or last name (TODO)
    """
Skia's avatar
Skia committed
38
    code = forms.CharField(label="Code", max_length=10, required=False)
39
    id = AutoCompleteSelectField('users', required=False, label=_("Select user"), help_text=None)
Skia's avatar
Skia committed
40

Skia's avatar
Skia committed
41
42
43
44
    def as_p(self):
        self.fields['code'].widget.attrs['autofocus'] = True
        return super(GetUserForm, self).as_p()

45
46
    def clean(self):
        cleaned_data = super(GetUserForm, self).clean()
Skia's avatar
Skia committed
47
        cus = None
48
        if cleaned_data['code'] != "":
Skia's avatar
Skia committed
49
            cus = Customer.objects.filter(account_id__iexact=cleaned_data['code']).first()
50
        elif cleaned_data['id'] is not None:
Skia's avatar
Skia committed
51
52
            cus = Customer.objects.filter(user=cleaned_data['id']).first()
        sub = get_subscriber(cus.user) if cus is not None else None
53
54
        if (cus is None or sub is None or not sub.subscriptions.last() or
            (date.today() - sub.subscriptions.last().subscription_end) > timedelta(days=90)):
Skia's avatar
Skia committed
55
            raise forms.ValidationError(_("User not found"))
Skia's avatar
Skia committed
56
57
        cleaned_data['user_id'] = cus.user.id
        cleaned_data['user'] = cus.user
58
59
        return cleaned_data

60
61
62
63
64
65
66
class RefillForm(forms.ModelForm):
    error_css_class = 'error'
    required_css_class = 'required'
    class Meta:
        model = Refilling
        fields = ['amount', 'payment_method', 'bank']

Skia's avatar
Skia committed
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
class CounterTabsMixin(TabedViewMixin):
    def get_tabs_title(self):
        return self.object
    def get_list_of_tabs(self):
        tab_list = []
        tab_list.append({
            'url': reverse_lazy('counter:details', kwargs={'counter_id': self.object.id}),
            'slug': 'counter',
            'name': _("Counter"),
            })
        if self.object.type == "BAR":
            tab_list.append({
                'url': reverse_lazy('counter:cash_summary', kwargs={'counter_id': self.object.id}),
                'slug': 'cash_summary',
                'name': _("Cash summary"),
                })
            tab_list.append({
                'url': reverse_lazy('counter:last_ops', kwargs={'counter_id': self.object.id}),
                'slug': 'last_ops',
                'name': _("Last operations"),
                })
        return tab_list

Skia's avatar
Skia committed
90
class CounterMain(CounterTabsMixin, DetailView, ProcessFormView, FormMixin):
Skia's avatar
Skia committed
91
92
93
    """
    The public (barman) view
    """
Skia's avatar
Skia committed
94
    model = Counter
Skia's avatar
Skia committed
95
    template_name = 'counter/counter_main.jinja'
Skia's avatar
Skia committed
96
    pk_url_kwarg = "counter_id"
97
    form_class = GetUserForm # Form to enter a client code and get the corresponding user id
Skia's avatar
Skia committed
98
    current_tab = "counter"
Skia's avatar
Skia committed
99

100
101
102
103
104
105
106
107
    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        if self.object.type == "BAR" and not ('counter_token' in self.request.session.keys() and
                self.request.session['counter_token'] == self.object.token): # Check the token to avoid the bar to be stolen
            return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args,
                kwargs={'counter_id': self.object.id})+'?bad_location')
        return super(CounterMain, self).post(request, *args, **kwargs)

Skia's avatar
Skia committed
108
    def get_context_data(self, **kwargs):
Skia's avatar
Skia committed
109
        """
110
        We handle here the login form for the barman
Skia's avatar
Skia committed
111
        """
112
113
        if self.request.method == 'POST':
            self.object = self.get_object()
Skia's avatar
Skia committed
114
        self.object.update_activity()
Skia's avatar
Skia committed
115
        kwargs = super(CounterMain, self).get_context_data(**kwargs)
Skia's avatar
Skia committed
116
        kwargs['login_form'] = LoginForm()
Skia's avatar
Skia committed
117
        kwargs['login_form'].fields['username'].widget.attrs['autofocus'] = True
Skia's avatar
Skia committed
118
119
120
        kwargs['login_form'].cleaned_data = {} # add_error fails if there are no cleaned_data
        if "credentials" in self.request.GET:
            kwargs['login_form'].add_error(None, _("Bad credentials"))
121
122
        if "sellers" in self.request.GET:
            kwargs['login_form'].add_error(None, _("User is not barman"))
Skia's avatar
Skia committed
123
        kwargs['form'] = self.get_form()
124
125
        kwargs['form'].cleaned_data = {} # same as above
        if "bad_location" in self.request.GET:
Skia's avatar
Skia committed
126
            kwargs['form'].add_error(None, _("Bad location, someone is already logged in somewhere else"))
Skia's avatar
Skia committed
127
        if self.object.type == 'BAR':
Skia's avatar
Skia committed
128
            kwargs['barmen'] = self.object.get_barmen_list()
Skia's avatar
Skia committed
129
130
        elif self.request.user.is_authenticated():
            kwargs['barmen'] = [self.request.user]
131
132
133
134
135
        if 'last_basket' in self.request.session.keys():
            kwargs['last_basket'] = self.request.session.pop('last_basket')
            kwargs['last_customer'] = self.request.session.pop('last_customer')
            kwargs['last_total'] = self.request.session.pop('last_total')
            kwargs['new_customer_amount'] = self.request.session.pop('new_customer_amount')
Skia's avatar
Skia committed
136
137
        return kwargs

138
139
140
141
142
143
144
145
146
147
    def form_valid(self, form):
        """
        We handle here the redirection, passing the user id of the asked customer
        """
        self.kwargs['user_id'] = form.cleaned_data['user_id']
        return super(CounterMain, self).form_valid(form)

    def get_success_url(self):
        return reverse_lazy('counter:click', args=self.args, kwargs=self.kwargs)

Skia's avatar
Skia committed
148
class CounterClick(CounterTabsMixin, DetailView):
Skia's avatar
Skia committed
149
150
    """
    The click view
151
152
    This is a detail view not to have to worry about loading the counter
    Everything is made by hand in the post method
Skia's avatar
Skia committed
153
    """
154
    model = Counter
Skia's avatar
Skia committed
155
156
    template_name = 'counter/counter_click.jinja'
    pk_url_kwarg = "counter_id"
Skia's avatar
Skia committed
157
    current_tab = "counter"
158
159

    def get(self, request, *args, **kwargs):
160
        """Simple get view"""
161
        self.customer = Customer.objects.filter(user__id=self.kwargs['user_id']).first()
162
163
164
        if 'basket' not in request.session.keys(): # Init the basket session entry
            request.session['basket'] = {}
            request.session['basket_total'] = 0
Skia's avatar
Skia committed
165
        request.session['not_enough'] = False # Reset every variable
166
        request.session['too_young'] = False
167
        request.session['not_allowed'] = False
Skia's avatar
Skia committed
168
        request.session['no_age'] = False
169
        self.refill_form = None
170
        ret = super(CounterClick, self).get(request, *args, **kwargs)
Skia's avatar
Skia committed
171
172
        if ((self.object.type != "BAR" and not request.user.is_authenticated()) or
                (self.object.type == "BAR" and
Skia's avatar
Skia committed
173
                len(self.object.get_barmen_list()) < 1)): # Check that at least one barman is logged in
Skia's avatar
Skia committed
174
            ret = self.cancel(request) # Otherwise, go to main view
175
        return ret
Skia's avatar
Skia committed
176
177

    def post(self, request, *args, **kwargs):
178
        """ Handle the many possibilities of the post request """
179
180
        self.object = self.get_object()
        self.customer = Customer.objects.filter(user__id=self.kwargs['user_id']).first()
181
        self.refill_form = None
Skia's avatar
Skia committed
182
183
        if ((self.object.type != "BAR" and not request.user.is_authenticated()) or
                (self.object.type == "BAR" and
Skia's avatar
Skia committed
184
                len(self.object.get_barmen_list()) < 1)): # Check that at least one barman is logged in
185
            return self.cancel(request)
186
187
188
189
        if self.object.type == "BAR" and not ('counter_token' in self.request.session.keys() and
                self.request.session['counter_token'] == self.object.token): # Also check the token to avoid the bar to be stolen
            return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args,
                kwargs={'counter_id': self.object.id})+'?bad_location')
190
191
        if 'basket' not in request.session.keys():
            request.session['basket'] = {}
192
            request.session['basket_total'] = 0
Skia's avatar
Skia committed
193
        request.session['not_enough'] = False # Reset every variable
194
        request.session['too_young'] = False
195
        request.session['not_allowed'] = False
Skia's avatar
Skia committed
196
        request.session['no_age'] = False
Skia's avatar
Skia committed
197
198
199
200
201
        if self.object.type != "BAR":
            self.operator = request.user
        elif self.is_barman_price():
            self.operator = self.customer.user
        else:
Skia's avatar
Skia committed
202
            self.operator = self.object.get_random_barman()
203
204
205
206
207

        if 'add_product' in request.POST['action']:
            self.add_product(request)
        elif 'del_product' in request.POST['action']:
            self.del_product(request)
Skia's avatar
Skia committed
208
209
        elif 'refill' in request.POST['action']:
            self.refill(request)
Skia's avatar
Skia committed
210
211
        elif 'code' in request.POST['action']:
            return self.parse_code(request)
212
213
214
215
216
217
218
        elif 'cancel' in request.POST['action']:
            return self.cancel(request)
        elif 'finish' in request.POST['action']:
            return self.finish(request)
        context = self.get_context_data(object=self.object)
        return self.render_to_response(context)

Skia's avatar
Skia committed
219
    def is_barman_price(self):
Skia's avatar
Skia committed
220
        if self.object.type == "BAR" and self.customer.user.id in [s.id for s in self.object.get_barmen_list()]:
221
222
223
224
            return True
        else:
            return False

225
226
227
    def get_product(self, pid):
        return Product.objects.filter(pk=int(pid)).first()

228
    def get_price(self, pid):
229
        p = self.get_product(pid)
230
231
232
233
234
235
236
237
238
239
        if self.is_barman_price():
            price = p.special_selling_price
        else:
            price = p.selling_price
        return price

    def sum_basket(self, request):
        total = 0
        for pid,infos in request.session['basket'].items():
            total += infos['price'] * infos['qty']
240
        return total / 100
241

Skia's avatar
Skia committed
242
243
244
245
246
247
248
    def get_total_quantity_for_pid(self, request, pid):
        pid = str(pid)
        try:
            return request.session['basket'][pid]['qty'] + request.session['basket'][pid]['bonus_qty']
        except:
            return 0

Skia's avatar
Skia committed
249
    def add_product(self, request, q = 1, p=None):
250
251
252
253
254
        """
        Add a product to the basket
        q is the quantity passed as integer
        p is the product id, passed as an integer
        """
Skia's avatar
Skia committed
255
256
        pid = p or request.POST['product_id']
        pid = str(pid)
257
        price = self.get_price(pid)
258
        total = self.sum_basket(request)
Skia's avatar
Skia committed
259
        product = self.get_product(pid)
260
        can_buy = False
261
262
263
264
265
266
        if not product.buying_groups.exists():
            can_buy = True
        else:
            for g in product.buying_groups.all():
                if self.customer.user.is_in_group(g.name):
                    can_buy = True
267
268
269
        if not can_buy:
            request.session['not_allowed'] = True
            return False
Skia's avatar
Skia committed
270
271
272
273
274
275
        bq = 0 # Bonus quantity, for trays
        if product.tray: # Handle the tray to adjust the quantity q to add and the bonus quantity bq
            total_qty_mod_6 = self.get_total_quantity_for_pid(request, pid) % 6
            bq = int((total_qty_mod_6 + q) / 6) # Integer division
            q -= bq
        if self.customer.amount < (total + q*float(price)): # Check for enough money
276
            request.session['not_enough'] = True
Skia's avatar
Skia committed
277
            return False
Skia's avatar
Skia committed
278
279
280
        if product.limit_age >= 18 and not self.customer.user.date_of_birth:
            request.session['no_age'] = True
            return False
Sli's avatar
Sli committed
281
282
283
        if product.limit_age >= 18 and self.customer.user.is_banned_alcohol:
            request.session['not_allowed'] = True
            return False
Skia's avatar
Skia committed
284
        if self.customer.user.date_of_birth and self.customer.user.get_age() < product.limit_age: # Check if affordable
285
286
            request.session['too_young'] = True
            return False
Skia's avatar
Skia committed
287
        if pid in request.session['basket']: # Add if already in basket
288
            request.session['basket'][pid]['qty'] += q
Skia's avatar
Skia committed
289
290
291
            request.session['basket'][pid]['bonus_qty'] += bq
        else: # or create if not
            request.session['basket'][pid] = {'qty': q, 'price': int(price*100), 'bonus_qty': bq}
292
        request.session.modified = True
Skia's avatar
Skia committed
293
        return True
294
295
296

    def del_product(self, request):
        """ Delete a product from the basket """
297
        pid = str(request.POST['product_id'])
Skia's avatar
Skia committed
298
        product = self.get_product(pid)
299
        if pid in request.session['basket']:
Skia's avatar
Skia committed
300
301
302
303
            if product.tray and (self.get_total_quantity_for_pid(request, pid) % 6 == 0) and request.session['basket'][pid]['bonus_qty']:
                request.session['basket'][pid]['bonus_qty'] -= 1
            else:
                request.session['basket'][pid]['qty'] -= 1
304
            if request.session['basket'][pid]['qty'] <= 0:
305
                del request.session['basket'][pid]
306
        else:
Skia's avatar
Skia committed
307
            request.session['basket'][pid] = None
308
309
        request.session.modified = True

Skia's avatar
Skia committed
310
311
312
    def parse_code(self, request):
        """Parse the string entered by the barman"""
        string = str(request.POST['code']).upper()
Skia's avatar
Skia committed
313
        if string == _("END"):
Skia's avatar
Skia committed
314
            return self.finish(request)
Skia's avatar
Skia committed
315
        elif string == _("CAN"):
Skia's avatar
Skia committed
316
317
318
319
320
321
322
323
324
325
            return self.cancel(request)
        regex = re.compile(r"^((?P<nb>[0-9]+)X)?(?P<code>[A-Z0-9]+)$")
        m = regex.match(string)
        if m is not None:
            nb = m.group('nb')
            code = m.group('code')
            if nb is None:
                nb = 1
            else:
                nb = int(nb)
Skia's avatar
Skia committed
326
            p = self.object.products.filter(code=code).first()
Skia's avatar
Skia committed
327
328
329
330
331
332
            if p is not None:
                while nb > 0 and not self.add_product(request, nb, p.id):
                    nb -= 1
        context = self.get_context_data(object=self.object)
        return self.render_to_response(context)

333
334
    def finish(self, request):
        """ Finish the click session, and validate the basket """
Skia's avatar
Skia committed
335
336
337
338
339
340
341
342
343
344
345
        with transaction.atomic():
            request.session['last_basket'] = []
            for pid,infos in request.session['basket'].items():
                # This duplicates code for DB optimization (prevent to load many times the same object)
                p = Product.objects.filter(pk=pid).first()
                if self.is_barman_price():
                    uprice = p.special_selling_price
                else:
                    uprice = p.selling_price
                if uprice * infos['qty'] > self.customer.amount:
                    raise DataError(_("You have not enough money to buy all the basket"))
Skia's avatar
Skia committed
346
                request.session['last_basket'].append("%d x %s" % (infos['qty']+infos['bonus_qty'], p.name))
347
                s = Selling(label=p.name, product=p, club=p.club, counter=self.object, unit_price=uprice,
Skia's avatar
Skia committed
348
                       quantity=infos['qty'], seller=self.operator, customer=self.customer)
Skia's avatar
Skia committed
349
                s.save()
Skia's avatar
Skia committed
350
351
352
353
                if infos['bonus_qty']:
                    s = Selling(label=p.name + " (Plateau)", product=p, club=p.club, counter=self.object, unit_price=0,
                           quantity=infos['bonus_qty'], seller=self.operator, customer=self.customer)
                    s.save()
Skia's avatar
Skia committed
354
355
356
357
358
359
360
361
362
            request.session['last_customer'] = self.customer.user.get_display_name()
            request.session['last_total'] = "%0.2f" % self.sum_basket(request)
            request.session['new_customer_amount'] = str(self.customer.amount)
            del request.session['basket']
            request.session.modified = True
            kwargs = {
                    'counter_id': self.object.id,
                    }
            return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, kwargs=kwargs))
363
364
365
366

    def cancel(self, request):
        """ Cancel the click session """
        kwargs = {'counter_id': self.object.id}
367
        request.session.pop('basket', None)
368
        return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, kwargs=kwargs))
369

Skia's avatar
Skia committed
370
371
    def refill(self, request):
        """Refill the customer's account"""
372
373
374
        form = RefillForm(request.POST)
        if form.is_valid():
            form.instance.counter = self.object
Skia's avatar
Skia committed
375
            form.instance.operator = self.operator
376
377
378
379
            form.instance.customer = self.customer
            form.instance.save()
        else:
            self.refill_form = form
Skia's avatar
Skia committed
380

381
    def get_context_data(self, **kwargs):
382
        """ Add customer to the context """
383
384
        kwargs = super(CounterClick, self).get_context_data(**kwargs)
        kwargs['customer'] = self.customer
385
        kwargs['basket_total'] = self.sum_basket(self.request)
386
        kwargs['refill_form'] = self.refill_form or RefillForm()
387
        kwargs['categories'] = ProductType.objects.all()
388
389
        return kwargs

Skia's avatar
Skia committed
390
class CounterLogin(RedirectView):
Skia's avatar
Skia committed
391
392
393
    """
    Handle the login of a barman

394
    Logged barmen are stored in the Permanency model
Skia's avatar
Skia committed
395
    """
Skia's avatar
Skia committed
396
    permanent = False
Skia's avatar
Skia committed
397
    def post(self, request, *args, **kwargs):
Skia's avatar
Skia committed
398
399
400
        """
        Register the logged user as barman for this counter
        """
Skia's avatar
Skia committed
401
        self.counter_id = kwargs['counter_id']
402
        self.counter = Counter.objects.filter(id=kwargs['counter_id']).first()
Skia's avatar
Skia committed
403
        form = LoginForm(request, data=request.POST)
Skia's avatar
Skia committed
404
        self.errors = []
Skia's avatar
Skia committed
405
        if form.is_valid():
406
            user = User.objects.filter(username=form.cleaned_data['username']).first()
407
            if user in self.counter.sellers.all() and not user in self.counter.get_barmen_list():
408
409
410
                if len(self.counter.get_barmen_list()) <= 0:
                    self.counter.gen_token()
                    request.session['counter_token'] = self.counter.token
411
                self.counter.add_barman(user)
Skia's avatar
Skia committed
412
            else:
413
                self.errors += ["sellers"]
Skia's avatar
Skia committed
414
        else:
Skia's avatar
Skia committed
415
            self.errors += ["credentials"]
Skia's avatar
Skia committed
416
417
418
        return super(CounterLogin, self).post(request, *args, **kwargs)

    def get_redirect_url(self, *args, **kwargs):
Skia's avatar
Skia committed
419
        return reverse_lazy('counter:details', args=args, kwargs=kwargs)+"?"+'&'.join(self.errors)
Skia's avatar
Skia committed
420
421
422
423

class CounterLogout(RedirectView):
    permanent = False
    def post(self, request, *args, **kwargs):
Skia's avatar
Skia committed
424
425
426
        """
        Unregister the user from the barman
        """
427
428
429
        self.counter = Counter.objects.filter(id=kwargs['counter_id']).first()
        user = User.objects.filter(id=request.POST['user_id']).first()
        self.counter.del_barman(user)
Skia's avatar
Skia committed
430
431
432
433
        return super(CounterLogout, self).post(request, *args, **kwargs)

    def get_redirect_url(self, *args, **kwargs):
        return reverse_lazy('counter:details', args=args, kwargs=kwargs)
Skia's avatar
Skia committed
434

435
436
## Counter admin views

Skia's avatar
Skia committed
437
class CounterAdminTabsMixin(TabedViewMixin):
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
    tabs_title = _("Counter administration")
    list_of_tabs = [
            {
                'url': reverse_lazy('counter:admin_list'),
                'slug': 'counters',
                'name': _("Counters"),
                },
            {
                'url': reverse_lazy('counter:product_list'),
                'slug': 'products',
                'name': _("Products"),
                },
            {
                'url': reverse_lazy('counter:product_list_archived'),
                'slug': 'archive',
                'name': _("Archived products"),
                },
            {
                'url': reverse_lazy('counter:producttype_list'),
                'slug': 'product_types',
                'name': _("Product types"),
                },
Skia's avatar
Skia committed
460
461
462
463
464
            {
                'url': reverse_lazy('counter:cash_summary_list'),
                'slug': 'cash_summary',
                'name': _("Cash register summaries"),
                },
Skia's avatar
Skia committed
465
466
467
468
469
            {
                'url': reverse_lazy('counter:invoices_call'),
                'slug': 'invoices_call',
                'name': _("Invoices call"),
                },
Skia's avatar
Skia committed
470
471
472
473
474
            {
                'url': reverse_lazy('counter:eticket_list'),
                'slug': 'etickets',
                'name': _("Etickets"),
                },
475
476
            ]

Skia's avatar
Skia committed
477
class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView):
Skia's avatar
Skia committed
478
479
480
481
482
    """
    A list view for the admins
    """
    model = Counter
    template_name = 'counter/counter_list.jinja'
483
    current_tab = "counters"
Skia's avatar
Skia committed
484

485
486
487
488
class CounterEditForm(forms.ModelForm):
    class Meta:
        model = Counter
        fields = ['sellers', 'products']
489
490
    sellers = make_ajax_field(Counter, 'sellers', 'users', help_text="")
    products = make_ajax_field(Counter, 'products', 'products', help_text="")
491

Skia's avatar
Skia committed
492
class CounterEditView(CounterAdminTabsMixin, CanEditMixin, UpdateView):
493
494
495
496
497
498
    """
    Edit a counter's main informations (for the counter's manager)
    """
    model = Counter
    form_class = CounterEditForm
    pk_url_kwarg = "counter_id"
499
    template_name = 'core/edit.jinja'
500
    current_tab = "counters"
501
502
503
504

    def get_success_url(self):
        return reverse_lazy('counter:admin', kwargs={'counter_id': self.object.id})

Skia's avatar
Skia committed
505
class CounterEditPropView(CounterAdminTabsMixin, CanEditPropMixin, UpdateView):
Skia's avatar
Skia committed
506
    """
Skia's avatar
Skia committed
507
    Edit a counter's main informations (for the counter's admin)
Skia's avatar
Skia committed
508
509
    """
    model = Counter
510
    form_class = modelform_factory(Counter, fields=['name', 'club', 'type'])
Skia's avatar
Skia committed
511
    pk_url_kwarg = "counter_id"
512
    template_name = 'core/edit.jinja'
513
    current_tab = "counters"
Skia's avatar
Skia committed
514

Skia's avatar
Skia committed
515
class CounterCreateView(CounterAdminTabsMixin, CanEditMixin, CreateView):
Skia's avatar
Skia committed
516
    """
Skia's avatar
Skia committed
517
    Create a counter (for the admins)
Skia's avatar
Skia committed
518
519
520
521
    """
    model = Counter
    form_class = modelform_factory(Counter, fields=['name', 'club', 'type', 'products'],
            widgets={'products':CheckboxSelectMultiple})
522
    template_name = 'core/create.jinja'
523
    current_tab = "counters"
Skia's avatar
Skia committed
524

Skia's avatar
Skia committed
525
class CounterDeleteView(CounterAdminTabsMixin, CanEditMixin, DeleteView):
Skia's avatar
Skia committed
526
    """
Skia's avatar
Skia committed
527
    Delete a counter (for the admins)
Skia's avatar
Skia committed
528
529
530
531
532
    """
    model = Counter
    pk_url_kwarg = "counter_id"
    template_name = 'core/delete_confirm.jinja'
    success_url = reverse_lazy('counter:admin_list')
533
    current_tab = "counters"
Skia's avatar
Skia committed
534

Skia's avatar
Skia committed
535
536
# Product management

Skia's avatar
Skia committed
537
class ProductTypeListView(CounterAdminTabsMixin, CanEditPropMixin, ListView):
Skia's avatar
Skia committed
538
539
540
541
542
    """
    A list view for the admins
    """
    model = ProductType
    template_name = 'counter/producttype_list.jinja'
543
    current_tab = "product_types"
Skia's avatar
Skia committed
544

Skia's avatar
Skia committed
545
class ProductTypeCreateView(CounterAdminTabsMixin, CanCreateMixin, CreateView):
Skia's avatar
Skia committed
546
547
548
549
550
551
    """
    A create view for the admins
    """
    model = ProductType
    fields = ['name', 'description', 'icon']
    template_name = 'core/create.jinja'
552
    current_tab = "products"
Skia's avatar
Skia committed
553

Skia's avatar
Skia committed
554
class ProductTypeEditView(CounterAdminTabsMixin, CanEditPropMixin, UpdateView):
Skia's avatar
Skia committed
555
556
557
558
559
560
561
    """
    An edit view for the admins
    """
    model = ProductType
    template_name = 'core/edit.jinja'
    fields = ['name', 'description', 'icon']
    pk_url_kwarg = "type_id"
562
    current_tab = "products"
Skia's avatar
Skia committed
563

Skia's avatar
Skia committed
564
class ProductArchivedListView(CounterAdminTabsMixin, CanEditPropMixin, ListView):
Skia's avatar
Skia committed
565
566
567
568
569
570
571
    """
    A list view for the admins
    """
    model = Product
    template_name = 'counter/product_list.jinja'
    queryset = Product.objects.filter(archived=True)
    ordering = ['name']
572
    current_tab = "archive"
Skia's avatar
Skia committed
573

Skia's avatar
Skia committed
574
class ProductListView(CounterAdminTabsMixin, CanEditPropMixin, ListView):
Skia's avatar
Skia committed
575
576
577
578
579
    """
    A list view for the admins
    """
    model = Product
    template_name = 'counter/product_list.jinja'
Skia's avatar
Skia committed
580
    queryset = Product.objects.filter(archived=False)
Skia's avatar
Skia committed
581
    ordering = ['name']
582
    current_tab = "products"
Skia's avatar
Skia committed
583

584
585
586
587
class ProductEditForm(forms.ModelForm):
    class Meta:
        model = Product
        fields = ['name', 'description', 'product_type', 'code', 'parent_product', 'buying_groups', 'purchase_price',
Skia's avatar
Skia committed
588
                'selling_price', 'special_selling_price', 'icon', 'club', 'limit_age', 'tray', 'archived']
589
    parent_product = AutoCompleteSelectField('products', show_help_text=False, label=_("Parent product"), required=False)
Skia's avatar
Skia committed
590
    buying_groups = AutoCompleteSelectMultipleField('groups', show_help_text=False, help_text="", label=_("Buying groups"), required=False)
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
    club = AutoCompleteSelectField('clubs', show_help_text=False)
    counters = AutoCompleteSelectMultipleField('counters', show_help_text=False, help_text="", label=_("Counters"), required=False)

    def __init__(self, *args, **kwargs):
        super(ProductEditForm, self).__init__(*args, **kwargs)
        if self.instance.id:
            self.fields['counters'].initial = [str(c.id) for c in self.instance.counters.all()]

    def save(self, *args, **kwargs):
        ret = super(ProductEditForm, self).save(*args, **kwargs)
        if self.fields['counters'].initial:
            for cid in self.fields['counters'].initial:
                c = Counter.objects.filter(id=int(cid)).first()
                c.products.remove(self.instance)
                c.save()
        for cid in self.cleaned_data['counters']:
            c = Counter.objects.filter(id=int(cid)).first()
            c.products.add(self.instance)
            c.save()
        return ret

Skia's avatar
Skia committed
612
class ProductCreateView(CounterAdminTabsMixin, CanCreateMixin, CreateView):
Skia's avatar
Skia committed
613
614
615
616
    """
    A create view for the admins
    """
    model = Product
617
    form_class = ProductEditForm
Skia's avatar
Skia committed
618
    template_name = 'core/create.jinja'
619
    current_tab = "products"
Skia's avatar
Skia committed
620

Skia's avatar
Skia committed
621
class ProductEditView(CounterAdminTabsMixin, CanEditPropMixin, UpdateView):
Skia's avatar
Skia committed
622
623
624
625
    """
    An edit view for the admins
    """
    model = Product
626
    form_class = ProductEditForm
Skia's avatar
Skia committed
627
628
    pk_url_kwarg = "product_id"
    template_name = 'core/edit.jinja'
629
    current_tab = "products"
Skia's avatar
Skia committed
630

Skia's avatar
Skia committed
631
class RefillingDeleteView(DeleteView):
Skia's avatar
Skia committed
632
633
634
635
636
637
638
    """
    Delete a refilling (for the admins)
    """
    model = Refilling
    pk_url_kwarg = "refilling_id"
    template_name = 'core/delete_confirm.jinja'

Skia's avatar
Skia committed
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
    def dispatch(self, request, *args, **kwargs):
        """
        We have here a very particular right handling, we can't inherit from CanEditPropMixin
        """
        self.object = self.get_object()
        if (timezone.now() - self.object.date <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) and
                'counter_token' in request.session.keys() and
                request.session['counter_token'] and # check if not null for counters that have no token set
                Counter.objects.filter(token=request.session['counter_token']).exists()):
            self.success_url = reverse('counter:details', kwargs={'counter_id': self.object.counter.id})
            return super(RefillingDeleteView, self).dispatch(request, *args, **kwargs)
        elif self.object.is_owned_by(request.user):
            self.success_url = reverse('core:user_account', kwargs={'user_id': self.object.customer.user.id})
            return super(RefillingDeleteView, self).dispatch(request, *args, **kwargs)
        raise PermissionDenied
Skia's avatar
Skia committed
654

Skia's avatar
Skia committed
655
class SellingDeleteView(DeleteView):
Skia's avatar
Skia committed
656
657
658
659
660
661
662
    """
    Delete a selling (for the admins)
    """
    model = Selling
    pk_url_kwarg = "selling_id"
    template_name = 'core/delete_confirm.jinja'

Skia's avatar
Skia committed
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
    def dispatch(self, request, *args, **kwargs):
        """
        We have here a very particular right handling, we can't inherit from CanEditPropMixin
        """
        self.object = self.get_object()
        if (timezone.now() - self.object.date <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) and
                'counter_token' in request.session.keys() and
                request.session['counter_token'] and # check if not null for counters that have no token set
                Counter.objects.filter(token=request.session['counter_token']).exists()):
            self.success_url = reverse('counter:details', kwargs={'counter_id': self.object.counter.id})
            return super(SellingDeleteView, self).dispatch(request, *args, **kwargs)
        elif self.object.is_owned_by(request.user):
            self.success_url = reverse('core:user_account', kwargs={'user_id': self.object.customer.user.id})
            return super(SellingDeleteView, self).dispatch(request, *args, **kwargs)
        raise PermissionDenied
Skia's avatar
Skia committed
678

Skia's avatar
Skia committed
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
# Cash register summaries

class CashRegisterSummaryForm(forms.Form):
    """
    Provide the cash summary form
    """
    ten_cents = forms.IntegerField(label=_("10 cents"), required=False)
    twenty_cents = forms.IntegerField(label=_("20 cents"), required=False)
    fifty_cents = forms.IntegerField(label=_("50 cents"), required=False)
    one_euro = forms.IntegerField(label=_("1 euro"), required=False)
    two_euros = forms.IntegerField(label=_("2 euros"), required=False)
    five_euros = forms.IntegerField(label=_("5 euros"), required=False)
    ten_euros = forms.IntegerField(label=_("10 euros"), required=False)
    twenty_euros = forms.IntegerField(label=_("20 euros"), required=False)
    fifty_euros = forms.IntegerField(label=_("50 euros"), required=False)
    hundred_euros = forms.IntegerField(label=_("100 euros"), required=False)
    check_1_value = forms.DecimalField(label=_("Check amount"), required=False)
    check_1_quantity = forms.IntegerField(label=_("Check quantity"), required=False)
    check_2_value = forms.DecimalField(label=_("Check amount"), required=False)
    check_2_quantity = forms.IntegerField(label=_("Check quantity"), required=False)
    check_3_value = forms.DecimalField(label=_("Check amount"), required=False)
    check_3_quantity = forms.IntegerField(label=_("Check quantity"), required=False)
    check_4_value = forms.DecimalField(label=_("Check amount"), required=False)
    check_4_quantity = forms.IntegerField(label=_("Check quantity"), required=False)
    check_5_value = forms.DecimalField(label=_("Check amount"), required=False)
    check_5_quantity = forms.IntegerField(label=_("Check quantity"), required=False)
    comment = forms.CharField(label=_("Comment"), required=False)
    emptied = forms.BooleanField(label=_("Emptied"), required=False)

708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
    def __init__(self, *args, **kwargs):
        instance = kwargs.pop('instance', None)
        super(CashRegisterSummaryForm, self).__init__(*args, **kwargs)
        if instance:
            self.fields['ten_cents'].initial = instance.ten_cents.quantity if instance.ten_cents else 0
            self.fields['twenty_cents'].initial = instance.twenty_cents.quantity if instance.twenty_cents else 0
            self.fields['fifty_cents'].initial = instance.fifty_cents.quantity if instance.fifty_cents else 0
            self.fields['one_euro'].initial = instance.one_euro.quantity if instance.one_euro else 0
            self.fields['two_euros'].initial = instance.two_euros.quantity if instance.two_euros else 0
            self.fields['five_euros'].initial = instance.five_euros.quantity if instance.five_euros else 0
            self.fields['ten_euros'].initial = instance.ten_euros.quantity if instance.ten_euros else 0
            self.fields['twenty_euros'].initial = instance.twenty_euros.quantity if instance.twenty_euros else 0
            self.fields['fifty_euros'].initial = instance.fifty_euros.quantity if instance.fifty_euros else 0
            self.fields['hundred_euros'].initial = instance.hundred_euros.quantity if instance.hundred_euros else 0
            self.fields['check_1_quantity'].initial = instance.check_1.quantity if instance.check_1 else 0
            self.fields['check_2_quantity'].initial = instance.check_2.quantity if instance.check_2 else 0
            self.fields['check_3_quantity'].initial = instance.check_3.quantity if instance.check_3 else 0
            self.fields['check_4_quantity'].initial = instance.check_4.quantity if instance.check_4 else 0
            self.fields['check_5_quantity'].initial = instance.check_5.quantity if instance.check_5 else 0
            self.fields['check_1_value'].initial = instance.check_1.value if instance.check_1 else 0
            self.fields['check_2_value'].initial = instance.check_2.value if instance.check_2 else 0
            self.fields['check_3_value'].initial = instance.check_3.value if instance.check_3 else 0
            self.fields['check_4_value'].initial = instance.check_4.value if instance.check_4 else 0
            self.fields['check_5_value'].initial = instance.check_5.value if instance.check_5 else 0
            self.fields['comment'].initial = instance.comment
            self.fields['emptied'].initial = instance.emptied
            self.instance = instance
        else:
            self.instance = None

    def save(self, counter=None):
Skia's avatar
Skia committed
739
        cd = self.cleaned_data
740
        summary = self.instance or CashRegisterSummary(
Skia's avatar
Skia committed
741
742
743
                counter=counter,
                user=counter.get_random_barman(),
                )
744
745
        summary.comment = cd['comment']
        summary.emptied = cd['emptied']
Skia's avatar
Skia committed
746
        summary.save()
747
        summary.items.all().delete()
Skia's avatar
Skia committed
748
749
750
751
752
753
754
755
756
757
758
759
        # Cash
        if cd['ten_cents']: CashRegisterSummaryItem(cash_summary=summary, value=0.1, quantity=cd['ten_cents']).save()
        if cd['twenty_cents']: CashRegisterSummaryItem(cash_summary=summary, value=0.2, quantity=cd['twenty_cents']).save()
        if cd['fifty_cents']: CashRegisterSummaryItem(cash_summary=summary, value=0.5, quantity=cd['fifty_cents']).save()
        if cd['one_euro']: CashRegisterSummaryItem(cash_summary=summary, value=1, quantity=cd['one_euro']).save()
        if cd['two_euros']: CashRegisterSummaryItem(cash_summary=summary, value=2, quantity=cd['two_euros']).save()
        if cd['five_euros']: CashRegisterSummaryItem(cash_summary=summary, value=5, quantity=cd['five_euros']).save()
        if cd['ten_euros']: CashRegisterSummaryItem(cash_summary=summary, value=10, quantity=cd['ten_euros']).save()
        if cd['twenty_euros']: CashRegisterSummaryItem(cash_summary=summary, value=20, quantity=cd['twenty_euros']).save()
        if cd['fifty_euros']: CashRegisterSummaryItem(cash_summary=summary, value=50, quantity=cd['fifty_euros']).save()
        if cd['hundred_euros']: CashRegisterSummaryItem(cash_summary=summary, value=100, quantity=cd['hundred_euros']).save()
        # Checks
760
761
762
763
764
765
766
767
768
769
        if cd['check_1_quantity']: CashRegisterSummaryItem(cash_summary=summary, value=cd['check_1_value'],
                quantity=cd['check_1_quantity'], check=True).save()
        if cd['check_2_quantity']: CashRegisterSummaryItem(cash_summary=summary, value=cd['check_2_value'],
                quantity=cd['check_2_quantity'], check=True).save()
        if cd['check_3_quantity']: CashRegisterSummaryItem(cash_summary=summary, value=cd['check_3_value'],
                quantity=cd['check_3_quantity'], check=True).save()
        if cd['check_4_quantity']: CashRegisterSummaryItem(cash_summary=summary, value=cd['check_4_value'],
                quantity=cd['check_4_quantity'], check=True).save()
        if cd['check_5_quantity']: CashRegisterSummaryItem(cash_summary=summary, value=cd['check_5_value'],
                quantity=cd['check_5_quantity'], check=True).save()
Skia's avatar
Skia committed
770
771
772
        if summary.items.count() < 1:
            summary.delete()

Skia's avatar
Skia committed
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView):
    """
    Provide the last operations to allow barmen to delete them
    """
    model = Counter
    pk_url_kwarg = "counter_id"
    template_name = 'counter/last_ops.jinja'
    current_tab = "last_ops"

    def dispatch(self, request, *args, **kwargs):
        """
        We have here again a very particular right handling
        """
        self.object = self.get_object()
        if (self.object.get_barmen_list() and 'counter_token' in request.session.keys() and
            request.session['counter_token'] and # check if not null for counters that have no token set
            Counter.objects.filter(token=request.session['counter_token']).exists()):
            return super(CounterLastOperationsView, self).dispatch(request, *args, **kwargs)
        return HttpResponseRedirect(reverse('counter:details', kwargs={'counter_id': self.object.id})+'?bad_location')

    def get_context_data(self, **kwargs):
        """Add form to the context """
        kwargs = super(CounterLastOperationsView, self).get_context_data(**kwargs)
        threshold = timezone.now() - timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT)
        kwargs['last_refillings'] = self.object.refillings.filter(date__gte=threshold).all()
        kwargs['last_sellings'] = self.object.sellings.filter(date__gte=threshold).all()
        return kwargs

class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView):
Skia's avatar
Skia committed
802
803
804
805
806
807
    """
    Provide the cash summary form
    """
    model = Counter
    pk_url_kwarg = "counter_id"
    template_name = 'counter/cash_register_summary.jinja'
Skia's avatar
Skia committed
808
809
810
811
812
813
814
815
816
817
818
819
    current_tab = "cash_summary"

    def dispatch(self, request, *args, **kwargs):
        """
        We have here again a very particular right handling
        """
        self.object = self.get_object()
        if (self.object.get_barmen_list() and 'counter_token' in request.session.keys() and
            request.session['counter_token'] and # check if not null for counters that have no token set
            Counter.objects.filter(token=request.session['counter_token']).exists()):
            return super(CounterCashSummaryView, self).dispatch(request, *args, **kwargs)
        return HttpResponseRedirect(reverse('counter:details', kwargs={'counter_id': self.object.id})+'?bad_location')
Skia's avatar
Skia committed
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        self.form = CashRegisterSummaryForm()
        return super(CounterCashSummaryView, self).get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        self.form = CashRegisterSummaryForm(request.POST)
        if self.form.is_valid():
            self.form.save(self.object)
            return HttpResponseRedirect(self.get_success_url())
        return super(CounterCashSummaryView, self).get(request, *args, **kwargs)

    def get_success_url(self):
        return reverse_lazy('counter:details', kwargs={'counter_id': self.object.id})

    def get_context_data(self, **kwargs):
        """ Add form to the context """
        kwargs = super(CounterCashSummaryView, self).get_context_data(**kwargs)
        kwargs['form'] = self.form
        return kwargs
Skia's avatar
Skia committed
842
843
844
845
846
847
848
849
850

class CounterActivityView(DetailView):
    """
    Show the bar activity
    """
    model = Counter
    pk_url_kwarg = "counter_id"
    template_name = 'counter/activity.jinja'

Sli's avatar
Sli committed
851
class CounterStatView(DetailView, CanEditMixin):
Skia's avatar
Skia committed
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
    """
    Show the bar stats
    """
    model = Counter
    pk_url_kwarg = "counter_id"
    template_name = 'counter/stats.jinja'

    def get_context_data(self, **kwargs):
        """ Add stats to the context """
        from django.db.models import Sum, Case, When, F, DecimalField
        kwargs = super(CounterStatView, self).get_context_data(**kwargs)
        kwargs['Customer'] = Customer
        semester_start = Subscription.compute_start(d=date.today(), duration=3)
        kwargs['total_sellings'] = Selling.objects.filter(date__gte=semester_start,
                counter=self.object).aggregate(total_sellings=Sum(F('quantity')*F('unit_price'),
                    output_field=CurrencyField()))['total_sellings']
        kwargs['top'] = Selling.objects.values('customer__user').annotate(
                selling_sum=Sum(
                    Case(When(counter=self.object,
                            date__gte=semester_start,
                            unit_price__gt=0,
                        then=F('unit_price')*F('quantity')),
                        output_field=CurrencyField()
                        )
                    )
                ).exclude(selling_sum=None).order_by('-selling_sum').all()[:100]
        return kwargs

880
    def dispatch(self, request, *args, **kwargs):
Sli's avatar
Sli committed
881
882
883
        try:
            return super(CounterStatView, self).dispatch(request, *args, **kwargs)
        except:
884
885
886
887
            if (request.user.is_root
                or request.user.is_board_member
                or self.object.is_owned_by(request.user)):
                return super(CanEditMixin, self).dispatch(request, *args, **kwargs)
888
889
        raise PermissionDenied

890
891
892
893
894
895
896
897
898
899
900
class CashSummaryEditView(CanEditPropMixin, CounterAdminTabsMixin, UpdateView):
    """Edit cash summaries"""
    model = CashRegisterSummary
    template_name = 'counter/cash_register_summary.jinja'
    context_object_name = "cashsummary"
    pk_url_kwarg = "cashsummary_id"
    form_class = CashRegisterSummaryForm
    current_tab = "cash_summary"

    def get_success_url(self):
        return reverse('counter:cash_summary_list')
Skia's avatar
Skia committed
901

902
903
904
905
class CashSummaryFormBase(forms.Form):
    begin_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Begin date"), required=False, widget=SelectDateTime)
    end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), required=False, widget=SelectDateTime)

Skia's avatar
Skia committed
906
class CashSummaryListView(CanEditPropMixin, CounterAdminTabsMixin, ListView):
Skia's avatar
Skia committed
907
908
909
910
911
912
913
914
915
    """Display a list of cash summaries"""
    model = CashRegisterSummary
    template_name = 'counter/cash_summary_list.jinja'
    context_object_name = "cashsummary_list"
    current_tab = "cash_summary"

    def get_context_data(self, **kwargs):
        """ Add sums to the context """
        kwargs = super(CashSummaryListView, self).get_context_data(**kwargs)
916
917
        form = CashSummaryFormBase(self.request.GET)
        kwargs['form'] = form
Skia's avatar
Skia committed
918
919
920
        kwargs['summaries_sums'] = {}
        kwargs['refilling_sums'] = {}
        for c in Counter.objects.filter(type="BAR").all():
921
922
923
924
925
            refillings = Refilling.objects.filter(counter=c)
            cashredistersummaries = CashRegisterSummary.objects.filter(counter=c)
            if form.is_valid() and form.cleaned_data['begin_date']:
                refillings = refillings.filter(date__gte=form.cleaned_data['begin_date'])
                cashredistersummaries = cashredistersummaries.filter(date__gte=form.cleaned_data['begin_date'])
Skia's avatar
Skia committed
926
            else:
927
928
929
930
931
932
933
934
935
936
937
938
                last_summary = CashRegisterSummary.objects.filter(counter=c, emptied=True).order_by('-date').first()
                if last_summary:
                    refillings = refillings.filter(date__gte=last_summary.date)
                    cashredistersummaries = cashredistersummaries.filter(date__gte=last_summary.date)
                else:
                    refillings = refillings.filter(date__gte=datetime(year=1994, month=5, day=17, tzinfo=pytz.UTC)) # My birth date should be old enough
                    cashredistersummaries = cashredistersummaries.filter(date__gte=datetime(year=1994, month=5, day=17, tzinfo=pytz.UTC))
            if form.is_valid() and form.cleaned_data['end_date']:
                refillings = refillings.filter(date__lte=form.cleaned_data['end_date'])
                cashredistersummaries = cashredistersummaries.filter(date__lte=form.cleaned_data['end_date'])
            kwargs['summaries_sums'][c.name] = sum([s.get_total() for s in cashredistersummaries.all()])
            kwargs['refilling_sums'][c.name] = sum([s.amount for s in refillings.all()])
Skia's avatar
Skia committed
939
        return kwargs
940

Skia's avatar
Skia committed
941
942
943
944
945
946
947
948
949
950
951
952
953
954
class InvoiceCallView(CounterAdminTabsMixin, TemplateView):
    template_name = 'counter/invoices_call.jinja'
    current_tab = 'invoices_call'

    def get_context_data(self, **kwargs):
        """ Add sums to the context """
        kwargs = super(InvoiceCallView, self).get_context_data(**kwargs)
        kwargs['months'] = Selling.objects.datetimes('date', 'month', order='DESC')
        start_date = None
        end_date = None
        try:
            start_date = datetime.strptime(self.request.GET['month'], '%Y-%m')
        except:
            start_date = datetime(year=timezone.now().year, month=(timezone.now().month+10)%12+1, day=1)
Skia's avatar
Skia committed
955
        start_date = start_date.replace(tzinfo=pytz.UTC)
Skia's avatar
Skia committed
956
957
        end_date = (start_date + timedelta(days=32)).replace(day=1, hour=0, minute=0, microsecond=0)
        from django.db.models import Sum, Case, When, F, DecimalField
Skia's avatar
Skia committed
958
        kwargs['start_date'] = start_date
Skia's avatar
Skia committed
959
960
961
962
963
964
965
966
967
        kwargs['sums'] = Selling.objects.values('club__name').annotate(selling_sum=Sum(
            Case(When(date__gte=start_date,
                date__lt=end_date,
                then=F('unit_price')*F('quantity')),
                output_field=CurrencyField()
                )
            )).exclude(selling_sum=None).order_by('-selling_sum')
        return kwargs

Skia's avatar
Skia committed
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
class EticketListView(CounterAdminTabsMixin, CanEditPropMixin, ListView):
    """
    A list view for the admins
    """
    model = Eticket
    template_name = 'counter/eticket_list.jinja'
    ordering = ['id']
    current_tab = "etickets"

class EticketForm(forms.ModelForm):
    class Meta:
        model = Eticket
        fields = ['product', 'banner', 'event_title', 'event_date']
        widgets = {
                'event_date': SelectDate,
                }
    product = AutoCompleteSelectField('products', show_help_text=False, label=_("Product"), required=True)

class EticketCreateView(CounterAdminTabsMixin, CanEditPropMixin, CreateView):
    """
    Create an eticket
    """
    model = Eticket
    template_name = 'core/create.jinja'
    form_class = EticketForm
    current_tab = "etickets"

class EticketEditView(CounterAdminTabsMixin, CanEditPropMixin, UpdateView):
    """
    Edit an eticket
    """
    model = Eticket
    template_name = 'core/edit.jinja'
    form_class = EticketForm
    pk_url_kwarg = "eticket_id"
    current_tab = "etickets"

class EticketPDFView(CanViewMixin, DetailView):
    """
    Display the PDF of an eticket
    """
    model = Selling
    pk_url_kwarg = "selling_id"

    def get(self, request, *args, **kwargs):
        from reportlab.pdfgen import canvas
        from reportlab.lib.utils import ImageReader
        from reportlab.lib.units import cm
        from reportlab.graphics.shapes import Drawing
        from reportlab.graphics.barcode.qr import QrCodeWidget
        from reportlab.graphics import renderPDF
        self.object = self.get_object()
        eticket = self.object.product.eticket
        user = self.object.customer.user
Skia's avatar
Skia committed
1022
        code = "%s %s %s" % (self.object.customer.user.id, self.object.product.id, self.object.quantity)
Skia's avatar
Skia committed
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
        code += " " + eticket.get_hash(code)[:8].upper()
        response = HttpResponse(content_type='application/pdf')
        response['Content-Disposition'] = 'filename="eticket.pdf"'
        p = canvas.Canvas(response)
        p.setTitle("Eticket")
        im = ImageReader("core/static/core/img/eticket.jpg")
        width, height = im.getSize()
        size = max(width, height)
        width = 8 * cm * width / size
        height = 8 * cm * height / size
        p.drawImage(im, 10 * cm, 25 * cm, width, height)
        if eticket.banner:
            im = ImageReader(eticket.banner)
            width, height = im.getSize()
            size = max(width, height)
            width = 6 * cm * width / size
            height = 6 * cm * height / size
            p.drawImage(im, 1 * cm, 25 * cm, width, height)
        if user.profile_pict:
            im = ImageReader(user.profile_pict.file)
            width, height = im.getSize()
            size = max(width, height)
            width = 150 * width / size
            height = 150 * height / size
            p.drawImage(im, 10.5 * cm - width / 2, 16 * cm, width, height)
        if eticket.event_title:
            p.setFont("Helvetica-Bold", 20)
            p.drawCentredString(10.5 * cm, 23.6 * cm, eticket.event_title)
        if eticket.event_date:
            p.setFont("Helvetica-Bold", 16)
Skia's avatar
Skia committed
1053
            p.drawCentredString(10.5 * cm, 22.6 * cm, eticket.event_date.strftime("%d %b %Y")) # FIXME with a locale
Skia's avatar
Skia committed
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
        p.setFont("Helvetica-Bold", 14)
        p.drawCentredString(10.5 * cm, 15 * cm, user.get_display_name())
        p.setFont("Courier-Bold", 14)
        qrcode = QrCodeWidget(code)
        bounds = qrcode.getBounds()
        width = bounds[2] - bounds[0]
        height = bounds[3] - bounds[1]
        d = Drawing(260, 260, transform=[260./width, 0, 0, 260./height, 0, 0])
        d.add(qrcode)
        renderPDF.draw(d, p, 10.5 * cm - 130, 6.1 * cm)
        p.drawCentredString(10.5 * cm, 6 * cm, code)

        p.showPage()
        p.save()
        return response