views.py 48.2 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
class RefillForm(forms.ModelForm):
    error_css_class = 'error'
    required_css_class = 'required'
    class Meta:
        model = Refilling
        fields = ['amount', 'payment_method', 'bank']
66
67
68
        widgets = {
            'amount': forms.NumberInput(attrs={'class':'focus'},)
        }
69

Skia's avatar
Skia committed
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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
93
class CounterMain(CounterTabsMixin, DetailView, ProcessFormView, FormMixin):
Skia's avatar
Skia committed
94
95
96
    """
    The public (barman) view
    """
Skia's avatar
Skia committed
97
    model = Counter
Skia's avatar
Skia committed
98
    template_name = 'counter/counter_main.jinja'
Skia's avatar
Skia committed
99
    pk_url_kwarg = "counter_id"
100
    form_class = GetUserForm # Form to enter a client code and get the corresponding user id
Skia's avatar
Skia committed
101
    current_tab = "counter"
Skia's avatar
Skia committed
102

103
104
105
106
107
108
109
110
    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
111
    def get_context_data(self, **kwargs):
Skia's avatar
Skia committed
112
        """
113
        We handle here the login form for the barman
Skia's avatar
Skia committed
114
        """
115
116
        if self.request.method == 'POST':
            self.object = self.get_object()
Skia's avatar
Skia committed
117
        self.object.update_activity()
Skia's avatar
Skia committed
118
        kwargs = super(CounterMain, self).get_context_data(**kwargs)
Skia's avatar
Skia committed
119
        kwargs['login_form'] = LoginForm()
Skia's avatar
Skia committed
120
        kwargs['login_form'].fields['username'].widget.attrs['autofocus'] = True
Skia's avatar
Skia committed
121
122
123
        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"))
124
125
        if "sellers" in self.request.GET:
            kwargs['login_form'].add_error(None, _("User is not barman"))
Skia's avatar
Skia committed
126
        kwargs['form'] = self.get_form()
127
128
        kwargs['form'].cleaned_data = {} # same as above
        if "bad_location" in self.request.GET:
Skia's avatar
Skia committed
129
            kwargs['form'].add_error(None, _("Bad location, someone is already logged in somewhere else"))
Skia's avatar
Skia committed
130
        if self.object.type == 'BAR':
Skia's avatar
Skia committed
131
            kwargs['barmen'] = self.object.get_barmen_list()
Skia's avatar
Skia committed
132
133
        elif self.request.user.is_authenticated():
            kwargs['barmen'] = [self.request.user]
134
135
136
137
138
        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
139
140
        return kwargs

141
142
143
144
145
146
147
148
149
150
    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
151
class CounterClick(CounterTabsMixin, DetailView):
Skia's avatar
Skia committed
152
153
    """
    The click view
154
155
    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
156
    """
157
    model = Counter
Skia's avatar
Skia committed
158
159
    template_name = 'counter/counter_click.jinja'
    pk_url_kwarg = "counter_id"
Skia's avatar
Skia committed
160
    current_tab = "counter"
161
162

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

    def post(self, request, *args, **kwargs):
181
        """ Handle the many possibilities of the post request """
182
183
        self.object = self.get_object()
        self.customer = Customer.objects.filter(user__id=self.kwargs['user_id']).first()
184
        self.refill_form = None
Skia's avatar
Skia committed
185
186
        if ((self.object.type != "BAR" and not request.user.is_authenticated()) or
                (self.object.type == "BAR" and
Skia's avatar
Skia committed
187
                len(self.object.get_barmen_list()) < 1)): # Check that at least one barman is logged in
188
            return self.cancel(request)
189
190
191
192
        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')
193
194
        if 'basket' not in request.session.keys():
            request.session['basket'] = {}
195
            request.session['basket_total'] = 0
Skia's avatar
Skia committed
196
        request.session['not_enough'] = False # Reset every variable
197
        request.session['too_young'] = False
198
        request.session['not_allowed'] = False
Skia's avatar
Skia committed
199
        request.session['no_age'] = False
Skia's avatar
Skia committed
200
201
202
203
204
        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
205
            self.operator = self.object.get_random_barman()
206
207
208
209
210

        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
211
212
        elif 'refill' in request.POST['action']:
            self.refill(request)
Skia's avatar
Skia committed
213
214
        elif 'code' in request.POST['action']:
            return self.parse_code(request)
215
216
217
218
219
220
221
        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
222
    def is_barman_price(self):
Skia's avatar
Skia committed
223
        if self.object.type == "BAR" and self.customer.user.id in [s.id for s in self.object.get_barmen_list()]:
224
225
226
227
            return True
        else:
            return False

228
229
230
    def get_product(self, pid):
        return Product.objects.filter(pk=int(pid)).first()

231
    def get_price(self, pid):
232
        p = self.get_product(pid)
233
234
235
236
237
238
239
240
241
242
        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']
243
        return total / 100
244

Skia's avatar
Skia committed
245
246
247
248
249
250
251
    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
252
    def add_product(self, request, q = 1, p=None):
253
254
255
256
257
        """
        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
258
259
        pid = p or request.POST['product_id']
        pid = str(pid)
260
        price = self.get_price(pid)
261
        total = self.sum_basket(request)
Skia's avatar
Skia committed
262
        product = self.get_product(pid)
263
        can_buy = False
264
265
266
267
268
269
        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
270
271
272
        if not can_buy:
            request.session['not_allowed'] = True
            return False
Skia's avatar
Skia committed
273
274
275
276
277
278
        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
279
            request.session['not_enough'] = True
Skia's avatar
Skia committed
280
            return False
Skia's avatar
Skia committed
281
282
283
        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
284
285
286
        if product.limit_age >= 18 and self.customer.user.is_banned_alcohol:
            request.session['not_allowed'] = True
            return False
287
288
289
        if self.customer.user.is_banned_counter:
            request.session['not_allowed'] = True
            return False
Skia's avatar
Skia committed
290
        if self.customer.user.date_of_birth and self.customer.user.get_age() < product.limit_age: # Check if affordable
291
292
            request.session['too_young'] = True
            return False
Skia's avatar
Skia committed
293
        if pid in request.session['basket']: # Add if already in basket
294
            request.session['basket'][pid]['qty'] += q
Skia's avatar
Skia committed
295
296
297
            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}
298
        request.session.modified = True
Skia's avatar
Skia committed
299
        return True
300
301
302

    def del_product(self, request):
        """ Delete a product from the basket """
303
        pid = str(request.POST['product_id'])
Skia's avatar
Skia committed
304
        product = self.get_product(pid)
305
        if pid in request.session['basket']:
Skia's avatar
Skia committed
306
307
308
309
            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
310
            if request.session['basket'][pid]['qty'] <= 0:
311
                del request.session['basket'][pid]
312
        else:
Skia's avatar
Skia committed
313
            request.session['basket'][pid] = None
314
315
        request.session.modified = True

Skia's avatar
Skia committed
316
317
318
    def parse_code(self, request):
        """Parse the string entered by the barman"""
        string = str(request.POST['code']).upper()
Skia's avatar
Skia committed
319
        if string == _("END"):
Skia's avatar
Skia committed
320
            return self.finish(request)
Skia's avatar
Skia committed
321
        elif string == _("CAN"):
Skia's avatar
Skia committed
322
323
324
325
326
327
328
329
330
331
            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
332
            p = self.object.products.filter(code=code).first()
Skia's avatar
Skia committed
333
334
335
336
337
338
            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)

339
340
    def finish(self, request):
        """ Finish the click session, and validate the basket """
Skia's avatar
Skia committed
341
342
343
344
345
346
347
348
349
350
351
        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
352
                request.session['last_basket'].append("%d x %s" % (infos['qty']+infos['bonus_qty'], p.name))
353
                s = Selling(label=p.name, product=p, club=p.club, counter=self.object, unit_price=uprice,
Skia's avatar
Skia committed
354
                       quantity=infos['qty'], seller=self.operator, customer=self.customer)
Skia's avatar
Skia committed
355
                s.save()
Skia's avatar
Skia committed
356
357
358
359
                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
360
361
362
363
364
365
366
367
368
            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))
369
370
371
372

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

Skia's avatar
Skia committed
376
377
    def refill(self, request):
        """Refill the customer's account"""
378
379
380
        form = RefillForm(request.POST)
        if form.is_valid():
            form.instance.counter = self.object
Skia's avatar
Skia committed
381
            form.instance.operator = self.operator
382
383
384
385
            form.instance.customer = self.customer
            form.instance.save()
        else:
            self.refill_form = form
Skia's avatar
Skia committed
386

387
    def get_context_data(self, **kwargs):
388
        """ Add customer to the context """
389
390
        kwargs = super(CounterClick, self).get_context_data(**kwargs)
        kwargs['customer'] = self.customer
391
        kwargs['basket_total'] = self.sum_basket(self.request)
392
        kwargs['refill_form'] = self.refill_form or RefillForm()
393
        kwargs['categories'] = ProductType.objects.all()
394
395
        return kwargs

Skia's avatar
Skia committed
396
class CounterLogin(RedirectView):
Skia's avatar
Skia committed
397
398
399
    """
    Handle the login of a barman

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

    def get_redirect_url(self, *args, **kwargs):
Skia's avatar
Skia committed
425
        return reverse_lazy('counter:details', args=args, kwargs=kwargs)+"?"+'&'.join(self.errors)
Skia's avatar
Skia committed
426
427
428
429

class CounterLogout(RedirectView):
    permanent = False
    def post(self, request, *args, **kwargs):
Skia's avatar
Skia committed
430
431
432
        """
        Unregister the user from the barman
        """
433
434
435
        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
436
437
438
439
        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
440

441
442
## Counter admin views

Skia's avatar
Skia committed
443
class CounterAdminTabsMixin(TabedViewMixin):
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
    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
466
467
468
469
470
            {
                'url': reverse_lazy('counter:cash_summary_list'),
                'slug': 'cash_summary',
                'name': _("Cash register summaries"),
                },
Skia's avatar
Skia committed
471
472
473
474
475
            {
                'url': reverse_lazy('counter:invoices_call'),
                'slug': 'invoices_call',
                'name': _("Invoices call"),
                },
Skia's avatar
Skia committed
476
477
478
479
480
            {
                'url': reverse_lazy('counter:eticket_list'),
                'slug': 'etickets',
                'name': _("Etickets"),
                },
481
482
            ]

Skia's avatar
Skia committed
483
class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView):
Skia's avatar
Skia committed
484
485
486
487
488
    """
    A list view for the admins
    """
    model = Counter
    template_name = 'counter/counter_list.jinja'
489
    current_tab = "counters"
Skia's avatar
Skia committed
490

491
492
493
494
class CounterEditForm(forms.ModelForm):
    class Meta:
        model = Counter
        fields = ['sellers', 'products']
495
496
    sellers = make_ajax_field(Counter, 'sellers', 'users', help_text="")
    products = make_ajax_field(Counter, 'products', 'products', help_text="")
497

Skia's avatar
Skia committed
498
class CounterEditView(CounterAdminTabsMixin, CanEditMixin, UpdateView):
499
500
501
502
503
504
    """
    Edit a counter's main informations (for the counter's manager)
    """
    model = Counter
    form_class = CounterEditForm
    pk_url_kwarg = "counter_id"
505
    template_name = 'core/edit.jinja'
506
    current_tab = "counters"
507
508
509
510

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

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

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

Skia's avatar
Skia committed
531
class CounterDeleteView(CounterAdminTabsMixin, CanEditMixin, DeleteView):
Skia's avatar
Skia committed
532
    """
Skia's avatar
Skia committed
533
    Delete a counter (for the admins)
Skia's avatar
Skia committed
534
535
536
537
538
    """
    model = Counter
    pk_url_kwarg = "counter_id"
    template_name = 'core/delete_confirm.jinja'
    success_url = reverse_lazy('counter:admin_list')
539
    current_tab = "counters"
Skia's avatar
Skia committed
540

Skia's avatar
Skia committed
541
542
# Product management

Skia's avatar
Skia committed
543
class ProductTypeListView(CounterAdminTabsMixin, CanEditPropMixin, ListView):
Skia's avatar
Skia committed
544
545
546
547
548
    """
    A list view for the admins
    """
    model = ProductType
    template_name = 'counter/producttype_list.jinja'
549
    current_tab = "product_types"
Skia's avatar
Skia committed
550

Skia's avatar
Skia committed
551
class ProductTypeCreateView(CounterAdminTabsMixin, CanCreateMixin, CreateView):
Skia's avatar
Skia committed
552
553
554
555
556
557
    """
    A create view for the admins
    """
    model = ProductType
    fields = ['name', 'description', 'icon']
    template_name = 'core/create.jinja'
558
    current_tab = "products"
Skia's avatar
Skia committed
559

Skia's avatar
Skia committed
560
class ProductTypeEditView(CounterAdminTabsMixin, CanEditPropMixin, UpdateView):
Skia's avatar
Skia committed
561
562
563
564
565
566
567
    """
    An edit view for the admins
    """
    model = ProductType
    template_name = 'core/edit.jinja'
    fields = ['name', 'description', 'icon']
    pk_url_kwarg = "type_id"
568
    current_tab = "products"
Skia's avatar
Skia committed
569

Skia's avatar
Skia committed
570
class ProductArchivedListView(CounterAdminTabsMixin, CanEditPropMixin, ListView):
Skia's avatar
Skia committed
571
572
573
574
575
576
577
    """
    A list view for the admins
    """
    model = Product
    template_name = 'counter/product_list.jinja'
    queryset = Product.objects.filter(archived=True)
    ordering = ['name']
578
    current_tab = "archive"
Skia's avatar
Skia committed
579

Skia's avatar
Skia committed
580
class ProductListView(CounterAdminTabsMixin, CanEditPropMixin, ListView):
Skia's avatar
Skia committed
581
582
583
584
585
    """
    A list view for the admins
    """
    model = Product
    template_name = 'counter/product_list.jinja'
Skia's avatar
Skia committed
586
    queryset = Product.objects.filter(archived=False)
Skia's avatar
Skia committed
587
    ordering = ['name']
588
    current_tab = "products"
Skia's avatar
Skia committed
589

590
591
592
593
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
594
                'selling_price', 'special_selling_price', 'icon', 'club', 'limit_age', 'tray', 'archived']
595
    parent_product = AutoCompleteSelectField('products', show_help_text=False, label=_("Parent product"), required=False)
Skia's avatar
Skia committed
596
    buying_groups = AutoCompleteSelectMultipleField('groups', show_help_text=False, help_text="", label=_("Buying groups"), required=False)
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
    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
618
class ProductCreateView(CounterAdminTabsMixin, CanCreateMixin, CreateView):
Skia's avatar
Skia committed
619
620
621
622
    """
    A create view for the admins
    """
    model = Product
623
    form_class = ProductEditForm
Skia's avatar
Skia committed
624
    template_name = 'core/create.jinja'
625
    current_tab = "products"
Skia's avatar
Skia committed
626

Skia's avatar
Skia committed
627
class ProductEditView(CounterAdminTabsMixin, CanEditPropMixin, UpdateView):
Skia's avatar
Skia committed
628
629
630
631
    """
    An edit view for the admins
    """
    model = Product
632
    form_class = ProductEditForm
Skia's avatar
Skia committed
633
634
    pk_url_kwarg = "product_id"
    template_name = 'core/edit.jinja'
635
    current_tab = "products"
Skia's avatar
Skia committed
636

Skia's avatar
Skia committed
637
class RefillingDeleteView(DeleteView):
Skia's avatar
Skia committed
638
639
640
641
642
643
644
    """
    Delete a refilling (for the admins)
    """
    model = Refilling
    pk_url_kwarg = "refilling_id"
    template_name = 'core/delete_confirm.jinja'

Skia's avatar
Skia committed
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
    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
660

Skia's avatar
Skia committed
661
class SellingDeleteView(DeleteView):
Skia's avatar
Skia committed
662
663
664
665
666
667
668
    """
    Delete a selling (for the admins)
    """
    model = Selling
    pk_url_kwarg = "selling_id"
    template_name = 'core/delete_confirm.jinja'

Skia's avatar
Skia committed
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
    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
684

Skia's avatar
Skia committed
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
# 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)

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
739
740
741
742
743
744
    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
745
        cd = self.cleaned_data
746
        summary = self.instance or CashRegisterSummary(
Skia's avatar
Skia committed
747
748
749
                counter=counter,
                user=counter.get_random_barman(),
                )
750
751
        summary.comment = cd['comment']
        summary.emptied = cd['emptied']
Skia's avatar
Skia committed
752
        summary.save()
753
        summary.items.all().delete()
Skia's avatar
Skia committed
754
755
756
757
758
759
760
761
762
763
764
765
        # 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
766
767
768
769
770
771
772
773
774
775
        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
776
777
778
        if summary.items.count() < 1:
            summary.delete()

Skia's avatar
Skia committed
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
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
808
809
810
811
812
813
    """
    Provide the cash summary form
    """
    model = Counter
    pk_url_kwarg = "counter_id"
    template_name = 'counter/cash_register_summary.jinja'
Skia's avatar
Skia committed
814
815
816
817
818
819
820
821
822
823
824
825
    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
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847

    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
848
849
850
851
852
853
854
855
856

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

Sli's avatar
Sli committed
857
class CounterStatView(DetailView, CanEditMixin):
Skia's avatar
Skia committed
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
    """
    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

886
    def dispatch(self, request, *args, **kwargs):
Sli's avatar
Sli committed
887
888
889
        try:
            return super(CounterStatView, self).dispatch(request, *args, **kwargs)
        except:
890
891
892
893
            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)
894
895
        raise PermissionDenied

896
897
898
899
900
901
902
903
904
905
906
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
907

908
909
910
911
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
912
class CashSummaryListView(CanEditPropMixin, CounterAdminTabsMixin, ListView):
Skia's avatar
Skia committed
913
914
915
916
917
918
919
920
921
    """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)
922
923
        form = CashSummaryFormBase(self.request.GET)
        kwargs['form'] = form
Skia's avatar
Skia committed
924
925
926
        kwargs['summaries_sums'] = {}
        kwargs['refilling_sums'] = {}
        for c in Counter.objects.filter(type="BAR").all():
927
928
929
930
931
            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
932
            else:
933
934
935
936
937
938
939
940
941
942
943
944
                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
945
        return kwargs
946

Skia's avatar
Skia committed
947
948
949
950
951
952
953
954
955
956
957
958
959
960
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
961
        start_date = start_date.replace(tzinfo=pytz.UTC)
Skia's avatar
Skia committed
962
963
        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
964
        kwargs['start_date'] = start_date
Skia's avatar
Skia committed
965
966
967
968
969
970
971
972
973
        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
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
1022
1023
1024
1025
1026
1027
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
Sli's avatar
Sli committed
1028
1029
        print(self.object)
        code = "%s %s %s %s" % (self.object.customer.user.id, self.object.product.id, self.object.id, self.object.quantity)
Skia's avatar
Skia committed
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
        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
1060
            p.drawCentredString(10.5 * cm, 22.6 * cm, eticket.event_date.strftime("%d %b %Y")) # FIXME with a locale
Skia's avatar
Skia committed
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
        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