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

import calendar
5
import datetime
Patrick's avatar
Patrick committed
6 7
import json
import time
Patrick's avatar
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 25
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
Patrick's avatar
Patrick committed
26
from django.db.models import Q
27
from django.http import Http404
Patrick's avatar
Patrick committed
28
from django.template.loader import render_to_string
Patrick's avatar
Patrick committed
29
from django.utils import timezone
Patrick's avatar
Patrick committed
30
from django.utils import translation
pi's avatar
pi committed
31
from django.utils.formats import number_format
Patrick's avatar
Patrick committed
32 33 34
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
35
from six import string_types
Patrick's avatar
Patrick committed
36

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


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
57

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

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

Patrick's avatar
Patrick committed
67 68
    return decorator

Patrick's avatar
Patrick committed
69

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


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


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


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


128 129 130 131 132
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]
133
        customer = models.Customer.objects.filter(user__email=email_to, subscribe_to_email=True).exclude(valid_email=False).only('id').order_by('?').first()
134
        if customer is None:
135 136
            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
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 182
    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:
183 184 185 186
                # 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
187 188 189 190 191


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


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


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

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

Patrick's avatar
Patrick committed
264

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

295

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

306

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

319

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


330 331 332 333
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
334
        if permanence.status in [PERMANENCE_INVOICED, PERMANENCE_ARCHIVED]:
335
            if permanence.permanence_date < (
Patrick's avatar
Patrick committed
336
                        timezone.now() - datetime.timedelta(weeks=LIMIT_DISPLAYED_PERMANENCE)
337 338 339 340 341 342
            ).date():
                raise Http404
        else:
            raise Http404


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


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


Patrick's avatar
Patrick committed
381 382 383 384
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
    ):
385
        if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
386
            base_unit = EMPTY_STRING
387 388 389 390
        else:
            base_unit = _('kg')
    elif order_unit == PRODUCT_ORDER_UNIT_LT:
        if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
391
            base_unit = EMPTY_STRING
392 393 394 395
        else:
            base_unit = _('l')
    else:
        if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
396
            base_unit = EMPTY_STRING
397 398 399 400 401 402 403
        elif qty < 2:
            base_unit = _('piece')
        else:
            base_unit = _('pieces')
    return base_unit


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


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

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

Patrick's avatar
Patrick committed
563 564 565 566 567 568 569 570
    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
571
        else:
Patrick's avatar
Patrick committed
572 573 574 575 576 577 578 579
            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
580

Patrick's avatar
Patrick committed
581
    if customer_invoice.customer_id != customer_invoice.customer_charged_id:
Patrick's avatar
Patrick committed
582
        customer_on_hold_movement = EMPTY_STRING
Patrick's avatar
Patrick committed
583 584 585
        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
586
            }
Patrick's avatar
Patrick committed
587 588 589 590 591 592 593 594 595
        )
    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
596 597 598 599
            payment_needed = - (customer.balance - order_not_invoiced + bank_not_invoiced)
        else:
            payment_needed = total_price_with_tax

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

617
            else:
Patrick's avatar
Patrick committed
618 619 620 621 622 623
                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
624 625 626 627

    return customer_last_balance, customer_on_hold_movement, customer_payment_needed, customer_order_amount


628
def display_selected_value(offer_item, quantity_ordered):
Patrick's avatar
Patrick committed
629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
    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,
Patrick's avatar
Patrick committed
673
                'html': '<option value="%d" selected>%s</option>' % (quantity_ordered, display)
Patrick's avatar
Patrick committed
674 675 676 677
            }
    else:
        option_dict = {
            'id'  : "#box_offer_item%d" % offer_item.id,
Patrick's avatar
Patrick committed
678
            'html': EMPTY_STRING
Patrick's avatar
Patrick committed
679 680 681 682
        }
    return option_dict


Patrick's avatar
Patrick committed
683 684
def display_selected_box_value(offer_item, box_purchase):
    if box_purchase.is_box_content:
Patrick's avatar
Patrick committed
685 686
        box_name = BOX_UNICODE
        # Select one purchase
687
        if box_purchase is not None:
Patrick's avatar
Patrick committed
688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718
            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
719 720


Patrick's avatar
Patrick committed
721 722 723 724
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
Patrick committed
725
    from repanier.apps import REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS
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 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767
    # 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
768
                else:
Patrick's avatar
Patrick committed
769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794
                    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()
795
                    else:
Patrick's avatar
Patrick committed
796 797 798 799 800 801 802 803 804 805 806
                        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
807
                    offer_item_id=offer_item.id,
Patrick's avatar
Patrick committed
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
                    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,
                                   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
    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
846
                else:
Patrick's avatar
Patrick committed
847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904
                    # 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
        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
            ).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('?'):
                    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
                        ).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
                        purchase, updated = create_or_update_one_purchase(
                            customer.id, box_offer_item, q_order=quantity_ordered,
                            batch_job=batch_job, is_box_content=True
                        )
                    else:
                        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()
Patrick's avatar
Patrick committed
905 906 907 908 909
                        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,
Patrick's avatar
Patrick committed
910
                                is_box_content=False
911
                            ).order_by('?').first()
Patrick's avatar
Patrick committed
912 913 914
                            option_dict = display_selected_value(
                                box_offer_item,
                                purchase.quantity_ordered if purchase is not None else DECIMAL_ZERO
Patrick's avatar
Patrick committed
915
                            )
Patrick's avatar
Patrick committed
916 917 918 919 920
                            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
Patrick's avatar
Patrick committed
921
                            ).order_by('?').first()
Patrick's avatar
Patrick committed
922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977
                            option_dict = display_selected_box_value(box_offer_item, box_purchase)
                            to_json.append(option_dict)
                    transaction.savepoint_commit(sid)
                else:
                    transaction.savepoint_rollback(sid)
        if not offer_item.is_box or updated:
            purchase, updated = create_or_update_one_purchase(
                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()

        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}
Patrick's avatar
Patrick committed
978
                    to_json.append(option_dict)
Patrick's avatar
Patrick committed
979 980 981 982 983
                customer_invoice.save()
                my_basket(customer_invoice.is_order_confirm_send, customer_invoice.get_total_price_with_tax(),
                          to_json)
                if basket:
                    basket_message = calc_basket_message(customer, permanence, PERMANENCE_OPENED)
Patrick's avatar
Patrick committed
984
                else:
Patrick's avatar
Patrick committed
985 986 987 988 989 990 991 992
                    basket_message = EMPTY_STRING
                my_order_confirmation(
                    permanence=permanence,
                    customer_invoice=customer_invoice,
                    is_basket=basket,
                    basket_message=basket_message,
                    to_json=to_json
                )
Patrick's avatar
Patrick committed
993 994 995 996
    return json.dumps(to_json, cls=DjangoJSONEncoder)


def my_basket(is_order_confirm_send, order_amount, to_json):
Patrick's avatar
Patrick committed
997 998 999
    from repanier.apps import REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS

    if not is_order_confirm_send and REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS:
Patrick's avatar
Patrick committed
1000 1001
        msg_html = '<span class="glyphicon glyphicon-shopping-cart"></span> %s&nbsp;&nbsp;&nbsp;<span class="glyphicon glyphicon-exclamation-sign"></span>&nbsp;<span class="glyphicon glyphicon-floppy-remove"></span></a>' % (
            order_amount,)
Patrick's avatar
Patrick committed
1002
    else:
Patrick's avatar
Patrick committed
1003
        msg_html = '<span class="glyphicon glyphicon-shopping-cart"></span> %s&nbsp;&nbsp;&nbsp;<span class="glyphicon glyphicon-ok"></span></a>' % (
Patrick's avatar
Patrick committed
1004 1005 1006 1007 1008 1009 1010 1011 1012
        order_amount,)
    option_dict = {'id': "#my_basket", 'html': msg_html}
    to_json.append(option_dict)
    option_dict = {'id': "#prepared_amount_visible_xs", 'html': msg_html}
    to_json.append(option_dict)


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

Patrick's avatar
Patrick committed
1015 1016 1017 1018
    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
1019
        else:
Patrick's avatar
Patrick committed
1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054
            delivery_id = -1
            if customer_invoice.customer.delivery_point is not None:
                qs = models.DeliveryBoard.objects.filter(
                    Q(
                        permanence_id=permanence.id,
                        delivery_point_id=customer_invoice.customer.delivery_point_id,
                        status=PERMANENCE_OPENED
                    ) | Q(
                        permanence_id=permanence.id,
                        delivery_point__closed_group=False,
                        status=PERMANENCE_OPENED
                    )
                ).order_by('?')
            else:
                qs = models.DeliveryBoard.objects.filter(
                    permanence_id=permanence.id,
                    delivery_point__closed_group=False,
                    status=PERMANENCE_OPENED
                ).order_by('?')
            if qs.exists():
                label = "%s" % _('Please, select a delivery point')
                models.CustomerInvoice.objects.filter(
                    permanence_id=permanence.id,
                    customer_id=customer_invoice.customer_id).order_by('?').update(
                    status=PERMANENCE_OPENED)
            else:
                label = "%s" % _('No delivery point is open for you. You can not place order.')
                # IMPORTANT :
                # 1 / This prohibit to place an order into the customer UI
                # 2 / task_order.close_send_order will delete any CLOSED orders without any delivery point
                models.CustomerInvoice.objects.filter(
                    permanence_id=permanence.id,
                    customer_id=customer_invoice.customer_id
                ).order_by('?').update(
                    status=PERMANENCE_CLOSED)
Patrick's avatar
Patrick committed
1055
        if customer_invoice.customer_id != customer_invoice.customer_charged_id:
Patrick's avatar
Patrick committed
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 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114
            msg_price = msg_transport = EMPTY_STRING
        else:
            if customer_invoice.transport.amount <= DECIMAL_ZERO:
                transport = False
                msg_transport = EMPTY_STRING
            else:
                transport = True
                if customer_invoice.min_transport.amount > DECIMAL_ZERO:
                    msg_transport = "%s<br/>" % \
                                    _(
                                        'The shipping costs for this delivery point amount to %(transport)s for orders of less than %(min_transport)s.') % {
                                        'transport'    : customer_invoice.transport,
                                        'min_transport': customer_invoice.min_transport
                                    }
                else:
                    msg_transport = "%s<br/>" % \
                                    _(
                                        'The shipping costs for this delivery point amount to %(transport)s.') % {
                                        'transport': customer_invoice.transport,
                                    }
            if customer_invoice.price_list_multiplier == DECIMAL_ONE:
                msg_price = EMPTY_STRING
            else:
                if transport:
                    if customer_invoice.price_list_multiplier > DECIMAL_ONE:
                        msg_price = "%s<br/>" % \
                                        _(
                                            'A price increase of %(increase)s %% of the total invoiced is due for this delivery point. This does not apply to the cost of transport which is fixed.') % {
                                            'increase'    : number_format((customer_invoice.price_list_multiplier - DECIMAL_ONE) * 100, 2)
                                        }
                    else:
                        msg_price = "%s<br/>" % \
                                        _(
                                            'A price decrease of %(decrease)s %% of the total invoiced is given for this delivery point. This does not apply to the cost of transport which is fixed.') % {
                                            'decrease': number_format((DECIMAL_ONE - customer_invoice.price_list_multiplier) * 100, 2)
                                        }
                else:
                    if customer_invoice.price_list_multiplier > DECIMAL_ONE:
                        msg_price = "%s<br/>" % \
                                        _(
                                            'A price increase of %(increase)s %% of the total invoiced is due for this delivery point.') % {
                                            'increase'    : number_format((customer_invoice.price_list_multiplier - DECIMAL_ONE) * 100, 2)
                                        }
                    else:
                        msg_price = "%s<br/>" % \
                                        _(
                                            'A price decrease of %(decrease)s %% of the total invoiced is given for this delivery point.') % {
                                            'decrease': number_format((DECIMAL_ONE - customer_invoice.price_list_multiplier) * 100, 2)
                                        }

        msg_delivery = '%s<b><i><select name="delivery" id="delivery" onmouseover="delivery_select_ajax()" onchange="delivery_ajax()" class="form-control"><option value="%d" selected>%s</option></select></i></b><br/>%s%s' % (
            _("Delivery point"),
            delivery_id,
            label,
            msg_transport,
            msg_price
        )
    else:
        msg_delivery = EMPTY_STRING
1115
    msg_confirmation1 = EMPTY_STRING
Patrick's avatar
Patrick committed
1116
    if not is_basket and not REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS:
Patrick's avatar
Patrick committed
1117 1118 1119
        # 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
1120
        msg_html = EMPTY_STRING
Patrick's avatar
Patrick committed
1121 1122
    else:
        if customer_invoice.is_order_confirm_send:
1123
            msg_confirmation2 = my_order_confirmation_email_send_to(customer_invoice.customer)
Patrick's avatar
Patrick committed
1124 1125 1126 1127 1128
            msg_html = """
            <div class="row">
            <div class="panel panel-default">
            <div class="panel-heading">
            %s
1129
            <p><font color="#51a351">%s</font><p/>
Patrick's avatar
Patrick committed
1130 1131 1132 1133
            %s
            </div>
            </div>
            </div>
1134
             """ % (msg_delivery, msg_confirmation2, basket_message)
Patrick's avatar
Patrick committed
1135 1136
        else:
            msg_html = None
1137
            btn_disabled = EMPTY_STRING if permanence.status == PERMANENCE_OPENED else "disabled"
1138
            msg_confirmation2 = EMPTY_STRING
Patrick's avatar
Patrick committed
1139
            if REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS:
Patrick's avatar
Patrick committed
1140
                if is_basket:
1141
                    if customer_invoice.status == PERMANENCE_OPENED:
1142 1143
                        if (permanence.with_delivery_point and customer_invoice.delivery is None) \
                                or customer_invoice.total_price_with_tax == DECIMAL_ZERO:
1144
                            btn_disabled = "disabled"
1145 1146
                        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.")
1147
                else:
Patrick's avatar
Patrick committed
1148 1149 1150
                    href = urlresolvers.reverse(
                        'basket_view', args=(permanence.id,)
                    )
Patrick's avatar
Patrick committed
1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164
                    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
                            <a href="%s" class="btn btn-info" %s>%s</a>
                            </div>
                            </div>
                            </div>
                             """ % (msg_delivery, msg_confirmation1, href, btn_disabled, msg_confirmation2)
Patrick's avatar
Patrick committed
1165 1166
            else:
                if is_basket:
1167
                    msg_confirmation2 = _("Receive an email containing this order summary.")
Patrick's avatar
Patrick committed
1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180
                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:
1181
                if msg_confirmation2 == EMPTY_STRING:
1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198
                    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
1199
                    %s
1200 1201 1202 1203 1204 1205
                    <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>
1206
                     """ % (msg_delivery, msg_confirmation1, btn_disabled, msg_confirmation2, basket_message)
Patrick's avatar
Patrick committed
1207 1208 1209
    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
1210 1211


Patrick's avatar
Patrick committed
1212
def my_order_confirmation_email_send_to(customer):
Patrick's avatar
Patrick committed
1213 1214
    from repanier.apps import REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS

1215
    if customer is not None and customer.email2:
Patrick's avatar
Patrick committed
1216 1217 1218 1219
        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
Patrick committed
1220
    if REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS:
Patrick's avatar
Patrick committed
1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233
        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
        return
    getcontext().rounding = ROUND_HALF_UP
Patrick's avatar
Patrick committed
1234
    for offer_item in queryset.select_related("producer", "product"):
Patrick's avatar
Patrick committed
1235 1236
        product = offer_item.product
        producer = offer_item.producer
1237 1238
        if product.order_unit < PRODUCT_ORDER_UNIT_DEPOSIT:
            offer_item.is_active = product.is_into_offer
Patrick's avatar
Patrick committed
1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270
        offer_item.picture2 = product.picture2
        offer_item.reference = product.reference
        offer_item.department_for_customer_id = product.department_for_customer_id
        offer_item.producer_id = product.producer_id
        offer_item.order_unit = product.order_unit
        offer_item.wrapped = product.wrapped
        offer_item.order_average_weight = product.order_average_weight
        offer_item.placement = product.placement
        offer_item.producer_vat = product.producer_vat
        offer_item.customer_vat = product.customer_vat
        # Important : 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 = product.producer_unit_price + offer_item.producer_vat
            offer_item.producer_price_are_wo_vat = False
        else:
            offer_item.producer_unit_price = product.producer_unit_price
            offer_item.producer_price_are_wo_vat = producer.producer_price_are_wo_vat
        offer_item.customer_unit_price = product.customer_unit_price
        # Important : for purchasing : the price is * by order_average_weight
        # Thus, the unit deposit must be Zero.
        offer_item.unit_deposit = DECIMAL_ZERO if product.order_unit == PRODUCT_ORDER_UNIT_PC_KG else product.unit_deposit
        offer_item.vat_level = product.vat_level
        offer_item.limit_order_quantity_to_stock = product.limit_order_quantity_to_stock
        offer_item.producer_pre_opening = producer.producer_pre_opening
        if offer_item.order_unit < PRODUCT_ORDER_UNIT_DEPOSIT:
            offer_item.manage_replenishment = producer.manage_replenishment
        else:
            offer_item.manage_replenishment = False
        offer_item.manage_production = producer.manage_production
        # Important : or product.is_box -> impact into Purchase.get_customer_unit_price
        # The boxes prices are not subjects to price modifications
1271
        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
1272 1273 1274
        offer_item.price_list_multiplier = DECIMAL_ONE if offer_item.is_resale_price_fixed else producer.price_list_multiplier
        offer_item.stock = product.stock
        if reset_add_2_stock:
Patrick's avatar
Patrick committed
1275
            offer_item.add_2_stock = DECIMAL_ZERO
Patrick's avatar
Patrick committed
1276 1277 1278 1279 1280
        offer_item.customer_minimum_order_quantity = product.customer_minimum_order_quantity
        offer_item.customer_increment_order_quantity = product.customer_increment_order_quantity
        offer_item.customer_alert_order_quantity = product.customer_alert_order_quantity
        offer_item.producer_order_by_quantity = product.producer_order_by_quantity
        offer_item.is_box = product.is_box
Patrick's avatar
Patrick committed
1281
        # offer_item.is_membership_fee = product.is_membership_fee
Patrick's avatar
Patrick committed
1282 1283 1284
        offer_item.save()

    # Now got everything to calculate the sort order of the order display screen
Patrick's avatar
Patrick committed
1285
    cur_language = translation.get_language()
Patrick's avatar
Patrick committed
1286 1287
    for language in settings.PARLER_LANGUAGES[settings.SITE_ID]:
        translation.activate(language["code"])
Patrick's avatar
Patrick committed
1288
        for offer_item in queryset.select_related("producer", "department_for_customer"):
Patrick's avatar
Patrick committed
1289
            offer_item.long_name = offer_item.product.long_name
Patrick Colmant's avatar
Patrick Colmant committed
1290 1291 1292 1293 1294 1295
            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})
            offer_item.cache_part_e = render_to_string('repanier/cache_part_e.html',
                                                       {'offer': offer_item, 'MEDIA_URL': settings.MEDIA_URL})
Patrick's avatar
Patrick committed
1296 1297 1298
            offer_item.save_translations()

        departementforcustomer_set = models.LUT_DepartmentForCustomer.objects.filter(
Patrick's avatar
Patrick committed
1299
            offeritem__permanence_id=permanence.id,
Patrick's avatar
Patrick committed
1300
            offeritem__may_order=True) \
Patrick's avatar
Patrick committed
1301
            .order_by("tree_id", "lft") \
Patrick's avatar
Patrick committed
1302 1303 1304
            .distinct("id", "tree_id", "lft")
        if departementforcustomer_set:
            pass
1305
        if apps.REPANIER_SETTINGS_DISPLAY_PRODUCER_ON_ORDER_FORM:
Patrick's avatar
Patrick committed
1306 1307 1308 1309
            producer_set = models.Producer.objects.filter(permanence=permanence.id).only("id", "short_profile_name")
        else:
            producer_set = None
        permanence.cache_part_d = render_to_string('repanier/cache_part_d.html',
Patrick's avatar
Patrick committed
1310 1311
                                                   {'producer_set'              : producer_set,
                                                    'departementforcustomer_set': departementforcustomer_set})
Patrick's avatar
Patrick committed
1312 1313
        permanence.save_translations()
    translation.activate(cur_language)
Patrick's avatar
Patrick committed
1314 1315


Patrick's avatar
Patrick committed
1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331
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
1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344
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(
1345
            "department_for_customer",
Patrick's avatar
Patrick committed
1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361
            "translations__long_name",
            "order_average_weight",
            "producer__short_profile_name"
        )
        for offer_item in reorder_queryset:
            offer_item.producer_sort_order = offer_item.order_sort_order = i
            offer_item.save_translations()
            if i < 9999:
                i += 1
        # producer lists sort order : sort by reference if needed, otherwise sort by order_sort_order
        i = 9999
        reorder_queryset = offer_item_qs.filter(
            is_box=False,
            producer__sort_products_by_reference=True,
            translations__language_code=language_code
        ).order_by(
1362
            "department_for_customer",
Patrick's avatar
Patrick committed
1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375
            "reference"
        )
        for offer_item in reorder_queryset:
            offer_item.producer_sort_order = i
            offer_item.save_translations()
            if i < 19999:
                i += 1
        # preparation lists sort order
        i = 0
        reorder_queryset = offer_item_qs.filter(
            is_box=False,
            translations__language_code=language_code
        ).order_by(
1376
            "department_for_customer",
Patrick's avatar
Patrick committed
1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425
            # "department_for_customer__lft",
            "translations__long_name",
            "order_average_weight",
            "producer__short_profile_name"
        )
        for offer_item in reorder_queryset:
            offer_item.preparation_sort_order = i
            offer_item.save_translations()
            if i < 9999:
                i += 1
        i = -9999
        reorder_queryset = offer_item_qs.filter(
            is_box=True,
            translations__language_code=language_code
        ).order_by(
            "customer_unit_price",
            "department_for_customer__lft",
            "unit_deposit",
            "translations__long_name"
        )
        # 'TranslatableQuerySet' object has no attribute 'desc'
        for offer_item in reorder_queryset:
            # display box on top
            offer_item.order_sort_order = i
            offer_item.producer_sort_order = i
            offer_item.preparation_sort_order = i
            offer_item.save_translations()
            if i < -1:
                i += 1
    translation.activate(cur_language)


def update_offer_item(product_id=None, producer_id=None):
    # Important : If the user want to modify the price of a product PERMANENCE_SEND
    # Then he can do it via "rule_of_3_per_product"
    for permanence in models.Permanence.objects.filter(
            status__in=[PERMANENCE_PRE_OPEN, PERMANENCE_OPENED, PERMANENCE_CLOSED]
    ):
        if producer_id is None:
            offer_item_qs = models.OfferItem.objects.filter(
                permanence_id=permanence.id,
                product_id=product_id,
            ).order_by('?')
        else:
            offer_item_qs = models.OfferItem.objects.filter(
                permanence_id=permanence.id,
                producer_id=producer_id,
            ).order_by('?')
        clean_offer_item(permanence, offer_item_qs)
Patrick's avatar
Patrick committed
1426
        permanence.recalculate_order_amount(offer_item_qs=offer_item_qs)
Patrick's avatar
Patrick committed
1427 1428 1429
    cache.clear()


Patrick's avatar
Patrick committed
1430
@transaction.atomic()
1431
def get_or_create_offer_item(permanence, product):
Patrick's avatar
Patrick committed
1432 1433
    offer_item_qs = models.OfferItem.objects.filter(
        permanence_id=permanence.id,
1434
        product_id=product.id,
Patrick's avatar
Patrick committed
1435 1436 1437
    ).order_by('?')
    if not offer_item_qs.exists():
        models.OfferItem.objects.create(
1438 1439 1440
            permanence=permanence,
            product=product,
            producer=product.producer,
Patrick's avatar
Patrick committed
1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453
            is_active=False,
            may_order=False
        )
        clean_offer_item(permanence, offer_item_qs)
    offer_item = offer_item_qs.first()
    return offer_item


def producer_web_services_activated(reference_site=None):
    web_services_activated = False
    web_service_version = None
    if reference_site is not None and len(reference_site) > 0:
        try:
Patrick's avatar
Patrick committed
1454
            web_services = urlopen(
Patrick's avatar
Patrick committed
1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477
                '%s%s' % (reference_site, urlresolvers.reverse('version_rest')),
                timeout=0.5
            )
            version_rest = json.load(web_services)
            if version_rest['version'] == '1':
                web_services_activated = True
                web_service_version = 1
        except:
            pass
    return web_services_activated, "Repanier", web_service_version


def add_months(sourcedate, months):
    # months must be an integer
    months = int(months)
    month = sourcedate.month - 1 + months
    year = int(sourcedate.year + month / 12)
    month = month % 12 + 1
    day = min(sourcedate.day, calendar.monthrange(year, month)[1])
    return datetime.date(year, month, day)


def calc_basket_message(customer, permanence, status):
Patrick's avatar
Patrick committed
1478
    from repanier.apps import REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS
1479
    if status == PERMANENCE_OPENED:
Patrick's avatar
Patrick committed
1480
        if REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS:
Patrick's avatar
Patrick committed
1481 1482
            if permanence.with_delivery_point:
                you_can_change = "<br/>%s" % (
1483
                    _("You can increase the order quantities as long as the orders are open for your delivery point."),
Patrick's avatar
Patrick committed
1484 1485 1486
                )
            else:
                you_can_change = "<br/>%s" % (
1487
                    _("You can increase the order quantities as long as the orders are open."),
Patrick's avatar
Patrick committed
1488 1489 1490 1491
                )
        else:
            if permanence.with_delivery_point:
                you_can_change = "<br/>%s" % (
1492
                    _("You can change the order quantities as long as the orders are open for your delivery point."),
Patrick's avatar
Patrick committed
1493 1494 1495
                )
            else:
                you_can_change = "<br/>%s" % (
1496
                    _("You can change the order quantities as long as the orders are open."),
Patrick's avatar
Patrick committed
1497
                )
1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515
    else:
        if permanence.with_delivery_point:
            you_can_change = "<br/>%s" % (
                _('The orders are closed for your delivery point.'),
            )
        else:
            you_can_change = "<br/>%s" % (
                _('The orders are closed.'),
            )
    invoice_msg = EMPTY_STRING
    payment_msg = EMPTY_STRING
    customer_last_balance, customer_on_hold_movement, customer_payment_needed, customer_order_amount = payment_message(
        customer, permanence)
    if apps.REPANIER_SETTINGS_INVOICE:
        if customer_last_balance:
            invoice_msg = "<br/>%s %s" % (
                customer_last_balance,
                customer_on_hold_movement,
Patrick's avatar
Patrick committed
1516
            )
1517 1518 1519
    if apps.REPANIER_SETTINGS_BANK_ACCOUNT is not None:
        payment_msg = "<br/>%s" % (
            customer_payment_needed,
Patrick's avatar
Patrick committed
1520
        )
1521 1522 1523 1524 1525 1526 1527
    basket_message = "%s%s%s%s" % (
        customer_order_amount,
        invoice_msg,
        payment_msg,
        you_can_change
    )
    return basket_message
Patrick's avatar
Patrick committed
1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565


def html_box_content(offer_item, user, result=EMPTY_STRING):
    if offer_item.is_box:
        box_id = offer_item.product_id
        if result is not None and result != EMPTY_STRING:
            result += '<hr/>'
        product_ids = models.BoxContent.objects.filter(
            box_id=box_id
        ).only("product_id")
        qs = models.OfferItem.objects.filter(
            permanence_id=offer_item.permanence_id,  # is_active=True,
            product__box_content__in=product_ids,
            translations__language_code=translation.get_language()
        ).order_by(
            "translations__order_sort_order"
        )
        result += '<ul>' + ("".join('<li>%s * %s, %s <span class="btn_like%s" style="cursor: pointer;">%s</span></li>' % (
            get_display(
                qty=models.BoxContent.objects.filter(box_id=box_id, product_id=o.product_id).only(
                    "content_quantity").order_by('?').first().content_quantity,
                order_average_weight=o.order_average_weight,
                order_unit=o.order_unit,
                without_price_display=True),
            o.long_name,
            o.producer.short_profile_name,
            o.id,
            o.get_like(user)
        ) for o in qs)) + '</ul>'
    return result


def get_full_status_display(permanence):
    need_to_refresh_status = permanence.status in [
        PERMANENCE_WAIT_FOR_PRE_OPEN,
        PERMANENCE_WAIT_FOR_OPEN,
        PERMANENCE_WAIT_FOR_CLOSED,
        PERMANENCE_WAIT_FOR_SEND,
Patrick's avatar
Patrick committed
1566
        PERMANENCE_WAIT_FOR_INVOICED
Patrick's avatar
Patrick committed
1567 1568 1569 1570 1571 1572
    ]
    if permanence.with_delivery_point:
        status_list = []
        status = None
        status_counter = 0
        for delivery in models.DeliveryBoard.objects.filter(permanence_id=permanence.id).order_by("status", "id"):
1573 1574 1575 1576 1577
            need_to_refresh_status |= delivery.status in [
                PERMANENCE_WAIT_FOR_PRE_OPEN,
                PERMANENCE_WAIT_FOR_OPEN,
                PERMANENCE_WAIT_FOR_CLOSED,
                PERMANENCE_WAIT_FOR_SEND,
Patrick's avatar
Patrick committed
1578