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

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

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

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

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

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

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

60 61 62 63 64 65
class RefillForm(forms.ModelForm):
    error_css_class = 'error'
    required_css_class = 'required'
    class Meta:
        model = Refilling
        fields = ['amount', 'payment_method', 'bank']
66 67 68
        widgets = {
            'amount': forms.NumberInput(attrs={'class':'focus'},)
        }
69

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

441 442
## Counter admin views

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

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

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

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

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

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

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

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

Skia's avatar
Skia committed
541 542
# Product management

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

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

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

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

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

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

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

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

Skia's avatar
Skia committed
618
class ProductCreateView(CounterAdminTabsMixin, CanCreateMixin, CreateView):
Skia's avatar
Skia committed
619 620 621 622
    """
    A create view for the admins
    """
    model = Product
623
    form_class = ProductEditForm
Skia's avatar
Skia committed
624
    template_name = 'core/create.jinja'
625
    current_tab = "products"
Skia's avatar
Skia committed
626

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

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

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

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

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

Skia's avatar
Skia committed
685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713
# Cash register summaries

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

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

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

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

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

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

class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView):
Skia's avatar
Skia committed
808 809 810 811 812 813
    """
    Provide the cash summary form
    """
    model = Counter
    pk_url_kwarg = "counter_id"
    template_name = 'counter/cash_register_summary.jinja'
Skia's avatar
Skia committed
814 815 816 817 818 819 820 821 822 823 824 825
    current_tab = "cash_summary"

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

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

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

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

    def get_context_data(self, **kwargs):
        """ Add form to the context """
        kwargs = super(CounterCashSummaryView, self).get_context_data(**kwargs)
        kwargs['form'] = self.form
        return kwargs
Skia's avatar
Skia committed
848 849 850 851 852 853 854 855 856

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

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

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

886
    def dispatch(self, request, *args, **kwargs):
Sli's avatar
Sli committed
887 888 889
        try:
            return super(CounterStatView, self).dispatch(request, *args, **kwargs)
        except:
890 891 892 893
            if (request.user.is_root
                or request.user.is_board_member
                or self.object.is_owned_by(request.user)):
                return super(CanEditMixin, self).dispatch(request, *args, **kwargs)
894 895
        raise PermissionDenied

896 897 898 899 900 901 902 903 904 905 906
class CashSummaryEditView(CanEditPropMixin, CounterAdminTabsMixin, UpdateView):
    """Edit cash summaries"""
    model = CashRegisterSummary
    template_name = 'counter/cash_register_summary.jinja'
    context_object_name = "cashsummary"
    pk_url_kwarg = "cashsummary_id"
    form_class = CashRegisterSummaryForm
    current_tab = "cash_summary"

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

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

Skia's avatar
Skia committed
912
class CashSummaryListView(CanEditPropMixin, CounterAdminTabsMixin, ListView):
Skia's avatar
Skia committed
913 914 915 916 917 918 919 920 921
    """Display a list of cash summaries"""
    model = CashRegisterSummary
    template_name = 'counter/cash_summary_list.jinja'
    context_object_name = "cashsummary_list"
    current_tab = "cash_summary"

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

Skia's avatar
Skia committed
947 948 949 950 951 952 953 954 955 956 957 958 959 960
class InvoiceCallView(CounterAdminTabsMixin, TemplateView):
    template_name = 'counter/invoices_call.jinja'
    current_tab = 'invoices_call'

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

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

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

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

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

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

    def get(self, request, *args, **kwargs):
        from reportlab.pdfgen import canvas
        from reportlab.lib.utils import ImageReader
        from reportlab.lib.units import cm
        from reportlab.graphics.shapes import Drawing
        from reportlab.graphics.barcode.qr import QrCodeWidget
        from reportlab.graphics import renderPDF
        self.object = self.get_object()
        eticket = self.object.product.eticket
        user = self.object.customer.user
Sli's avatar
Sli committed
1028
        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
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 1054 1055 1056 1057 1058
        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
1059
            p.drawCentredString(10.5 * cm, 22.6 * cm, eticket.event_date.strftime("%d %b %Y")) # FIXME with a locale
Skia's avatar
Skia committed
1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075
        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