tools.py 66 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
import json
import time
Patrick's avatar
RC  
Patrick committed
8 9 10 11 12 13 14 15

try:
    # For Python 3.0 and later
    from urllib.request import urlopen
except ImportError:
    # Fall back to Python 2's urllib2
    from urllib2 import urlopen

Patrick's avatar
Patrick committed
16 17 18 19 20 21 22 23 24
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.db import transaction
from django.db.models import F
Patrick's avatar
RC  
Patrick committed
25
from django.db.models import Q
26
from django.http import Http404
Patrick's avatar
Patrick committed
27
from django.template.loader import render_to_string
Patrick's avatar
Patrick committed
28
from django.utils import timezone
Patrick's avatar
Patrick committed
29
from django.utils import translation
pi's avatar
pi committed
30
from django.utils.formats import number_format
Patrick's avatar
Patrick committed
31 32 33
from django.utils.safestring import mark_safe
from django.utils.text import slugify
from django.utils.translation import ugettext_lazy as _
Patrick's avatar
Patrick committed
34
from six import string_types
Patrick's avatar
Patrick committed
35

Patrick's avatar
Patrick committed
36
import models
Patrick's avatar
Patrick committed
37 38 39
from const import *
from repanier import apps
from repanier.fields.RepanierMoneyField import RepanierMoney
Patrick's avatar
Patrick committed
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55


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
56

Patrick's avatar
Patrick committed
57 58 59 60 61 62
    def decorator(function):
        def wrapper(*args, **kwargs):
            try:
                return function(*args, **kwargs)
            except exception:
                return default_val
Patrick's avatar
Patrick committed
63

Patrick's avatar
Patrick committed
64
        return wrapper
Patrick's avatar
Patrick committed
65

Patrick's avatar
Patrick committed
66 67
    return decorator

Patrick's avatar
Patrick committed
68

Patrick's avatar
Patrick committed
69
sint = ignore_exception(ValueError)(int)
70
sboolean = ignore_exception(ValueError)(bool)
Patrick's avatar
Patrick committed
71 72 73 74 75 76 77


def next_row(query_iterator):
    try:
        return next(query_iterator)
    except StopIteration:
        # No rows were found, so do nothing.
Patrick's avatar
Patrick committed
78
        return
Patrick's avatar
Patrick committed
79 80


Patrick's avatar
Patrick committed
81 82 83 84 85
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)
86
    return list(set(testers))
Patrick's avatar
Patrick committed
87 88


89
def send_email(email=None, from_name=EMPTY_STRING, track_customer_on_error=False):
Patrick's avatar
Patrick committed
90 91 92 93
    if settings.DJANGO_SETTINGS_DEMO:
        email.to = [DEMO_EMAIL]
        email.cc = []
        email.bcc = []
94
        send_email_with_error_log(email, from_name)
95 96
    else:
        from repanier.apps import REPANIER_SETTINGS_TEST_MODE
Patrick's avatar
Patrick committed
97 98 99 100
        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
101 102
                email.cc = []
                email.bcc = []
103
                send_email_with_error_log(email, from_name)
104
            else:
Patrick's avatar
Patrick committed
105 106 107
                print('############################ test mode, without tester...')
        else:
            if settings.DEBUG:
108 109 110 111 112
                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
Patrick committed
113 114
                # chunks = [email.to[x:x+100] for x in xrange(0, len(email.to), 100)]
                # for chunk in chunks:
115 116 117 118 119
                # 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
Patrick committed
120
                    customer = None
121 122
                    for email_to in send_email_to:
                        email.to = [email_to]
123
                        send_email_with_error_log(email, from_name, track_customer_on_error)
124
                        time.sleep(1)
Patrick's avatar
Patrick committed
125 126


127 128 129 130 131
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]
Patrick's avatar
Patrick committed
132
        customer = models.Customer.objects.filter(user__email=email_to, subscribe_to_email=True).exclude(valid_email=False).only('id').order_by('?').first()
133
        if customer is None:
Patrick's avatar
Patrick committed
134 135
            customer = models.Customer.objects.filter(email2=email_to, subscribe_to_email=True).exclude(valid_email=False).only('id').order_by('?').first()
        send_mail = customer is not None
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 180 181
    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:
Patrick's avatar
Patrick committed
182 183 184 185
                # customer.valid_email = valid_email
                # customer.save(update_fields=['valid_email'])
                # use vvvv because ^^^^^ will call "pre_save" function which reset valid_email to None
                models.Customer.objects.filter(id=customer.id).order_by('?').update(valid_email=valid_email)
Patrick's avatar
Patrick committed
186 187 188 189 190


def send_email_to_who(is_email_send, board=False):
    if not is_email_send:
        if board:
Patrick's avatar
Patrick committed
191 192 193 194
            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
195 196
                    return False, _("No email will be sent.")
                else:
Patrick's avatar
Patrick committed
197
                    return True, _("This email will be sent to the staff.")
Patrick's avatar
Patrick committed
198 199
        else:
            return False, _("No email will be sent.")
Patrick's avatar
Patrick committed
200
    else:
Patrick's avatar
Patrick committed
201 202
        if apps.REPANIER_SETTINGS_TEST_MODE:
            return True, _("This email will be sent to %s.") % ', '.join(emails_of_testers())
Patrick's avatar
Patrick committed
203
        else:
Patrick's avatar
Patrick committed
204 205
            if settings.DEBUG:
                return False, _("No email will be sent.")
Patrick's avatar
Patrick committed
206
            else:
Patrick's avatar
Patrick committed
207 208 209 210
                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
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232


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
233 234 235 236


def get_signature(is_reply_to_order_email=False, is_reply_to_invoice_email=False):
    sender_email = None
Patrick's avatar
Patrick committed
237 238
    sender_function = EMPTY_STRING
    signature = EMPTY_STRING
Patrick's avatar
Patrick committed
239
    cc_email_staff = []
Patrick's avatar
Patrick committed
240
    for staff in models.Staff.objects.filter(is_active=True).order_by('?'):
Patrick's avatar
Patrick committed
241 242
        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):
243
            cc_email_staff.append(staff.user.email)
Patrick's avatar
Patrick committed
244
            sender_email = staff.user.email
Patrick's avatar
Patrick committed
245 246 247
            sender_function = staff.safe_translation_getter(
                'long_name', any_language=True, default=EMPTY_STRING
            )
Patrick's avatar
Patrick committed
248 249 250 251 252 253
            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
254
                if r.phone2 and len(r.phone2.strip()) > 0:
Patrick's avatar
Patrick committed
255
                    signature += " / %s" % (r.phone2,)
256 257 258
        elif staff.is_coordinator:
            cc_email_staff.append(staff.user.email)

Patrick's avatar
Patrick committed
259
    if sender_email is None:
Patrick's avatar
Patrick committed
260
        sender_email = settings.DEFAULT_FROM_EMAIL
Patrick's avatar
Patrick committed
261
    return sender_email, sender_function, signature, cc_email_staff
pi's avatar
pi committed
262

Patrick's avatar
Patrick committed
263

Patrick's avatar
Patrick committed
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
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
286
LENGTH_BY_PREFIX = [
287 288 289 290 291
    (0xC0, 2),  # first byte mask, total codepoint length
    (0xE0, 3),
    (0xF0, 4),
    (0xF8, 5),
    (0xFC, 6),
Patrick's avatar
Patrick committed
292 293
]

294

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

305

Patrick's avatar
Patrick committed
306
def cap_to_bytes_length(unicode_text, byte_limit):
Patrick's avatar
Patrick committed
307
    utf8_bytes = unicode_text.encode("utf8")
Patrick's avatar
Patrick committed
308 309 310 311 312 313 314 315 316 317
    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

318

Patrick's avatar
Patrick committed
319
def cap(s, l):
Patrick's avatar
Patrick committed
320
    if s is not None:
Patrick's avatar
Patrick committed
321
        if not isinstance(s, string_types):
322
            s = str(s)
Patrick's avatar
Patrick committed
323
        s = s if len(s) <= l else s[0:l - 4] + '...'
324
        return s
Patrick's avatar
Patrick committed
325
    else:
Patrick's avatar
Patrick committed
326
        return
327 328


329 330 331 332
def permanence_ok_or_404(permanence):
    if permanence is None:
        raise Http404
    if permanence.status not in [PERMANENCE_OPENED, PERMANENCE_CLOSED, PERMANENCE_SEND]:
Patrick's avatar
Patrick committed
333
        if permanence.status in [PERMANENCE_INVOICED, PERMANENCE_ARCHIVED]:
334
            if permanence.permanence_date < (
Patrick's avatar
Patrick committed
335
                        timezone.now() - datetime.timedelta(weeks=LIMIT_DISPLAYED_PERMANENCE)
336 337 338 339 340 341
            ).date():
                raise Http404
        else:
            raise Http404


342 343
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
344
        unit = _("/ kg")
345
    elif order_unit == PRODUCT_ORDER_UNIT_LT:
Patrick's avatar
Patrick committed
346
        unit = _("/ l")
Patrick's avatar
Patrick committed
347
    else:
348
        if qty < 2:
Patrick's avatar
Patrick committed
349
            unit = _("/ piece")
350
        else:
Patrick's avatar
Patrick committed
351
            unit = _("/ pieces")
352 353 354
    return unit


Patrick's avatar
Patrick committed
355 356 357 358 359 360 361 362 363 364 365
def get_reverse_invoice_unit(unit):
    # reverse of tools get_invoice_unit
    if unit == _("/ kg"):
        order_unit = PRODUCT_ORDER_UNIT_KG
    elif unit == _("/ l"):
        order_unit = PRODUCT_ORDER_UNIT_LT
    else:
        order_unit = PRODUCT_ORDER_UNIT_PC
    return order_unit


Patrick's avatar
Patrick committed
366
def get_preparator_unit(order_unit=PRODUCT_ORDER_UNIT_PC):
367
    # Used when producing the preparation list.
368 369
    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
370
        unit = _("Piece(s) :")
371
    elif order_unit in [PRODUCT_ORDER_UNIT_KG, PRODUCT_ORDER_UNIT_PC_KG]:
Patrick's avatar
Patrick committed
372
        unit = _("%s or kg :") % (apps.REPANIER_SETTINGS_CURRENCY_DISPLAY.decode('utf-8'),)
373
    elif order_unit == PRODUCT_ORDER_UNIT_LT:
Patrick's avatar
Patrick committed
374
        unit = _("L :")
375
    else:
Patrick's avatar
Patrick committed
376
        unit = _("Kg :")
377 378 379
    return unit


Patrick's avatar
RC  
Patrick committed
380 381 382 383
def get_base_unit(qty=0, order_unit=PRODUCT_ORDER_UNIT_PC, status=None, producer=False):
    if order_unit == PRODUCT_ORDER_UNIT_KG or (
                        status >= PERMANENCE_SEND and order_unit == PRODUCT_ORDER_UNIT_PC_KG and not producer
    ):
384
        if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
385
            base_unit = EMPTY_STRING
386 387 388 389
        else:
            base_unit = _('kg')
    elif order_unit == PRODUCT_ORDER_UNIT_LT:
        if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
390
            base_unit = EMPTY_STRING
391 392 393 394
        else:
            base_unit = _('l')
    else:
        if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
395
            base_unit = EMPTY_STRING
396 397 398 399 400 401 402
        elif qty < 2:
            base_unit = _('piece')
        else:
            base_unit = _('pieces')
    return base_unit


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


Patrick's avatar
Patrick committed
554 555
def payment_message(customer, permanence):
    from repanier.apps import REPANIER_SETTINGS_INVOICE
Patrick's avatar
Patrick committed
556

Patrick's avatar
Patrick committed
557 558 559 560
    customer_invoice = models.CustomerInvoice.objects.filter(
        customer_id=customer.id,
        permanence_id=permanence.id
    ).order_by('?').first()
Patrick's avatar
Patrick committed
561

Patrick's avatar
Patrick committed
562 563 564 565 566 567 568 569
    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 customer.balance.amount != DECIMAL_ZERO:
        if customer.balance.amount < DECIMAL_ZERO:
            balance = '<font color="#bd0926">%s</font>' % customer.balance
Patrick's avatar
Patrick committed
570
        else:
Patrick's avatar
Patrick committed
571 572 573 574 575 576 577 578
            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
            }
    else:
        customer_last_balance = EMPTY_STRING
Patrick's avatar
Patrick committed
579

Patrick's avatar
Patrick committed
580
    if customer_invoice.customer_id != customer_invoice.customer_charged_id:
Patrick's avatar
Patrick committed
581
        customer_on_hold_movement = EMPTY_STRING
Patrick's avatar
Patrick committed
582 583 584
        customer_payment_needed = '<font color="#51a351">%s</font>' % (
            _('Invoices for this delivery point are sent to %(name)s who is responsible for collecting the payments.') % {
                'name': customer_invoice.customer_charged.long_basket_name
Patrick's avatar
Patrick committed
585
            }
Patrick's avatar
Patrick committed
586 587 588 589 590 591 592 593 594
        )
    else:
        bank_not_invoiced = customer.get_bank_not_invoiced()
        order_not_invoiced = customer.get_order_not_invoiced()

        customer_on_hold_movement = customer.get_on_hold_movement_html(
            bank_not_invoiced, order_not_invoiced, total_price_with_tax
        )
        if REPANIER_SETTINGS_INVOICE:
Patrick's avatar
Patrick committed
595 596 597 598
            payment_needed = - (customer.balance - order_not_invoiced + bank_not_invoiced)
        else:
            payment_needed = total_price_with_tax

Patrick's avatar
Patrick committed
599 600 601 602 603
        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)
Patrick's avatar
Patrick committed
604
                else:
Patrick's avatar
Patrick committed
605 606 607 608 609 610 611 612 613 614
                    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
615

616
            else:
Patrick's avatar
Patrick committed
617 618 619 620 621 622
                if customer.balance.amount != DECIMAL_ZERO:
                    customer_payment_needed = '<br/><font color="#51a351">%s.</font>' % (_('Your account balance is sufficient'))
                else:
                    customer_payment_needed = EMPTY_STRING
        else:
            customer_payment_needed = EMPTY_STRING
Patrick's avatar
Patrick committed
623 624 625 626

    return customer_last_balance, customer_on_hold_movement, customer_payment_needed, customer_order_amount


Patrick's avatar
Patrick committed
627 628 629 630
def display_selected_value(offer_item, quantity_ordered, is_open=True):
    option_dict = {
        'id': "#offer_item%d" % offer_item.id,
    }
Patrick's avatar
Patrick committed
631 632
    if offer_item.may_order:
        if quantity_ordered <= DECIMAL_ZERO:
Patrick's avatar
Patrick committed
633 634 635 636 637 638
            if is_open:
                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
Patrick's avatar
Patrick committed
639
                else:
Patrick's avatar
Patrick committed
640 641 642 643 644
                    q_alert = offer_item.customer_alert_order_quantity
                if q_min <= q_alert:
                    label = "---"
                else:
                    label = _("Sold out")
Patrick's avatar
Patrick committed
645
            else:
Patrick's avatar
Patrick committed
646 647
                label = _("Closed")
            option_dict["html"] = '<option value="0" selected>%s</option>' % label
Patrick's avatar
Patrick committed
648 649 650 651 652 653 654 655 656 657

        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
            )
Patrick's avatar
Patrick committed
658
            option_dict["html"] = '<option value="%d" selected>%s</option>' % (quantity_ordered, display)
Patrick's avatar
Patrick committed
659
    else:
Patrick's avatar
Patrick committed
660
        option_dict["html"] = EMPTY_STRING
Patrick's avatar
Patrick committed
661 662 663
    return option_dict


Patrick's avatar
Patrick committed
664
def display_selected_box_value(offer_item, box_purchase):
Patrick's avatar
Patrick committed
665 666 667
    option_dict = {
        'id': "#box_offer_item%d" % offer_item.id,
    }
Patrick's avatar
Patrick committed
668
    if box_purchase.is_box_content:
Patrick's avatar
Patrick committed
669 670
        box_name = BOX_UNICODE
        # Select one purchase
671
        if box_purchase is not None:
Patrick's avatar
Patrick committed
672 673 674 675 676 677 678 679
            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
                )
Patrick's avatar
Patrick committed
680 681 682
                option_dict[
                    "html"] = '<select id="box_offer_item%d" name="box_offer_item%d" disabled class="form-control"><option value="0" selected>☑ %s %s</option></select>' % \
                                      (offer_item.id, offer_item.id, qty_display, box_name)
Patrick's avatar
Patrick committed
683
            else:
Patrick's avatar
Patrick committed
684 685 686
                option_dict[
                    "html"] = '<select id="box_offer_item%d" name="box_offer_item%d" disabled class="form-control"><option value="0" selected>☑ --- %s</option></select>' % \
                              (offer_item.id, offer_item.id, box_name)
Patrick's avatar
Patrick committed
687
        else:
Patrick's avatar
Patrick committed
688 689 690
            option_dict[
                "html"] = '<select id="box_offer_item%d" name="box_offer_item%d" disabled class="form-control"><option value="0" selected>☑ --- %s</option></select>' % \
                          (offer_item.id, offer_item.id, box_name)
Patrick's avatar
Patrick committed
691
    else:
Patrick's avatar
Patrick committed
692
        option_dict["html"] = EMPTY_STRING
Patrick's avatar
Patrick committed
693
    return option_dict
694 695


Patrick's avatar
Patrick committed
696 697 698 699
def create_or_update_one_purchase(
        customer_id, offer_item,
        permanence_date=None, status=PERMANENCE_OPENED, q_order=None,
        batch_job=False, is_box_content=False, comment=EMPTY_STRING):
Patrick's avatar
RC  
Patrick committed
700
    from repanier.apps import REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS
Patrick's avatar
Patrick committed
701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742
    # The batch_job flag is used because we need to forbid
    # customers to add purchases during the close_orders_async or other batch_job process
    # when the status is PERMANENCE_WAIT_FOR_SEND
    purchase = models.Purchase.objects.filter(
        customer_id=customer_id,
        offer_item_id=offer_item.id,
        is_box_content=is_box_content
    ).order_by('?').first()
    if batch_job:
        if purchase is None:
            permanence_date = permanence_date or models.Permanence.objects.filter(
                id=offer_item.permanence_id).only("permanence_date").order_by('?').first().permanence_date
            purchase = models.Purchase.objects.create(
                permanence_id=offer_item.permanence_id,
                permanence_date=permanence_date,
                offer_item_id=offer_item.id,
                producer_id=offer_item.producer_id,
                customer_id=customer_id,
                quantity_ordered=q_order if status < PERMANENCE_SEND else DECIMAL_ZERO,
                quantity_invoiced=q_order if status >= PERMANENCE_SEND else DECIMAL_ZERO,
                is_box_content=is_box_content,
                status=status,
                comment=comment
            )
        else:
            purchase.set_comment(comment)
            if status < PERMANENCE_SEND:
                purchase.quantity_ordered = q_order
            else:
                purchase.quantity_invoiced = q_order
            purchase.save()
        return purchase, True
    else:
        permanence_is_opened = models.CustomerInvoice.objects.filter(
            permanence_id=offer_item.permanence_id,
            customer_id=customer_id,
            status=status
        ).order_by('?').exists()
        if permanence_is_opened:
            if offer_item.limit_order_quantity_to_stock:
                if purchase is not None:
                    q_previous_order = purchase.quantity_ordered
743
                else:
Patrick's avatar
Patrick committed
744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769
                    q_previous_order = DECIMAL_ZERO
                q_alert = offer_item.stock - offer_item.quantity_invoiced + q_previous_order
                if is_box_content and q_alert < q_order:
                    # Select one purchase
                    non_box_purchase = models.Purchase.objects.filter(
                        customer_id=customer_id,
                        offer_item_id=offer_item.id,
                        is_box_content=False
                    ).order_by('?').first()
                    if non_box_purchase is not None:
                        tbd_qty = min(q_order - q_alert, non_box_purchase.quantity_ordered)
                        tbk_qty = non_box_purchase.quantity_ordered - tbd_qty
                        non_box_purchase.quantity_ordered = tbk_qty
                        non_box_purchase.save()
                        q_alert += tbd_qty
            else:
                if is_box_content:
                    q_alert = q_order
                else:
                    q_alert = offer_item.customer_alert_order_quantity
            if purchase is not None:
                purchase.set_comment(comment)
                if q_order <= q_alert:
                    if not REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS or purchase.quantity_confirmed <= q_order:
                        purchase.quantity_ordered = q_order
                        purchase.save()
770
                    else:
Patrick's avatar
Patrick committed
771 772 773 774 775 776 777 778 779 780 781
                        purchase.quantity_ordered = purchase.quantity_confirmed
                        purchase.save()
                else:
                    return purchase, False
            else:
                permanence = models.Permanence.objects.filter(id=offer_item.permanence_id) \
                    .only("permanence_date") \
                    .order_by('?').first()
                purchase = models.Purchase.objects.create(
                    permanence_id=offer_item.permanence_id,
                    permanence_date=permanence.permanence_date,
Patrick's avatar
Patrick committed
782
                    offer_item_id=offer_item.id,
Patrick's avatar
Patrick committed
783 784 785 786 787 788 789 790 791 792 793 794 795 796
                    producer_id=offer_item.producer_id,
                    customer_id=customer_id,
                    quantity_ordered=q_order,
                    quantity_invoiced=DECIMAL_ZERO,
                    is_box_content=is_box_content,
                    status=status,
                    comment=comment
                )
            return purchase, True
        else:
            return purchase, False

@transaction.atomic
def create_or_update_one_cart_item(customer, offer_item_id, q_order=None, value_id=None,
Patrick's avatar
Patrick committed
797 798
                                   is_basket=False, batch_job=False, comment=EMPTY_STRING):
    # from repanier.apps import REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS, REPANIER_SETTINGS_DISPLAY_PRODUCER_ON_ORDER_FORM
Patrick's avatar
Patrick committed
799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820
    to_json = []
    offer_item = models.OfferItem.objects.select_for_update(nowait=False) \
        .filter(id=offer_item_id, is_active=True, may_order=True) \
        .order_by('?').select_related("producer").first()
    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)
Patrick's avatar
Patrick committed
821
                else:
Patrick's avatar
Patrick committed
822 823 824 825 826
                    # 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
827
        is_box_updated = True
Patrick's avatar
Patrick committed
828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846
        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
            ).order_by('?').first()
            if purchase is not None:
                delta_q_order = q_order - purchase.quantity_ordered
            else:
                delta_q_order = q_order
            with transaction.atomic():
                sid = transaction.savepoint()
                # This code executes inside a transaction.
                for content in models.BoxContent.objects.filter(
                    box=offer_item.product_id
                ).only(
                    "product_id", "content_quantity"
                ).order_by('?'):
Patrick's avatar
Patrick committed
847 848 849 850
                    box_offer_item = models.OfferItem.objects.filter(
                        product_id=content.product_id,
                        permanence_id=offer_item.permanence_id
                    ).order_by('?').select_related("producer").first()
Patrick's avatar
Patrick committed
851 852 853 854 855 856 857 858 859 860 861 862 863
                    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
                        ).order_by('?').first()
                        if purchase is not None:
                            quantity_ordered = purchase.quantity_ordered + delta_q_order * content.content_quantity
                        else:
                            quantity_ordered = delta_q_order * content.content_quantity
                        if quantity_ordered < DECIMAL_ZERO:
                            quantity_ordered = DECIMAL_ZERO
Patrick's avatar
Patrick committed
864
                        purchase, is_box_updated = create_or_update_one_purchase(
Patrick's avatar
Patrick committed
865 866 867 868
                            customer.id, box_offer_item, q_order=quantity_ordered,
                            batch_job=batch_job, is_box_content=True
                        )
                    else:
Patrick's avatar
Patrick committed
869 870
                        is_box_updated = False
                    if not is_box_updated:
Patrick's avatar
Patrick committed
871
                        break
Patrick's avatar
Patrick committed
872
                if is_box_updated:
Patrick's avatar
Patrick committed
873 874 875
                    transaction.savepoint_commit(sid)
                else:
                    transaction.savepoint_rollback(sid)
Patrick's avatar
Patrick committed
876 877
        if not offer_item.is_box or is_box_updated:
            return create_or_update_one_purchase(
Patrick's avatar
Patrick committed
878 879 880 881 882 883 884 885 886 887
                customer.id, offer_item, q_order=q_order, batch_job=batch_job,
                is_box_content=False, comment=comment
            )
        elif not batch_job:
            # Select one purchase
            purchase = models.Purchase.objects.filter(
                customer_id=customer.id,
                offer_item_id=offer_item.id,
                is_box_content=False
            ).order_by('?').first()
Patrick's avatar
Patrick committed
888
            return purchase, False
Patrick's avatar
Patrick committed
889

Patrick's avatar
Patrick committed
890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943
        # if not batch_job:
        #     if purchase is None:
        #         if offer_item.is_box:
        #             sold_out = _("Sold out")
        #             option_dict = {
        #                 'id'  : "#offer_item%d" % offer_item.id,
        #                 'html': '<option value="0" selected>%s</option>' % sold_out
        #             }
        #         else:
        #             option_dict = display_selected_value(offer_item, DECIMAL_ZERO)
        #         to_json.append(option_dict)
        #     else:
        #         offer_item = models.OfferItem.objects.filter(id=offer_item_id).order_by('?').first()
        #         if offer_item is not None:
        #             option_dict = display_selected_value(offer_item, purchase.quantity_ordered)
        #             to_json.append(option_dict)
        #
        #     if REPANIER_SETTINGS_DISPLAY_PRODUCER_ON_ORDER_FORM:
        #         producer_invoice = models.ProducerInvoice.objects.filter(
        #             producer_id=offer_item.producer_id, permanence_id=offer_item.permanence_id
        #         ).only("total_price_with_tax").order_by('?').first()
        #         producer_invoice.get_order_json(to_json)
        #
        #     customer_invoice = models.CustomerInvoice.objects.filter(
        #         permanence_id=permanence_id,
        #         customer_id=customer.id
        #     ).order_by('?').first()
        #     permanence = models.Permanence.objects.filter(
        #         id=permanence_id
        #     ).only(
        #         "id", "with_delivery_point", "status"
        #     ).order_by('?').first()
        #     if customer_invoice is not None and permanence is not None:
        #         status_changed = customer_invoice.cancel_confirm_order()
        #         if REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS and status_changed:
        #             html = render_to_string(
        #                 'repanier/communication_confirm_order.html')
        #             option_dict = {'id': "#communication", 'html': html}
        #             to_json.append(option_dict)
        #         customer_invoice.save()
        #         my_basket(customer_invoice.is_order_confirm_send, customer_invoice.get_total_price_with_tax(),
        #                   to_json)
        #         if is_basket:
        #             basket_message = calc_basket_message(customer, permanence, PERMANENCE_OPENED)
        #         else:
        #             basket_message = EMPTY_STRING
        #         my_order_confirmation(
        #             permanence=permanence,
        #             customer_invoice=customer_invoice,
        #             is_basket=is_basket,
        #             basket_message=basket_message,
        #             to_json=to_json
        #         )
    # return json.dumps(to_json, cls=DjangoJSONEncoder)
Patrick's avatar
Patrick committed
944 945 946


def my_basket(is_order_confirm_send, order_amount, to_json):
Patrick's avatar
RC  
Patrick committed
947 948
    from repanier.apps import REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS

949 950 951 952 953 954 955 956 957 958 959 960
    if REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS and not is_order_confirm_send:
        if order_amount.amount <= DECIMAL_ZERO:
            msg_confirm = EMPTY_STRING
        else:
            msg_confirm = '<span class="glyphicon glyphicon-exclamation-sign"></span>&nbsp;<span class="glyphicon glyphicon-floppy-remove"></span>'
        msg_html = """
        {order_amount}&nbsp;&nbsp;&nbsp;
        {msg_confirm}
            """.format(
            order_amount=order_amount,
            msg_confirm=msg_confirm
        )
Patrick's avatar
Patrick committed
961
    else:
962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980
        if order_amount.amount <= DECIMAL_ZERO:
            msg_confirm = cart_content = EMPTY_STRING
        else:
            msg_confirm = '<span class="glyphicon glyphicon-ok"></span>'
            cart_content = '<div class="cart-line-3" style="background-color: #E5E9EA"></div>'
        msg_html = """
        <div class="icon-cart" style="float: left">
            <div class="cart-line-1" style="background-color: #fff"></div>
            <div class="cart-line-2" style="background-color: #fff"></div>
            {cart_content}
            <div class="cart-wheel" style="background-color: #fff"></div>
        </div> {order_amount}&nbsp;&nbsp;&nbsp;
        {msg_confirm}
            """.format(
            cart_content=cart_content,
            order_amount=order_amount,
            msg_confirm=msg_confirm
        )

Patrick's avatar
Patrick committed
981 982
    option_dict = {'id': "#my_basket", 'html': msg_html}
    to_json.append(option_dict)
983
    msg_html = """
Patrick's avatar
Patrick committed
984 985
    {order_amount}&nbsp;&nbsp;&nbsp;
    {msg_confirm}
986
        """.format(
Patrick's avatar
Patrick committed
987 988 989
            order_amount=order_amount,
            msg_confirm=msg_confirm
        )
Patrick's avatar
Patrick committed
990 991 992 993 994 995
    option_dict = {'id': "#prepared_amount_visible_xs", 'html': msg_html}
    to_json.append(option_dict)


def my_order_confirmation(permanence, customer_invoice, is_basket=False,
                          basket_message=EMPTY_STRING, to_json=None):
Patrick's avatar
RC  
Patrick committed
996 997
    from repanier.apps import REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS

Patrick's avatar
Patrick committed
998 999 1000 1001
    if permanence.with_delivery_point:
        if customer_invoice.delivery is not None:
            label = customer_invoice.delivery.get_delivery_customer_display()
            delivery_id = customer_invoice.delivery_id
1002
        else:
1003
            delivery_id = 0
Patrick's avatar
Patrick committed
1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
            if customer_invoice.customer.delivery_point is not None:
                qs = models.DeliveryBoard.objects.filter(
                    Q(
                        permanence_id=permanence.id,
                        delivery_point_id=customer_invoice.customer.delivery_point_id,
                        status=PERMANENCE_OPENED
                    ) | Q(
                        permanence_id=permanence.id,
                        delivery_point__closed_group=False,
                        status=PERMANENCE_OPENED
                    )
                ).order_by('?')
            else:
                qs = models.DeliveryBoard.objects.filter(
                    permanence_id=permanence.id,
                    delivery_point__closed_group=False,
                    status=PERMANENCE_OPENED
                ).order_by('?')
            if qs.exists():
                label = "%s" % _('Please, select a delivery point')
                models.CustomerInvoice.objects.filter(
                    permanence_id=permanence.id,
                    customer_id=customer_invoice.customer_id).order_by('?').update(
                    status=PERMANENCE_OPENED)
            else:
                label = "%s" % _('No delivery point is open for you. You can not place order.')
                # IMPORTANT :
                # 1 / This prohibit to place an order into the customer UI
                # 2 / task_order.close_send_order will delete any CLOSED orders without any delivery point
                models.CustomerInvoice.objects.filter(
                    permanence_id=permanence.id,
                    customer_id=customer_invoice.customer_id
                ).order_by('?').update(
                    status=PERMANENCE_CLOSED)
Patrick's avatar
Patrick committed
1038
        if customer_invoice.customer_id != customer_invoice.customer_charged_id:
Patrick's avatar
Patrick committed
1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088
            msg_price = msg_transport = EMPTY_STRING
        else:
            if customer_invoice.transport.amount <= DECIMAL_ZERO:
                transport = False
                msg_transport = EMPTY_STRING
            else:
                transport = True
                if customer_invoice.min_transport.amount > DECIMAL_ZERO:
                    msg_transport = "%s<br/>" % \
                                    _(
                                        'The shipping costs for this delivery point amount to %(transport)s for orders of less than %(min_transport)s.') % {
                                        'transport'    : customer_invoice.transport,
                                        'min_transport': customer_invoice.min_transport
                                    }
                else:
                    msg_transport = "%s<br/>" % \
                                    _(
                                        'The shipping costs for this delivery point amount to %(transport)s.') % {
                                        'transport': customer_invoice.transport,
                                    }
            if customer_invoice.price_list_multiplier == DECIMAL_ONE:
                msg_price = EMPTY_STRING
            else:
                if transport:
                    if customer_invoice.price_list_multiplier > DECIMAL_ONE:
                        msg_price = "%s<br/>" % \
                                        _(
                                            'A price increase of %(increase)s %% of the total invoiced is due for this delivery point. This does not apply to the cost of transport which is fixed.') % {
                                            'increase'    : number_format((customer_invoice.price_list_multiplier - DECIMAL_ONE) * 100, 2)
                                        }
                    else:
                        msg_price = "%s<br/>" % \
                                        _(
                                            'A price decrease of %(decrease)s %% of the total invoiced is given for this delivery point. This does not apply to the cost of transport which is fixed.') % {
                                            'decrease': number_format((DECIMAL_ONE - customer_invoice.price_list_multiplier) * 100, 2)
                                        }
                else:
                    if customer_invoice.price_list_multiplier > DECIMAL_ONE:
                        msg_price = "%s<br/>" % \
                                        _(
                                            'A price increase of %(increase)s %% of the total invoiced is due for this delivery point.') % {
                                            'increase'    : number_format((customer_invoice.price_list_multiplier - DECIMAL_ONE) * 100, 2)
                                        }
                    else:
                        msg_price = "%s<br/>" % \
                                        _(
                                            'A price decrease of %(decrease)s %% of the total invoiced is given for this delivery point.') % {
                                            'decrease': number_format((DECIMAL_ONE - customer_invoice.price_list_multiplier) * 100, 2)
                                        }

Patrick's avatar
Patrick committed
1089
        msg_delivery = '%s<b><i><select name="delivery" id="delivery" onmouseover="show_select_delivery_list_ajax(%d)" onchange="delivery_ajax(%d)" class="form-control"><option value="%d" selected>%s</option></select></i></b><br/>%s%s' % (
Patrick's avatar
Patrick committed
1090 1091
            _("Delivery point"),
            delivery_id,
Patrick's avatar
Patrick committed
1092 1093
            delivery_id,
            delivery_id,
Patrick's avatar
Patrick committed
1094 1095 1096 1097 1098 1099
            label,
            msg_transport,
            msg_price
        )
    else:
        msg_delivery = EMPTY_STRING
1100
    msg_confirmation1 = EMPTY_STRING
Patrick's avatar
RC  
Patrick committed
1101
    if not is_basket and not REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS:
Patrick's avatar
Patrick committed
1102 1103 1104
        # or customer_invoice.total_price_with_tax.amount != DECIMAL_ZERO:
        # If apps.REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS is True,
        # then permanence.with_delivery_point is also True
1105
        msg_html = EMPTY_STRING
Patrick's avatar
Patrick committed
1106 1107
    else:
        if customer_invoice.is_order_confirm_send:
1108
            msg_confirmation2 = my_order_confirmation_email_send_to(customer_invoice.customer)
Patrick's avatar
Patrick committed
1109 1110 1111 1112 1113
            msg_html = """
            <div class="row">
            <div class="panel panel-default">
            <div class="panel-heading">
            %s
1114
            <p><font color="#51a351">%s</font><p/>
Patrick's avatar
Patrick committed
1115 1116 1117 1118
            %s
            </div>
            </div>
            </div>
1119
             """ % (msg_delivery, msg_confirmation2, basket_message)
Patrick's avatar
Patrick committed
1120 1121
        else:
            msg_html = None
1122
            btn_disabled = EMPTY_STRING if permanence.status == PERMANENCE_OPENED else "disabled"
1123
            msg_confirmation2 = EMPTY_STRING
Patrick's avatar
RC  
Patrick committed
1124
            if REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS:
Patrick's avatar
Patrick committed
1125
                if is_basket:
1126
                    if customer_invoice.status == PERMANENCE_OPENED:
1127 1128
                        if (permanence.with_delivery_point and customer_invoice.delivery is None) \
                                or customer_invoice.total_price_with_tax == DECIMAL_ZERO:
1129
                            btn_disabled = "disabled"
1130 1131
                        msg_confirmation1 = '<font color="red">%s</font><br/>' % _("An unconfirmed order will be canceled.")
                        msg_confirmation2 = '<span class="glyphicon glyphicon-floppy-disk"></span>&nbsp;&nbsp;%s' % _("Confirm this order and receive an email containing its summary.")
1132
                else:
Patrick's avatar
Patrick committed
1133
                    href = urlresolvers.reverse(
1134
                        'order_view', args=(permanence.id,)
Patrick's avatar
Patrick committed
1135
                    )
Patrick's avatar
Patrick committed
1136 1137 1138 1139 1140 1141 1142 1143 1144
                    if customer_invoice.status == PERMANENCE_OPENED:
                        msg_confirmation1 = '<font color="red">%s</font><br/>' % _("An unconfirmed order will be canceled.")
                        msg_confirmation2 = _("Verify my order content before validating it.")
                        msg_html = """
                            <div class="row">
                            <div class="panel panel-default">
                            <div class="panel-heading">
                            %s
                            %s
1145
                            <a href="%s?is_basket=yes" class="btn btn-info" %s>%s</a>
Patrick's avatar
Patrick committed
1146 1147 1148 1149
                            </div>
                            </div>
                            </div>
                             """ % (msg_delivery, msg_confirmation1, href, btn_disabled, msg_confirmation2)
Patrick's avatar
Patrick committed
1150 1151
            else:
                if is_basket:
1152
                    msg_confirmation2 = _("Receive an email containing this order summary.")
Patrick's avatar
Patrick committed
1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165
                elif permanence.with_delivery_point:
                    msg_html = """
                        <div class="row">
                        <div class="panel panel-default">
                        <div class="panel-heading">
                        %s
                        </div>
                        </div>
                        </div>
                         """ % msg_delivery
                else:
                    msg_html = EMPTY_STRING
            if msg_html is None:
1166
                if msg_confirmation2 == EMPTY_STRING:
1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183
                    msg_html = """
                    <div class="row">
                    <div class="panel panel-default">
                    <div class="panel-heading">
                    %s
                    <div class="clearfix"></div>
                    %s
                    </div>
                    </div>
                    </div>
                     """ % (msg_delivery, basket_message)
                else:
                    msg_html = """
                    <div class="row">
                    <div class="panel panel-default">
                    <div class="panel-heading">
                    %s
1184
                    %s
1185 1186 1187 1188 1189 1190
                    <button id="btn_confirm_order" class="btn btn-info" %s onclick="btn_receive_order_email();">%s</button>
                    <div class="clearfix"></div>
                    %s
                    </div>
                    </div>
                    </div>
1191
                     """ % (msg_delivery, msg_confirmation1, btn_disabled, msg_confirmation2, basket_message)
Patrick's avatar
Patrick committed
1192 1193 1194
    if to_json is not None:
        option_dict = {'id': "#span_btn_confirm_order", 'html': msg_html}
        to_json.append(option_dict)
Patrick's avatar
Patrick committed
1195 1196


Patrick's avatar
Patrick committed
1197
def my_order_confirmation_email_send_to(customer):
Patrick's avatar
RC  
Patrick committed
1198 1199
    from repanier.apps import REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS

1200
    if customer is not None and customer.email2:
Patrick's avatar
Patrick committed
1201 1202 1203 1204
        to_email = (customer.user.email, customer.email2)
    else:
        to_email = (customer.user.email,)
    sent_to = ", ".join(to_email) if to_email is not None else EMPTY_STRING
Patrick's avatar
RC  
Patrick committed
1205
    if REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS:
Patrick's avatar
Patrick committed
1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216
        msg_confirmation = _(
            "Your order is confirmed. An email containing this order summary has been sent to %s.") % sent_to
    else:
        msg_confirmation = _("An email containing this order summary has been sent to %s.") % sent_to
    return msg_confirmation


def clean_offer_item(permanence, queryset, reset_add_2_stock=False):
    if permanence.status > PERMANENCE_SEND:
        # The purchases are already invoiced.
        # The offer item may not be modified any more
1217
        raise ValueError("Not offer item may be created when permanece status > PERMANENCE_SEND")
Patrick's avatar
Patrick committed
1218
    getcontext().rounding = ROUND_HALF_UP
Patrick's avatar
Patrick committed
1219
    for offer_item in queryset.select_related("producer", "product"):
Patrick's avatar
Patrick committed
1220 1221
        product = offer_item.product
        producer = offer_item.producer
1222 1223 1224

        offer_item.set_from(product)

Patrick's avatar
Patrick committed
1225 1226
        offer_item.producer_pre_opening = producer.producer_pre_opening
        offer_item.manage_production = producer.manage_production
1227
        # Those offer_items not subjects to price modifications
1228
        offer_item.is_resale_price_fixed = producer.is_resale_price_fixed or product.is_box or product.order_unit >= PRODUCT_ORDER_UNIT_DEPOSIT
Patrick's avatar
Patrick committed
1229
        offer_item.price_list_multiplier = DECIMAL_ONE if offer_item.is_resale_price_fixed else producer.price_list_multiplier
1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244

        offer_item.may_order = False
        offer_item.manage_replenishment = False
        if offer_item.is_active:
            if offer_item.order_unit < PRODUCT_ORDER_UNIT_DEPOSIT:
                offer_item.may_order = product.is_into_offer
                offer_item.manage_replenishment = producer.manage_replenishment

        # The group must pay the VAT, so it's easier to allways have
        # offer_item with VAT included
        if producer.producer_price_are_wo_vat:
            offer_item.producer_unit_price += offer_item.producer_vat
        offer_item.producer_price_are_wo_vat = False


Patrick's avatar
Patrick committed
1245
        if reset_add_2_stock:
Patrick's avatar
Patrick committed
1246
            offer_item.add_2_stock = DECIMAL_ZERO
1247

Patrick's avatar
Patrick committed
1248 1249 1250
        offer_item.save()

    # Now got everything to calculate the sort order of the order display screen
Patrick's avatar
Patrick committed
1251
    cur_language = translation.get_language()
Patrick's avatar
Patrick committed
1252 1253
    for language in settings.PARLER_LANGUAGES[settings.SITE_ID]:
        translation.activate(language["code"])
Patrick's avatar
Patrick committed
1254
        for offer_item in queryset.select_related("producer", "department_for_customer"):
Patrick's avatar
Patrick committed
1255
            offer_item.long_name = offer_item.product.long_name
Patrick Colmant's avatar
Patrick Colmant committed
1256 1257 1258 1259
            offer_item.cache_part_a = render_to_string('repanier/cache_part_a.html',
                                                       {'offer': offer_item, 'MEDIA_URL': settings.MEDIA_URL})
            offer_item.cache_part_b = render_to_string('repanier/cache_part_b.html',
                                                       {'offer': offer_item, 'MEDIA_URL': settings.MEDIA_URL})
Patrick's avatar
Patrick committed
1260 1261 1262
            offer_item.save_translations()

    translation.activate(cur_language)
Patrick's avatar
Patrick committed
1263 1264


Patrick's avatar
Patrick committed
1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280
def reorder_purchases(permanence_id):
    # Order the purchases such that lower quantity are before larger quantity
    models.Purchase.objects.filter(
        permanence_id=permanence_id
    ).update(
        quantity_for_preparation_sort_order=DECIMAL_ZERO
    )
    models.Purchase.objects.filter(
        permanence_id=permanence_id,
        offer_item__wrapped=False,
        offer_item__order_unit__in=[PRODUCT_ORDER_UNIT_KG, PRODUCT_ORDER_UNIT_PC_KG]
    ).update(
        quantity_for_preparation_sort_order=F('quantity_invoiced')
    )


Patrick's avatar
Patrick committed
1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293
def reorder_offer_items(permanence_id):
    # calculate the sort order of the order display screen
    cur_language = translation.get_language()
    offer_item_qs = models.OfferItem.objects.filter(permanence_id=permanence_id).order_by('?')
    for language in settings.PARLER_LANGUAGES[settings.SITE_ID]:
        language_code = language["code"]
        translation.activate(language_code)
        # customer order lists sort order
        i = 0
        reorder_queryset = offer_item_qs.filter(
            is_box=False,
            translations__language_code=language_code
        ).order_by(
1294
            "department_for_customer",