tools.py 81.4 KB
Newer Older
Patrick's avatar
Patrick committed
1
2
# -*- coding: utf-8
from __future__ import unicode_literals
Patrick's avatar
Patrick committed
3
4

import calendar
5
import datetime
Patrick's avatar
Patrick committed
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import json
import time
import urllib2
from smtplib import SMTPRecipientsRefused

from django.conf import settings
from django.core import mail
from django.core import urlresolvers
from django.core.cache import cache
from django.core.mail import EmailMessage, mail_admins
from django.core.serializers.json import DjangoJSONEncoder
from django.db import transaction
from django.db.models import F
from django.db.models import Q, Sum
20
from django.http import Http404
Patrick's avatar
Patrick committed
21
from django.template.loader import render_to_string
Patrick's avatar
Patrick committed
22
from django.utils import timezone
Patrick's avatar
Patrick committed
23
from django.utils import translation
pi's avatar
pi committed
24
from django.utils.formats import number_format
Patrick's avatar
Patrick committed
25
26
27
28
29
from django.utils.safestring import mark_safe
from django.utils.text import slugify
from django.utils.translation import ugettext_lazy as _
from parler.models import TranslationDoesNotExist

Patrick's avatar
Patrick committed
30
import models
Patrick's avatar
Patrick committed
31
32
33
from const import *
from repanier import apps
from repanier.fields.RepanierMoneyField import RepanierMoney
Patrick's avatar
Patrick committed
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49


def ignore_exception(exception=Exception, default_val=0):
    """Returns a decorator that ignores an exception raised by the function it
    decorates.

    Using it as a decorator:

    @ignore_exception(ValueError)
    def my_function():
    pass

    Using it as a function wrapper:

    int_try_parse = ignore_exception(ValueError)(int)
    """
Patrick's avatar
Patrick committed
50

Patrick's avatar
Patrick committed
51
52
53
54
55
56
    def decorator(function):
        def wrapper(*args, **kwargs):
            try:
                return function(*args, **kwargs)
            except exception:
                return default_val
Patrick's avatar
Patrick committed
57

Patrick's avatar
Patrick committed
58
        return wrapper
Patrick's avatar
Patrick committed
59

Patrick's avatar
Patrick committed
60
61
    return decorator

Patrick's avatar
Patrick committed
62

Patrick's avatar
Patrick committed
63
sint = ignore_exception(ValueError)(int)
64
sboolean = ignore_exception(ValueError)(bool)
Patrick's avatar
Patrick committed
65
66
67
68
69
70
71
72
73
74


def next_row(query_iterator):
    try:
        return next(query_iterator)
    except StopIteration:
        # No rows were found, so do nothing.
        return None


Patrick's avatar
Change    
Patrick committed
75
76
77
78
79
def emails_of_testers():
    tester_qs = models.Staff.objects.filter(is_tester=True, is_active=True).order_by("id")
    testers = []
    for tester in tester_qs:
        testers.append(tester.user.email)
80
    return list(set(testers))
Patrick's avatar
Change    
Patrick committed
81
82


83
def send_email(email=None, from_name=EMPTY_STRING, track_customer_on_error=False):
Patrick's avatar
Patrick committed
84
85
86
87
    if settings.DJANGO_SETTINGS_DEMO:
        email.to = [DEMO_EMAIL]
        email.cc = []
        email.bcc = []
88
        send_email_with_error_log(email, from_name)
89
90
    else:
        from repanier.apps import REPANIER_SETTINGS_TEST_MODE
Patrick's avatar
Change    
Patrick committed
91
92
93
94
        if REPANIER_SETTINGS_TEST_MODE:
            email.to = emails_of_testers()
            if len(email.to) > 0:
                # Send the mail only if there is at least one tester
95
96
                email.cc = []
                email.bcc = []
97
                send_email_with_error_log(email, from_name)
98
            else:
Patrick's avatar
Change    
Patrick committed
99
100
101
                print('############################ test mode, without tester...')
        else:
            if settings.DEBUG:
102
103
104
105
106
                print("to : %s" % email.to)
                print("cc : %s" % email.cc)
                print("bcc : %s" % email.bcc)
                print("subject : %s" % slugify(email.subject))
            else:
Patrick's avatar
Change    
Patrick committed
107
108
                # chunks = [email.to[x:x+100] for x in xrange(0, len(email.to), 100)]
                # for chunk in chunks:
109
110
111
112
113
                # Remove duplicates
                send_email_to = list(set(email.to + email.cc + email.bcc))
                email.cc = []
                email.bcc = []
                if len(send_email_to) >= 1:
Patrick's avatar
Change    
Patrick committed
114
                    customer = None
115
116
                    for email_to in send_email_to:
                        email.to = [email_to]
117
                        send_email_with_error_log(email, from_name, track_customer_on_error)
118
                        time.sleep(1)
Patrick's avatar
Patrick committed
119
120


121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
def send_email_with_error_log(email, from_name=None, track_customer_on_error=False):
    send_mail = True
    if track_customer_on_error:
        # select the customer based on user__email or customer__email2
        email_to = email.to[0]
        customer = models.Customer.objects.filter(user__email=email_to).order_by('?').first()
        if customer is None:
            customer = models.Customer.objects.filter(email2=email_to).order_by('?').first()
        if customer is None or customer.valid_email is False:
            send_mail = False
            print("################################## send_email customer.valid_email == False")
    else:
        customer = None
    if send_mail:
        with mail.get_connection() as connection:
            email.connection = connection
            message = EMPTY_STRING
            if not email.from_email.endswith(settings.DJANGO_SETTINGS_ALLOWED_MAIL_EXTENSION):
                email.reply_to = [email.from_email]
                email.from_email = "%s <%s>" % (from_name or apps.REPANIER_SETTINGS_GROUP_NAME, settings.DEFAULT_FROM_EMAIL)
            else:
                email.from_email = "%s <%s>" % (from_name or apps.REPANIER_SETTINGS_GROUP_NAME, email.from_email)
            try:
                print("################################## send_email")
                reply_to = "reply_to : %s" % email.reply_to
                to = "to : %s" % email.to
                cc = "cc : %s" % email.cc
                bcc = "bcc : %s" % email.bcc
                subject = "subject : %s" % slugify(email.subject)
                print(reply_to)
                print(to)
                print(cc)
                print(bcc)
                print(subject)
                message = "%s\n%s\n%s\n%s" % (to, cc, bcc, subject)
                email.send()
                valid_email = True
            except SMTPRecipientsRefused as error_str:
                valid_email = False
                print("################################## send_email error")
                print(error_str)
                time.sleep(1)
                connection = mail.get_connection()
                connection.open()
                mail_admins("ERROR", "%s\n%s" % (message, error_str), connection=connection)
                connection.close()
            except Exception as error_str:
                valid_email = None
                print("################################## send_email error")
                print(error_str)
                time.sleep(1)
                connection = mail.get_connection()
                connection.open()
                mail_admins("ERROR", "%s\n%s" % (message, error_str), connection=connection)
                connection.close()
            print("##################################")
            if customer is not None:
                customer.valid_email = valid_email
                customer.save(update_fields=['valid_email'])
Patrick's avatar
Patrick committed
180
181
182
183
184


def send_email_to_who(is_email_send, board=False):
    if not is_email_send:
        if board:
Patrick's avatar
Change    
Patrick committed
185
186
187
188
            if apps.REPANIER_SETTINGS_TEST_MODE:
                return True, _("This email will be sent to %s.") % ', '.join(emails_of_testers())
            else:
                if settings.DEBUG:
Patrick's avatar
Patrick committed
189
190
                    return False, _("No email will be sent.")
                else:
Patrick's avatar
Change    
Patrick committed
191
                    return True, _("This email will be sent to the staff.")
Patrick's avatar
Patrick committed
192
193
        else:
            return False, _("No email will be sent.")
Patrick's avatar
Patrick committed
194
    else:
Patrick's avatar
Change    
Patrick committed
195
196
        if apps.REPANIER_SETTINGS_TEST_MODE:
            return True, _("This email will be sent to %s.") % ', '.join(emails_of_testers())
Patrick's avatar
Patrick committed
197
        else:
Patrick's avatar
Change    
Patrick committed
198
199
            if settings.DEBUG:
                return False, _("No email will be sent.")
Patrick's avatar
Patrick committed
200
            else:
Patrick's avatar
Change    
Patrick committed
201
202
203
204
                if board:
                    return True, _("This email will be sent to the preparation team and the staff.")
                else:
                    return True, _("This email will be sent to customers or producers depending of the case.")
Patrick's avatar
Patrick committed
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226


def send_sms(sms_nr=None, sms_msg=None):
    try:
        if sms_nr is not None and sms_msg is not None:
            valid_nr = "0"
            i = 0
            while i < len(sms_nr) and not sms_nr[i] == '4':
                i += 1
            while i < len(sms_nr):
                if '0' <= sms_nr[i] <= '9':
                    valid_nr += sms_nr[i]
                i += 1
            if len(valid_nr) == 10 \
                    and apps.REPANIER_SETTINGS_SMS_GATEWAY_MAIL is not None \
                    and len(apps.REPANIER_SETTINGS_SMS_GATEWAY_MAIL) > 0:
                # Send SMS with free gateway : Sms Gateway - Android.
                email = EmailMessage(valid_nr, sms_msg, "no-reply@repanier.be",
                                     [apps.REPANIER_SETTINGS_SMS_GATEWAY_MAIL, ])
                send_email(email=email)
    except:
        pass
Patrick's avatar
Patrick committed
227
228
229
230


def get_signature(is_reply_to_order_email=False, is_reply_to_invoice_email=False):
    sender_email = None
Patrick's avatar
Patrick committed
231
232
    sender_function = EMPTY_STRING
    signature = EMPTY_STRING
Patrick's avatar
Patrick committed
233
    cc_email_staff = []
Patrick's avatar
Patrick committed
234
    for staff in models.Staff.objects.filter(is_active=True).order_by('?'):
Patrick's avatar
Patrick committed
235
236
        if (is_reply_to_order_email and staff.is_reply_to_order_email) \
                or (is_reply_to_invoice_email and staff.is_reply_to_invoice_email):
237
            cc_email_staff.append(staff.user.email)
Patrick's avatar
Patrick committed
238
            sender_email = staff.user.email
Patrick's avatar
Patrick committed
239
240
241
242
            try:
                sender_function = staff.long_name
            except TranslationDoesNotExist:
                sender_function = EMPTY_STRING
Patrick's avatar
Patrick committed
243
244
245
246
247
248
            r = staff.customer_responsible
            if r:
                if r.long_basket_name:
                    signature = "%s - %s" % (r.long_basket_name, r.phone1)
                else:
                    signature = "%s - %s" % (r.short_basket_name, r.phone1)
Patrick's avatar
Patrick committed
249
                if r.phone2 and len(r.phone2.strip()) > 0:
Patrick's avatar
Patrick committed
250
                    signature += " / %s" % (r.phone2,)
251
252
253
        elif staff.is_coordinator:
            cc_email_staff.append(staff.user.email)

Patrick's avatar
Patrick committed
254
    if sender_email is None:
Patrick's avatar
Patrick committed
255
        sender_email = settings.DEFAULT_FROM_EMAIL
Patrick's avatar
Patrick committed
256
    return sender_email, sender_function, signature, cc_email_staff
pi's avatar
pi committed
257

Patrick's avatar
Patrick committed
258

Patrick's avatar
Patrick committed
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
def get_board_composition(permanence_id):
    board_composition = EMPTY_STRING
    board_composition_and_description = EMPTY_STRING
    for permanenceboard in models.PermanenceBoard.objects.filter(
            permanence=permanence_id).order_by(
        "permanence_role__tree_id",
        "permanence_role__lft"
    ):
        r = permanenceboard.permanence_role
        c = permanenceboard.customer
        if c is not None:
            if c.phone2 is not None:
                c_part = "%s, <b>%s</b>, <b>%s</b>" % (c.long_basket_name, c.phone1, c.phone2)
            else:
                c_part = "%s, <b>%s</b>" % (c.long_basket_name, c.phone1)
            member = "<b>%s</b> : %s, %s<br/>" % (r.short_name, c_part, c.user.email)
            board_composition += member
            board_composition_and_description += "%s%s<br/>" % (member, r.description)

    return board_composition, board_composition_and_description


Patrick's avatar
Patrick committed
281
LENGTH_BY_PREFIX = [
282
283
284
285
286
    (0xC0, 2),  # first byte mask, total codepoint length
    (0xE0, 3),
    (0xF0, 4),
    (0xF8, 5),
    (0xFC, 6),
Patrick's avatar
Patrick committed
287
288
]

289

Patrick's avatar
Patrick committed
290
291
def codepoint_length(first_byte):
    if first_byte < 128:
292
        return 1  # ASCII
Patrick's avatar
Patrick committed
293
294
    for mask, length in LENGTH_BY_PREFIX:
        if first_byte & 0xF0 == mask:
295
            return length
Patrick's avatar
Patrick committed
296
        elif first_byte & 0xF8 == 0xF8:
297
            return length
Patrick's avatar
Patrick committed
298
299
    assert False, 'Invalid byte %r' % first_byte

300

Patrick's avatar
Patrick committed
301
def cap_to_bytes_length(unicode_text, byte_limit):
Patrick's avatar
Patrick committed
302
    utf8_bytes = unicode_text.encode("utf8")
Patrick's avatar
Patrick committed
303
304
305
306
307
308
309
310
311
312
    cut_index = 0
    while cut_index < len(utf8_bytes):
        step = codepoint_length(ord(utf8_bytes[cut_index]))
        if cut_index + step > byte_limit:
            # can't go a whole codepoint further, time to cut
            return utf8_bytes[:cut_index] + '...'
        else:
            cut_index += step
    return utf8_bytes

313

Patrick's avatar
Patrick committed
314
def cap(s, l):
Patrick's avatar
Patrick committed
315
    if s is not None:
316
317
        if not isinstance(s, basestring):
            s = str(s)
Patrick's avatar
Patrick committed
318
        s = s if len(s) <= l else s[0:l - 4] + '...'
319
        return s
Patrick's avatar
Patrick committed
320
    else:
321
322
323
        return None


324
325
326
327
328
329
def permanence_ok_or_404(permanence):
    if permanence is None:
        raise Http404
    if permanence.status not in [PERMANENCE_OPENED, PERMANENCE_CLOSED, PERMANENCE_SEND]:
        if permanence.status in [PERMANENCE_DONE, PERMANENCE_ARCHIVED]:
            if permanence.permanence_date < (
Patrick's avatar
Patrick committed
330
                        timezone.now() - datetime.timedelta(weeks=LIMIT_DISPLAYED_PERMANENCE)
331
332
333
334
335
336
            ).date():
                raise Http404
        else:
            raise Http404


337
338
def get_invoice_unit(order_unit=PRODUCT_ORDER_UNIT_PC, qty=0):
    if order_unit in [PRODUCT_ORDER_UNIT_KG, PRODUCT_ORDER_UNIT_PC_KG]:
Patrick's avatar
Patrick committed
339
        unit = _("/ kg")
340
    elif order_unit == PRODUCT_ORDER_UNIT_LT:
Patrick's avatar
Patrick committed
341
        unit = _("/ l")
Patrick's avatar
Patrick committed
342
    else:
343
        if qty < 2:
Patrick's avatar
Patrick committed
344
            unit = _("/ piece")
345
        else:
Patrick's avatar
Patrick committed
346
            unit = _("/ pieces")
347
348
349
    return unit


Patrick's avatar
Patrick committed
350
def get_preparator_unit(order_unit=PRODUCT_ORDER_UNIT_PC):
351
    # Used when producing the preparation list.
352
353
    if order_unit in [PRODUCT_ORDER_UNIT_PC, PRODUCT_ORDER_UNIT_PC_PRICE_KG, PRODUCT_ORDER_UNIT_PC_PRICE_LT,
                      PRODUCT_ORDER_UNIT_PC_PRICE_PC]:
Patrick's avatar
Patrick committed
354
        unit = _("Piece(s) :")
355
    elif order_unit in [PRODUCT_ORDER_UNIT_KG, PRODUCT_ORDER_UNIT_PC_KG]:
Patrick's avatar
Patrick committed
356
        unit = _("%s or kg :") % (apps.REPANIER_SETTINGS_CURRENCY_DISPLAY.decode('utf-8'),)
357
    elif order_unit == PRODUCT_ORDER_UNIT_LT:
Patrick's avatar
Patrick committed
358
        unit = _("L :")
359
    else:
Patrick's avatar
Patrick committed
360
        unit = _("Kg :")
361
362
363
    return unit


Patrick's avatar
Patrick committed
364
365
def get_base_unit(qty=0, order_unit=PRODUCT_ORDER_UNIT_PC, status=None):
    if order_unit == PRODUCT_ORDER_UNIT_KG or (status >= PERMANENCE_SEND and order_unit == PRODUCT_ORDER_UNIT_PC_KG):
366
        if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
367
            base_unit = EMPTY_STRING
368
369
370
371
        else:
            base_unit = _('kg')
    elif order_unit == PRODUCT_ORDER_UNIT_LT:
        if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
372
            base_unit = EMPTY_STRING
373
374
375
376
        else:
            base_unit = _('l')
    else:
        if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
377
            base_unit = EMPTY_STRING
378
379
380
381
382
383
384
        elif qty < 2:
            base_unit = _('piece')
        else:
            base_unit = _('pieces')
    return base_unit


Patrick's avatar
Patrick committed
385
386
def get_display(qty=0, order_average_weight=0, order_unit=PRODUCT_ORDER_UNIT_PC, unit_price_amount=None,
                for_customer=True, for_order_select=False, without_price_display=False):
387
388
    magnitude = None
    display_qty = True
389
    if order_unit == PRODUCT_ORDER_UNIT_KG:
390
        if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
391
            unit = EMPTY_STRING
392
393
394
        elif for_customer and qty < 1:
            unit = "%s" % (_('gr'))
            magnitude = 1000
395
        else:
396
            unit = "%s" % (_('kg'))
397
    elif order_unit == PRODUCT_ORDER_UNIT_LT:
398
        if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
399
            unit = EMPTY_STRING
400
401
402
        elif for_customer and qty < 1:
            unit = "%s" % (_('cl'))
            magnitude = 100
403
        else:
404
            unit = "%s" % (_('l'))
405
    elif order_unit in [PRODUCT_ORDER_UNIT_PC_KG, PRODUCT_ORDER_UNIT_PC_PRICE_KG]:
406
        # display_qty = not (order_average_weight == 1 and order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_KG)
407
408
409
        average_weight = order_average_weight
        if for_customer:
            average_weight *= qty
Patrick's avatar
Patrick committed
410
411
        if order_unit == PRODUCT_ORDER_UNIT_PC_KG and unit_price_amount is not None:
            unit_price_amount *= order_average_weight
412
        if average_weight < 1:
Patrick's avatar
Patrick committed
413
            average_weight_unit = _('gr')
414
415
            average_weight *= 1000
        else:
Patrick's avatar
Patrick committed
416
            average_weight_unit = _('kg')
417
418
419
420
421
422
423
        decimal = 3
        if average_weight == int(average_weight):
            decimal = 0
        elif average_weight * 10 == int(average_weight * 10):
            decimal = 1
        elif average_weight * 100 == int(average_weight * 100):
            decimal = 2
Patrick's avatar
Patrick committed
424
        tilde = EMPTY_STRING
425
426
        if order_unit == PRODUCT_ORDER_UNIT_PC_KG:
            tilde = '~'
427
        if for_customer:
Patrick's avatar
Patrick committed
428
            if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
429
                unit = EMPTY_STRING
430
            else:
431
                if order_average_weight == 1 and order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_KG:
432
                    unit = "%s%s %s" % (tilde, number_format(average_weight, decimal), average_weight_unit)
433
434
                else:
                    unit = "%s%s%s" % (tilde, number_format(average_weight, decimal), average_weight_unit)
435
        else:
Patrick's avatar
Patrick committed
436
            if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
437
                unit = EMPTY_STRING
438
            else:
439
                unit = "%s%s%s" % (tilde, number_format(average_weight, decimal), average_weight_unit)
440
    elif order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_LT:
441
        display_qty = order_average_weight != 1
442
443
444
        average_weight = order_average_weight
        if for_customer:
            average_weight *= qty
445
        if average_weight < 1:
Patrick's avatar
Patrick committed
446
            average_weight_unit = _('cl')
447
448
            average_weight *= 100
        else:
Patrick's avatar
Patrick committed
449
            average_weight_unit = _('l')
450
451
452
453
454
455
456
        decimal = 3
        if average_weight == int(average_weight):
            decimal = 0
        elif average_weight * 10 == int(average_weight * 10):
            decimal = 1
        elif average_weight * 100 == int(average_weight * 100):
            decimal = 2
457
        if for_customer:
Patrick's avatar
Patrick committed
458
            if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
459
                unit = EMPTY_STRING
460
            else:
461
462
463
464
                if display_qty:
                    unit = "%s%s" % (number_format(average_weight, decimal), average_weight_unit)
                else:
                    unit = "%s %s" % (number_format(average_weight, decimal), average_weight_unit)
465
        else:
Patrick's avatar
Patrick committed
466
            if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
467
                unit = EMPTY_STRING
468
            else:
469
                unit = "%s%s" % (number_format(average_weight, decimal), average_weight_unit)
470
    elif order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_PC:
471
        display_qty = order_average_weight != 1
472
473
474
        average_weight = order_average_weight
        if for_customer:
            average_weight *= qty
Patrick's avatar
Patrick committed
475
            if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
476
                unit = EMPTY_STRING
477
            else:
Patrick's avatar
Patrick committed
478
                if average_weight < 2:
479
480
481
482
483
                    pc_pcs = _('pc')
                else:
                    pc_pcs = _('pcs')
                if display_qty:
                    unit = "%s%s" % (number_format(average_weight, 0), pc_pcs)
Patrick's avatar
Patrick committed
484
                else:
485
                    unit = "%s %s" % (number_format(average_weight, 0), pc_pcs)
486
        else:
Patrick's avatar
Patrick committed
487
            if average_weight == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
488
                unit = EMPTY_STRING
Patrick's avatar
Patrick committed
489
            elif average_weight < 2:
490
                unit = '%s %s' % (number_format(average_weight, 0), _('pc'))
491
            else:
492
493
                unit = '%s %s' % (number_format(average_weight, 0), _('pcs'))
    else:
Patrick's avatar
Patrick committed
494
495
496
497
498
499
500
        if for_order_select:
            if qty == DECIMAL_ZERO:
                unit = EMPTY_STRING
            elif qty < 2:
                unit = "%s" % (_('unit'))
            else:
                unit = "%s" % (_('units'))
501
        else:
Patrick's avatar
Patrick committed
502
503
            unit = EMPTY_STRING
    if unit_price_amount is not None:
504
        price_display = " = %s" % RepanierMoney(unit_price_amount * qty)
Patrick's avatar
Patrick committed
505
    else:
Patrick's avatar
Patrick committed
506
        price_display = EMPTY_STRING
507
508
    if magnitude is not None:
        qty *= magnitude
509
510
511
512
513
514
515
    decimal = 3
    if qty == int(qty):
        decimal = 0
    elif qty * 10 == int(qty * 10):
        decimal = 1
    elif qty * 100 == int(qty * 100):
        decimal = 2
Patrick's avatar
Patrick committed
516
    if for_customer or for_order_select:
517
518
519
        if unit:
            if display_qty:
                qty_display = "%s (%s)" % (number_format(qty, decimal), unit)
520
            else:
521
522
523
                qty_display = "%s" % unit
        else:
            qty_display = "%s" % number_format(qty, decimal)
Patrick's avatar
Patrick committed
524
    else:
525
526
        if unit:
            qty_display = "(%s)" % unit
527
        else:
Patrick's avatar
Patrick committed
528
529
530
531
            qty_display = EMPTY_STRING
    if without_price_display:
        return qty_display
    else:
532
        display = "%s%s" % (qty_display, price_display)
Patrick's avatar
Patrick committed
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
        return display


def on_hold_movement_message(customer, bank_not_invoiced=None, order_not_invoiced=None, total_price_with_tax=REPANIER_MONEY_ZERO):
    # If permanence_id is None, only "customer_on_hold_movement" is calculated
    if customer is None:
        customer_on_hold_movement = EMPTY_STRING
    else:
        if apps.REPANIER_SETTINGS_INVOICE:
            bank_not_invoiced = bank_not_invoiced if bank_not_invoiced is not None else customer.get_bank_not_invoiced()
            order_not_invoiced = order_not_invoiced if order_not_invoiced is not None else customer.get_order_not_invoiced()
            other_order_not_invoiced = order_not_invoiced - total_price_with_tax
        else:
            bank_not_invoiced = REPANIER_MONEY_ZERO
            other_order_not_invoiced = REPANIER_MONEY_ZERO

        if other_order_not_invoiced.amount != DECIMAL_ZERO or bank_not_invoiced.amount != DECIMAL_ZERO:
            if other_order_not_invoiced.amount != DECIMAL_ZERO:
                if bank_not_invoiced.amount == DECIMAL_ZERO:
                    customer_on_hold_movement = \
                        _('This balance does not take account of any unbilled sales %(other_order)s.') % {
                            'other_order': other_order_not_invoiced
                        }
                else:
                    customer_on_hold_movement = \
                        _(
                            'This balance does not take account of any unrecognized payments %(bank)s and any unbilled order %(other_order)s.') \
                        % {
                            'bank'       : bank_not_invoiced,
                            'other_order': other_order_not_invoiced
                        }
564
            else:
Patrick's avatar
Patrick committed
565
566
567
568
569
                customer_on_hold_movement = \
                    _(
                        'This balance does not take account of any unrecognized payments %(bank)s.') % {
                        'bank': bank_not_invoiced
                    }
570
        else:
Patrick's avatar
Patrick committed
571
572
573
574
575
576
577
578
579
580
581
582
            customer_on_hold_movement = EMPTY_STRING

    return customer_on_hold_movement


def payment_message(customer, permanence):
    # If permanence_id is None, only "customer_on_hold_movement" is calculated
    customer_last_balance = EMPTY_STRING
    if customer is None or permanence is None:
        customer_order_amount = EMPTY_STRING
        customer_payment_needed = EMPTY_STRING
        customer_on_hold_movement = EMPTY_STRING
583
    else:
Patrick's avatar
Patrick committed
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
        customer_invoice = models.CustomerInvoice.objects.filter(
            customer_id=customer.id,
            permanence_id=permanence.id
        ).order_by('?').first()
        if customer_invoice is None:
            total_price_with_tax = REPANIER_MONEY_ZERO
        else:
            total_price_with_tax = customer_invoice.get_total_price_with_tax()
        customer_order_amount = \
            _('The amount of your order is %(amount)s.') % {
                'amount': total_price_with_tax
            }
        if apps.REPANIER_SETTINGS_INVOICE:
            bank_not_invoiced = customer.get_bank_not_invoiced()
            order_not_invoiced = customer.get_order_not_invoiced()
            payment_needed = - (customer.balance - order_not_invoiced + bank_not_invoiced)
            # other_order_not_invoiced = order_not_invoiced - total_price_with_tax
        else:
            bank_not_invoiced = REPANIER_MONEY_ZERO
            order_not_invoiced = DECIMAL_ZERO
            payment_needed = total_price_with_tax
            # other_order_not_invoiced = REPANIER_MONEY_ZERO

        if customer_invoice.customer_id != customer_invoice.customer_who_pays_id:
608
            customer_payment_needed = '<font color="green">%s</font>' % (
Patrick's avatar
Patrick committed
609
610
611
612
                _('Invoices for this delivery point are sent to %(name)s who is responsible for collecting the payments.') % {
                    'name': customer_invoice.customer_who_pays.long_basket_name
                }
            )
613
            customer_on_hold_movement = EMPTY_STRING
Patrick's avatar
Patrick committed
614
        else:
615
616
617
618
619
620
621
622
623
624
625
626
            customer_on_hold_movement = on_hold_movement_message(customer, bank_not_invoiced, order_not_invoiced, total_price_with_tax)
            if apps.REPANIER_SETTINGS_INVOICE:
                if customer.balance.amount != DECIMAL_ZERO:
                    if customer.balance.amount < DECIMAL_ZERO:
                        balance = '<font color="#bd0926">%s</font>' % customer.balance
                    else:
                        balance = '%s' % customer.balance
                    customer_last_balance = \
                        _('The balance of your account as of %(date)s is %(balance)s.') % {
                            'date'   : customer.date_balance.strftime(settings.DJANGO_SETTINGS_DATE),
                            'balance': balance
                        }
Patrick's avatar
Patrick committed
627
                else:
628
                    customer_last_balance = EMPTY_STRING
Patrick's avatar
Patrick committed
629

630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
            bank_account_number = apps.REPANIER_SETTINGS_BANK_ACCOUNT
            if bank_account_number is not None:
                if payment_needed.amount > DECIMAL_ZERO:
                    if permanence.short_name:
                        communication = "%s (%s)" % (customer.short_basket_name, permanence.short_name)
                    else:
                        communication = customer.short_basket_name
                    group_name = apps.REPANIER_SETTINGS_GROUP_NAME
                    customer_payment_needed = '<br/><font color="#bd0926">%s</font>' % (
                        _('Please pay %(payment)s to the bank account %(name)s %(number)s with communication %(communication)s.') % {
                            'payment': payment_needed,
                            'name': group_name,
                            'number': bank_account_number,
                            'communication': communication
                        }
                    )
Patrick's avatar
Patrick committed
646
647

                else:
648
649
650
651
652
653
654
                    if customer.balance.amount != DECIMAL_ZERO:
                        customer_payment_needed = '<br/><font color="green">%s.</font>' % (_('Your account balance is sufficient'))
                    else:
                        customer_payment_needed = EMPTY_STRING
            else:
                customer_payment_needed = EMPTY_STRING

Patrick's avatar
Patrick committed
655
656
657
658
659

    return customer_last_balance, customer_on_hold_movement, customer_payment_needed, customer_order_amount


def recalculate_order_amount(permanence_id,
Patrick's avatar
Patrick committed
660
661
                             customer_id=None,
                             offer_item_queryset=None,
Patrick's avatar
Patrick committed
662
663
                             all_producers=True,
                             producers_id=None,
Patrick's avatar
Patrick committed
664
                             send_to_producer=False,
665
                             re_init=False):
Patrick's avatar
Patrick committed
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
    if send_to_producer or re_init:
        if all_producers:
            models.ProducerInvoice.objects.filter(
                permanence_id=permanence_id
            ).update(
                total_price_with_tax=DECIMAL_ZERO,
                total_vat=DECIMAL_ZERO,
                total_deposit=DECIMAL_ZERO,
                total_profit_with_tax=DECIMAL_ZERO,
                total_profit_vat=DECIMAL_ZERO
            )
            models.CustomerInvoice.objects.filter(
                permanence_id=permanence_id
            ).update(
                total_price_with_tax=DECIMAL_ZERO,
                total_vat=DECIMAL_ZERO,
                total_deposit=DECIMAL_ZERO
            )
            models.CustomerProducerInvoice.objects.filter(
                permanence_id=permanence_id
            ).update(
                total_purchase_with_tax=DECIMAL_ZERO,
                total_selling_with_tax=DECIMAL_ZERO
            )
            models.OfferItem.objects.filter(
                permanence_id=permanence_id
            ).update(
                quantity_invoiced=DECIMAL_ZERO,
                total_purchase_with_tax=DECIMAL_ZERO,
                total_selling_with_tax=DECIMAL_ZERO
            )
            for offer_item in models.OfferItem.objects.filter(
                    permanence_id=permanence_id,
                    is_active=True,
                    manage_replenishment=True
            ).exclude(add_2_stock=DECIMAL_ZERO).order_by('?'):
                # Recalculate the total_price_with_tax of ProducerInvoice and
                # the total_purchase_with_tax of OfferItem
                # taking into account "add_2_stock"
                offer_item.previous_add_2_stock = DECIMAL_ZERO
                offer_item.save()
        else:
            models.ProducerInvoice.objects.filter(
                permanence_id=permanence_id, producer_id__in=producers_id
            ).update(
                total_price_with_tax=DECIMAL_ZERO,
                total_vat=DECIMAL_ZERO,
                total_deposit=DECIMAL_ZERO
            )
            for ci in models.CustomerInvoice.objects.filter(
                    permanence_id=permanence_id):
                result_set = models.CustomerProducerInvoice.objects.filter(
                    permanence_id=permanence_id,
                    customer_id=ci.customer_id,
                    producer_id__in=producers_id
                ).order_by('?').aggregate(
                    Sum('total_selling_with_tax'),
Patrick's avatar
Patrick committed
723
                )
Patrick's avatar
Patrick committed
724
725
726
727
728
729
730
731
                if result_set["total_selling_with_tax__sum"] is not None:
                    sum_total_selling_with_tax = result_set["total_selling_with_tax__sum"]
                else:
                    sum_total_selling_with_tax = DECIMAL_ZERO
                models.CustomerInvoice.objects.filter(
                    permanence_id=permanence_id
                ).update(
                    total_price_with_tax=F('total_price_with_tax') - sum_total_selling_with_tax
Patrick's avatar
Patrick committed
732
                )
Patrick's avatar
Patrick committed
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
            models.CustomerProducerInvoice.objects.filter(
                permanence_id=permanence_id,
                producer_id__in=producers_id
            ).update(
                total_purchase_with_tax=DECIMAL_ZERO,
                total_selling_with_tax=DECIMAL_ZERO
            )
            models.OfferItem.objects.filter(
                permanence_id=permanence_id,
                producer_id__in=producers_id
            ).update(
                quantity_invoiced=DECIMAL_ZERO,
                total_purchase_with_tax=DECIMAL_ZERO,
                total_selling_with_tax=DECIMAL_ZERO
            )
            for offer_item in models.OfferItem.objects.filter(
                    permanence_id=permanence_id,
                    producer_id__in=producers_id,
                    is_active=True,
                    manage_replenishment=True
            ).exclude(add_2_stock=DECIMAL_ZERO).order_by('?'):
Patrick's avatar
Patrick committed
754
755
756
757
758
                # Recalculate the total_price_with_tax of ProducerInvoice and
                # the total_purchase_with_tax of OfferItem
                # taking into account "add_2_stock"
                offer_item.previous_add_2_stock = DECIMAL_ZERO
                offer_item.save()
Patrick's avatar
Patrick committed
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791

    if customer_id is None:
        if offer_item_queryset is not None:
            purchase_set = models.Purchase.objects \
                .filter(permanence_id=permanence_id, offer_item__in=offer_item_queryset) \
                .order_by('?')
        else:
            purchase_set = models.Purchase.objects \
                .filter(permanence_id=permanence_id) \
                .order_by('?')
            if not all_producers:
                purchase_set = purchase_set.filter(producer_id__in=producers_id)
    else:
        purchase_set = models.Purchase.objects \
            .filter(permanence_id=permanence_id, customer_id=customer_id) \
            .order_by('?')
        if not all_producers:
            purchase_set = purchase_set.filter(producer_id__in=producers_id)

    for purchase in purchase_set.select_related("offer_item"):
        # Recalcuate the total_price_with_tax of ProducerInvoice,
        # the total_price_with_tax of CustomerInvoice,
        # the total_purchase_with_tax + total_selling_with_tax of CustomerProducerInvoice,
        # and quantity_invoiced + total_purchase_with_tax + total_selling_with_tax of OfferItem
        offer_item = purchase.offer_item
        if send_to_producer or re_init:
            # purchase.admin_update = True
            purchase.previous_quantity = DECIMAL_ZERO
            purchase.previous_purchase_price = DECIMAL_ZERO
            purchase.previous_selling_price = DECIMAL_ZERO
            purchase.previous_producer_vat = DECIMAL_ZERO
            purchase.previous_customer_vat = DECIMAL_ZERO
            purchase.previous_deposit = DECIMAL_ZERO
Patrick's avatar
Patrick committed
792
793
            if send_to_producer:
                if offer_item.order_unit == PRODUCT_ORDER_UNIT_PC_KG:
Patrick's avatar
Patrick committed
794
                    purchase.quantity_invoiced = (purchase.quantity_ordered * offer_item.order_average_weight) \
Patrick's avatar
Patrick committed
795
796
797
798
799
                        .quantize(FOUR_DECIMALS)
                    if offer_item.wrapped:
                        purchase.quantity_for_preparation_sort_order = DECIMAL_ZERO
                    else:
                        purchase.quantity_for_preparation_sort_order = purchase.quantity_ordered
800
801
802
803
804
805
                elif offer_item.order_unit == PRODUCT_ORDER_UNIT_KG:
                    purchase.quantity_invoiced = purchase.quantity_ordered
                    if offer_item.wrapped:
                        purchase.quantity_for_preparation_sort_order = DECIMAL_ZERO
                    else:
                        purchase.quantity_for_preparation_sort_order = purchase.quantity_ordered
Patrick's avatar
Patrick committed
806
807
808
                else:
                    purchase.quantity_invoiced = purchase.quantity_ordered
                    purchase.quantity_for_preparation_sort_order = DECIMAL_ZERO
Patrick's avatar
Patrick committed
809
810
811
        purchase.save()


812
def display_selected_value(offer_item, quantity_ordered):
Patrick's avatar
Patrick committed
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
    if offer_item.may_order:
        if quantity_ordered <= DECIMAL_ZERO:
            q_min = offer_item.customer_minimum_order_quantity
            if offer_item.limit_order_quantity_to_stock:
                q_alert = offer_item.stock - offer_item.quantity_invoiced
                if q_alert < DECIMAL_ZERO:
                    q_alert = DECIMAL_ZERO
            else:
                q_alert = offer_item.customer_alert_order_quantity
            if q_min <= q_alert:
                qs = models.ProducerInvoice.objects.filter(
                    permanence__offeritem=offer_item.id,
                    producer__offeritem=offer_item.id,
                    status=PERMANENCE_OPENED
                ).order_by('?')
                if qs.exists():
                    option_dict = {
                        'id'  : "#offer_item%d" % offer_item.id,
                        'html': '<option value="0" selected>---</option>'
                    }
                else:
                    closed = _("Closed")
                    option_dict = {
                        'id'  : "#offer_item%d" % offer_item.id,
                        'html': '<option value="0" selected>%s</option>' % closed
                    }
            else:
                sold_out = _("Sold out")
                option_dict = {
                    'id'  : "#offer_item%d" % offer_item.id,
                    'html': '<option value="0" selected>%s</option>' % sold_out
                }

        else:
            unit_price_amount = offer_item.customer_unit_price.amount + offer_item.unit_deposit.amount
            display = get_display(
                qty=quantity_ordered,
                order_average_weight=offer_item.order_average_weight,
                order_unit=offer_item.order_unit,
                unit_price_amount=unit_price_amount,
                for_order_select=True
            )
            option_dict = {
                'id'  : "#offer_item%d" % offer_item.id,
                'html': '<option value="%d" selected>%s</option>' % (quantity_ordered, display,)
            }
    else:
        option_dict = {
            'id'  : "#box_offer_item%d" % offer_item.id,
            'html': ''
        }
    return option_dict


867
def display_selected_box_value(customer, offer_item, box_purchase):
Patrick's avatar
Patrick committed
868
869
870
871
    if offer_item.is_box_content:
        # box_name = _not_lazy("Composition")
        box_name = BOX_UNICODE
        # Select one purchase
872
        if box_purchase is not None:
Patrick's avatar
Patrick committed
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
            if box_purchase.quantity_ordered > DECIMAL_ZERO:
                qty_display = get_display(
                    qty=box_purchase.quantity_ordered,
                    order_average_weight=offer_item.order_average_weight,
                    order_unit=offer_item.order_unit,
                    for_order_select=True,
                    without_price_display=True
                )
                option_dict = {
                    'id'  : "#box_offer_item%d" % offer_item.id,
                    'html': '<select name="box_offer_item%d" disabled class="form-control"><option value="0" selected>☑ %s %s</option></select>'
                            % (offer_item.id, qty_display, box_name)
                }
            else:
                option_dict = {
                    'id'  : "#box_offer_item%d" % offer_item.id,
                    'html': '<select name="box_offer_item%d" disabled class="form-control"><option value="0" selected>☑ --- %s</option></select>'
                            % (offer_item.id, box_name)
                }
        else:
            option_dict = {
                'id'  : "#box_offer_item%d" % offer_item.id,
                'html': '<select name="box_offer_item%d" disabled class="form-control"><option value="0" selected>☑ --- %s</option></select>'
                        % (offer_item.id, box_name)
            }
    else:
        option_dict = {
            'id'  : "#box_offer_item%d" % offer_item.id,
            'html': ''
        }
    return option_dict
904
905
906


@transaction.atomic
907
def update_or_create_purchase(customer=None, offer_item_id=None, q_order=None, value_id=None, basket=False, batch_job=False):
Patrick's avatar
Patrick committed
908
    to_json = []
909
    if offer_item_id is not None and (q_order is not None or value_id is not None) and customer is not None:
Patrick's avatar
Patrick committed
910
        offer_item = models.OfferItem.objects.select_for_update(nowait=False) \
911
            .filter(id=offer_item_id, is_active=True, may_order=True) \
Patrick's avatar
Patrick committed
912
            .order_by('?').select_related("product", "producer").first()
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
        if offer_item is not None:
            if q_order is None:
                # Transform value_id into a q_order.
                # This is done here and not in the order_ajax to avoid to access twice to offer_item
                q_min = offer_item.customer_minimum_order_quantity
                q_step = offer_item.customer_increment_order_quantity
                if value_id <= 0:
                    q_order = DECIMAL_ZERO
                elif value_id == 1:
                    q_order = q_min
                else:
                    if q_min < q_step:
                        # 1; 2; 4; 6; 8 ... q_min = 1; q_step = 2
                        # 0,5; 1; 2; 3 ... q_min = 0,5; q_step = 1
                        if value_id == 2:
                            q_order = q_step
                        else:
                            q_order = q_step * (value_id - 1)
                    else:
                        # 1; 2; 3; 4 ... q_min = 1; q_step = 1
                        # 0,125; 0,175; 0,225 ... q_min = 0,125; q_step = 0,50
                        q_order = q_min + q_step * (value_id - 1)
            if q_order < DECIMAL_ZERO:
                q_order = DECIMAL_ZERO
Patrick's avatar
Patrick committed
937
938
939
940
941
942
943
944
945
            purchase = None
            permanence_id = offer_item.permanence_id
            updated = True
            if offer_item.is_box:
                # Select one purchase
                purchase = models.Purchase.objects.filter(
                    customer_id=customer.id,
                    offer_item_id=offer_item.id,
                    is_box_content=False
946
                ).order_by('?').first()
Patrick's avatar
Patrick committed
947
                if purchase is not None:
948
                    delta_q_order = q_order - purchase.quantity_ordered
Patrick's avatar
Patrick committed
949
                else:
950
                    delta_q_order = q_order
Patrick's avatar
Patrick committed
951
952
953
954
                with transaction.atomic():
                    sid = transaction.savepoint()
                    # This code executes inside a transaction.
                    for content in models.BoxContent.objects.filter(
955
                        box=offer_item.product_id
Patrick's avatar
Patrick committed
956
                    ).only(
957
                        "product_id", "content_quantity"
Patrick's avatar
Patrick committed
958
959
960
961
962
963
964
965
966
967
                    ).order_by('?'):
                        box_offer_item = models.OfferItem.objects \
                            .filter(product_id=content.product_id, permanence_id=offer_item.permanence_id) \
                            .order_by('?').select_related("producer").first()
                        if box_offer_item is not None:
                            # Select one purchase
                            purchase = models.Purchase.objects.filter(
                                customer_id=customer.id,
                                offer_item_id=box_offer_item.id,
                                is_box_content=True
968
                            ).order_by('?').first()
Patrick's avatar
Patrick committed
969
                            if purchase is not None:
970
                                quantity_ordered = purchase.quantity_ordered + delta_q_order * content.content_quantity
Patrick's avatar
Patrick committed
971
                            else:
972
                                quantity_ordered = delta_q_order * content.content_quantity
Patrick's avatar
Patrick committed
973
974
975
                            if quantity_ordered < DECIMAL_ZERO:
                                quantity_ordered = DECIMAL_ZERO
                            purchase, updated = create_or_update_one_purchase(
976
                                customer, box_offer_item, q_order=quantity_ordered, batch_job=batch_job, is_box_content=True
Patrick's avatar
Patrick committed
977
                            )
Patrick's avatar
Patrick committed
978
                        else:
Patrick's avatar
Patrick committed
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
                            updated = False
                        if not updated:
                            break
                    if updated:
                        for content in models.BoxContent.objects.filter(box=offer_item.product_id).only(
                                "product_id").order_by('?'):
                            box_offer_item = models.OfferItem.objects.filter(
                                product_id=content.product_id,
                                permanence_id=offer_item.permanence_id
                            ).order_by('?').first()
                            if box_offer_item is not None:
                                # Select one purchase
                                purchase = models.Purchase.objects.filter(
                                    customer_id=customer.id,
                                    offer_item_id=box_offer_item.id,
                                    is_box_content=False
995
                                ).order_by('?').first()
996
997
998
999
1000
1001
1002
1003
1004
                                option_dict = display_selected_value(
                                    box_offer_item,
                                    purchase.quantity_ordered if purchase is not None else DECIMAL_ZERO
                                )
                                to_json.append(option_dict)
                                box_purchase = models.Purchase.objects.filter(
                                    customer_id=customer.id,
                                    offer_item_id=box_offer_item.id,
                                    is_box_content=True
1005
                                ).order_by('?').first()
1006
                                option_dict = display_selected_box_value(customer, box_offer_item, box_purchase)
Patrick's avatar
Patrick committed
1007
1008
1009
1010
1011
                                to_json.append(option_dict)
                        transaction.savepoint_commit(sid)
                    else:
                        transaction.savepoint_rollback(sid)
            if not offer_item.is_box or updated:
1012
1013
1014
1015
                purchase, updated = create_or_update_one_purchase(
                    customer, offer_item, q_order=q_order, batch_job=batch_job,
                    is_box_content=False
                )
Patrick's avatar
Patrick committed
1016
1017
1018
1019
                if not batch_job and apps.REPANIER_SETTINGS_DISPLAY_PRODUCER_ON_ORDER_FORM:
                    producer_invoice = models.ProducerInvoice.objects.filter(
                        producer_id=offer_item.producer_id, permanence_id=offer_item.permanence_id
                    ).only("total_price_with_tax").order_by('?').first()
1020
1021
                    if producer_invoice is not None and offer_item.producer.minimum_order_value.amount > DECIMAL_ZERO:
                        ratio = producer_invoice.total_price_with_tax.amount / offer_item.producer.minimum_order_value.amount
Patrick's avatar
Patrick committed
1022
1023
                        if ratio >= DECIMAL_ONE:
                            ratio = 100
Patrick's avatar
Patrick committed
1024
                        else:
Patrick's avatar
Patrick committed
1025
                            ratio *= 100
1026
1027
1028
                        option_dict = {'id'  : "#order_procent" + str(offer_item.producer_id),
                                       'html': "%s%%" % number_format(ratio, 0)}
                        to_json.append(option_dict)
Patrick's avatar
Patrick committed
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
            elif not batch_job:
                # Select one purchase
                purchase = models.Purchase.objects.filter(
                    customer_id=customer.id,
                    offer_item_id=offer_item.id,
                    is_box_content=False
                ).order_by('?').first()

            if not batch_job:
                if purchase is None:
                    if offer_item.is_box:
                        sold_out = _("Sold out")
                        option_dict = {
                            'id'  : "#offer_item%d" % offer_item.id,
                            'html': '<option value="0" selected>%s</option>' % sold_out
                        }
                    else:
1046
                        option_dict = display_selected_value(offer_item, DECIMAL_ZERO)
Patrick's avatar
Patrick committed
1047
1048
1049
1050
                    to_json.append(option_dict)
                else:
                    offer_item = models.OfferItem.objects.filter(id=offer_item_id).order_by('?').first()
                    if offer_item is not None:
1051
                        option_dict = display_selected_value(offer_item, purchase.quantity_ordered)
Patrick's avatar
Patrick committed
1052
                        to_json.append(option_dict)
Patrick's avatar
Patrick committed
1053

Patrick's avatar
Patrick committed
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
                customer_invoice = models.CustomerInvoice.objects.filter(
                    permanence_id=permanence_id,
                    customer_id=customer.id
                ).order_by('?').first()
                permanence = models.Permanence.objects.filter(
                    id=permanence_id
                ).only("id", "with_delivery_point", "status").order_by('?').first()
                if customer_invoice is not None and permanence is not None:
                    order_amount = customer_invoice.get_total_price_with_tax()
                    customer_invoice.cancel_confirm_order()
                    customer_invoice.save()
                    my_basket(customer_invoice.is_order_confirm_send, order_amount, to_json)
                    if basket:
1067
                        basket_message = calc_basket_message(customer, permanence, PERMANENCE_OPENED)
Patrick's avatar
Patrick committed
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
                    else:
                        basket_message = EMPTY_STRING
                    my_order_confirmation(
                        permanence=permanence,
                        customer_invoice=customer_invoice,
                        is_basket=basket,
                        basket_message=basket_message,
                        to_json=to_json
                    )
    return json.dumps(to_json, cls=DjangoJSONEncoder)


def my_basket(is_order_confirm_send, order_amount, to_json):
    if not is_order_confirm_send and apps.REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS:
Patrick's avatar
Patrick committed
1082
1083
        msg_html = '<span class="glyphicon glyphicon-shopping-cart"></span> %s&nbsp;&nbsp;&nbsp;<span class="glyphicon glyphicon-exclamation-sign"></span>&nbsp;<span class="glyphicon glyphicon-floppy-remove"></span></a>' % (
            order_amount,)
Patrick's avatar
Patrick committed
1084
    else:
Patrick's avatar
Patrick committed
1085
        msg_html = '<span class="glyphicon glyphicon-shopping-cart"></span> %s&nbsp;&nbsp;&nbsp;<span class="glyphicon glyphicon-floppy-saved"></span></a>' % (
Patrick's avatar
Patrick committed
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
        order_amount,)
    option_dict = {'id': "#my_basket", 'html': msg_html}
    to_json.append(option_dict)
    option_dict = {'id': "#prepared_amount_visible_xs", 'html': msg_html}
    to_json.append(option_dict)


def my_order_confirmation(permanence, customer_invoice, is_basket=False,
                          basket_message=EMPTY_STRING, to_json=None):
    if permanence.with_delivery_point:
        if customer_invoice.delivery is not None:
            label = customer_invoice.delivery.get_delivery_customer_display()
            delivery_id = customer_invoice.delivery_id
1099
        else:
Patrick's avatar
Patrick committed
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
            delivery_id = -1
            if customer_invoice.customer.delivery_point is not None:
                qs = models.DeliveryBoard.objects.filter(
                    Q(
                        permanence_id=permanence.id,
                        delivery_point_id=customer_invoice.customer.delivery_point_id,
                        status=PERMANENCE_OPENED
                    ) | Q(
                        permanence_id=permanence.id,
                        delivery_point__closed_group=False,
                        status=PERMANENCE_OPENED
                    )
                ).order_by('?')
            else:
                qs = models.DeliveryBoard.objects.filter(
                    permanence_id=permanence.id,
                    delivery_point__closed_group=False,
                    status=PERMANENCE_OPENED
                ).order_by('?')
            if qs.exists():
                label = "%s" % _('Please, select a delivery point')
                models.CustomerInvoice.objects.filter(
                    permanence_id=permanence.id,
                    customer_id=customer_invoice.customer_id).order_by('?').update(
                    status=PERMANENCE_OPENED)
            else:
                label = "%s" % _('No delivery point is open for you. You can not place order.')
                # IMPORTANT :
                # 1 / This prohibit to place an order into the customer UI
                # 2 / task_order.close_send_order will delete any CLOSED orders without any delivery point
                models.CustomerInvoice.objects.filter(
                    permanence_id=permanence.id,
                    customer_id=customer_invoice.customer_id
                ).order_by('?').update(
                    status=PERMANENCE_CLOSED)
        if customer_invoice.customer_id != customer_invoice.customer_who_pays_id:
            msg_price = msg_transport = EMPTY_STRING
        else:
            if customer_invoice.transport.amount <= DECIMAL_ZERO:
                transport = False
                msg_transport = EMPTY_STRING
            else:
                transport = True
                if customer_invoice.min_transport.amount > DECIMAL_ZERO:
                    msg_transport = "%s<br/>" % \
                                    _(
                                        'The shipping costs for this delivery point amount to %(transport)s for orders of less than %(min_transport)s.') % {
                                        'transport'    : customer_invoice.transport,
                                        'min_transport': customer_invoice.min_transport
                                    }
                else:
                    msg_transport = "%s<br/>" % \
                                    _(
                                        'The shipping costs for this delivery point amount to %(transport)s.') % {
                                        'transport': customer_invoice.transport,
                                    }
            if customer_invoice.price_list_multiplier == DECIMAL_ONE:
                msg_price = EMPTY_STRING
            else:
                if transport:
                    if customer_invoice.price_list_multiplier > DECIMAL_ONE:
                        msg_price = "%s<br/>" % \
                                        _(
                                            'A price increase of %(increase)s %% of the total invoiced is due for this delivery point. This does not apply to the cost of transport which is fixed.') % {
                                            'increase'    : number_format((customer_invoice.price_list_multiplier - DECIMAL_ONE) * 100, 2)
                                        }
                    else:
                        msg_price = "%s<br/>" % \
                                        _(
                                            'A price decrease of %(decrease)s %% of the total invoiced is given for this delivery point. This does not apply to the cost of transport which is fixed.') % {
                                            'decrease': number_format((DECIMAL_ONE - customer_invoice.price_list_multiplier) * 100, 2)
                                        }
                else:
                    if customer_invoice.price_list_multiplier > DECIMAL_ONE:
                        msg_price = "%s<br/>" % \
                                        _(
                                            'A price increase of %(increase)s %% of the total invoiced is due for this delivery point.') % {
                                            'increase'    : number_format((customer_invoice.price_list_multiplier - DECIMAL_ONE) * 100, 2)
                                        }
                    else:
                        msg_price = "%s<br/>" % \
                                        _(
                                            'A price decrease of %(decrease)s %% of the total invoiced is given for this delivery point.') % {
                                            'decrease': number_format((DECIMAL_ONE - customer_invoice.price_list_multiplier) * 100, 2)
                                        }

        msg_delivery = '%s<b><i><select name="delivery" id="delivery" onmouseover="delivery_select_ajax()" onchange="delivery_ajax()" class="form-control"><option value="%d" selected>%s</option></select></i></b><br/>%s%s' % (
            _("Delivery point"),
            delivery_id,
            label,
            msg_transport,
            msg_price
        )
    else:
        msg_delivery = EMPTY_STRING
1195
    msg_confirmation1 = EMPTY_STRING
Patrick's avatar
Patrick committed
1196
1197
1198
1199
    if not is_basket and not apps.REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS:
        # or customer_invoice.total_price_with_tax.amount != DECIMAL_ZERO:
        # If apps.REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS is True,
        # then permanence.with_delivery_point is also True
1200
        msg_html = EMPTY_STRING
Patrick's avatar
Patrick committed
1201
1202
    else:
        if customer_invoice.is_order_confirm_send:
1203
            msg_confirmation2 = my_order_confirmation_email_send_to(customer_invoice.customer)
Patrick's avatar
Patrick committed
1204
1205
1206
1207
1208
            msg_html = """
            <div class="row">
            <div class="panel panel-default">
            <div class="panel-heading">
            %s
1209
            <p><font color="green">%s</font><p/>
Patrick's avatar
Patrick committed
1210
1211
1212
1213
            %s
            </div>
            </div>
            </div>
1214
             """ % (msg_delivery, msg_confirmation2, basket_message)
Patrick's avatar
Patrick committed
1215
1216
        else:
            msg_html = None
1217
            btn_disabled = EMPTY_STRING if permanence.status == PERMANENCE_OPENED else "disabled"
1218
            msg_confirmation2 = EMPTY_STRING
Patrick's avatar
Patrick committed
1219
1220
            if apps.REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS:
                if is_basket:
1221
                    if customer_invoice.status == PERMANENCE_OPENED:
1222
1223
                        if (permanence.with_delivery_point and customer_invoice.delivery is None) \
                                or customer_invoice.total_price_with_tax == DECIMAL_ZERO:
1224
                            btn_disabled = "disabled"
1225
1226
                        msg_confirmation1 = '<font color="red">%s</font><br/>' % _("An unconfirmed order will be canceled.")
                        msg_confirmation2 = '<span class="glyphicon glyphicon-floppy-disk"></span>&nbsp;&nbsp;%s' % _("Confirm this order and receive an email containing its summary.")
1227
                else:
Patrick's avatar
Patrick committed
1228
1229
1230
                    href = urlresolvers.reverse(
                        'basket_view', args=(permanence.id,)
                    )
1231
1232
                    msg_confirmation1 = '<font color="red">%s</font><br/>' % _("An unconfirmed order will be canceled.")
                    msg_confirmation2 = _("Verify my order content before validating it.")
Patrick's avatar
Patrick committed
1233
1234
1235
1236
1237
                    msg_html = """
                        <div class="row">
                        <div class="panel panel-default">
                        <div class="panel-heading">
                        %s
1238
                        %s
Patrick's avatar
Patrick committed
1239
1240
1241
1242
                        <a href="%s" class="btn btn-info" %s>%s</a>
                        </div>
                        </div>
                        </div>
1243
                         """ % (msg_delivery, msg_confirmation1, href, btn_disabled, msg_confirmation2)
Patrick's avatar
Patrick committed
1244
1245
            else:
                if is_basket:
1246
                    msg_confirmation2 = _("Receive an email containing this order summary.")
Patrick's avatar
Patrick committed
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
                elif permanence.with_delivery_point:
                    msg_html = """
                        <div class="row">
                        <div class="panel panel-default">
                        <div class="panel-heading">
                        %s
                        </div>
                        </div>
                        </div>
                         """ % msg_delivery
                else:
                    msg_html = EMPTY_STRING
            if msg_html is None:
1260
                if msg_confirmation2 == EMPTY_STRING:
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
                    msg_html = """
                    <div class="row">
                    <div class="panel panel-default">
                    <div class="panel-heading">
                    %s
                    <div class="clearfix"></div>
                    %s
                    </div>
                    </div>
                    </div>
                     """ % (msg_delivery, basket_message)
                else:
                    msg_html = """
                    <div class="row">
                    <div class="panel panel-default">
                    <div class="panel-heading">
                    %s
1278
                    %s
1279
1280
1281
1282
1283
1284
                    <button id="btn_confirm_order" class="btn btn-info" %s onclick="btn_receive_order_email();">%s</button>
                    <div class="clearfix"></div>
                    %s
                    </div>
                    </div>
                    </div>
1285
                     """ % (msg_delivery, msg_confirmation1, btn_disabled, msg_confirmation2, basket_message)
Patrick's avatar
Patrick committed
1286
1287
1288
    if to_json is not None:
        option_dict = {'id': "#span_btn_confirm_order", 'html': msg_html}
        to_json.append(option_dict)
Patrick's avatar
Patrick committed
1289
1290


Patrick's avatar
Patrick committed
1291
def my_order_confirmation_email_send_to(customer):
1292
    if customer is not None and customer.email2:
Patrick's avatar
Patrick committed
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
        to_email = (customer.user.email, customer.email2)
    else:
        to_email = (customer.user.email,)
    sent_to = ", ".join(to_email) if to_email is not None else EMPTY_STRING
    if apps.REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS:
        msg_confirmation = _(
            "Your order is confirmed. An email containing this order summary has been sent to %s.") % sent_to
    else:
        msg_confirmation = _("An email containing this order summary has been sent to %s.") % sent_to
    return msg_confirmation


1305
1306
1307
1308
def create_or_update_one_purchase(customer, offer_item, q_order=None, batch_job=False, is_box_content=False):

    # The batch_job flag is used because we need to forbid
    # customers to add purchases during the close_orders_async or other batch_job process
Patrick's avatar
Patrick committed
1309
    # when the status is PERMANENCE_WAIT_FOR_SEND
1310
1311
1312
1313
1314
1315
    purchase = models.Purchase.objects.filter(
        customer_id=customer.id,
        offer_item_id=offer_item.id,
        is_box_content=is_box_content
    ).order_by('?').first()
    if batch_job:
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
        if purchase is None:
            permanence = models.Permanence.objects.filter(id=offer_item.permanence_id) \
                .only("permanence_date") \
                .order_by('?').first()
            purchase = models.Purchase.objects.create(
                permanence_id=offer_item.permanence_id,
                permanence_date=permanence.permanence_date,
                offer_item_id=offer_item.id,
                producer_id=offer_item.producer_id,
                customer_id