views.py 47.8 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
281
        if product.limit_age >= 18 and not self.customer.user.date_of_birth:
            request.session['no_age'] = True
            return False
        if self.customer.user.date_of_birth and self.customer.user.get_age() < product.limit_age: # Check if affordable
282
283
            request.session['too_young'] = True
            return False
Skia's avatar
Skia committed
284
        if pid in request.session['basket']: # Add if already in basket
285
            request.session['basket'][pid]['qty'] += q
Skia's avatar
Skia committed
286
287
288
            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}
289
        request.session.modified = True
Skia's avatar
Skia committed
290
        return True
291
292
293

    def del_product(self, request):
        """ Delete a product from the basket """
294
        pid = str(request.POST['product_id'])
Skia's avatar
Skia committed
295
        product = self.get_product(pid)
296
        if pid in request.session['basket']:
Skia's avatar
Skia committed
297
298
299
300
            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
301
            if request.session['basket'][pid]['qty'] <= 0:
302
                del request.session['basket'][pid]
303
        else:
Skia's avatar
Skia committed
304
            request.session['basket'][pid] = None
305
306
        request.session.modified = True

Skia's avatar
Skia committed
307
308
309
    def parse_code(self, request):
        """Parse the string entered by the barman"""
        string = str(request.POST['code']).upper()
Skia's avatar
Skia committed
310
        if string == _("END"):
Skia's avatar
Skia committed
311
            return self.finish(request)
Skia's avatar
Skia committed
312
        elif string == _("CAN"):
Skia's avatar
Skia committed
313
314
315
316
317
318
319
320
321
322
            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
323
            p = self.object.products.filter(code=code).first()
Skia's avatar
Skia committed
324
325
326
327
328
329
            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)

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

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

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

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

Skia's avatar
Skia committed
387
class CounterLogin(RedirectView):
Skia's avatar
Skia committed
388
389
390
    """
    Handle the login of a barman

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

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

class CounterLogout(RedirectView):
    permanent = False
    def post(self, request, *args, **kwargs):
Skia's avatar
Skia committed
421
422
423
        """
        Unregister the user from the barman
        """
424
425
426
        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
427
428
429
430
        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
431

432
433
## Counter admin views

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

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

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

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

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

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

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

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

Skia's avatar
Skia committed
532
533
# Product management

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

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

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

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

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

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

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

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

Skia's avatar
Skia committed
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
    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
651

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

Skia's avatar
Skia committed
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
    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
675

Skia's avatar
Skia committed
676
677
678
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
# 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)

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

Skia's avatar
Skia committed
770
771
772
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
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
799
800
801
802
803
804
    """
    Provide the cash summary form
    """
    model = Counter
    pk_url_kwarg = "counter_id"
    template_name = 'counter/cash_register_summary.jinja'
Skia's avatar
Skia committed
805
806
807
808
809
810
811
812
813
814
815
816
    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
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838

    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
839
840
841
842
843
844
845
846
847

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

Sli's avatar
Sli committed
848
class CounterStatView(DetailView, CanEditMixin):
Skia's avatar
Skia committed
849
850
851
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
    """
    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

877
    def dispatch(self, request, *args, **kwargs):
Sli's avatar
Sli committed
878
879
880
        try:
            return super(CounterStatView, self).dispatch(request, *args, **kwargs)
        except:
881
882
883
884
            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)
885
886
        raise PermissionDenied

887
888
889
890
891
892
893
894
895
896
897
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
898

899
900
901
902
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
903
class CashSummaryListView(CanEditPropMixin, CounterAdminTabsMixin, ListView):
Skia's avatar
Skia committed
904
905
906
907
908
909
910
911
912
    """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)
913
914
        form = CashSummaryFormBase(self.request.GET)
        kwargs['form'] = form
Skia's avatar
Skia committed
915
916
917
        kwargs['summaries_sums'] = {}
        kwargs['refilling_sums'] = {}
        for c in Counter.objects.filter(type="BAR").all():
918
919
920
921
922
            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
923
            else:
924
925
                last_summary = CashRegisterSummary.objects.filter(counter=c, emptied=True).order_by('-date').first()
                if last_summary:
Skia's avatar
Skia committed
926
927
                    refillings = refillings.filter(date__gt=last_summary.date)
                    cashredistersummaries = cashredistersummaries.filter(date__gt=last_summary.date)
928
929
930
931
932
933
934
935
                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
936
        return kwargs
937

Skia's avatar
Skia committed
938
939
940
941
942
943
944
945
946
947
948
949
950
951
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
952
        start_date = start_date.replace(tzinfo=pytz.UTC)
Skia's avatar
Skia committed
953
954
        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
955
        kwargs['start_date'] = start_date
Skia's avatar
Skia committed
956
957
958
959
960
961
962
963
964
        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
965
966
967
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
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
1019
        code = "%s %s %s" % (self.object.customer.user.id, self.object.product.id, self.object.quantity)
Skia's avatar
Skia committed
1020
1021
1022
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
        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
1050
            p.drawCentredString(10.5 * cm, 22.6 * cm, eticket.event_date.strftime("%d %b %Y")) # FIXME with a locale
Skia's avatar
Skia committed
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
        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