tools.py 80.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
80
81
82
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)
    return testers


Patrick's avatar
Patrick committed
83
84
85
86
87
88
def send_email(email=None, track_customer_on_error=False):
    if settings.DJANGO_SETTINGS_DEMO:
        email.to = [DEMO_EMAIL]
        email.cc = []
        email.bcc = []
        send_email_with_error_log(email)
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
97
98
                email.cc = []
                email.bcc = []
                send_email_with_error_log(email)
            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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
                # chunks = [email.to[x:x+100] for x in xrange(0, len(email.to), 100)]
                # for chunk in chunks:
                if len(email.bcc) > 1:
                    # Remove duplicate
                    email_bcc = list(set(email.bcc))
                    customer = None
                    for bcc in email_bcc:
                        email.bcc = [bcc]
                        if track_customer_on_error:
                            # If the email is conained both in user__email and customer__email2
                            # select the customer based on user__email
                            customer = models.Customer.objects.filter(user__email=bcc).order_by('?').first()
                            if customer is None:
                                customer = models.Customer.objects.filter(email2=bcc).order_by('?').first()
                        send_email_with_error_log(email, customer)
                        time.sleep(2)
                else:
                    send_email_with_error_log(email)
Patrick's avatar
Patrick committed
125
126
127
128
129
130


def send_email_with_error_log(email, customer=None):
    with mail.get_connection() as connection:
        email.connection = connection
        message = EMPTY_STRING
131
132
133
134
135
        if not email.from_email.endswith(settings.DJANGO_SETTINGS_ALLOWED_MAIL_EXTENSION):
            email.reply_to = [email.from_email]
            email.from_email = "%s <%s>" % (apps.REPANIER_SETTINGS_GROUP_NAME, settings.DEFAULT_FROM_EMAIL)
        else:
            email.from_email = "%s <%s>" % (apps.REPANIER_SETTINGS_GROUP_NAME, email.from_email)
Patrick's avatar
Patrick committed
136
137
        try:
            print("################################## send_email")
138
            reply_to = "reply_to : %s" % email.reply_to
Patrick's avatar
Patrick committed
139
140
141
142
            to = "to : %s" % email.to
            cc = "cc : %s" % email.cc
            bcc = "bcc : %s" % email.bcc
            subject = "subject : %s" % slugify(email.subject)
143
            print(reply_to)
Patrick's avatar
Patrick committed
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
            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(2)
            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(2)
            connection = mail.get_connection()
            connection.open()
            mail_admins("ERROR", "%s\n%s" % (message, error_str), connection=connection)
            connection.close()
        if customer is not None:
            customer.valid_email = valid_email
            customer.save(update_fields=['valid_email'])
        print("##################################")


def send_email_to_who(is_email_send, board=False):
    if not is_email_send:
        if board:
Patrick's avatar
Change    
Patrick committed
178
179
180
181
            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
182
183
                    return False, _("No email will be sent.")
                else:
Patrick's avatar
Change    
Patrick committed
184
                    return True, _("This email will be sent to the staff.")
Patrick's avatar
Patrick committed
185
186
        else:
            return False, _("No email will be sent.")
Patrick's avatar
Patrick committed
187
    else:
Patrick's avatar
Change    
Patrick committed
188
189
        if apps.REPANIER_SETTINGS_TEST_MODE:
            return True, _("This email will be sent to %s.") % ', '.join(emails_of_testers())
Patrick's avatar
Patrick committed
190
        else:
Patrick's avatar
Change    
Patrick committed
191
192
            if settings.DEBUG:
                return False, _("No email will be sent.")
Patrick's avatar
Patrick committed
193
            else:
Patrick's avatar
Change    
Patrick committed
194
195
196
197
                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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219


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
220
221
222
223


def get_signature(is_reply_to_order_email=False, is_reply_to_invoice_email=False):
    sender_email = None
Patrick's avatar
Patrick committed
224
225
    sender_function = EMPTY_STRING
    signature = EMPTY_STRING
Patrick's avatar
Patrick committed
226
    cc_email_staff = []
Patrick's avatar
Patrick committed
227
    for staff in models.Staff.objects.filter(is_active=True).order_by('?'):
Patrick's avatar
Patrick committed
228
229
        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):
230
            cc_email_staff.append(staff.user.email)
Patrick's avatar
Patrick committed
231
            sender_email = staff.user.email
Patrick's avatar
Patrick committed
232
233
234
235
            try:
                sender_function = staff.long_name
            except TranslationDoesNotExist:
                sender_function = EMPTY_STRING
Patrick's avatar
Patrick committed
236
237
238
239
240
241
            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
242
                if r.phone2 and len(r.phone2.strip()) > 0:
Patrick's avatar
Patrick committed
243
                    signature += " / %s" % (r.phone2,)
244
245
246
        elif staff.is_coordinator:
            cc_email_staff.append(staff.user.email)

Patrick's avatar
Patrick committed
247
    if sender_email is None:
Patrick's avatar
Patrick committed
248
        sender_email = settings.DEFAULT_FROM_EMAIL
Patrick's avatar
Patrick committed
249
    return sender_email, sender_function, signature, cc_email_staff
pi's avatar
pi committed
250

Patrick's avatar
Patrick committed
251

Patrick's avatar
Patrick committed
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
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
274
LENGTH_BY_PREFIX = [
275
276
277
278
279
    (0xC0, 2),  # first byte mask, total codepoint length
    (0xE0, 3),
    (0xF0, 4),
    (0xF8, 5),
    (0xFC, 6),
Patrick's avatar
Patrick committed
280
281
]

282

Patrick's avatar
Patrick committed
283
284
def codepoint_length(first_byte):
    if first_byte < 128:
285
        return 1  # ASCII
Patrick's avatar
Patrick committed
286
287
    for mask, length in LENGTH_BY_PREFIX:
        if first_byte & 0xF0 == mask:
288
            return length
Patrick's avatar
Patrick committed
289
        elif first_byte & 0xF8 == 0xF8:
290
            return length
Patrick's avatar
Patrick committed
291
292
    assert False, 'Invalid byte %r' % first_byte

293

Patrick's avatar
Patrick committed
294
def cap_to_bytes_length(unicode_text, byte_limit):
Patrick's avatar
Patrick committed
295
    utf8_bytes = unicode_text.encode("utf8")
Patrick's avatar
Patrick committed
296
297
298
299
300
301
302
303
304
305
    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

306

Patrick's avatar
Patrick committed
307
def cap(s, l):
Patrick's avatar
Patrick committed
308
    if s is not None:
309
310
        if not isinstance(s, basestring):
            s = str(s)
Patrick's avatar
Patrick committed
311
        s = s if len(s) <= l else s[0:l - 4] + '...'
312
        return s
Patrick's avatar
Patrick committed
313
    else:
314
315
316
        return None


317
318
319
320
321
322
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
323
                        timezone.now() - datetime.timedelta(weeks=LIMIT_DISPLAYED_PERMANENCE)
324
325
326
327
328
329
            ).date():
                raise Http404
        else:
            raise Http404


330
331
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
332
        unit = _("/ kg")
333
    elif order_unit == PRODUCT_ORDER_UNIT_LT:
Patrick's avatar
Patrick committed
334
        unit = _("/ l")
Patrick's avatar
Patrick committed
335
    else:
336
        if qty < 2:
Patrick's avatar
Patrick committed
337
            unit = _("/ piece")
338
        else:
Patrick's avatar
Patrick committed
339
            unit = _("/ pieces")
340
341
342
    return unit


Patrick's avatar
Patrick committed
343
def get_preparator_unit(order_unit=PRODUCT_ORDER_UNIT_PC):
344
    # Used when producing the preparation list.
345
346
    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
347
        unit = _("Piece(s) :")
348
    elif order_unit in [PRODUCT_ORDER_UNIT_KG, PRODUCT_ORDER_UNIT_PC_KG]:
Patrick's avatar
Patrick committed
349
        unit = _("%s or kg :") % (apps.REPANIER_SETTINGS_CURRENCY_DISPLAY.decode('utf-8'),)
350
    elif order_unit == PRODUCT_ORDER_UNIT_LT:
Patrick's avatar
Patrick committed
351
        unit = _("L :")
352
    else:
Patrick's avatar
Patrick committed
353
        unit = _("Kg :")
354
355
356
    return unit


Patrick's avatar
Patrick committed
357
358
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):
359
        if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
360
            base_unit = EMPTY_STRING
361
362
363
364
        else:
            base_unit = _('kg')
    elif order_unit == PRODUCT_ORDER_UNIT_LT:
        if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
365
            base_unit = EMPTY_STRING
366
367
368
369
        else:
            base_unit = _('l')
    else:
        if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
370
            base_unit = EMPTY_STRING
371
372
373
374
375
376
377
        elif qty < 2:
            base_unit = _('piece')
        else:
            base_unit = _('pieces')
    return base_unit


Patrick's avatar
Patrick committed
378
379
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):
380
381
    magnitude = None
    display_qty = True
382
    if order_unit == PRODUCT_ORDER_UNIT_KG:
383
        if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
384
            unit = EMPTY_STRING
385
386
387
        elif for_customer and qty < 1:
            unit = "%s" % (_('gr'))
            magnitude = 1000
388
        else:
389
            unit = "%s" % (_('kg'))
390
    elif order_unit == PRODUCT_ORDER_UNIT_LT:
391
        if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
392
            unit = EMPTY_STRING
393
394
395
        elif for_customer and qty < 1:
            unit = "%s" % (_('cl'))
            magnitude = 100
396
        else:
397
            unit = "%s" % (_('l'))
398
    elif order_unit in [PRODUCT_ORDER_UNIT_PC_KG, PRODUCT_ORDER_UNIT_PC_PRICE_KG]:
399
        # display_qty = not (order_average_weight == 1 and order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_KG)
400
401
402
        average_weight = order_average_weight
        if for_customer:
            average_weight *= qty
Patrick's avatar
Patrick committed
403
404
        if order_unit == PRODUCT_ORDER_UNIT_PC_KG and unit_price_amount is not None:
            unit_price_amount *= order_average_weight
405
        if average_weight < 1:
Patrick's avatar
Patrick committed
406
            average_weight_unit = _('gr')
407
408
            average_weight *= 1000
        else:
Patrick's avatar
Patrick committed
409
            average_weight_unit = _('kg')
410
411
412
413
414
415
416
        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
417
        tilde = EMPTY_STRING
418
419
        if order_unit == PRODUCT_ORDER_UNIT_PC_KG:
            tilde = '~'
420
        if for_customer:
Patrick's avatar
Patrick committed
421
            if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
422
                unit = EMPTY_STRING
423
            else:
424
                if order_average_weight == 1 and order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_KG:
425
                    unit = "%s%s %s" % (tilde, number_format(average_weight, decimal), average_weight_unit)
426
427
                else:
                    unit = "%s%s%s" % (tilde, number_format(average_weight, decimal), average_weight_unit)
428
        else:
Patrick's avatar
Patrick committed
429
            if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
430
                unit = EMPTY_STRING
431
            else:
432
                unit = "%s%s%s" % (tilde, number_format(average_weight, decimal), average_weight_unit)
433
    elif order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_LT:
434
        display_qty = order_average_weight != 1
435
436
437
        average_weight = order_average_weight
        if for_customer:
            average_weight *= qty
438
        if average_weight < 1:
Patrick's avatar
Patrick committed
439
            average_weight_unit = _('cl')
440
441
            average_weight *= 100
        else:
Patrick's avatar
Patrick committed
442
            average_weight_unit = _('l')
443
444
445
446
447
448
449
        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
450
        if for_customer:
Patrick's avatar
Patrick committed
451
            if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
452
                unit = EMPTY_STRING
453
            else:
454
455
456
457
                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)
458
        else:
Patrick's avatar
Patrick committed
459
            if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
460
                unit = EMPTY_STRING
461
            else:
462
                unit = "%s%s" % (number_format(average_weight, decimal), average_weight_unit)
463
    elif order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_PC:
464
        display_qty = order_average_weight != 1
465
466
467
        average_weight = order_average_weight
        if for_customer:
            average_weight *= qty
Patrick's avatar
Patrick committed
468
            if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
469
                unit = EMPTY_STRING
470
            else:
Patrick's avatar
Patrick committed
471
                if average_weight < 2:
472
473
474
475
476
                    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
477
                else:
478
                    unit = "%s %s" % (number_format(average_weight, 0), pc_pcs)
479
        else:
Patrick's avatar
Patrick committed
480
            if average_weight == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
481
                unit = EMPTY_STRING
Patrick's avatar
Patrick committed
482
            elif average_weight < 2:
483
                unit = '%s %s' % (number_format(average_weight, 0), _('pc'))
484
            else:
485
486
                unit = '%s %s' % (number_format(average_weight, 0), _('pcs'))
    else:
Patrick's avatar
Patrick committed
487
488
489
490
491
492
493
        if for_order_select:
            if qty == DECIMAL_ZERO:
                unit = EMPTY_STRING
            elif qty < 2:
                unit = "%s" % (_('unit'))
            else:
                unit = "%s" % (_('units'))
494
        else:
Patrick's avatar
Patrick committed
495
496
            unit = EMPTY_STRING
    if unit_price_amount is not None:
497
        price_display = " = %s" % RepanierMoney(unit_price_amount * qty)
Patrick's avatar
Patrick committed
498
    else:
Patrick's avatar
Patrick committed
499
        price_display = EMPTY_STRING
500
501
    if magnitude is not None:
        qty *= magnitude
502
503
504
505
506
507
508
    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
509
    if for_customer or for_order_select:
510
511
512
        if unit:
            if display_qty:
                qty_display = "%s (%s)" % (number_format(qty, decimal), unit)
513
            else:
514
515
516
                qty_display = "%s" % unit
        else:
            qty_display = "%s" % number_format(qty, decimal)
Patrick's avatar
Patrick committed
517
    else:
518
519
        if unit:
            qty_display = "(%s)" % unit
520
        else:
Patrick's avatar
Patrick committed
521
522
523
524
            qty_display = EMPTY_STRING
    if without_price_display:
        return qty_display
    else:
525
        display = "%s%s" % (qty_display, price_display)
Patrick's avatar
Patrick committed
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
        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
                        }
557
            else:
Patrick's avatar
Patrick committed
558
559
560
561
562
                customer_on_hold_movement = \
                    _(
                        'This balance does not take account of any unrecognized payments %(bank)s.') % {
                        'bank': bank_not_invoiced
                    }
563
        else:
Patrick's avatar
Patrick committed
564
565
566
567
568
569
570
571
572
573
574
575
            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
576
    else:
Patrick's avatar
Patrick committed
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
        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:
601
            customer_payment_needed = '<font color="green">%s</font>' % (
Patrick's avatar
Patrick committed
602
603
604
605
                _('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
                }
            )
606
            customer_on_hold_movement = EMPTY_STRING
Patrick's avatar
Patrick committed
607
        else:
608
609
610
611
612
613
614
615
616
617
618
619
            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
620
                else:
621
                    customer_last_balance = EMPTY_STRING
Patrick's avatar
Patrick committed
622

623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
            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
639
640

                else:
641
642
643
644
645
646
647
                    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
648
649
650
651
652

    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
653
654
                             customer_id=None,
                             offer_item_queryset=None,
Patrick's avatar
Patrick committed
655
656
                             all_producers=True,
                             producers_id=None,
Patrick's avatar
Patrick committed
657
                             send_to_producer=False,
658
                             re_init=False):
Patrick's avatar
Patrick committed
659
660
661
662
663
664
665
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
    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
716
                )
Patrick's avatar
Patrick committed
717
718
719
720
721
722
723
724
                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
725
                )
Patrick's avatar
Patrick committed
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
            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
747
748
749
750
751
                # 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
752
753
754
755
756
757
758
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

    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
785
786
            if send_to_producer:
                if offer_item.order_unit == PRODUCT_ORDER_UNIT_PC_KG:
Patrick's avatar
Patrick committed
787
                    purchase.quantity_invoiced = (purchase.quantity_ordered * offer_item.order_average_weight) \
Patrick's avatar
Patrick committed
788
789
790
791
792
                        .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
793
794
795
796
797
798
                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
799
800
801
                else:
                    purchase.quantity_invoiced = purchase.quantity_ordered
                    purchase.quantity_for_preparation_sort_order = DECIMAL_ZERO
Patrick's avatar
Patrick committed
802
803
804
        purchase.save()


805
def display_selected_value(offer_item, quantity_ordered):
Patrick's avatar
Patrick committed
806
807
808
809
810
811
812
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
    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


860
def display_selected_box_value(customer, offer_item, box_purchase):
Patrick's avatar
Patrick committed
861
862
863
864
    if offer_item.is_box_content:
        # box_name = _not_lazy("Composition")
        box_name = BOX_UNICODE
        # Select one purchase
865
        if box_purchase is not None:
Patrick's avatar
Patrick committed
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
            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
897
898
899


@transaction.atomic
900
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
901
    to_json = []
902
    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
903
        offer_item = models.OfferItem.objects.select_for_update(nowait=False) \
904
            .filter(id=offer_item_id, is_active=True, may_order=True) \
Patrick's avatar
Patrick committed
905
            .order_by('?').select_related("product", "producer").first()
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
        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
930
931
932
933
934
935
936
937
938
            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
939
                ).order_by('?').first()
Patrick's avatar
Patrick committed
940
                if purchase is not None:
941
                    delta_q_order = q_order - purchase.quantity_ordered
Patrick's avatar
Patrick committed
942
                else:
943
                    delta_q_order = q_order
Patrick's avatar
Patrick committed
944
945
946
947
                with transaction.atomic():
                    sid = transaction.savepoint()
                    # This code executes inside a transaction.
                    for content in models.BoxContent.objects.filter(
948
                        box=offer_item.product_id
Patrick's avatar
Patrick committed
949
                    ).only(
950
                        "product_id", "content_quantity"
Patrick's avatar
Patrick committed
951
952
953
954
955
956
957
958
959
960
                    ).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
961
                            ).order_by('?').first()
Patrick's avatar
Patrick committed
962
                            if purchase is not None:
963
                                quantity_ordered = purchase.quantity_ordered + delta_q_order * content.content_quantity
Patrick's avatar
Patrick committed
964
                            else:
965
                                quantity_ordered = delta_q_order * content.content_quantity
Patrick's avatar
Patrick committed
966
967
968
                            if quantity_ordered < DECIMAL_ZERO:
                                quantity_ordered = DECIMAL_ZERO
                            purchase, updated = create_or_update_one_purchase(
969
                                customer, box_offer_item, q_order=quantity_ordered, batch_job=batch_job, is_box_content=True
Patrick's avatar
Patrick committed
970
                            )
Patrick's avatar
Patrick committed
971
                        else:
Patrick's avatar
Patrick committed
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
                            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
988
                                ).order_by('?').first()
989
990
991
992
993
994
995
996
997
                                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
998
                                ).order_by('?').first()
999
                                option_dict = display_selected_box_value(customer, box_offer_item, box_purchase)
Patrick's avatar
Patrick committed
1000
                                to_json.append(option_dict)
For faster browsing, not all history is shown. View entire blame