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

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

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

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


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

    Using it as a decorator:

    @ignore_exception(ValueError)
    def my_function():
    pass

    Using it as a function wrapper:

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

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

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

Patrick's avatar
Patrick committed
60 61
    return decorator

Patrick's avatar
Patrick committed
62

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


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


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


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


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


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


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


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

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

Patrick's avatar
Patrick committed
257

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

288

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

299

Patrick's avatar
Patrick committed
300
def cap_to_bytes_length(unicode_text, byte_limit):
Patrick's avatar
Patrick committed
301
    utf8_bytes = unicode_text.encode("utf8")
Patrick's avatar
Patrick committed
302 303 304 305 306 307 308 309 310 311
    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

312

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


323 324 325 326
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
327
        if permanence.status in [PERMANENCE_INVOICED, PERMANENCE_ARCHIVED]:
328
            if permanence.permanence_date < (
Patrick's avatar
Patrick committed
329
                        timezone.now() - datetime.timedelta(weeks=LIMIT_DISPLAYED_PERMANENCE)
330 331 332 333 334 335
            ).date():
                raise Http404
        else:
            raise Http404


336 337
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
338
        unit = _("/ kg")
339
    elif order_unit == PRODUCT_ORDER_UNIT_LT:
Patrick's avatar
Patrick committed
340
        unit = _("/ l")
Patrick's avatar
Patrick committed
341
    else:
342
        if qty < 2:
Patrick's avatar
Patrick committed
343
            unit = _("/ piece")
344
        else:
Patrick's avatar
Patrick committed
345
            unit = _("/ pieces")
346 347 348
    return unit


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


Patrick's avatar
Patrick committed
374 375
def get_base_unit(qty=0, order_unit=PRODUCT_ORDER_UNIT_PC, status=None):
    if order_unit == PRODUCT_ORDER_UNIT_KG or (status >= PERMANENCE_SEND and order_unit == PRODUCT_ORDER_UNIT_PC_KG):
376
        if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
377
            base_unit = EMPTY_STRING
378 379 380 381
        else:
            base_unit = _('kg')
    elif order_unit == PRODUCT_ORDER_UNIT_LT:
        if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
382
            base_unit = EMPTY_STRING
383 384 385 386
        else:
            base_unit = _('l')
    else:
        if qty == DECIMAL_ZERO:
Patrick's avatar
Patrick committed
387
            base_unit = EMPTY_STRING
388 389 390 391 392 393 394
        elif qty < 2:
            base_unit = _('piece')
        else:
            base_unit = _('pieces')
    return base_unit


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


Patrick's avatar
Patrick committed
546
def customer_on_hold_movement_message(customer, bank_not_invoiced=None, order_not_invoiced=None, total_price_with_tax=REPANIER_MONEY_ZERO):
Patrick's avatar
Patrick committed
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
    # If permanence_id is None, only "customer_on_hold_movement" is calculated
    if customer is None:
        customer_on_hold_movement = EMPTY_STRING
    else:
        if apps.REPANIER_SETTINGS_INVOICE:
            bank_not_invoiced = bank_not_invoiced if bank_not_invoiced is not None else customer.get_bank_not_invoiced()
            order_not_invoiced = order_not_invoiced if order_not_invoiced is not None else customer.get_order_not_invoiced()
            other_order_not_invoiced = order_not_invoiced - total_price_with_tax
        else:
            bank_not_invoiced = REPANIER_MONEY_ZERO
            other_order_not_invoiced = REPANIER_MONEY_ZERO

        if other_order_not_invoiced.amount != DECIMAL_ZERO or bank_not_invoiced.amount != DECIMAL_ZERO:
            if other_order_not_invoiced.amount != DECIMAL_ZERO:
                if bank_not_invoiced.amount == DECIMAL_ZERO:
                    customer_on_hold_movement = \
                        _('This balance does not take account of any unbilled sales %(other_order)s.') % {
                            'other_order': other_order_not_invoiced
                        }
                else:
                    customer_on_hold_movement = \
                        _(
                            'This balance does not take account of any unrecognized payments %(bank)s and any unbilled order %(other_order)s.') \
                        % {
                            'bank'       : bank_not_invoiced,
                            'other_order': other_order_not_invoiced
                        }
574
            else:
Patrick's avatar
Patrick committed
575 576 577 578 579
                customer_on_hold_movement = \
                    _(
                        'This balance does not take account of any unrecognized payments %(bank)s.') % {
                        'bank': bank_not_invoiced
                    }
580
        else:
Patrick's avatar
Patrick committed
581 582 583 584 585
            customer_on_hold_movement = EMPTY_STRING

    return customer_on_hold_movement


Patrick's avatar
Patrick committed
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624
def producer_on_hold_movement_message(producer):
    # If permanence_id is None, only "customer_on_hold_movement" is calculated
    if producer is None:
        producer_on_hold_movement = EMPTY_STRING
    else:
        if apps.REPANIER_SETTINGS_INVOICE:
            bank_not_invoiced = producer.get_bank_not_invoiced()
            order_not_invoiced = producer.get_order_not_invoiced()
        else:
            bank_not_invoiced = REPANIER_MONEY_ZERO
            order_not_invoiced = REPANIER_MONEY_ZERO

        if order_not_invoiced.amount != DECIMAL_ZERO or bank_not_invoiced.amount != DECIMAL_ZERO:
            if order_not_invoiced.amount != DECIMAL_ZERO:
                if bank_not_invoiced.amount == DECIMAL_ZERO:
                    producer_on_hold_movement = \
                        _('This balance does not take account of any unbilled sales %(other_order)s.') % {
                            'other_order': order_not_invoiced
                        }
                else:
                    producer_on_hold_movement = \
                        _(
                            'This balance does not take account of any unrecognized payments %(bank)s and any unbilled order %(other_order)s.') \
                        % {
                            'bank'       : bank_not_invoiced,
                            'other_order': order_not_invoiced
                        }
            else:
                producer_on_hold_movement = \
                    _(
                        'This balance does not take account of any unrecognized payments %(bank)s.') % {
                        'bank': bank_not_invoiced
                    }
        else:
            producer_on_hold_movement = EMPTY_STRING

    return producer_on_hold_movement


Patrick's avatar
Patrick committed
625 626 627 628 629 630 631
def payment_message(customer, permanence):
    # If permanence_id is None, only "customer_on_hold_movement" is calculated
    customer_last_balance = EMPTY_STRING
    if customer is None or permanence is None:
        customer_order_amount = EMPTY_STRING
        customer_payment_needed = EMPTY_STRING
        customer_on_hold_movement = EMPTY_STRING
632
    else:
Patrick's avatar
Patrick committed
633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655
        customer_invoice = models.CustomerInvoice.objects.filter(
            customer_id=customer.id,
            permanence_id=permanence.id
        ).order_by('?').first()
        if customer_invoice is None:
            total_price_with_tax = REPANIER_MONEY_ZERO
        else:
            total_price_with_tax = customer_invoice.get_total_price_with_tax()
        customer_order_amount = \
            _('The amount of your order is %(amount)s.') % {
                'amount': total_price_with_tax
            }
        if apps.REPANIER_SETTINGS_INVOICE:
            bank_not_invoiced = customer.get_bank_not_invoiced()
            order_not_invoiced = customer.get_order_not_invoiced()
            payment_needed = - (customer.balance - order_not_invoiced + bank_not_invoiced)
            # other_order_not_invoiced = order_not_invoiced - total_price_with_tax
        else:
            bank_not_invoiced = REPANIER_MONEY_ZERO
            order_not_invoiced = DECIMAL_ZERO
            payment_needed = total_price_with_tax
            # other_order_not_invoiced = REPANIER_MONEY_ZERO

Patrick's avatar
Patrick committed
656
        if customer_invoice.customer_id != customer_invoice.customer_charged_id:
657
            customer_payment_needed = '<font color="#51a351">%s</font>' % (
Patrick's avatar
Patrick committed
658
                _('Invoices for this delivery point are sent to %(name)s who is responsible for collecting the payments.') % {
Patrick's avatar
Patrick committed
659
                    'name': customer_invoice.customer_charged.long_basket_name
Patrick's avatar
Patrick committed
660 661
                }
            )
662
            customer_on_hold_movement = EMPTY_STRING
Patrick's avatar
Patrick committed
663
        else:
Patrick's avatar
Patrick committed
664
            customer_on_hold_movement = customer_on_hold_movement_message(customer, bank_not_invoiced, order_not_invoiced, total_price_with_tax)
665 666 667 668 669 670 671 672 673 674 675
            if apps.REPANIER_SETTINGS_INVOICE:
                if customer.balance.amount != DECIMAL_ZERO:
                    if customer.balance.amount < DECIMAL_ZERO:
                        balance = '<font color="#bd0926">%s</font>' % customer.balance
                    else:
                        balance = '%s' % customer.balance
                    customer_last_balance = \
                        _('The balance of your account as of %(date)s is %(balance)s.') % {
                            'date'   : customer.date_balance.strftime(settings.DJANGO_SETTINGS_DATE),
                            'balance': balance
                        }
Patrick's avatar
Patrick committed
676
                else:
677
                    customer_last_balance = EMPTY_STRING
Patrick's avatar
Patrick committed
678

679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694
            bank_account_number = apps.REPANIER_SETTINGS_BANK_ACCOUNT
            if bank_account_number is not None:
                if payment_needed.amount > DECIMAL_ZERO:
                    if permanence.short_name:
                        communication = "%s (%s)" % (customer.short_basket_name, permanence.short_name)
                    else:
                        communication = customer.short_basket_name
                    group_name = apps.REPANIER_SETTINGS_GROUP_NAME
                    customer_payment_needed = '<br/><font color="#bd0926">%s</font>' % (
                        _('Please pay %(payment)s to the bank account %(name)s %(number)s with communication %(communication)s.') % {
                            'payment': payment_needed,
                            'name': group_name,
                            'number': bank_account_number,
                            'communication': communication
                        }
                    )
Patrick's avatar
Patrick committed
695 696

                else:
697
                    if customer.balance.amount != DECIMAL_ZERO:
698
                        customer_payment_needed = '<br/><font color="#51a351">%s.</font>' % (_('Your account balance is sufficient'))
699 700 701 702 703
                    else:
                        customer_payment_needed = EMPTY_STRING
            else:
                customer_payment_needed = EMPTY_STRING

Patrick's avatar
Patrick committed
704 705 706 707 708

    return customer_last_balance, customer_on_hold_movement, customer_payment_needed, customer_order_amount


def recalculate_order_amount(permanence_id,
Patrick's avatar
Patrick committed
709
                             customer_id=None,
Patrick's avatar
Patrick committed
710
                             offer_item_qs=None,
Patrick's avatar
Patrick committed
711
                             producers_id=None,
Patrick's avatar
Patrick committed
712
                             send_to_producer=False,
713
                             re_init=False):
Patrick's avatar
Patrick committed
714
    if send_to_producer or re_init:
Patrick's avatar
Patrick committed
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 743 744 745 746 747
        models.ProducerInvoice.objects.filter(
            permanence_id=permanence_id
        ).update(
            total_price_with_tax=DECIMAL_ZERO,
            total_vat=DECIMAL_ZERO,
            total_deposit=DECIMAL_ZERO,
        )
        models.CustomerInvoice.objects.filter(
            permanence_id=permanence_id
        ).update(
            total_price_with_tax=DECIMAL_ZERO,
            total_vat=DECIMAL_ZERO,
            total_deposit=DECIMAL_ZERO
        )
        models.CustomerProducerInvoice.objects.filter(
            permanence_id=permanence_id
        ).update(
            total_purchase_with_tax=DECIMAL_ZERO,
            total_selling_with_tax=DECIMAL_ZERO
        )
        models.OfferItem.objects.filter(
            permanence_id=permanence_id
        ).update(
            quantity_invoiced=DECIMAL_ZERO,
            total_purchase_with_tax=DECIMAL_ZERO,
            total_selling_with_tax=DECIMAL_ZERO
        )
        models.Permanence.objects.filter(
            id=permanence_id
        ).update(
            total_profit=DECIMAL_ZERO,
        )
        for offer_item in models.OfferItem.objects.filter(
Patrick's avatar
Patrick committed
748
                permanence_id=permanence_id,
Patrick's avatar
Patrick committed
749 750 751 752 753 754 755 756
                is_active=True,
                manage_replenishment=True
        ).exclude(add_2_stock=DECIMAL_ZERO).order_by('?'):
            # Recalculate the total_price_with_tax of ProducerInvoice and
            # the total_purchase_with_tax of OfferItem
            # taking into account "add_2_stock"
            offer_item.previous_add_2_stock = DECIMAL_ZERO
            offer_item.save()
Patrick's avatar
Patrick committed
757 758

    if customer_id is None:
Patrick's avatar
Patrick committed
759
        if offer_item_qs is not None:
Patrick's avatar
Patrick committed
760
            purchase_set = models.Purchase.objects \
Patrick's avatar
Patrick committed
761
                .filter(permanence_id=permanence_id, offer_item__in=offer_item_qs) \
Patrick's avatar
Patrick committed
762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785
                .order_by('?')
        else:
            purchase_set = models.Purchase.objects \
                .filter(permanence_id=permanence_id) \
                .order_by('?')
    else:
        purchase_set = models.Purchase.objects \
            .filter(permanence_id=permanence_id, customer_id=customer_id) \
            .order_by('?')

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


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

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


861
def display_selected_box_value(customer, offer_item, box_purchase):
Patrick's avatar
Patrick committed
862 863 864 865
    if offer_item.is_box_content:
        # box_name = _not_lazy("Composition")
        box_name = BOX_UNICODE
        # Select one purchase
866
        if box_purchase is not None:
Patrick's avatar
Patrick committed
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
            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
898 899 900


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