tools.py 81.7 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
                                option_dict = display_selected_value(
                                    box_offer_item,
                                    purchase.quantity_ordered if purchase is not None else DECIMAL_ZERO
                                )
                                to_json.append(option_dict)
For faster browsing, not all history is shown. View entire blame