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

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

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

Skia's avatar
Skia committed
31
class GetUserForm(forms.Form):
32
33
34
35
36
37
38
    """
    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
39
    code = forms.CharField(label="Code", max_length=10, required=False)
40
    id = AutoCompleteSelectField('users', required=False, label=_("Select user"), help_text=None)
Skia's avatar
Skia committed
41

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

46
47
    def clean(self):
        cleaned_data = super(GetUserForm, self).clean()
Skia's avatar
Skia committed
48
        cus = None
49
        if cleaned_data['code'] != "":
Skia's avatar
Skia committed
50
            cus = Customer.objects.filter(account_id__iexact=cleaned_data['code']).first()
51
        elif cleaned_data['id'] is not None:
Skia's avatar
Skia committed
52
            cus = Customer.objects.filter(user=cleaned_data['id']).first()
53
        if (cus is None or not cus.can_buy):
Skia's avatar
Skia committed
54
            raise forms.ValidationError(_("User not found"))
Skia's avatar
Skia committed
55
56
        cleaned_data['user_id'] = cus.user.id
        cleaned_data['user'] = cus.user
57
58
        return cleaned_data

59
60
61
class RefillForm(forms.ModelForm):
    error_css_class = 'error'
    required_css_class = 'required'
Sli's avatar
Sli committed
62
    amount = forms.FloatField(min_value=0, widget=forms.NumberInput(attrs={'class':'focus'}))
63
64
65
66
    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, CanViewMixin, 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, CanViewMixin, 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
160
161
162
163
164
    def dispatch(self, request, *args, **kwargs):
        self.customer = get_object_or_404(Customer, user__id=self.kwargs['user_id'])
        if not self.customer.can_buy:
            raise Http404
        return super(CounterClick, self).dispatch(request, *args, **kwargs)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

442
443
## Counter admin views

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

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

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

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

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

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

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

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

Skia's avatar
Skia committed
542
543
# Product management

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

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

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

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

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

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

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

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

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

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

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

Skia's avatar
Skia committed
686
687
688
689
690
691
# Cash register summaries

class CashRegisterSummaryForm(forms.Form):
    """
    Provide the cash summary form
    """
Sli's avatar
Sli committed
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
    ten_cents = forms.IntegerField(label=_("10 cents"), required=False, min_value=0)
    twenty_cents = forms.IntegerField(label=_("20 cents"), required=False, min_value=0)
    fifty_cents = forms.IntegerField(label=_("50 cents"), required=False, min_value=0)
    one_euro = forms.IntegerField(label=_("1 euro"), required=False, min_value=0)
    two_euros = forms.IntegerField(label=_("2 euros"), required=False, min_value=0)
    five_euros = forms.IntegerField(label=_("5 euros"), required=False, min_value=0)
    ten_euros = forms.IntegerField(label=_("10 euros"), required=False, min_value=0)
    twenty_euros = forms.IntegerField(label=_("20 euros"), required=False, min_value=0)
    fifty_euros = forms.IntegerField(label=_("50 euros"), required=False, min_value=0)
    hundred_euros = forms.IntegerField(label=_("100 euros"), required=False, min_value=0)
    check_1_value = forms.DecimalField(label=_("Check amount"), required=False, min_value=0)
    check_1_quantity = forms.IntegerField(label=_("Check quantity"), required=False, min_value=0)
    check_2_value = forms.DecimalField(label=_("Check amount"), required=False, min_value=0)
    check_2_quantity = forms.IntegerField(label=_("Check quantity"), required=False, min_value=0)
    check_3_value = forms.DecimalField(label=_("Check amount"), required=False, min_value=0)
    check_3_quantity = forms.IntegerField(label=_("Check quantity"), required=False, min_value=0)
    check_4_value = forms.DecimalField(label=_("Check amount"), required=False, min_value=0)
    check_4_quantity = forms.IntegerField(label=_("Check quantity"), required=False, min_value=0)
    check_5_value = forms.DecimalField(label=_("Check amount"), required=False, min_value=0)
    check_5_quantity = forms.IntegerField(label=_("Check quantity"), required=False, min_value=0)
Skia's avatar
Skia committed
712
713
714
    comment = forms.CharField(label=_("Comment"), required=False)
    emptied = forms.BooleanField(label=_("Emptied"), required=False)

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

Skia's avatar
Skia committed
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
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)
Skia's avatar
Skia committed
804
805
        kwargs['last_refillings'] = self.object.refillings.filter(date__gte=threshold).order_by('-id')[:20]
        kwargs['last_sellings'] = self.object.sellings.filter(date__gte=threshold).order_by('-id')[:20]
Skia's avatar
Skia committed
806
807
808
        return kwargs

class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView):
Skia's avatar
Skia committed
809
810
811
812
813
814
    """
    Provide the cash summary form
    """
    model = Counter
    pk_url_kwarg = "counter_id"
    template_name = 'counter/cash_register_summary.jinja'
Skia's avatar
Skia committed
815
816
817
818
819
820
821
822
823
824
825
826
    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
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848

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

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

Sli's avatar
Sli committed
858
class CounterStatView(DetailView, CanEditMixin):
Skia's avatar
Skia committed
859
860
861
862
863
864
865
866
867
868
869
870
    """
    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
Skia's avatar
Skia committed
871
        kwargs['User'] = User
Skia's avatar
Skia committed
872
873
874
875
876
877
878
879
880
881
882
883
884
885
        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]
Skia's avatar
Skia committed
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
        kwargs['top_barman'] = Permanency.objects.values('user').annotate(
                perm_sum=Sum(
                    Case(When(counter=self.object,
                            end__gt=datetime(year=1999, month=1, day=1),
                        then=F('end')-F('start')),
                        output_field=models.DateTimeField()
                        )
                    )
                ).exclude(perm_sum=None).order_by('-perm_sum').all()[:100]
        kwargs['top_barman_semester'] = Permanency.objects.values('user').annotate(
                perm_sum=Sum(
                    Case(When(counter=self.object,
                            start__gt=semester_start,
                            end__gt=datetime(year=1999, month=1, day=1),
                        then=F('end')-F('start')),
                        output_field=models.DateTimeField()
                        )
                    )
                ).exclude(perm_sum=None).order_by('-perm_sum').all()[:100]

        kwargs['sith_date']=settings.SITH_START_DATE[0]
        kwargs['semester_start']=semester_start
Skia's avatar
Skia committed
908
909
        return kwargs

910
    def dispatch(self, request, *args, **kwargs):
Sli's avatar
Sli committed
911
912
913
        try:
            return super(CounterStatView, self).dispatch(request, *args, **kwargs)
        except:
914
915
916
917
            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)
918
919
        raise PermissionDenied

920
921
922
923
924
925
926
927
928
929
930
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
931

932
933
934
935
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
936
class CashSummaryListView(CanEditPropMixin, CounterAdminTabsMixin, ListView):
Skia's avatar
Skia committed
937
938
939
940
941
    """Display a list of cash summaries"""
    model = CashRegisterSummary
    template_name = 'counter/cash_summary_list.jinja'
    context_object_name = "cashsummary_list"
    current_tab = "cash_summary"
Sli's avatar
Sli committed
942
    queryset = CashRegisterSummary.objects.all().order_by('-date')
Sli's avatar
Sli committed
943
    paginate_by = settings.SITH_COUNTER_CASH_SUMMARY_LENGTH
Skia's avatar
Skia committed
944
945
946
947

    def get_context_data(self, **kwargs):
        """ Add sums to the context """
        kwargs = super(CashSummaryListView, self).get_context_data(**kwargs)
948
949
        form = CashSummaryFormBase(self.request.GET)
        kwargs['form'] = form
Skia's avatar
Skia committed
950
951
952
        kwargs['summaries_sums'] = {}
        kwargs['refilling_sums'] = {}
        for c in Counter.objects.filter(type="BAR").all():
953
954
955
956
957
            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
958
            else:
959
960
                last_summary = CashRegisterSummary.objects.filter(counter=c, emptied=True).order_by('-date').first()
                if last_summary:
Skia's avatar
Skia committed
961
962
                    refillings = refillings.filter(date__gt=last_summary.date)
                    cashredistersummaries = cashredistersummaries.filter(date__gt=last_summary.date)
963
964
965
966
967
968
969
970
                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
971
        return kwargs
972

Skia's avatar
Skia committed
973
974
975
976
977
978
979
980
981
982
983
984
985
986
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
987
        start_date = start_date.replace(tzinfo=pytz.UTC)
Skia's avatar
Skia committed
988
989
        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
990
        kwargs['start_date'] = start_date
Skia's avatar
Skia committed
991
992
993
994
995
996
997
998
999
        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
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
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
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
1054
        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
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
        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
1085
            p.drawCentredString(10.5 * cm, 22.6 * cm, eticket.event_date.strftime("%d %b %Y")) # FIXME with a locale
Skia's avatar
Skia committed
1086
        p.setFont("Helvetica-Bold", 14)
1087
        p.drawCentredString(10.5 * cm, 15 * cm, "%s : %d %s" % (user.get_display_name(), self.object.quantity, str(_("people(s)"))))
Skia's avatar
Skia committed
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
        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)

Krophil's avatar
Krophil committed
1098
1099
1100
1101
1102
1103
1104
        partners = ImageReader("core/static/core/img/partners.png")
        width, height = partners.getSize()
        size = max(width, height)
        width = width * 2 / 3
        height = height * 2 / 3
        p.drawImage(partners, 0 * cm, 0 * cm, width, height)

Skia's avatar
Skia committed
1105
1106
1107
1108
        p.showPage()
        p.save()
        return response