permanence.py 23.8 KB
Newer Older
Patrick's avatar
Patrick committed
1 2 3 4 5 6 7 8
# -*- coding: utf-8
from __future__ import unicode_literals

from django.conf import settings
from django.core import urlresolvers
from django.core.cache import cache
from django.db import models
from django.db.models import F
9 10
from django.db.models.signals import post_init
from django.dispatch import receiver
Patrick's avatar
Patrick committed
11
from django.utils import timezone
12
from django.utils import translation
Patrick's avatar
Patrick committed
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from djangocms_text_ckeditor.fields import HTMLField
from menus.menu_pool import menu_pool
from parler.models import TranslatableModel, TranslatedFields
from parler.models import TranslationDoesNotExist

import configuration
import customer
import deliveryboard
import invoice
import permanenceboard
import producer
import purchase
import repanier.apps
from repanier.const import *
from repanier.tools import get_full_status_display


@python_2_unicode_compatible
class Permanence(TranslatableModel):
    translations = TranslatedFields(
Patrick's avatar
Patrick committed
35
        short_name=models.CharField(_("offer name"), max_length=50, blank=True),
Patrick's avatar
Patrick committed
36
        offer_description=HTMLField(_("offer_description"),
37
                                    configuration='CKEDITOR_SETTINGS_MODEL2',
Patrick's avatar
Patrick committed
38 39 40 41 42
                                    help_text=_(
                                        "This message is send by mail to all customers when opening the order or on top "),
                                    blank=True, default=EMPTY_STRING),
        invoice_description=HTMLField(
            _("invoice_description"),
43
            configuration='CKEDITOR_SETTINGS_MODEL2',
Patrick's avatar
Patrick committed
44 45 46
            help_text=_(
                'This message is send by mail to all customers having bought something when closing the permanence.'),
            blank=True, default=EMPTY_STRING),
47
        cache_part_d=HTMLField(configuration='CKEDITOR_SETTINGS_MODEL2', blank=True, default=EMPTY_STRING)
Patrick's avatar
Patrick committed
48 49 50 51 52 53 54 55
    )

    status = models.CharField(
        max_length=3,
        choices=LUT_PERMANENCE_STATUS,
        default=PERMANENCE_PLANNED,
        verbose_name=_("permanence_status"),
        help_text=_('status of the permanence from planned, orders opened, orders closed, send, done'))
Patrick's avatar
Patrick committed
56
    permanence_date = models.DateField(_("date"), db_index=True)
Patrick's avatar
Patrick committed
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
    payment_date = models.DateField(_("payment_date"), blank=True, null=True, db_index=True)
    producers = models.ManyToManyField(
        'Producer',
        verbose_name=_("producers"),
        blank=True
    )
    with_delivery_point = models.BooleanField(
        _("with_delivery_point"), default=False)
    automatically_closed = models.BooleanField(
        _("automatically_closed"), default=False)
    is_updated_on = models.DateTimeField(
        _("is_updated_on"), auto_now=True)
    highest_status = models.CharField(
        max_length=3,
        choices=LUT_PERMANENCE_STATUS,
        default=PERMANENCE_PLANNED,
        verbose_name=_("highest permanence_status"),
        help_text=_('status of the permanence from planned, orders opened, orders closed, send, done'))

    def get_producers(self):
        if self.id is not None:
            if len(self.producers.all()) > 0:
                if self.status == PERMANENCE_PLANNED:
                    changelist_url = urlresolvers.reverse(
                        'admin:repanier_product_changelist',
                    )
                    link = []
                    for p in self.producers.all():
                        link.append(
86
                            '<a href=%s?producer=%d>&nbsp;%s</a>' % (
Patrick's avatar
Patrick committed
87
                                changelist_url, p.id, p.short_profile_name))
88
                    return '<div class="wrap-text">%s</div>' % ", ".join(link)
Patrick's avatar
Patrick committed
89
                elif self.status == PERMANENCE_PRE_OPEN:
90
                    return '<div class="wrap-text">%s</div>' % ", ".join([p.short_profile_name + " (" + p.phone1 + ")" for p in self.producers.all()])
Patrick's avatar
Patrick committed
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
                elif self.status in [PERMANENCE_OPENED, PERMANENCE_CLOSED]:
                    close_offeritem_changelist_url = urlresolvers.reverse(
                        'admin:repanier_offeritemclosed_changelist',
                    )
                    send_offeritem_changelist_url = urlresolvers.reverse(
                        'admin:repanier_offeritemsend_changelist',
                    )
                    send_customer_changelist_url = urlresolvers.reverse(
                        'admin:repanier_customersend_changelist',
                    )

                    link = []
                    for p in self.producers.all().only("id"):
                        pi = invoice.ProducerInvoice.objects.filter(producer_id=p.id, permanence_id=self.id) \
                            .order_by('?').first()
                        if pi is not None:
                            if pi.status > PERMANENCE_OPENED:
                                label = ('%s (%s) %s' % (p.short_profile_name, pi.get_total_price_with_tax(), LOCK_UNICODE)).replace(' ',
                                                                                                            '&nbsp;')
                                if pi.status == PERMANENCE_SEND:
                                    if p.invoice_by_basket:
                                        offeritem_changelist_url = send_customer_changelist_url
                                    else:
                                        offeritem_changelist_url = send_offeritem_changelist_url
                                else:
                                    offeritem_changelist_url = close_offeritem_changelist_url
                            else:
                                label = ('%s (%s) ' % (p.short_profile_name, pi.get_total_price_with_tax())).replace(' ', '&nbsp;')
                                offeritem_changelist_url = close_offeritem_changelist_url
                        else:
                            label = ('%s ' % (p.short_profile_name,)).replace(' ', '&nbsp;')
                            offeritem_changelist_url = close_offeritem_changelist_url
                        link.append(
124
                            '<a href="%s?permanence=%s&producer=%d">%s</a>' % (
Patrick's avatar
Patrick committed
125
                                offeritem_changelist_url, self.id, p.id, label))
126
                    return '<div class="wrap-text">%s</div>' % ", ".join(link)
Patrick's avatar
Patrick committed
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
                elif self.status == PERMANENCE_SEND:
                    send_offeritem_changelist_url = urlresolvers.reverse(
                        'admin:repanier_offeritemsend_changelist',
                    )
                    send_customer_changelist_url = urlresolvers.reverse(
                        'admin:repanier_customersend_changelist',
                    )
                    link = []
                    for p in self.producers.all().only("id", "short_profile_name", "invoice_by_basket"):
                        if p.invoice_by_basket:
                            changelist_url = send_customer_changelist_url
                        else:
                            changelist_url = send_offeritem_changelist_url
                        pi = invoice.ProducerInvoice.objects.filter(producer_id=p.id, permanence_id=self.id) \
                            .order_by('?').first()
142
                        # Important : no target="_blank"
Patrick's avatar
Patrick committed
143 144 145
                        if pi is not None:
                            label = '%s (%s) %s' % (p.short_profile_name, pi.get_total_price_with_tax(), LOCK_UNICODE)
                            link.append(
146
                                '<a href="%s?permanence=%d&producer=%d">&nbsp;%s</a>' % (
Patrick's avatar
Patrick committed
147 148 149 150
                                    changelist_url, self.id, p.id, label.replace(' ', '&nbsp;')
                                ))
                        else:
                            link.append(
151
                                '<a href="%s?permanence=%d&producer=%d">&nbsp;%s</a>' % (
Patrick's avatar
Patrick committed
152 153 154
                                    changelist_url, self.id, p.id, p.short_profile_name.replace(' ', '&nbsp;')
                                ))

155
                    return '<div class="wrap-text">%s</div>' % ", ".join(link)
Patrick's avatar
Patrick committed
156 157 158 159
                elif self.status in [PERMANENCE_DONE, PERMANENCE_ARCHIVED]:
                    link = []
                    for pi in invoice.ProducerInvoice.objects.filter(permanence_id=self.id).select_related(
                            "producer").order_by('producer'):
160
                        label = "%s (%s)" % (
Patrick's avatar
Patrick committed
161
                            pi.producer.short_profile_name,
162
                            pi.get_total_price_with_tax()
Patrick's avatar
Patrick committed
163
                        )
164 165
                        # Important : target="_blank" because the invoices must be displayed without the cms_toolbar
                        # Such that they can be accessed by the producer and by the staff
Patrick's avatar
Patrick committed
166
                        link.append(
167
                            '<a href="%s?producer=%d" target="_blank">%s</a>'
Patrick's avatar
Patrick committed
168 169 170 171 172 173
                            % (
                                urlresolvers.reverse('producer_invoice_view', args=(pi.id,)),
                                pi.producer_id,
                                label.replace(' ', '&nbsp;')))
                    producers = ", ".join(link)
                    msg_html = """
174
                        <div class="wrap-text"><button
Patrick's avatar
Patrick committed
175 176 177 178 179 180 181 182
                        onclick="django.jQuery('#id_get_producers_%d').toggle();
                            if(django.jQuery(this).html()=='%s'){
                                django.jQuery(this).html('%s')
                            }else{
                                django.jQuery(this).html('%s')
                            };
                            return false;"
                        >%s</button>
183
                        <div id="id_get_producers_%d" style="display:none;">%s</div></div>
Patrick's avatar
Patrick committed
184 185 186 187 188
                    """ % (
                        self.id, _("Show"), _("Hide"), _("Show"), _("Show"), self.id, producers
                    )
                    return msg_html
                else:
189
                    return '<div class="wrap-text">%s</div>' % ", ".join([p.short_profile_name
Patrick's avatar
Patrick committed
190 191 192 193 194
                                           for p in
                                           producer.Producer.objects.filter(
                                               producerinvoice__permanence_id=self.id).only(
                                               'short_profile_name')])
            else:
195
                return '<div class="wrap-text">%s</div>' % _("No offer")
Patrick's avatar
Patrick committed
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
        return "?"

    get_producers.short_description = (_("producers in this permanence"))
    get_producers.allow_tags = True

    def get_customers(self):
        if self.id is not None:
            if self.status in [
                PERMANENCE_OPENED, PERMANENCE_CLOSED, PERMANENCE_SEND
            ]:
                changelist_url = urlresolvers.reverse(
                    'admin:repanier_purchase_changelist',
                )
                link = []
                for ci in invoice.CustomerInvoice.objects.filter(permanence_id=self.id).select_related(
                    "customer").order_by('customer'):
                    if ci.is_order_confirm_send:
                        label = '%s (%s) %s' % (
                            ci.customer.short_basket_name, ci.get_total_price_with_tax(customer_who_pays=True),
                            ci.get_is_order_confirm_send_display())
                    else:
                        label = '%s (%s) %s' % (
                            ci.customer.short_basket_name, ci.total_price_with_tax,
                            ci.get_is_order_confirm_send_display())
220
                    # Important : no target="_blank"
Patrick's avatar
Patrick committed
221
                    link.append(
222
                        '<a href="%s?permanence=%d&customer=%d">%s</a>'
Patrick's avatar
Patrick committed
223 224 225 226 227 228 229 230 231 232 233
                        % (changelist_url, self.id, ci.customer_id, label.replace(' ', '&nbsp;')))
                customers = ", ".join(link)
            elif self.status == PERMANENCE_DONE:
                link = []
                for ci in invoice.CustomerInvoice.objects.filter(permanence_id=self.id).select_related(
                    "customer").order_by('customer'):
                    label = "%s (%s) %s" % (
                        ci.customer.short_basket_name,
                        ci.get_total_price_with_tax(customer_who_pays=True),
                        ci.get_is_order_confirm_send_display()
                    )
234 235
                    # Important : target="_blank" because the invoices must be displayed without the cms_toolbar
                    # Such that they can be accessed by the customer and by the staff
Patrick's avatar
Patrick committed
236
                    link.append(
237
                        '<a href="%s?customer=%d" target="_blank">%s</a>'
Patrick's avatar
Patrick committed
238 239 240 241 242 243 244 245 246 247 248 249 250 251
                        % (
                            urlresolvers.reverse('customer_invoice_view', args=(ci.id,)),
                            ci.customer_id,
                            label.replace(' ', '&nbsp;')
                        )
                    )
                customers = ", ".join(link)
            else:
                customers = ", ".join([c.short_basket_name
                                       for c in
                                       customer.Customer.objects.filter(customerinvoice__permanence_id=self.id).only(
                                           'short_basket_name')])
            if len(customers) > 0:
                msg_html = """
252
                    <div class="wrap-text"><button
Patrick's avatar
Patrick committed
253 254 255 256 257 258 259 260
                    onclick="django.jQuery('#id_get_customers_%d').toggle();
                        if(django.jQuery(this).html()=='%s'){
                            django.jQuery(this).html('%s')
                        }else{
                            django.jQuery(this).html('%s')
                        };
                        return false;"
                    >%s</button>
261
                    <div id="id_get_customers_%d" style="display:none;">%s</div></div>
Patrick's avatar
Patrick committed
262 263 264 265 266
                """ % (
                    self.id, _("Show"), _("Hide"), _("Show"), _("Show"), self.id, customers
                )
                return msg_html
            else:
267
                return '<div class="wrap-text">%s</div>' % _("No purchase")
Patrick's avatar
Patrick committed
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
        return "?"

    get_customers.short_description = (_("customers in this permanence"))
    get_customers.allow_tags = True

    def get_board(self):
        if self.id is not None:
            permanenceboard_set = permanenceboard.PermanenceBoard.objects.filter(
                permanence=self, permanence_role__rght=F('permanence_role__lft') + 1
            ).order_by("permanence_role__tree_id", "permanence_role__lft")
            first_board = True
            board = EMPTY_STRING
            if permanenceboard_set:
                for permanenceboard_row in permanenceboard_set:
                    r_link = EMPTY_STRING
                    r = permanenceboard_row.permanence_role
                    if r:
                        r_url = urlresolvers.reverse(
                            'admin:repanier_lut_permanencerole_change',
                            args=(r.id,)
                        )
                        r_link = '<a href="' + r_url + \
290
                                 '" target="_blank">' + r.short_name.replace(' ', '&nbsp;') + '</a>'
Patrick's avatar
Patrick committed
291 292 293 294 295 296 297 298
                    c_link = EMPTY_STRING
                    c = permanenceboard_row.customer
                    if c:
                        c_url = urlresolvers.reverse(
                            'admin:repanier_customer_change',
                            args=(c.id,)
                        )
                        c_link = '&nbsp;->&nbsp;<a href="' + c_url + \
299
                                 '" target="_blank">' + c.short_basket_name.replace(' ', '&nbsp;') + '</a>'
Patrick's avatar
Patrick committed
300 301 302 303 304 305 306
                    if not first_board:
                        board += '<br/>'
                    board += r_link + c_link
                    first_board = False
            if not first_board:
                # At least one role is defined in the permanence board
                msg_html = """
307
                    <div class="wrap-text"><button
Patrick's avatar
Patrick committed
308 309 310 311 312 313 314 315
                    onclick="django.jQuery('#id_get_board_%d').toggle();
                        if(django.jQuery(this).html()=='%s'){
                            django.jQuery(this).html('%s')
                        }else{
                            django.jQuery(this).html('%s')
                        };
                        return false;"
                    >%s</button>
316
                    <div id="id_get_board_%d" style="display:none;">%s</div></div>
Patrick's avatar
Patrick committed
317 318 319 320 321
                """ % (
                    self.id, _("Show"), _("Hide"), _("Show"), _("Show"), self.id, board
                )
                return msg_html
            else:
322
                return '<div class="wrap-text">%s</div>' % _("Empty board")
Patrick's avatar
Patrick committed
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 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 419
        return "?"

    get_board.short_description = (_("permanence board"))
    get_board.allow_tags = True

    def set_status(self, new_status, all_producers=True, producers_id=None, update_payment_date=False,
                   payment_date=None, allow_downgrade=True):
        if all_producers:
            now = timezone.now().date()
            self.is_updated_on = now
            self.status = new_status
            if self.highest_status < new_status:
                self.highest_status = new_status
            if update_payment_date:
                if payment_date is None:
                    self.payment_date = now
                else:
                    self.payment_date = payment_date
                self.save(
                    update_fields=['status', 'is_updated_on', 'highest_status', 'with_delivery_point', 'payment_date'])
            else:
                self.save(update_fields=['status', 'is_updated_on', 'with_delivery_point', 'highest_status'])
            menu_pool.clear()
            cache.clear()
            if new_status == PERMANENCE_WAIT_FOR_OPEN:
                self.with_delivery_point = deliveryboard.DeliveryBoard.objects.filter(
                    permanence_id=self.id
                ).order_by('?').exists()
                if self.with_delivery_point and not repanier.apps.REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS:
                    config = configuration.Configuration.objects.filter(id=DECIMAL_ONE).first()
                    # Important : Customer must confirm order if deliveries points are used
                    config.customers_must_confirm_orders = True
                    config.save()
                for a_producer in producer.Producer.objects.filter(
                        permanence=self.id
                ).only('id').order_by('?'):
                    # Create ProducerInvoice to be able to close those producer on demand
                    if not invoice.ProducerInvoice.objects.filter(
                            permanence_id=self.id,
                            producer_id=a_producer.id
                    ).order_by('?').exists():
                        invoice.ProducerInvoice.objects.create(
                            permanence_id=self.id,
                            producer_id=a_producer.id
                        )
            if self.with_delivery_point:
                qs = deliveryboard.DeliveryBoard.objects.filter(
                    permanence_id=self.id
                ).exclude(status=new_status).order_by('?')
                for delivery_point in qs:
                    if allow_downgrade or delivery_point.status < new_status:
                        # --> or delivery_point.status < new_status -->
                        # Set new status except if PERMANENCE_SEND, PERMANENCE_WAIT_FOR_SEND
                        #  -> PERMANENCE_CLOSED, PERMANENCE_WAIT_FOR_CLOSED
                        # This occur if directly sending order of a opened delivery point
                        delivery_point.set_status(new_status)
            invoice.CustomerInvoice.objects.filter(
                permanence_id=self.id,
                delivery__isnull=True
            ).order_by('?').update(
                status=new_status
            )
            purchase.Purchase.objects.filter(
                permanence_id=self.id,
                customer_invoice__delivery__isnull=True
            ).order_by('?').update(
                status=new_status)
            invoice.ProducerInvoice.objects.filter(
                permanence_id=self.id
            ).order_by('?').update(
                status=new_status
            )
            if update_payment_date:
                if payment_date is None:
                    self.payment_date = now
                else:
                    self.payment_date = payment_date
                self.save(
                    update_fields=['status', 'is_updated_on', 'highest_status', 'with_delivery_point', 'payment_date'])
            else:
                self.save(update_fields=['status', 'is_updated_on', 'with_delivery_point', 'highest_status'])
            menu_pool.clear()
            cache.clear()
        else:
            # /!\ If one delivery point has been closed, I may not close anymore by producer
            purchase.Purchase.objects.filter(permanence_id=self.id, producer__in=producers_id).order_by('?').update(
                status=new_status)
            invoice.ProducerInvoice.objects.filter(permanence_id=self.id, producer__in=producers_id).order_by(
                '?').update(status=new_status)

    def get_full_status_display(self):
        return get_full_status_display(self)

    get_full_status_display.short_description = (_("permanence_status"))
    get_full_status_display.allow_tags = True

    def get_permanence_display(self, with_status=True):
420 421 422 423
        permanence_display = '%s%s' % (
            repanier.apps.REPANIER_SETTINGS_PERMANENCE_ON_NAME,
            self.permanence_date.strftime(settings.DJANGO_SETTINGS_DATE)
        )
Patrick's avatar
Patrick committed
424
        try:
425 426
            if self.short_name:
                permanence_display = "%s" % self.short_name
Patrick's avatar
Patrick committed
427 428
        except TranslationDoesNotExist:
            pass
429

Patrick's avatar
Patrick committed
430 431 432 433 434 435 436 437 438 439 440
        if with_status:
            if self.with_delivery_point:
                if self.status == PERMANENCE_OPENED:
                    deliveries_count = 0
                else:
                    deliveries_qs = deliveryboard.DeliveryBoard.objects.filter(permanence_id=self.id,
                                                                               status=PERMANENCE_OPENED).order_by('?')
                    deliveries_count = deliveries_qs.count()
            else:
                deliveries_count = 0
            if deliveries_count == 0:
441 442
                return "%s - %s" % (permanence_display, self.get_status_display())
        return permanence_display
Patrick's avatar
Patrick committed
443 444

    def get_permanence_customer_display(self, with_status=True):
445
        permanence_display = self.get_permanence_display(with_status=False)
Patrick's avatar
Patrick committed
446 447 448 449 450 451 452 453 454 455 456 457 458 459
        if with_status:
            if self.with_delivery_point:
                if self.status == PERMANENCE_OPENED:
                    deliveries_count = 0
                else:
                    deliveries_qs = deliveryboard.DeliveryBoard.objects.filter(
                        permanence_id=self.id,
                        status=PERMANENCE_OPENED
                    ).order_by('?')
                    deliveries_count = deliveries_qs.count()
            else:
                deliveries_count = 0
            if deliveries_count == 0:
                if self.status != PERMANENCE_SEND:
460
                    return "%s - %s" % (permanence_display, self.get_status_display())
Patrick's avatar
Patrick committed
461
                else:
462 463
                    return "%s - %s" % (permanence_display, _('orders closed'))
        return permanence_display
Patrick's avatar
Patrick committed
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488

    def __str__(self):
        return self.get_permanence_display(with_status=False)

    class Meta:
        verbose_name = repanier.apps.REPANIER_SETTINGS_PERMANENCE_NAME or _("permanence")
        verbose_name_plural = repanier.apps.REPANIER_SETTINGS_PERMANENCES_NAME or _("permanences")

        index_together = [
            ["permanence_date"],
        ]


class PermanenceInPreparation(Permanence):
    class Meta:
        proxy = True
        verbose_name = _("X in preparation")
        verbose_name_plural = _("Xs in preparation")


class PermanenceDone(Permanence):
    class Meta:
        proxy = True
        verbose_name = _("X done")
        verbose_name_plural = _("Xs done")