tools.py 31.2 KB
Newer Older
Patrick's avatar
Patrick committed
1 2
# -*- coding: utf-8
from __future__ import unicode_literals
3 4 5 6
import datetime
from django.db.models import Sum
from django.utils import timezone
from django.http import Http404
Patrick's avatar
Patrick committed
7 8
from django.template.loader import render_to_string
from django.utils import translation
9
import apps
pi's avatar
pi committed
10 11 12 13
from const import *
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.utils.formats import number_format
14
from django.db import transaction
Patrick's avatar
Patrick committed
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
import models


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)
    """
    def decorator(function):
        def wrapper(*args, **kwargs):
            try:
                return function(*args, **kwargs)
            except exception:
                return default_val
        return wrapper
    return decorator

sint = ignore_exception(ValueError)(int)
42
sboolean = ignore_exception(ValueError)(bool)
Patrick's avatar
Patrick committed
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
# print sint("Hello World") # prints 0
# print sint("1340") # prints 1340


def get_allowed_mail_extension():
    allowed_mail_extension = "@%s" % settings.ALLOWED_HOSTS[0]
    cut_index = len(settings.ALLOWED_HOSTS[0]) - 1
    point_counter = 0
    while cut_index >= 0:
        if settings.ALLOWED_HOSTS[0][cut_index] == ".":
            point_counter += 1
            if point_counter == 2:
                allowed_mail_extension = "@%s" % settings.ALLOWED_HOSTS[0][cut_index + 1:]
                break
        cut_index -= 1
    return allowed_mail_extension


def send_email(email=None):
    if settings.DEBUG:
63 64
        if apps.REPANIER_SETTINGS_TEST_MODE:
            email.to = [v for k, v in settings.ADMINS]
Patrick's avatar
Patrick committed
65 66 67 68 69
            email.cc = []
            email.bcc = []
            email.send()
        else:
            pass
70
    elif apps.REPANIER_SETTINGS_TEST_MODE:
Patrick's avatar
Patrick committed
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
        coordinator = models.Staff.objects.filter(is_coordinator=True, is_active=True).order_by().first()
        if coordinator is not None:
            email.to = [coordinator.user.email]
        else:
            email.to = [v for k, v in settings.ADMINS]
        email.cc = []
        email.bcc = []
        email.send()
    else:
        email.send()


def get_signature(is_reply_to_order_email=False, is_reply_to_invoice_email=False):
    sender_email = None
    sender_function = ""
    signature = ""
    cc_email_staff = []
    for staff in models.Staff.objects.filter(is_active=True).order_by():
        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):
91
            cc_email_staff.append(staff.user.email)
Patrick's avatar
Patrick committed
92 93 94 95 96 97 98 99 100 101
            sender_email = staff.user.email
            sender_function = staff.long_name
            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)
                if r.phone2 and len(r.phone2) > 0:
                    signature += " / %s" % (r.phone2,)
102 103 104
        elif staff.is_coordinator:
            cc_email_staff.append(staff.user.email)

Patrick's avatar
Patrick committed
105 106 107
    if sender_email is None:
        sender_email = "no-reply" + get_allowed_mail_extension()
    return sender_email, sender_function, signature, cc_email_staff
pi's avatar
pi committed
108

Patrick's avatar
Patrick committed
109 110

LENGTH_BY_PREFIX = [
111 112 113 114 115
    (0xC0, 2),  # first byte mask, total codepoint length
    (0xE0, 3),
    (0xF0, 4),
    (0xF8, 5),
    (0xFC, 6),
Patrick's avatar
Patrick committed
116 117
]

118

Patrick's avatar
Patrick committed
119 120
def codepoint_length(first_byte):
    if first_byte < 128:
121
        return 1  # ASCII
Patrick's avatar
Patrick committed
122 123
    for mask, length in LENGTH_BY_PREFIX:
        if first_byte & 0xF0 == mask:
124
            return length
Patrick's avatar
Patrick committed
125
        elif first_byte & 0xF8 == 0xF8:
126
            return length
Patrick's avatar
Patrick committed
127 128
    assert False, 'Invalid byte %r' % first_byte

129

Patrick's avatar
Patrick committed
130
def cap_to_bytes_length(unicode_text, byte_limit):
Patrick's avatar
Patrick committed
131
    utf8_bytes = unicode_text.encode("utf8") # .encode('UTF-8', 'replace')
Patrick's avatar
Patrick committed
132 133 134 135 136 137 138 139 140 141
    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

142

Patrick's avatar
Patrick committed
143
def cap(s, l):
Patrick's avatar
Patrick committed
144
    if s is not None:
145 146
        if not isinstance(s, basestring):
            s = str(s)
Patrick's avatar
Patrick committed
147 148 149 150
        # if isinstance(s, unicode):
        #     s = cap_to_bytes_length(s, l - 4)
        # else:
        s = s if len(s) <= l else s[0:l - 4] + '...'
151
        return s
Patrick's avatar
Patrick committed
152
    else:
153 154 155
        return None


156 157 158 159 160 161 162 163 164 165 166 167 168
def permanence_ok_or_404(permanence):
    if permanence is None:
        raise Http404
    if permanence.status not in [PERMANENCE_OPENED, PERMANENCE_CLOSED, PERMANENCE_SEND]:
        if permanence.status in [PERMANENCE_DONE, PERMANENCE_ARCHIVED]:
            if permanence.permanence_date < (
                timezone.now() - datetime.timedelta(weeks=LIMIT_DISPLAYED_PERMANENCE)
            ).date():
                raise Http404
        else:
            raise Http404


169 170
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
171
        unit = _("/ kg")
172
    elif order_unit == PRODUCT_ORDER_UNIT_LT:
Patrick's avatar
Patrick committed
173
        unit = _("/ l")
Patrick's avatar
Patrick committed
174
    else:
175
        if qty < 2:
Patrick's avatar
Patrick committed
176
            unit = _("/ piece")
177
        else:
Patrick's avatar
Patrick committed
178
            unit = _("/ pieces")
179 180 181
    return unit


Patrick's avatar
Patrick committed
182
def get_preparator_unit(order_unit=PRODUCT_ORDER_UNIT_PC):
183
    # Used when producing the preparation list.
184 185
    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
186
        unit = _("Piece(s) :")
187
    elif order_unit in [PRODUCT_ORDER_UNIT_KG, PRODUCT_ORDER_UNIT_PC_KG]:
Patrick's avatar
Patrick committed
188
        unit = _("€ or kg :")
189
    elif order_unit == PRODUCT_ORDER_UNIT_LT:
Patrick's avatar
Patrick committed
190
        unit = _("L :")
191
    else:
Patrick's avatar
Patrick committed
192
        unit = _("Kg :")
193 194 195
    return unit


196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
def get_base_unit(qty=0, order_unit=PRODUCT_ORDER_UNIT_PC):
    if order_unit == PRODUCT_ORDER_UNIT_KG:
        if qty == DECIMAL_ZERO:
            base_unit = ""
        else:
            base_unit = _('kg')
    elif order_unit == PRODUCT_ORDER_UNIT_LT:
        if qty == DECIMAL_ZERO:
            base_unit = ""
        else:
            base_unit = _('l')
    else:
        if qty == DECIMAL_ZERO:
            base_unit = ""
        elif qty < 2:
            base_unit = _('piece')
        else:
            base_unit = _('pieces')
    return base_unit


217 218
def get_display(qty=0, order_average_weight=0, order_unit=PRODUCT_ORDER_UNIT_PC, price=None,
                for_customer=True):
219 220
    magnitude = None
    display_qty = True
221
    if order_unit == PRODUCT_ORDER_UNIT_KG:
222 223 224 225 226
        if qty == DECIMAL_ZERO:
            unit = ""
        elif for_customer and qty < 1:
            unit = "%s" % (_('gr'))
            magnitude = 1000
227
        else:
228
            unit = "%s" % (_('kg'))
229
    elif order_unit == PRODUCT_ORDER_UNIT_LT:
230 231 232 233 234
        if qty == DECIMAL_ZERO:
            unit = ""
        elif for_customer and qty < 1:
            unit = "%s" % (_('cl'))
            magnitude = 100
235
        else:
236
            unit = "%s" % (_('l'))
237
    elif order_unit in [PRODUCT_ORDER_UNIT_PC_KG, PRODUCT_ORDER_UNIT_PC_PRICE_KG]:
238
        display_qty = order_average_weight != 1
239 240 241
        average_weight = order_average_weight
        if for_customer:
            average_weight *= qty
242 243
        if order_unit == PRODUCT_ORDER_UNIT_PC_KG and price is not None:
            price *= order_average_weight
244
        if average_weight < 1:
Patrick's avatar
Patrick committed
245
            average_weight_unit = _('gr')
246 247
            average_weight *= 1000
        else:
Patrick's avatar
Patrick committed
248
            average_weight_unit = _('kg')
249 250 251 252 253 254 255
        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
256 257 258
        tilde = ''
        if order_unit == PRODUCT_ORDER_UNIT_PC_KG:
            tilde = '~'
259
        if for_customer:
Patrick's avatar
Patrick committed
260
            if qty == DECIMAL_ZERO:
261
                unit = ""
262
            else:
263 264 265 266
                if display_qty:
                    unit = "%s%s%s" % (tilde, number_format(average_weight, decimal), average_weight_unit)
                else:
                    unit = "%s%s %s" % (tilde, number_format(average_weight, decimal), average_weight_unit)
267
        else:
Patrick's avatar
Patrick committed
268
            if qty == DECIMAL_ZERO:
269
                unit = ""
270
            else:
271
                unit = "%s%s%s" % (tilde, number_format(average_weight, decimal), average_weight_unit)
272
    elif order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_LT:
273
        display_qty = order_average_weight != 1
274 275 276
        average_weight = order_average_weight
        if for_customer:
            average_weight *= qty
277
        if average_weight < 1:
Patrick's avatar
Patrick committed
278
            average_weight_unit = _('cl')
279 280
            average_weight *= 100
        else:
Patrick's avatar
Patrick committed
281
            average_weight_unit = _('l')
282 283 284 285 286 287 288
        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
289
        if for_customer:
Patrick's avatar
Patrick committed
290
            if qty == DECIMAL_ZERO:
291
                unit = ""
292
            else:
293 294 295 296
                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)
297
        else:
Patrick's avatar
Patrick committed
298
            if qty == DECIMAL_ZERO:
299
                unit = ""
300
            else:
301
                unit = "%s%s" % (number_format(average_weight, decimal), average_weight_unit)
302
    elif order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_PC:
303
        display_qty = order_average_weight != 1
304 305 306
        average_weight = order_average_weight
        if for_customer:
            average_weight *= qty
Patrick's avatar
Patrick committed
307 308
            if qty == DECIMAL_ZERO:
                unit = ""
309
            else:
Patrick's avatar
Patrick committed
310
                if average_weight < 2:
311 312 313 314 315
                    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
316
                else:
317
                    unit = "%s %s" % (number_format(average_weight, 0), pc_pcs)
318
        else:
Patrick's avatar
Patrick committed
319
            if average_weight == DECIMAL_ZERO:
320
                unit = ""
Patrick's avatar
Patrick committed
321
            elif average_weight < 2:
322
                unit = '%s %s' % (number_format(average_weight, 0), _('pc'))
323
            else:
324 325 326 327 328 329 330 331
                unit = '%s %s' % (number_format(average_weight, 0), _('pcs'))
    else:
        if qty == DECIMAL_ZERO:
            unit = ""
        elif qty < 2:
            unit = "%s" % (_('piece'))
        else:
            unit = "%s" % (_('pieces'))
Patrick's avatar
Patrick committed
332 333 334 335 336
    if price is not None:
        price = Decimal(price * qty).quantize(TWO_DECIMALS)
        price_display = " = %s" % (number_format(price, 2))
    else:
        price_display = ""
337 338
    if magnitude is not None:
        qty *= magnitude
339 340 341 342 343 344 345
    decimal = 3
    if qty == int(qty):
        decimal = 0
    elif qty * 10 == int(qty * 10):
        decimal = 1
    elif qty * 100 == int(qty * 100):
        decimal = 2
346
    if for_customer:
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
        if display_qty:
            qty_display = "%s (%s)" % (number_format(qty, decimal), unit)
        else:
            qty_display = "%s" % (unit)
    else:
        qty_display = "(%s)" % (unit)
    return qty_display, price_display


def payment_message(customer):
    if apps.REPANIER_SETTINGS_INVOICE:
        result_set = models.CustomerInvoice.objects.filter(
            customer_id=customer.id, permanence__status__gte=PERMANENCE_OPENED,
            permanence__status__lte=PERMANENCE_SEND
        ).order_by().aggregate(Sum('total_price_with_tax'))
        orders_amount = result_set["total_price_with_tax__sum"] \
            if result_set["total_price_with_tax__sum"] is not None else DECIMAL_ZERO
        result_set = models.BankAccount.objects.filter(
            customer_id=customer.id, customer_invoice__isnull=True
        ).order_by().aggregate(Sum('bank_amount_in'), Sum('bank_amount_out'))
        bank_amount_in = result_set["bank_amount_in__sum"] \
            if result_set["bank_amount_in__sum"] is not None else DECIMAL_ZERO
        bank_amount_out = result_set["bank_amount_out__sum"] \
            if result_set["bank_amount_out__sum"] is not None else DECIMAL_ZERO
        bank_amount_not_invoiced = bank_amount_in - bank_amount_out
        customer_last_balance = "%s %s %s %s &euro;." % (
            _('The balance of your account as of'),
            customer.date_balance.strftime('%d-%m-%Y'), _('is'),
            number_format(customer.balance, 2))
        if orders_amount != DECIMAL_ZERO or bank_amount_not_invoiced != DECIMAL_ZERO:
            if orders_amount != DECIMAL_ZERO:
                unbilled_sales = "%s &euro;" % (
                    number_format(orders_amount, 2)
                )
            if bank_amount_not_invoiced != DECIMAL_ZERO:
                unrecognized_payments = "%s &euro;" % (
                    number_format(bank_amount_not_invoiced, 2)
                )
            if orders_amount == DECIMAL_ZERO:
                customer_on_hold_movement = "%s (%s)." % (
                    _("This balance does not take account of any unrecognized payments"),
                    unrecognized_payments
                )
            elif bank_amount_not_invoiced == DECIMAL_ZERO:
                customer_on_hold_movement = "%s (%s)." % (
                    _("This balance does not take account of any unbilled sales"),
                    unbilled_sales
                )
            else:
                customer_on_hold_movement = "%s (%s) %s (%s)." % (
                    _("This balance does not take account of any unrecognized payments"),
                    unrecognized_payments,
                    _("and any unbilled sales"),
                    unbilled_sales
                )
        else:
            customer_on_hold_movement = ""
        if apps.REPANIER_SETTINGS_BANK_ACCOUNT is not None:
            to_pay = orders_amount - customer.balance - bank_amount_not_invoiced
            if to_pay > DECIMAL_ZERO:
                customer_payment_needed = "%s %s &euro; %s (%s) %s \"%s\"." % (
                    _('Please pay'),
                    number_format(to_pay, 2),
                    _('to the bank account number'),
                    apps.REPANIER_SETTINGS_BANK_ACCOUNT,
                    _('with communication'),
                    customer.short_basket_name)
            else:
                customer_payment_needed = "%s." % (_('Your account balance is sufficient'))
        else:
            customer_payment_needed = ""
            customer_on_hold_movement = ""
419
    else:
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
        customer_last_balance = ""
        customer_payment_needed = ""
        customer_on_hold_movement = ""
    # print("----------------------")
    # import sys
    # import codecs
    # sys.stdout = codecs.getwriter('utf8')(sys.stdout)
    # print basket_amount
    # print "%s" % customer_last_balance
    # print "%s" % customer_on_hold_movement
    # print "%s" % customer_payment_needed
    return customer_last_balance, customer_on_hold_movement, customer_payment_needed


def basket_amount(customer, permanence):
    result = models.CustomerInvoice.objects.filter(
        customer_id=customer.id, permanence_id=permanence.id
    ).order_by().only("total_price_with_tax").first()
    return number_format(result.total_price_with_tax if result is not None else DECIMAL_ZERO, 2)
439

pi's avatar
pi committed
440

Patrick's avatar
Patrick committed
441 442 443 444 445
def recalculate_order_amount(permanence_id=None,
                             permanence_status=None,
                             customer_id=None,
                             offer_item_queryset=None,
                             send_to_producer=False,
446
                             re_init=False):
Patrick's avatar
Patrick committed
447
    if permanence_id is not None:
448
        if send_to_producer or re_init:
Patrick's avatar
Patrick committed
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
            models.ProducerInvoice.objects.filter(permanence_id=permanence_id)\
                .update(total_price_with_tax=DECIMAL_ZERO)
            models.CustomerInvoice.objects.filter(permanence_id=permanence_id)\
                .update(total_price_with_tax=DECIMAL_ZERO)
            models.CustomerProducerInvoice.objects.filter(permanence_id=permanence_id)\
                .update(
                    total_purchase_with_tax=DECIMAL_ZERO,
                    total_selling_with_tax=DECIMAL_ZERO
                )
            models.OfferItem.objects.filter(permanence_id=permanence_id)\
                .update(
                    quantity_invoiced=DECIMAL_ZERO,
                    total_purchase_with_tax=DECIMAL_ZERO,
                    total_selling_with_tax=DECIMAL_ZERO
                )
            for offer_item in models.OfferItem.objects.filter(permanence_id=permanence_id, is_active=True, manage_stock=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_stock = DECIMAL_ZERO
                offer_item.previous_add_2_stock = DECIMAL_ZERO
                offer_item.save()
        if customer_id is None:
            if offer_item_queryset is not None:
                if permanence_status < PERMANENCE_SEND:
Patrick Colmant's avatar
Patrick Colmant committed
475
                    purchase_set = models.PurchaseOpenedOrClosedForUpdate.objects \
Patrick's avatar
Patrick committed
476 477 478
                        .filter(permanence_id=permanence_id, offer_item__in=offer_item_queryset)\
                        .order_by()
                else:
Patrick Colmant's avatar
Patrick Colmant committed
479
                    purchase_set = models.PurchaseSendForUpdate.objects \
Patrick's avatar
Patrick committed
480 481 482 483
                        .filter(permanence_id=permanence_id, offer_item__in=offer_item_queryset)\
                        .order_by()
            else:
                if permanence_status < PERMANENCE_SEND:
Patrick Colmant's avatar
Patrick Colmant committed
484
                    purchase_set = models.PurchaseOpenedOrClosedForUpdate.objects \
Patrick's avatar
Patrick committed
485 486 487
                        .filter(permanence_id=permanence_id)\
                        .order_by()
                else:
Patrick Colmant's avatar
Patrick Colmant committed
488
                    purchase_set = models.PurchaseSendForUpdate.objects \
Patrick's avatar
Patrick committed
489 490
                        .filter(permanence_id=permanence_id)\
                        .order_by()
491
        else:
Patrick's avatar
Patrick committed
492
            if permanence_status < PERMANENCE_SEND:
Patrick Colmant's avatar
Patrick Colmant committed
493
                purchase_set = models.PurchaseOpenedOrClosedForUpdate.objects \
Patrick's avatar
Patrick committed
494 495 496
                    .filter(permanence_id=permanence_id, customer_id=customer_id)\
                    .order_by()
            else:
Patrick Colmant's avatar
Patrick Colmant committed
497
                purchase_set = models.PurchaseSendForUpdate.objects \
Patrick's avatar
Patrick committed
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517
                    .filter(permanence_id=permanence_id, customer_id=customer_id)\
                    .order_by()

        for purchase in purchase_set:
            # 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
            if send_to_producer:
                purchase.previous_quantity_invoiced = DECIMAL_ZERO
                purchase.previous_purchase_price = DECIMAL_ZERO
                purchase.previous_selling_price = DECIMAL_ZERO
                offer_item = purchase.offer_item
                if offer_item.order_unit == PRODUCT_ORDER_UNIT_PC_KG:
                    purchase.quantity_invoiced = (purchase.quantity_ordered * offer_item.order_average_weight)\
                        .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
518 519 520 521 522 523
                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
524 525 526
                else:
                    purchase.quantity_invoiced = purchase.quantity_ordered
                    purchase.quantity_for_preparation_sort_order = DECIMAL_ZERO
527 528 529 530
            elif re_init:
                purchase.previous_quantity_invoiced = DECIMAL_ZERO
                purchase.previous_purchase_price = DECIMAL_ZERO
                purchase.previous_selling_price = DECIMAL_ZERO
Patrick's avatar
Patrick committed
531
            purchase.save()
532 533 534


@transaction.atomic
Patrick's avatar
Patrick committed
535
def update_or_create_purchase(user_id=None, customer=None, offer_item_id=None, value_id=None, close_orders=False):
536
    result = "ko"
Patrick's avatar
Patrick committed
537
    if offer_item_id is not None and value_id is not None:
538
        # try:
Patrick's avatar
Patrick committed
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555
        if user_id is not None:
            customer = models.Customer.objects.filter(user_id=user_id, is_active=True, may_order=True)\
                .only("id", "is_active", "vat_id")\
                .order_by().first()
        if customer is not None:
            offer_item = models.OfferItem.objects.select_for_update(nowait=False)\
                .filter(id=offer_item_id, is_active=True)\
                .order_by().first()
            if offer_item is not None:
                permanence = models.Permanence.objects.filter(id=offer_item.permanence_id)\
                    .only("status", "permanence_date")\
                    .order_by().first()
                # The close_orders flag is used because we need to forbid
                # customers to add purchases during the close_orders_async process
                # when the status is PERMANENCE_WAIT_FOR_SEND
                if (permanence.status == PERMANENCE_OPENED) or close_orders:
                    # The offer_item belong to an open permanence
Patrick Colmant's avatar
Patrick Colmant committed
556
                    purchase = models.PurchaseOpenedOrClosedForUpdate.objects.filter(
Patrick's avatar
Patrick committed
557 558 559 560 561 562
                        offer_item_id=offer_item.id,
                        permanence_id=permanence.id,
                        customer_id=customer.id)\
                        .order_by().first()
                    if purchase is not None:
                        q_previous_order = purchase.quantity_ordered
563
                    else:
Patrick's avatar
Patrick committed
564 565
                        q_previous_order = DECIMAL_ZERO
                    q_min = offer_item.customer_minimum_order_quantity
566 567
                    if permanence.status == PERMANENCE_OPENED and offer_item.limit_order_quantity_to_stock:
                        q_alert = offer_item.stock - offer_item.quantity_invoiced + q_previous_order
Patrick's avatar
Patrick committed
568 569
                        if q_alert < DECIMAL_ZERO:
                            q_alert = DECIMAL_ZERO
570
                    else:
Patrick's avatar
Patrick committed
571 572 573 574 575 576
                        q_alert = offer_item.customer_alert_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
577
                    else:
Patrick's avatar
Patrick committed
578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599
                        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 <= q_alert:
                        if purchase is not None:
                            purchase.quantity_ordered = q_order
                            purchase.save()
                        else:
                            if offer_item.vat_level in [VAT_200, VAT_300] \
                                    and customer.vat_id is not None \
                                    and len(customer.vat_id) > 0:
                                is_compensation = True
                            else:
                                is_compensation = False
Patrick Colmant's avatar
Patrick Colmant committed
600
                            models.PurchaseOpenedOrClosedForUpdate.objects.create(
Patrick's avatar
Patrick committed
601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620
                                permanence_id=permanence.id,
                                permanence_date=permanence.permanence_date,
                                offer_item_id=offer_item.id,
                                producer_id=offer_item.producer_id,
                                customer_id=customer.id,
                                quantity_ordered=q_order,
                                invoiced_price_with_compensation=is_compensation
                            )
                        customer_invoice = models.CustomerInvoice.objects.filter(permanence_id=permanence.id,
                                           customer_id=customer.id)\
                            .only("total_price_with_tax")\
                            .order_by().first()
                        if customer_invoice is None:
                            result = "ok0"
                        else:
                            result = "ok" + number_format(customer_invoice.total_price_with_tax, 2)

                            # except:
                            # # user.customer doesn't exist -> the user is not a customer.
                            #   pass
621 622
        else:
            result = "ok0"
623
    return result
Patrick's avatar
Patrick committed
624 625 626 627


def clean_offer_item(permanence, queryset, reorder=False):
    cur_language = translation.get_language()
628
    for offer_item in queryset.select_related("product", "producer"):
Patrick Colmant's avatar
Patrick Colmant committed
629
        offer_item.picture2 = offer_item.product.picture2
Patrick's avatar
Patrick committed
630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
        offer_item.reference = offer_item.product.reference
        offer_item.department_for_customer_id = offer_item.product.department_for_customer_id
        offer_item.producer_id = offer_item.product.producer_id
        offer_item.order_unit = offer_item.product.order_unit
        offer_item.wrapped = offer_item.product.wrapped
        offer_item.order_average_weight = offer_item.product.order_average_weight
        offer_item.placement = offer_item.product.placement
        offer_item.producer_price_are_wo_vat = offer_item.producer.producer_price_are_wo_vat
        offer_item.producer_vat = offer_item.product.producer_vat
        offer_item.customer_vat = offer_item.product.customer_vat
        offer_item.compensation = offer_item.product.compensation
        # if offer_item.producer.producer_price_are_wo_vat:
        #     offer_item.producer_unit_price = offer_item.product.producer_unit_price + offer_item.producer_vat
        # else:
        offer_item.producer_unit_price = offer_item.product.producer_unit_price
        offer_item.customer_unit_price = offer_item.product.customer_unit_price
        offer_item.unit_deposit = offer_item.product.unit_deposit
        offer_item.vat_level = offer_item.product.vat_level
        offer_item.limit_order_quantity_to_stock = offer_item.product.limit_order_quantity_to_stock
649
        offer_item.producer_pre_opening = offer_item.producer.producer_pre_opening
Patrick's avatar
Patrick committed
650 651 652 653 654 655 656 657 658
        offer_item.manage_stock = offer_item.producer.manage_stock
        offer_item.price_list_multiplier = offer_item.producer.price_list_multiplier
        offer_item.is_resale_price_fixed = offer_item.producer.is_resale_price_fixed
        offer_item.stock = offer_item.product.stock
        if reorder:
            offer_item.add_2_stock = DECIMAL_ZERO
        offer_item.customer_minimum_order_quantity = offer_item.product.customer_minimum_order_quantity
        offer_item.customer_increment_order_quantity = offer_item.product.customer_increment_order_quantity
        offer_item.customer_alert_order_quantity = offer_item.product.customer_alert_order_quantity
659
        offer_item.producer_order_by_quantity = offer_item.product.producer_order_by_quantity
Patrick's avatar
Patrick committed
660 661 662 663 664
        offer_item.save()

    # Now got everything to calculate the sort order of the order display screen
    for language in settings.PARLER_LANGUAGES[settings.SITE_ID]:
        translation.activate(language["code"])
665
        for offer_item in queryset.select_related("product", "producer", "department_for_customer"):
Patrick's avatar
Patrick committed
666
            offer_item.long_name = offer_item.product.long_name
Patrick Colmant's avatar
Patrick Colmant committed
667 668 669 670 671 672 673 674
            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_c = render_to_string('repanier/cache_part_c.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
675 676 677 678 679 680 681 682
            offer_item.save_translations()
        if reorder:
            # The "order_by" of the queryset is only relevant after the previous "for" has been done.
            i = 0
            queryset = queryset.filter(
                translations__language_code=language["code"]
            ).order_by().order_by(
                "department_for_customer__tree_id",
683
                # "department_for_customer__lft",
Patrick's avatar
Patrick committed
684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699
                "translations__long_name",
                "order_average_weight",
                "producer__short_profile_name"
            )
            for offer_item in queryset:
                offer_item.order_sort_order = i
                i += 1
                offer_item.save_translations()

        departementforcustomer_set = models.LUT_DepartmentForCustomer.objects.filter(
                        offeritem__permanence_id=permanence.id,
                        offeritem__order_unit__lt=PRODUCT_ORDER_UNIT_DEPOSIT)\
            .order_by("tree_id", "lft")\
            .distinct("id", "tree_id", "lft")
        if departementforcustomer_set:
            pass
700
        if apps.REPANIER_SETTINGS_DISPLAY_PRODUCER_ON_ORDER_FORM:
Patrick's avatar
Patrick committed
701 702 703 704 705 706 707
            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',
           {'producer_set': producer_set, 'departementforcustomer_set': departementforcustomer_set})
        permanence.save_translations()
    translation.activate(cur_language)