Exemple #1
0
class LUT_ProductionMode(MPTTModel, TranslatableModel):
    parent = TreeForeignKey('self',
                            null=True,
                            blank=True,
                            related_name='children')
    translations = TranslatedFields(
        short_name=models.CharField(_("Short name"),
                                    max_length=50,
                                    default=EMPTY_STRING),
        description=HTMLField(_("Description"),
                              configuration='CKEDITOR_SETTINGS_MODEL2',
                              blank=True,
                              default=EMPTY_STRING),
    )
    picture2 = AjaxPictureField(verbose_name=_("Picture"),
                                null=True,
                                blank=True,
                                upload_to="label",
                                size=SIZE_XS)

    is_active = models.BooleanField(_("Active"), default=True)
    objects = LUT_ProductionModeManager()

    def __str__(self):
        # return self.short_name
        return self.safe_translation_getter('short_name',
                                            any_language=True,
                                            default=EMPTY_STRING)

    class Meta:
        verbose_name = _("Production mode")
        verbose_name_plural = _("Production modes")
Exemple #2
0
class Item(TranslatableModel):
    producer = models.ForeignKey('Producer',
                                 verbose_name=_("Producer"),
                                 on_delete=models.PROTECT)
    department_for_customer = models.ForeignKey('LUT_DepartmentForCustomer',
                                                verbose_name=_("Department"),
                                                blank=True,
                                                null=True,
                                                on_delete=models.PROTECT)

    picture2 = AjaxPictureField(verbose_name=_("Picture"),
                                null=True,
                                blank=True,
                                upload_to="product",
                                size=SIZE_L)
    reference = models.CharField(_("Reference"),
                                 max_length=36,
                                 blank=True,
                                 null=True)

    order_unit = models.CharField(
        max_length=3,
        choices=LUT_PRODUCT_ORDER_UNIT,
        default=PRODUCT_ORDER_UNIT_PC,
        verbose_name=_("Order unit"),
    )
    order_average_weight = models.DecimalField(
        _("Average weight / capacity"),
        default=DECIMAL_ZERO,
        max_digits=6,
        decimal_places=3,
        validators=[MinValueValidator(0)])

    producer_unit_price = ModelMoneyField(_("Producer unit price"),
                                          default=DECIMAL_ZERO,
                                          max_digits=8,
                                          decimal_places=2)
    customer_unit_price = ModelMoneyField(_("Customer unit price"),
                                          default=DECIMAL_ZERO,
                                          max_digits=8,
                                          decimal_places=2)
    producer_vat = ModelMoneyField(_("VAT"),
                                   default=DECIMAL_ZERO,
                                   max_digits=8,
                                   decimal_places=4)
    customer_vat = ModelMoneyField(_("VAT"),
                                   default=DECIMAL_ZERO,
                                   max_digits=8,
                                   decimal_places=4)
    unit_deposit = ModelMoneyField(
        _("Deposit"),
        help_text=_('Deposit to add to the original unit price'),
        default=DECIMAL_ZERO,
        max_digits=8,
        decimal_places=2,
        validators=[MinValueValidator(0)])
    vat_level = models.CharField(
        max_length=3,
        choices=LUT_ALL_VAT,  # settings.LUT_VAT,
        default=settings.DICT_VAT_DEFAULT,
        verbose_name=_("Tax level"))

    wrapped = models.BooleanField(_('Individually wrapped by the producer'),
                                  default=False)
    customer_minimum_order_quantity = models.DecimalField(
        _("Minimum order quantity"),
        default=DECIMAL_ONE,
        max_digits=6,
        decimal_places=3,
        validators=[MinValueValidator(0)])
    customer_increment_order_quantity = models.DecimalField(
        _("Then quantity of"),
        default=DECIMAL_ONE,
        max_digits=6,
        decimal_places=3,
        validators=[MinValueValidator(0)])
    customer_alert_order_quantity = models.DecimalField(
        _("Alert quantity"),
        default=LIMIT_ORDER_QTY_ITEM,
        max_digits=6,
        decimal_places=3,
        validators=[MinValueValidator(0)])
    producer_order_by_quantity = models.DecimalField(
        _("Producer order by quantity"),
        default=DECIMAL_ZERO,
        max_digits=6,
        decimal_places=3,
        validators=[MinValueValidator(0)])
    placement = models.CharField(
        max_length=3,
        choices=LUT_PRODUCT_PLACEMENT,
        default=PRODUCT_PLACEMENT_BASKET,
        verbose_name=_("Product placement"),
    )

    stock = models.DecimalField(_("Inventory"),
                                default=DECIMAL_MAX_STOCK,
                                max_digits=9,
                                decimal_places=3,
                                validators=[MinValueValidator(0)])
    limit_order_quantity_to_stock = models.BooleanField(
        _("Limit maximum order qty of the group to stock qty"), default=False)

    is_box = models.BooleanField(default=False)
    # is_membership_fee = models.BooleanField(_("is_membership_fee"), default=False)
    # may_order = models.BooleanField(_("may_order"), default=True)
    is_active = models.BooleanField(_("Active"), default=True)

    @property
    def email_offer_price_with_vat(self):
        offer_price = self.get_reference_price()
        if offer_price == EMPTY_STRING:
            offer_price = self.get_unit_price()
        return offer_price

    def set_from(self, source):
        self.is_active = source.is_active
        self.picture2 = source.picture2
        self.reference = source.reference
        self.department_for_customer_id = source.department_for_customer_id
        self.producer_id = source.producer_id
        self.order_unit = source.order_unit
        self.wrapped = source.wrapped
        self.order_average_weight = source.order_average_weight
        self.placement = source.placement
        self.vat_level = source.vat_level
        self.customer_unit_price = source.customer_unit_price
        self.customer_vat = source.customer_vat
        self.producer_unit_price = source.producer_unit_price
        self.producer_vat = source.producer_vat
        self.unit_deposit = source.unit_deposit
        self.limit_order_quantity_to_stock = source.limit_order_quantity_to_stock
        self.stock = source.stock
        self.customer_minimum_order_quantity = source.customer_minimum_order_quantity
        self.customer_increment_order_quantity = source.customer_increment_order_quantity
        self.customer_alert_order_quantity = source.customer_alert_order_quantity
        self.producer_order_by_quantity = source.producer_order_by_quantity
        self.is_box = source.is_box

    def recalculate_prices(self, producer_price_are_wo_vat,
                           is_resale_price_fixed, price_list_multiplier):
        vat = DICT_VAT[self.vat_level]
        vat_rate = vat[DICT_VAT_RATE]
        if producer_price_are_wo_vat:
            self.producer_vat.amount = (self.producer_unit_price.amount *
                                        vat_rate).quantize(FOUR_DECIMALS)
            if not is_resale_price_fixed:
                if self.order_unit < PRODUCT_ORDER_UNIT_DEPOSIT:
                    self.customer_unit_price.amount = (
                        self.producer_unit_price.amount *
                        price_list_multiplier).quantize(TWO_DECIMALS)
                else:
                    self.customer_unit_price = self.producer_unit_price
            self.customer_vat.amount = (self.customer_unit_price.amount *
                                        vat_rate).quantize(FOUR_DECIMALS)
            if not is_resale_price_fixed:
                self.customer_unit_price += self.customer_vat
        else:
            self.producer_vat.amount = self.producer_unit_price.amount - (
                self.producer_unit_price.amount /
                (DECIMAL_ONE + vat_rate)).quantize(FOUR_DECIMALS)
            if not is_resale_price_fixed:
                if self.order_unit < PRODUCT_ORDER_UNIT_DEPOSIT:
                    self.customer_unit_price.amount = (
                        self.producer_unit_price.amount *
                        price_list_multiplier).quantize(TWO_DECIMALS)
                else:
                    self.customer_unit_price = self.producer_unit_price

            self.customer_vat.amount = self.customer_unit_price.amount - (
                self.customer_unit_price.amount /
                (DECIMAL_ONE + vat_rate)).quantize(FOUR_DECIMALS)

    def get_unit_price(self, customer_price=True):
        if customer_price:
            unit_price = self.customer_unit_price
        else:
            unit_price = self.producer_unit_price
        if self.order_unit in [
                PRODUCT_ORDER_UNIT_KG, PRODUCT_ORDER_UNIT_PC_KG
        ]:
            return "{} {}".format(unit_price, _("/ kg"))
        elif self.order_unit == PRODUCT_ORDER_UNIT_LT:
            return "{} {}".format(unit_price, _("/ l"))
        elif self.order_unit not in [
                PRODUCT_ORDER_UNIT_PC_PRICE_KG, PRODUCT_ORDER_UNIT_PC_PRICE_LT,
                PRODUCT_ORDER_UNIT_PC_PRICE_PC
        ]:
            return "{} {}".format(unit_price, _("/ piece"))
        else:
            return "{}".format(unit_price)

    def get_reference_price(self, customer_price=True):
        if self.order_average_weight > DECIMAL_ZERO and self.order_average_weight != DECIMAL_ONE:
            if self.order_unit in [
                    PRODUCT_ORDER_UNIT_PC_PRICE_KG,
                    PRODUCT_ORDER_UNIT_PC_PRICE_LT,
                    PRODUCT_ORDER_UNIT_PC_PRICE_PC
            ]:
                if customer_price:
                    reference_price = self.customer_unit_price.amount / self.order_average_weight
                else:
                    reference_price = self.producer_unit_price.amount / self.order_average_weight
                reference_price = RepanierMoney(
                    reference_price.quantize(TWO_DECIMALS), 2)
                if self.order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_KG:
                    reference_unit = _("/ kg")
                elif self.order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_LT:
                    reference_unit = _("/ l")
                else:
                    reference_unit = _("/ pc")
                return "{} {}".format(reference_price, reference_unit)
            else:
                return EMPTY_STRING
        else:
            return EMPTY_STRING

    def get_display(self,
                    qty=0,
                    order_unit=PRODUCT_ORDER_UNIT_PC,
                    unit_price_amount=None,
                    for_customer=True,
                    for_order_select=False,
                    without_price_display=False):
        magnitude = None
        display_qty = True
        if order_unit == PRODUCT_ORDER_UNIT_KG:
            if qty == DECIMAL_ZERO:
                unit = EMPTY_STRING
            elif for_customer and qty < 1:
                unit = "{}".format(_('gr'))
                magnitude = 1000
            else:
                unit = "{}".format(_('kg'))
        elif order_unit == PRODUCT_ORDER_UNIT_LT:
            if qty == DECIMAL_ZERO:
                unit = EMPTY_STRING
            elif for_customer and qty < 1:
                unit = "{}".format(_('cl'))
                magnitude = 100
            else:
                unit = "{}".format(_('l'))
        elif order_unit in [
                PRODUCT_ORDER_UNIT_PC_KG, PRODUCT_ORDER_UNIT_PC_PRICE_KG
        ]:
            # display_qty = not (order_average_weight == 1 and order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_KG)
            average_weight = self.order_average_weight
            if for_customer:
                average_weight *= qty
            if order_unit == PRODUCT_ORDER_UNIT_PC_KG and unit_price_amount is not None:
                unit_price_amount *= self.order_average_weight
            if average_weight < 1:
                average_weight_unit = _('gr')
                average_weight *= 1000
            else:
                average_weight_unit = _('kg')
            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
            tilde = EMPTY_STRING
            if order_unit == PRODUCT_ORDER_UNIT_PC_KG:
                tilde = '~'
            if for_customer:
                if qty == DECIMAL_ZERO:
                    unit = EMPTY_STRING
                else:
                    if self.order_average_weight == 1 and order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_KG:
                        unit = "{}{} {}".format(
                            tilde, number_format(average_weight, decimal),
                            average_weight_unit)
                    else:
                        unit = "{}{}{}".format(
                            tilde, number_format(average_weight, decimal),
                            average_weight_unit)
            else:
                if qty == DECIMAL_ZERO:
                    unit = EMPTY_STRING
                else:
                    unit = "{}{}{}".format(
                        tilde, number_format(average_weight, decimal),
                        average_weight_unit)
        elif order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_LT:
            display_qty = self.order_average_weight != 1
            average_weight = self.order_average_weight
            if for_customer:
                average_weight *= qty
            if average_weight < 1:
                average_weight_unit = _('cl')
                average_weight *= 100
            else:
                average_weight_unit = _('l')
            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
            if for_customer:
                if qty == DECIMAL_ZERO:
                    unit = EMPTY_STRING
                else:
                    if display_qty:
                        unit = "{}{}".format(
                            number_format(average_weight, decimal),
                            average_weight_unit)
                    else:
                        unit = "{} {}".format(
                            number_format(average_weight, decimal),
                            average_weight_unit)
            else:
                if qty == DECIMAL_ZERO:
                    unit = EMPTY_STRING
                else:
                    unit = "{}{}".format(
                        number_format(average_weight, decimal),
                        average_weight_unit)
        elif order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_PC:
            display_qty = self.order_average_weight != 1
            average_weight = self.order_average_weight
            if for_customer:
                average_weight *= qty
                if qty == DECIMAL_ZERO:
                    unit = EMPTY_STRING
                else:
                    if average_weight < 2:
                        pc_pcs = _('pc')
                    else:
                        pc_pcs = _('pcs')
                    if display_qty:
                        unit = "{}{}".format(number_format(average_weight, 0),
                                             pc_pcs)
                    else:
                        unit = "{} {}".format(number_format(average_weight, 0),
                                              pc_pcs)
            else:
                if average_weight == DECIMAL_ZERO:
                    unit = EMPTY_STRING
                elif average_weight < 2:
                    unit = "{} {}".format(number_format(average_weight, 0),
                                          _('pc'))
                else:
                    unit = "{} {}".format(number_format(average_weight, 0),
                                          _('pcs'))
        else:
            if for_order_select:
                if qty == DECIMAL_ZERO:
                    unit = EMPTY_STRING
                elif qty < 2:
                    unit = "{}".format(_('unit'))
                else:
                    unit = "{}".format(_('units'))
            else:
                unit = EMPTY_STRING
        if unit_price_amount is not None:
            price_display = " = {}".format(
                RepanierMoney(unit_price_amount * qty))
        else:
            price_display = EMPTY_STRING
        if magnitude is not None:
            qty *= magnitude
        decimal = 3
        if qty == int(qty):
            decimal = 0
        elif qty * 10 == int(qty * 10):
            decimal = 1
        elif qty * 100 == int(qty * 100):
            decimal = 2
        if for_customer or for_order_select:
            if unit:
                if display_qty:
                    qty_display = "{} ({})".format(number_format(qty, decimal),
                                                   unit)
                else:
                    qty_display = "{}".format(unit)
            else:
                qty_display = "{}".format(number_format(qty, decimal))
        else:
            if unit:
                qty_display = "({})".format(unit)
            else:
                qty_display = EMPTY_STRING
        if without_price_display:
            return qty_display
        else:
            display = "{}{}".format(qty_display, price_display)
            return display

    def get_customer_alert_order_quantity(self):
        if settings.REPANIER_SETTINGS_STOCK and self.limit_order_quantity_to_stock:
            return "{}".format(_("Inventory"))
        return self.customer_alert_order_quantity

    get_customer_alert_order_quantity.short_description = (_("Alert quantity"))

    def get_long_name_with_producer_price(self):
        return self.get_long_name(customer_price=False)

    get_long_name_with_producer_price.short_description = (_("Long name"))
    get_long_name_with_producer_price.admin_order_field = 'translations__long_name'

    def get_qty_display(self):
        raise NotImplementedError

    def get_qty_and_price_display(self, customer_price=True):
        qty_display = self.get_qty_display()
        unit_price = self.get_unit_price(customer_price=customer_price)
        if len(qty_display) > 0:
            if self.unit_deposit.amount > DECIMAL_ZERO:
                return "{}; {} + ♻ {}".format(qty_display, unit_price,
                                              self.unit_deposit)
            else:
                return "{}; {}".format(qty_display, unit_price)
        else:
            if self.unit_deposit.amount > DECIMAL_ZERO:
                return "{} + ♻ {}".format(unit_price, self.unit_deposit)
            else:
                return "{}".format(unit_price)

    def get_long_name(self, customer_price=True):
        qty_and_price_display = self.get_qty_and_price_display(customer_price)
        if qty_and_price_display:
            result = "{} {}".format(
                self.safe_translation_getter('long_name', any_language=True),
                qty_and_price_display)
        else:
            result = "{}".format(
                self.safe_translation_getter('long_name', any_language=True))
        return result

    get_long_name.short_description = (_("Long name"))
    get_long_name.admin_order_field = 'translations__long_name'

    def get_long_name_with_producer(self):
        if self.id is not None:
            return "{}, {}".format(self.producer.short_profile_name,
                                   self.get_long_name())
        else:
            # Nedeed for django import export since django_import_export-0.4.5
            return 'N/A'

    get_long_name_with_producer.short_description = (_("Long name"))
    get_long_name_with_producer.allow_tags = False
    get_long_name_with_producer.admin_order_field = 'translations__long_name'

    def __str__(self):
        return EMPTY_STRING

    class Meta:
        abstract = True
Exemple #3
0
class Item(TranslatableModel):
    producer = models.ForeignKey('Producer',
                                 verbose_name=_("producer"),
                                 on_delete=models.PROTECT)
    department_for_customer = models.ForeignKey(
        'LUT_DepartmentForCustomer',
        verbose_name=_("department_for_customer"),
        blank=True,
        null=True,
        on_delete=models.PROTECT)

    picture2 = AjaxPictureField(verbose_name=_("picture"),
                                null=True,
                                blank=True,
                                upload_to="product",
                                size=SIZE_L)
    reference = models.CharField(_("reference"),
                                 max_length=36,
                                 blank=True,
                                 null=True)

    order_unit = models.CharField(
        max_length=3,
        choices=LUT_PRODUCT_ORDER_UNIT,
        default=PRODUCT_ORDER_UNIT_PC,
        verbose_name=_("order unit"),
    )
    order_average_weight = models.DecimalField(
        _("order_average_weight"),
        help_text=_(
            'if useful, average order weight (eg : 0,1 Kg [i.e. 100 gr], 3 Kg)'
        ),
        default=DECIMAL_ZERO,
        max_digits=6,
        decimal_places=3,
        validators=[MinValueValidator(0)])

    producer_unit_price = ModelMoneyField(_("producer unit price"),
                                          default=DECIMAL_ZERO,
                                          max_digits=8,
                                          decimal_places=2)
    customer_unit_price = ModelMoneyField(_("customer unit price"),
                                          default=DECIMAL_ZERO,
                                          max_digits=8,
                                          decimal_places=2)
    producer_vat = ModelMoneyField(_("vat"),
                                   default=DECIMAL_ZERO,
                                   max_digits=8,
                                   decimal_places=4)
    customer_vat = ModelMoneyField(_("vat"),
                                   default=DECIMAL_ZERO,
                                   max_digits=8,
                                   decimal_places=4)
    unit_deposit = ModelMoneyField(
        _("deposit"),
        help_text=_('deposit to add to the original unit price'),
        default=DECIMAL_ZERO,
        max_digits=8,
        decimal_places=2,
        validators=[MinValueValidator(0)])
    vat_level = models.CharField(
        max_length=3,
        choices=LUT_ALL_VAT,  # settings.LUT_VAT,
        default=settings.DICT_VAT_DEFAULT,
        verbose_name=_("tax"))

    wrapped = models.BooleanField(_('Individually wrapped by the producer'),
                                  default=False)
    customer_minimum_order_quantity = models.DecimalField(
        _("customer_minimum_order_quantity"),
        help_text=_(
            'minimum order qty (eg : 0,1 Kg [i.e. 100 gr], 1 piece, 3 Kg)'),
        default=DECIMAL_ONE,
        max_digits=6,
        decimal_places=3,
        validators=[MinValueValidator(0)])
    customer_increment_order_quantity = models.DecimalField(
        _("customer_increment_order_quantity"),
        help_text=_(
            'increment order qty (eg : 0,05 Kg [i.e. 50max 1 piece, 3 Kg)'),
        default=DECIMAL_ONE,
        max_digits=6,
        decimal_places=3,
        validators=[MinValueValidator(0)])
    customer_alert_order_quantity = models.DecimalField(
        _("customer_alert_order_quantity"),
        help_text=
        _('maximum order qty before alerting the customer to check (eg : 1,5 Kg, 12 pieces, 9 Kg)'
          ),
        default=LIMIT_ORDER_QTY_ITEM,
        max_digits=6,
        decimal_places=3,
        validators=[MinValueValidator(0)])
    producer_order_by_quantity = models.DecimalField(
        _("Producer order by quantity"),
        help_text=_('1,5 Kg [i.e. 1500 gr], 1 piece, 3 Kg)'),
        default=DECIMAL_ZERO,
        max_digits=6,
        decimal_places=3,
        validators=[MinValueValidator(0)])
    placement = models.CharField(
        max_length=3,
        choices=LUT_PRODUCT_PLACEMENT,
        default=PRODUCT_PLACEMENT_BASKET,
        verbose_name=_("product_placement"),
        help_text=
        _('used for helping to determine the order of preparation of this product'
          ))

    stock = models.DecimalField(_("Current stock"),
                                default=DECIMAL_MAX_STOCK,
                                max_digits=9,
                                decimal_places=3,
                                validators=[MinValueValidator(0)])
    limit_order_quantity_to_stock = models.BooleanField(
        _("limit maximum order qty of the group to stock qty"), default=False)

    is_box = models.BooleanField(_("is a box"), default=False)
    # is_membership_fee = models.BooleanField(_("is_membership_fee"), default=False)
    # may_order = models.BooleanField(_("may_order"), default=True)
    is_active = models.BooleanField(_("is_active"), default=True)

    @property
    def producer_unit_price_wo_tax(self):
        if self.producer_price_are_wo_vat:
            return self.producer_unit_price
        else:
            return self.producer_unit_price - self.producer_vat

    @property
    def email_offer_price_with_vat(self):
        offer_price = self.get_reference_price()
        if offer_price == EMPTY_STRING:
            offer_price = self.get_unit_price()
        return offer_price

    def set_from(self, source):
        self.is_active = source.is_active
        self.picture2 = source.picture2
        self.reference = source.reference
        self.department_for_customer_id = source.department_for_customer_id
        self.producer_id = source.producer_id
        self.order_unit = source.order_unit
        self.wrapped = source.wrapped
        self.order_average_weight = source.order_average_weight
        self.placement = source.placement
        self.vat_level = source.vat_level
        self.customer_unit_price = source.customer_unit_price
        self.customer_vat = source.customer_vat
        self.producer_unit_price = source.producer_unit_price
        self.producer_vat = source.producer_vat
        self.unit_deposit = source.unit_deposit
        self.limit_order_quantity_to_stock = source.limit_order_quantity_to_stock
        self.stock = source.stock
        self.customer_minimum_order_quantity = source.customer_minimum_order_quantity
        self.customer_increment_order_quantity = source.customer_increment_order_quantity
        self.customer_alert_order_quantity = source.customer_alert_order_quantity
        self.producer_order_by_quantity = source.producer_order_by_quantity
        self.is_box = source.is_box

    def recalculate_prices(self, producer_price_are_wo_vat,
                           is_resale_price_fixed, price_list_multiplier):
        getcontext().rounding = ROUND_HALF_UP
        vat = DICT_VAT[self.vat_level]
        vat_rate = vat[DICT_VAT_RATE]
        if producer_price_are_wo_vat:
            self.producer_vat.amount = (self.producer_unit_price.amount *
                                        vat_rate).quantize(FOUR_DECIMALS)
            if not is_resale_price_fixed:
                if self.order_unit < PRODUCT_ORDER_UNIT_DEPOSIT:
                    self.customer_unit_price.amount = (
                        self.producer_unit_price.amount *
                        price_list_multiplier).quantize(TWO_DECIMALS)
                else:
                    self.customer_unit_price = self.producer_unit_price
            self.customer_vat.amount = (self.customer_unit_price.amount *
                                        vat_rate).quantize(FOUR_DECIMALS)
            if not is_resale_price_fixed:
                self.customer_unit_price += self.customer_vat
        else:
            self.producer_vat.amount = self.producer_unit_price.amount - (
                self.producer_unit_price.amount /
                (DECIMAL_ONE + vat_rate)).quantize(FOUR_DECIMALS)
            if not is_resale_price_fixed:
                if self.order_unit < PRODUCT_ORDER_UNIT_DEPOSIT:
                    self.customer_unit_price.amount = (
                        self.producer_unit_price.amount *
                        price_list_multiplier).quantize(TWO_DECIMALS)
                else:
                    self.customer_unit_price = self.producer_unit_price

            self.customer_vat.amount = self.customer_unit_price.amount - (
                self.customer_unit_price.amount /
                (DECIMAL_ONE + vat_rate)).quantize(FOUR_DECIMALS)

    def get_unit_price(self, customer_price=True):
        if customer_price:
            unit_price = self.customer_unit_price
        else:
            unit_price = self.producer_unit_price
        if self.order_unit in [
                PRODUCT_ORDER_UNIT_KG, PRODUCT_ORDER_UNIT_PC_KG
        ]:
            return "%s %s" % (unit_price, _("/ kg"))
        elif self.order_unit == PRODUCT_ORDER_UNIT_LT:
            return "%s %s" % (unit_price, _("/ l"))
        elif self.order_unit not in [
                PRODUCT_ORDER_UNIT_PC_PRICE_KG, PRODUCT_ORDER_UNIT_PC_PRICE_LT,
                PRODUCT_ORDER_UNIT_PC_PRICE_PC
        ]:
            return "%s %s" % (unit_price, _("/ piece"))
        else:
            return "%s" % (unit_price, )

    def get_reference_price(self, customer_price=True):
        if self.order_average_weight > DECIMAL_ZERO and self.order_average_weight != DECIMAL_ONE:
            if self.order_unit in [
                    PRODUCT_ORDER_UNIT_PC_PRICE_KG,
                    PRODUCT_ORDER_UNIT_PC_PRICE_LT,
                    PRODUCT_ORDER_UNIT_PC_PRICE_PC
            ]:
                if customer_price:
                    reference_price = self.customer_unit_price.amount / self.order_average_weight
                else:
                    reference_price = self.producer_unit_price.amount / self.order_average_weight
                reference_price = RepanierMoney(
                    reference_price.quantize(TWO_DECIMALS), 2)
                if self.order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_KG:
                    reference_unit = _("/ kg")
                elif self.order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_LT:
                    reference_unit = _("/ l")
                else:
                    reference_unit = _("/ pc")
                return "%s %s" % (reference_price, reference_unit)
            else:
                return EMPTY_STRING
        else:
            return EMPTY_STRING

    def get_display(self,
                    qty=0,
                    order_unit=PRODUCT_ORDER_UNIT_PC,
                    unit_price_amount=None,
                    for_customer=True,
                    for_order_select=False,
                    without_price_display=False):
        magnitude = None
        display_qty = True
        if order_unit == PRODUCT_ORDER_UNIT_KG:
            if qty == DECIMAL_ZERO:
                unit = EMPTY_STRING
            elif for_customer and qty < 1:
                unit = "%s" % (_('gr'))
                magnitude = 1000
            else:
                unit = "%s" % (_('kg'))
        elif order_unit == PRODUCT_ORDER_UNIT_LT:
            if qty == DECIMAL_ZERO:
                unit = EMPTY_STRING
            elif for_customer and qty < 1:
                unit = "%s" % (_('cl'))
                magnitude = 100
            else:
                unit = "%s" % (_('l'))
        elif order_unit in [
                PRODUCT_ORDER_UNIT_PC_KG, PRODUCT_ORDER_UNIT_PC_PRICE_KG
        ]:
            # display_qty = not (order_average_weight == 1 and order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_KG)
            average_weight = self.order_average_weight
            if for_customer:
                average_weight *= qty
            if order_unit == PRODUCT_ORDER_UNIT_PC_KG and unit_price_amount is not None:
                unit_price_amount *= self.order_average_weight
            if average_weight < 1:
                average_weight_unit = _('gr')
                average_weight *= 1000
            else:
                average_weight_unit = _('kg')
            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
            tilde = EMPTY_STRING
            if order_unit == PRODUCT_ORDER_UNIT_PC_KG:
                tilde = '~'
            if for_customer:
                if qty == DECIMAL_ZERO:
                    unit = EMPTY_STRING
                else:
                    if self.order_average_weight == 1 and order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_KG:
                        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)
            else:
                if qty == DECIMAL_ZERO:
                    unit = EMPTY_STRING
                else:
                    unit = "%s%s%s" % (tilde,
                                       number_format(average_weight, decimal),
                                       average_weight_unit)
        elif order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_LT:
            display_qty = self.order_average_weight != 1
            average_weight = self.order_average_weight
            if for_customer:
                average_weight *= qty
            if average_weight < 1:
                average_weight_unit = _('cl')
                average_weight *= 100
            else:
                average_weight_unit = _('l')
            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
            if for_customer:
                if qty == DECIMAL_ZERO:
                    unit = EMPTY_STRING
                else:
                    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)
            else:
                if qty == DECIMAL_ZERO:
                    unit = EMPTY_STRING
                else:
                    unit = "%s%s" % (number_format(
                        average_weight, decimal), average_weight_unit)
        elif order_unit == PRODUCT_ORDER_UNIT_PC_PRICE_PC:
            display_qty = self.order_average_weight != 1
            average_weight = self.order_average_weight
            if for_customer:
                average_weight *= qty
                if qty == DECIMAL_ZERO:
                    unit = EMPTY_STRING
                else:
                    if average_weight < 2:
                        pc_pcs = _('pc')
                    else:
                        pc_pcs = _('pcs')
                    if display_qty:
                        unit = "%s%s" % (number_format(average_weight,
                                                       0), pc_pcs)
                    else:
                        unit = "%s %s" % (number_format(average_weight,
                                                        0), pc_pcs)
            else:
                if average_weight == DECIMAL_ZERO:
                    unit = EMPTY_STRING
                elif average_weight < 2:
                    unit = '%s %s' % (number_format(average_weight,
                                                    0), _('pc'))
                else:
                    unit = '%s %s' % (number_format(average_weight,
                                                    0), _('pcs'))
        else:
            if for_order_select:
                if qty == DECIMAL_ZERO:
                    unit = EMPTY_STRING
                elif qty < 2:
                    unit = "%s" % (_('unit'))
                else:
                    unit = "%s" % (_('units'))
            else:
                unit = EMPTY_STRING
        if unit_price_amount is not None:
            price_display = " = %s" % RepanierMoney(unit_price_amount * qty)
        else:
            price_display = EMPTY_STRING
        if magnitude is not None:
            qty *= magnitude
        decimal = 3
        if qty == int(qty):
            decimal = 0
        elif qty * 10 == int(qty * 10):
            decimal = 1
        elif qty * 100 == int(qty * 100):
            decimal = 2
        if for_customer or for_order_select:
            if unit:
                if display_qty:
                    qty_display = "%s (%s)" % (number_format(qty,
                                                             decimal), unit)
                else:
                    qty_display = "%s" % unit
            else:
                qty_display = "%s" % number_format(qty, decimal)
        else:
            if unit:
                qty_display = "(%s)" % unit
            else:
                qty_display = EMPTY_STRING
        if without_price_display:
            return qty_display
        else:
            display = "%s%s" % (qty_display, price_display)
            return display

    def get_qty_display(self, is_quantity_invoiced=False):
        if self.is_box:
            # To avoid unicode error in email_offer.send_open_order
            qty_display = BOX_UNICODE
        else:
            if is_quantity_invoiced and self.order_unit == PRODUCT_ORDER_UNIT_PC_KG:
                qty_display = self.get_display(
                    qty=1,
                    order_unit=PRODUCT_ORDER_UNIT_KG,
                    for_customer=False,
                    without_price_display=True)
            else:
                qty_display = self.get_display(qty=1,
                                               order_unit=self.order_unit,
                                               for_customer=False,
                                               without_price_display=True)
        return qty_display

    def get_qty_and_price_display(self,
                                  is_quantity_invoiced=False,
                                  customer_price=True):
        qty_display = self.get_qty_display(is_quantity_invoiced)
        unit_price = self.get_unit_price(customer_price=customer_price)
        if len(qty_display) > 0:
            if self.unit_deposit.amount > DECIMAL_ZERO:
                return '%s; %s + ♻ %s' % (qty_display, unit_price,
                                          self.unit_deposit)
            else:
                return '%s; %s' % (qty_display, unit_price)
        else:
            if self.unit_deposit.amount > DECIMAL_ZERO:
                return '%s + ♻ %s' % (unit_price, self.unit_deposit)
            else:
                return '%s' % unit_price

    def get_customer_alert_order_quantity(self):
        if self.limit_order_quantity_to_stock:
            return "%s" % _("Current stock")
        return self.customer_alert_order_quantity

    get_customer_alert_order_quantity.short_description = (
        _("customer_alert_order_quantity"))
    get_customer_alert_order_quantity.allow_tags = False

    def get_order_name(self):
        qty_display = self.get_qty_display()
        if qty_display:
            return '%s %s' % (self.long_name, qty_display)
        return '%s' % self.long_name

    def get_long_name_with_producer_price(self):
        return self.get_long_name(customer_price=False)

    get_long_name_with_producer_price.short_description = (_("Long name"))
    get_long_name_with_producer_price.allow_tags = False
    get_long_name_with_producer_price.admin_order_field = 'translations__long_name'

    def get_long_name(self,
                      is_quantity_invoiced=False,
                      customer_price=True,
                      with_box_unicode=True):
        qty_and_price_display = self.get_qty_and_price_display(
            is_quantity_invoiced, customer_price)
        if qty_and_price_display:
            result = '%s %s' % (self.long_name, qty_and_price_display)
        else:
            result = '%s' % self.long_name
        return result

    get_long_name.short_description = (_("Long name"))
    get_long_name.allow_tags = False
    get_long_name.admin_order_field = 'translations__long_name'

    def get_long_name_with_producer(self, is_quantity_invoiced=False):
        if self.id is not None:
            return '%s, %s' % (self.producer.short_profile_name,
                               self.get_long_name(
                                   is_quantity_invoiced=is_quantity_invoiced))
        else:
            # Nedeed for django import export since django_import_export-0.4.5
            return 'N/A'

    get_long_name_with_producer.short_description = (_("Long name"))
    get_long_name_with_producer.allow_tags = False
    get_long_name_with_producer.admin_order_field = 'translations__long_name'

    def __str__(self):
        return self.display()

    class Meta:
        abstract = True
Exemple #4
0
class Customer(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL)
    login_attempt_counter = models.DecimalField(
        _("login attempt counter"),
        default=DECIMAL_ZERO, max_digits=2, decimal_places=0)

    short_basket_name = models.CharField(
        _("Short name"), max_length=25, null=False, default=EMPTY_STRING,
        db_index=True, unique=True)
    long_basket_name = models.CharField(
        _("Long name"), max_length=100, null=True, default=EMPTY_STRING)
    email2 = models.EmailField(
        _("secondary email"), null=True, blank=True, default=EMPTY_STRING)
    language = models.CharField(
        max_length=5,
        choices=settings.LANGUAGES,
        default=settings.LANGUAGE_CODE,
        verbose_name=_("language"))

    picture = AjaxPictureField(
        verbose_name=_("picture"),
        null=True, blank=True,
        upload_to="customer", size=SIZE_S)
    phone1 = models.CharField(
        _("phone1"),
        max_length=25,
        null=True, blank=True, default=EMPTY_STRING)
    phone2 = models.CharField(
        _("phone2"), max_length=25, null=True, blank=True, default=EMPTY_STRING)
    bank_account1 = models.CharField(_("main bank account"), max_length=100, null=True, blank=True,
                                     default=EMPTY_STRING)
    bank_account2 = models.CharField(_("secondary bank account"), max_length=100, null=True, blank=True,
                                     default=EMPTY_STRING)
    vat_id = models.CharField(
        _("vat_id"), max_length=20, null=True, blank=True, default=EMPTY_STRING)
    address = models.TextField(
        _("address"), null=True, blank=True, default=EMPTY_STRING)
    city = models.CharField(
        _("city"), max_length=50, null=True, blank=True, default=EMPTY_STRING)
    about_me = models.TextField(
        _("about me"), null=True, blank=True, default=EMPTY_STRING)
    memo = models.TextField(
        _("memo"), null=True, blank=True, default=EMPTY_STRING)
    accept_mails_from_members = models.BooleanField(
        _("show my mail to other members"), default=False)
    accept_phone_call_from_members = models.BooleanField(
        _("show my phone to other members"), default=False)
    membership_fee_valid_until = models.DateField(
        _("membership fee valid until"),
        default=datetime.date.today
    )
    # If this customer is member of a closed group, the customer.price_list_multiplier is not used
    # Invoices are sent to the consumer responsible of the group who is
    # also responsible for collecting the payments.
    # The LUT_DeliveryPoint.price_list_multiplier will be used when invoicing the consumer responsible
    # At this stage, the link between the customer invoice and this customer responsible is made with
    # CustomerInvoice.customer_charged
    price_list_multiplier = models.DecimalField(
        _("Customer price list multiplier"),
        help_text=_("This multiplier is applied to each product ordered by this customer."),
        default=DECIMAL_ONE, max_digits=5, decimal_places=4, blank=True,
        validators=[MinValueValidator(0)])

    password_reset_on = models.DateTimeField(
        _("password_reset_on"), null=True, blank=True, default=None)
    date_balance = models.DateField(
        _("date_balance"), default=datetime.date.today)
    balance = ModelMoneyField(
        _("balance"), max_digits=8, decimal_places=2, default=DECIMAL_ZERO)
    # The initial balance is needed to compute the invoice control list
    initial_balance = ModelMoneyField(
        _("initial balance"), max_digits=8, decimal_places=2, default=DECIMAL_ZERO)
    represent_this_buyinggroup = models.BooleanField(
        _("represent_this_buyinggroup"), default=False)
    delivery_point = models.ForeignKey(
        'LUT_DeliveryPoint',
        verbose_name=_("delivery point"),
        blank=True, null=True, default=None)
    is_active = models.BooleanField(_("is_active"), default=True)
    is_group = models.BooleanField(_("is a group"), default=False)
    may_order = models.BooleanField(_("may_order"), default=True)
    valid_email = models.NullBooleanField(_("valid_email"), default=None)
    subscribe_to_email = models.BooleanField(_("subscribe to email"), default=True)
    preparation_order = models.IntegerField(null=True, blank=True, default=0)

    def get_admin_date_balance(self):
        return timezone.now().date().strftime(settings.DJANGO_SETTINGS_DATE)

    get_admin_date_balance.short_description = (_("date_balance"))
    get_admin_date_balance.allow_tags = False

    def get_admin_date_joined(self):
        return self.user.date_joined.strftime(settings.DJANGO_SETTINGS_DATE)

    get_admin_date_joined.short_description = _("date joined")
    get_admin_date_joined.allow_tags = False

    def get_admin_balance(self):
        return self.balance + self.get_bank_not_invoiced() - self.get_order_not_invoiced()

    get_admin_balance.short_description = (_("balance"))
    get_admin_balance.allow_tags = False

    def get_order_not_invoiced(self):
        from repanier.apps import REPANIER_SETTINGS_INVOICE
        if REPANIER_SETTINGS_INVOICE:
            result_set = CustomerInvoice.objects.filter(
                customer_id=self.id,
                status__gte=PERMANENCE_OPENED,
                status__lte=PERMANENCE_SEND,
                customer_charged_id=self.id
            ).order_by('?').aggregate(Sum('total_price_with_tax'), Sum('delta_price_with_tax'), Sum('delta_transport'))
            if result_set["total_price_with_tax__sum"] is not None:
                order_not_invoiced = RepanierMoney(result_set["total_price_with_tax__sum"])
            else:
                order_not_invoiced = REPANIER_MONEY_ZERO
            if result_set["delta_price_with_tax__sum"] is not None:
                order_not_invoiced += RepanierMoney(result_set["delta_price_with_tax__sum"])
            if result_set["delta_transport__sum"] is not None:
                order_not_invoiced += RepanierMoney(result_set["delta_transport__sum"])
        else:
            order_not_invoiced = REPANIER_MONEY_ZERO
        return order_not_invoiced

    def get_bank_not_invoiced(self):
        from repanier.apps import REPANIER_SETTINGS_INVOICE
        if REPANIER_SETTINGS_INVOICE:
            result_set = BankAccount.objects.filter(
                customer_id=self.id, customer_invoice__isnull=True
            ).order_by('?').aggregate(Sum('bank_amount_in'), Sum('bank_amount_out'))
            if result_set["bank_amount_in__sum"] is not None:
                bank_in = RepanierMoney(result_set["bank_amount_in__sum"])
            else:
                bank_in = REPANIER_MONEY_ZERO
            if result_set["bank_amount_out__sum"] is not None:
                bank_out = RepanierMoney(result_set["bank_amount_out__sum"])
            else:
                bank_out = REPANIER_MONEY_ZERO
            bank_not_invoiced = bank_in - bank_out
        else:
            bank_not_invoiced = REPANIER_MONEY_ZERO
        return bank_not_invoiced

    def get_balance(self):
        last_customer_invoice = CustomerInvoice.objects.filter(
            customer_id=self.id, invoice_sort_order__isnull=False
        ).order_by('?')
        balance = self.get_admin_balance()
        if last_customer_invoice.exists():
            if balance.amount >= 30:
                return '<a href="' + urlresolvers.reverse('customer_invoice_view', args=(0,)) + '?customer=' + str(
                    self.id) + '" class="btn" target="_blank" >' + (
                           '<span style="color:#32CD32">%s</span>' % (balance,)) + '</a>'
            elif balance.amount >= -10:
                return '<a href="' + urlresolvers.reverse('customer_invoice_view', args=(0,)) + '?customer=' + str(
                    self.id) + '" class="btn" target="_blank" >' + (
                           '<span style="color:#696969">%s</span>' % (balance,)) + '</a>'
            else:
                return '<a href="' + urlresolvers.reverse('customer_invoice_view', args=(0,)) + '?customer=' + str(
                    self.id) + '" class="btn" target="_blank" >' + (
                           '<span style="color:red">%s</span>' % (balance,)) + '</a>'
        else:
            if balance.amount >= 30:
                return '<span style="color:#32CD32">%s</span>' % (balance,)
            elif balance.amount >= -10:
                return '<span style="color:#696969">%s</span>' % (balance,)
            else:
                return '<span style="color:red">%s</span>' % (balance,)

    get_balance.short_description = _("balance")
    get_balance.allow_tags = True
    get_balance.admin_order_field = 'balance'

    def get_on_hold_movement_html(self, bank_not_invoiced=None, order_not_invoiced=None, total_price_with_tax=REPANIER_MONEY_ZERO):
        from repanier.apps import REPANIER_SETTINGS_INVOICE
        if REPANIER_SETTINGS_INVOICE:
            bank_not_invoiced = bank_not_invoiced if bank_not_invoiced is not None else self.get_bank_not_invoiced()
            order_not_invoiced = order_not_invoiced if order_not_invoiced is not None else self.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
                        }
            else:
                customer_on_hold_movement = \
                    _(
                        'This balance does not take account of any unrecognized payments %(bank)s.') % {
                        'bank': bank_not_invoiced
                    }
            customer_on_hold_movement = mark_safe(customer_on_hold_movement)
        else:
            customer_on_hold_movement = EMPTY_STRING

        return customer_on_hold_movement

    def get_last_membership_fee(self):
        from repanier.models.purchase import Purchase

        last_membership_fee = Purchase.objects.filter(
            customer_id=self.id,
            offer_item__order_unit=PRODUCT_ORDER_UNIT_MEMBERSHIP_FEE
        ).order_by("-id")
        if last_membership_fee.exists():
            return last_membership_fee.first().selling_price

    get_last_membership_fee.short_description = _("last membership fee")
    get_last_membership_fee.allow_tags = False

    def last_membership_fee_date(self):
        from repanier.models.purchase import Purchase

        last_membership_fee = Purchase.objects.filter(
            customer_id=self.id,
            offer_item__order_unit=PRODUCT_ORDER_UNIT_MEMBERSHIP_FEE
        ).order_by("-id").prefetch_related("customer_invoice")
        if last_membership_fee.exists():
            return last_membership_fee.first().customer_invoice.date_balance

    last_membership_fee_date.short_description = _("last membership fee date")
    last_membership_fee_date.allow_tags = False

    def get_last_membership_fee_date(self):
        # Format it for the admin
        # Don't format it form import/export
        last_membership_fee_date = self.last_membership_fee_date()
        if last_membership_fee_date is not None:
            return last_membership_fee_date.strftime(settings.DJANGO_SETTINGS_DATE)
        return EMPTY_STRING

    get_last_membership_fee_date.short_description = _("last membership fee date")
    get_last_membership_fee_date.allow_tags = False

    def get_participation(self):
        now = timezone.now()
        return PermanenceBoard.objects.filter(
            customer_id=self.id,
            permanence_date__gte=now - datetime.timedelta(days=ONE_YEAR),
            permanence_date__lt=now,
            permanence_role__is_counted_as_participation=True
        ).order_by('?').count()

    get_participation.short_description = _("participation")
    get_participation.allow_tags = False

    def get_purchase(self):
        now = timezone.now()
        # Do not count invoice having only products free of charge
        return CustomerInvoice.objects.filter(
            customer_id=self.id,
            total_price_with_tax__gt=DECIMAL_ZERO,
            date_balance__gte=now - datetime.timedelta(ONE_YEAR)
        ).count()

    get_purchase.short_description = _("purchase")
    get_purchase.allow_tags = False

    def my_order_confirmation_email_send_to(self):
        from repanier.apps import REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS

        if self.email2:
            to_email = (self.user.email, self.email2)
        else:
            to_email = (self.user.email,)
        sent_to = ", ".join(to_email) if to_email is not None else EMPTY_STRING
        if REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS:
            msg_confirmation = _(
                "Your order is confirmed. An email containing this order summary has been sent to %s.") % sent_to
        else:
            msg_confirmation = _("An email containing this order summary has been sent to %s.") % sent_to
        return msg_confirmation

    @property
    def who_is_who_display(self):
        return self.picture or self.accept_mails_from_members or self.accept_phone_call_from_members \
               or (self.about_me is not None and len(self.about_me.strip()) > 1)

    def get_unsubscribe_mail_footer(self):
        return '<br/><br/><hr/><br/><a href="%s">%s</a>' % (self._get_unsubscribe_link(), _("Unsubscribe to emails"))

    def _get_unsubscribe_link(self):
        customer_id, token = self.make_token().split(":", 1)
        return "https://%s%s" % (
            settings.ALLOWED_HOSTS[0],
            reverse(
                'unsubscribe_view',
                kwargs={'customer_id': customer_id, 'token': token, }
            )
        )

    def make_token(self):
        return TimestampSigner().sign(self.id)

    def check_token(self, token):
        try:
            key = '%s:%s' % (self.id, token)
            TimestampSigner().unsign(key, max_age=60 * 60 * 48)  # Valid for 2 days
        except (BadSignature, SignatureExpired):
            return False
        return True

    def __str__(self):
        return self.short_basket_name

    class Meta:
        verbose_name = _("customer")
        verbose_name_plural = _("customers")
        ordering = ("short_basket_name",)
        index_together = [
            ["user", "is_active", "may_order"],
        ]
Exemple #5
0
class Producer(models.Model):
    short_profile_name = models.CharField(_("Short name"),
                                          max_length=25,
                                          null=False,
                                          default=EMPTY_STRING,
                                          db_index=True,
                                          unique=True)
    long_profile_name = models.CharField(_("Long name"),
                                         max_length=100,
                                         null=True,
                                         default=EMPTY_STRING)
    email = models.EmailField(_("Email"),
                              null=True,
                              blank=True,
                              default=EMPTY_STRING)
    email2 = models.EmailField(_("Secondary email"),
                               null=True,
                               blank=True,
                               default=EMPTY_STRING)
    email3 = models.EmailField(_("Secondary email"),
                               null=True,
                               blank=True,
                               default=EMPTY_STRING)
    language = models.CharField(max_length=5,
                                choices=settings.LANGUAGES,
                                default=settings.LANGUAGE_CODE,
                                verbose_name=_("Language"))
    picture = AjaxPictureField(verbose_name=_("Picture"),
                               null=True,
                               blank=True,
                               upload_to="producer",
                               size=SIZE_L)
    phone1 = models.CharField(_("Phone1"),
                              max_length=25,
                              null=True,
                              blank=True,
                              default=EMPTY_STRING)
    phone2 = models.CharField(_("Phone2"),
                              max_length=25,
                              null=True,
                              blank=True,
                              default=EMPTY_STRING)
    bank_account = models.CharField(_("Bank account"),
                                    max_length=100,
                                    null=True,
                                    blank=True,
                                    default=EMPTY_STRING)
    vat_id = models.CharField(_("VAT id"),
                              max_length=20,
                              null=True,
                              blank=True,
                              default=EMPTY_STRING)
    fax = models.CharField(_("Fax"),
                           max_length=100,
                           null=True,
                           blank=True,
                           default=EMPTY_STRING)
    address = models.TextField(_("Address"),
                               null=True,
                               blank=True,
                               default=EMPTY_STRING)
    city = models.CharField(_("City"),
                            max_length=50,
                            null=True,
                            blank=True,
                            default=EMPTY_STRING)
    memo = models.TextField(_("Memo"),
                            null=True,
                            blank=True,
                            default=EMPTY_STRING)
    reference_site = models.URLField(_("Reference site"),
                                     null=True,
                                     blank=True,
                                     default=EMPTY_STRING)
    web_services_activated = models.BooleanField(_('Web services activated'),
                                                 default=False)
    # uuid used to access to producer invoices without login
    uuid = models.CharField("uuid",
                            max_length=36,
                            null=True,
                            default=EMPTY_STRING,
                            db_index=True)
    offer_uuid = models.CharField("uuid",
                                  max_length=36,
                                  null=True,
                                  default=EMPTY_STRING,
                                  db_index=True)
    offer_filled = models.BooleanField(_("Offer filled"), default=False)
    invoice_by_basket = models.BooleanField(_("Invoice by basket"),
                                            default=False)
    manage_replenishment = models.BooleanField(_("Manage replenishment"),
                                               default=False)
    producer_pre_opening = models.BooleanField(_("Pre-open the orders"),
                                               default=False)
    producer_price_are_wo_vat = models.BooleanField(
        _("Producer price are wo vat"), default=False)
    sort_products_by_reference = models.BooleanField(
        _("Sort products by reference"), default=False)

    price_list_multiplier = models.DecimalField(
        _("Coefficient applied to the producer tariff to calculate the consumer tariff"
          ),
        help_text=
        _("This multiplier is applied to each price automatically imported/pushed."
          ),
        default=DECIMAL_ONE,
        max_digits=5,
        decimal_places=4,
        blank=True,
        validators=[MinValueValidator(0)])
    is_resale_price_fixed = models.BooleanField(
        _("The resale price is set by the producer"), default=False)
    minimum_order_value = ModelMoneyField(
        _("Minimum order value"),
        help_text=_("0 mean : no minimum order value."),
        max_digits=8,
        decimal_places=2,
        default=DECIMAL_ZERO,
        validators=[MinValueValidator(0)])

    date_balance = models.DateField(_("Date_balance"),
                                    default=datetime.date.today)
    balance = ModelMoneyField(_("Balance"),
                              max_digits=8,
                              decimal_places=2,
                              default=DECIMAL_ZERO)
    initial_balance = ModelMoneyField(_("Initial balance"),
                                      max_digits=8,
                                      decimal_places=2,
                                      default=DECIMAL_ZERO)
    represent_this_buyinggroup = models.BooleanField(
        _("Represent this buyinggroup"), default=False)
    is_active = models.BooleanField(_("Active"), default=True)
    # This indicate that the user record data have been replaced with anonymous data in application of GDPR
    is_anonymized = models.BooleanField(default=False)

    @classmethod
    def get_or_create_group(cls):
        producer_buyinggroup = Producer.objects.filter(
            represent_this_buyinggroup=True).order_by('?').first()
        if producer_buyinggroup is None:
            long_name = settings.REPANIER_SETTINGS_GROUP_NAME
            short_name = long_name[:25]
            producer_buyinggroup = Producer.objects.create(
                short_profile_name=short_name,
                long_profile_name=long_name,
                phone1=settings.REPANIER_SETTINGS_COORDINATOR_PHONE,
                represent_this_buyinggroup=True)
            # Create this to also prevent the deletion of the producer representing the buying group
            membership_fee_product = Product.objects.filter(
                order_unit=PRODUCT_ORDER_UNIT_MEMBERSHIP_FEE,
                is_active=True).order_by('?')
            if not membership_fee_product.exists():
                membership_fee_product = Product.objects.create(
                    producer_id=producer_buyinggroup.id,
                    order_unit=PRODUCT_ORDER_UNIT_MEMBERSHIP_FEE,
                    vat_level=VAT_100)
                cur_language = translation.get_language()
                for language in settings.PARLER_LANGUAGES[settings.SITE_ID]:
                    language_code = language["code"]
                    translation.activate(language_code)
                    membership_fee_product.set_current_language(language_code)
                    membership_fee_product.long_name = "{}".format(
                        _("Membership fee"))
                    membership_fee_product.save()
                translation.activate(cur_language)
        return producer_buyinggroup

    def get_negative_balance(self):
        return -self.balance

    def get_products(self):
        # This producer may have product's list
        if self.is_active:
            changeproductslist_url = urlresolvers.reverse(
                'admin:repanier_product_changelist', )
            link = "<a href=\"{}?is_active__exact=1&producer={}\" class=\"btn addlink\">&nbsp;{}</a>".format(
                changeproductslist_url, str(self.id), _("Products"))
            return link
        return EMPTY_STRING

    get_products.short_description = (_("Link to his products"))
    get_products.allow_tags = True

    def get_admin_date_balance(self):
        if self.id is not None:
            bank_account = BankAccount.objects.filter(
                producer_id=self.id, producer_invoice__isnull=True).order_by(
                    "-operation_date").only("operation_date").first()
            if bank_account is not None:
                return bank_account.operation_date
            return self.date_balance
        else:
            return timezone.now().date()

    get_admin_date_balance.short_description = (_("Date_balance"))
    get_admin_date_balance.allow_tags = False

    def get_admin_balance(self):
        if self.id is not None:
            return self.balance - self.get_bank_not_invoiced(
            ) + self.get_order_not_invoiced()
        else:
            return REPANIER_MONEY_ZERO

    get_admin_balance.short_description = (_("Balance"))
    get_admin_balance.allow_tags = False

    def get_order_not_invoiced(self):
        if settings.REPANIER_SETTINGS_MANAGE_ACCOUNTING:
            result_set = ProducerInvoice.objects.filter(
                producer_id=self.id,
                status__gte=PERMANENCE_OPENED,
                status__lte=PERMANENCE_SEND).order_by('?').aggregate(
                    Sum('total_price_with_tax'), Sum('delta_price_with_tax'),
                    Sum('delta_transport'))
            if result_set["total_price_with_tax__sum"] is not None:
                order_not_invoiced = RepanierMoney(
                    result_set["total_price_with_tax__sum"])
            else:
                order_not_invoiced = REPANIER_MONEY_ZERO
            if result_set["delta_price_with_tax__sum"] is not None:
                order_not_invoiced += RepanierMoney(
                    result_set["delta_price_with_tax__sum"])
            if result_set["delta_transport__sum"] is not None:
                order_not_invoiced += RepanierMoney(
                    result_set["delta_transport__sum"])
        else:
            order_not_invoiced = REPANIER_MONEY_ZERO
        return order_not_invoiced

    def get_bank_not_invoiced(self):
        if settings.REPANIER_SETTINGS_MANAGE_ACCOUNTING:
            result_set = BankAccount.objects.filter(
                producer_id=self.id,
                producer_invoice__isnull=True).order_by('?').aggregate(
                    Sum('bank_amount_in'), Sum('bank_amount_out'))
            if result_set["bank_amount_in__sum"] is not None:
                bank_in = RepanierMoney(result_set["bank_amount_in__sum"])
            else:
                bank_in = REPANIER_MONEY_ZERO
            if result_set["bank_amount_out__sum"] is not None:
                bank_out = RepanierMoney(result_set["bank_amount_out__sum"])
            else:
                bank_out = REPANIER_MONEY_ZERO
            bank_not_invoiced = bank_in - bank_out
        else:
            bank_not_invoiced = REPANIER_MONEY_ZERO

        return bank_not_invoiced

    def get_calculated_invoiced_balance(self, permanence_id):
        bank_not_invoiced = self.get_bank_not_invoiced()
        # IMPORTANT : when is_resale_price_fixed=True then price_list_multiplier == 1
        # Do not take into account product whose order unit is >= PRODUCT_ORDER_UNIT_DEPOSIT
        result_set = OfferItemWoReceiver.objects.filter(
            permanence_id=permanence_id,
            producer_id=self.id,
            price_list_multiplier__lt=1).exclude(
                order_unit__gte=PRODUCT_ORDER_UNIT_DEPOSIT).order_by(
                    '?').aggregate(Sum('total_selling_with_tax'))
        if result_set["total_selling_with_tax__sum"] is not None:
            payment_needed = result_set["total_selling_with_tax__sum"]
        else:
            payment_needed = DECIMAL_ZERO
        result_set = OfferItemWoReceiver.objects.filter(
            permanence_id=permanence_id,
            producer_id=self.id,
            price_list_multiplier__gte=1,
        ).exclude(order_unit__gte=PRODUCT_ORDER_UNIT_DEPOSIT).order_by(
            '?').aggregate(Sum('total_purchase_with_tax'), )
        if result_set["total_purchase_with_tax__sum"] is not None:
            payment_needed += result_set["total_purchase_with_tax__sum"]
        calculated_invoiced_balance = self.balance - bank_not_invoiced + payment_needed
        if self.manage_replenishment:
            for offer_item in OfferItemWoReceiver.objects.filter(
                    is_active=True,
                    permanence_id=permanence_id,
                    producer_id=self.id,
                    manage_replenishment=True,
            ).order_by('?'):
                invoiced_qty, taken_from_stock, customer_qty = offer_item.get_producer_qty_stock_invoiced(
                )
                if offer_item.price_list_multiplier < DECIMAL_ONE:  # or offer_item.is_resale_price_fixed:
                    unit_price = offer_item.customer_unit_price.amount
                else:
                    unit_price = offer_item.producer_unit_price.amount
                if taken_from_stock > DECIMAL_ZERO:
                    delta_price_with_tax = (
                        (unit_price + offer_item.unit_deposit.amount) *
                        taken_from_stock).quantize(TWO_DECIMALS)
                    calculated_invoiced_balance -= delta_price_with_tax
        return calculated_invoiced_balance

    get_calculated_invoiced_balance.short_description = (_("Balance"))
    get_calculated_invoiced_balance.allow_tags = False

    def get_balance(self):
        last_producer_invoice_set = ProducerInvoice.objects.filter(
            producer_id=self.id,
            invoice_sort_order__isnull=False).order_by('?')

        balance = self.get_admin_balance()
        if last_producer_invoice_set.exists():
            if balance.amount < 0:
                return '<a href="' + urlresolvers.reverse(
                    'producer_invoice_view', args=(0, )) + '?producer=' + str(
                        self.id) + '" class="btn" target="_blank" >' + (
                            "<span style=\"color:#298A08\">{}</span>".format(
                                -balance)) + '</a>'
            elif balance.amount == 0:
                return '<a href="' + urlresolvers.reverse(
                    'producer_invoice_view', args=(0, )) + '?producer=' + str(
                        self.id) + '" class="btn" target="_blank" >' + (
                            "<span style=\"color:#32CD32\">{}</span>".format(
                                -balance)) + '</a>'
            elif balance.amount > 30:
                return '<a href="' + urlresolvers.reverse(
                    'producer_invoice_view', args=(0, )) + '?producer=' + str(
                        self.id) + '" class="btn" target="_blank" >' + (
                            "<span style=\"color:red\">{}</span>".format(
                                -balance)) + '</a>'
            else:
                return '<a href="' + urlresolvers.reverse(
                    'producer_invoice_view', args=(0, )) + '?producer=' + str(
                        self.id) + '" class="btn" target="_blank" >' + (
                            "<span style=\"color:#696969\">{}</span>".format(
                                -balance)) + '</a>'
        else:
            if balance.amount < 0:
                return "<span style=\"color:#298A08\">{}</span>".format(
                    -balance)
            elif balance.amount == 0:
                return "<span style=\"color:#32CD32\">{}</span>".format(
                    -balance)
            elif balance.amount > 30:
                return "<span style=\"color:red\">{}</span>".format(-balance)
            else:
                return "<span style=\"color:#696969\">{}</span>".format(
                    -balance)

    get_balance.short_description = _("Balance")
    get_balance.allow_tags = True
    get_balance.admin_order_field = 'balance'

    def get_last_invoice(self):
        producer_last_invoice = ProducerInvoice.objects.filter(
            producer_id=self.id,
            invoice_sort_order__isnull=False).order_by("-id").first()
        if producer_last_invoice is not None:
            if producer_last_invoice.total_price_with_tax < DECIMAL_ZERO:
                return "<span style=\"color:#298A08\">{}</span>".format(
                    number_format(producer_last_invoice.total_price_with_tax,
                                  2))
            elif producer_last_invoice.total_price_with_tax == DECIMAL_ZERO:
                return "<span style=\"color:#32CD32\">{}</span>".format(
                    number_format(producer_last_invoice.total_price_with_tax,
                                  2))
            elif producer_last_invoice.total_price_with_tax > 30:
                return "<span style=\"color:red\">{}</span>".format(
                    number_format(producer_last_invoice.total_price_with_tax,
                                  2))
            else:
                return "<span style=\"color:#696969\">{}</span>".format(
                    number_format(producer_last_invoice.total_price_with_tax,
                                  2))
        else:
            return "<span style=\"color:#32CD32\">{}</span>".format(
                number_format(0, 2))

    get_last_invoice.short_description = _("Last invoice")
    get_last_invoice.allow_tags = True

    def get_html_on_hold_movement(self):
        bank_not_invoiced = self.get_bank_not_invoiced()
        order_not_invoiced = self.get_order_not_invoiced()

        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
                    }
            return mark_safe(producer_on_hold_movement)

        return EMPTY_STRING

    def anonymize(self, also_group=False):
        if self.represent_this_buyinggroup:
            if not also_group:
                return
            self.short_profile_name = "{}-{}".format(_("GROUP"), self.id)
            self.long_profile_name = "{} {}".format(_("Group"), self.id)
        else:
            self.short_profile_name = "{}-{}".format(_("PRODUCER"), self.id)
            self.long_profile_name = "{} {}".format(_("Producer"), self.id)
        self.email = "{}@repanier.be".format(self.short_profile_name)
        self.email2 = EMPTY_STRING
        self.email3 = EMPTY_STRING
        self.phone1 = EMPTY_STRING
        self.phone2 = EMPTY_STRING
        self.bank_account = EMPTY_STRING
        self.vat_id = EMPTY_STRING
        self.fax = EMPTY_STRING
        self.address = EMPTY_STRING
        self.memo = EMPTY_STRING
        self.uuid = uuid.uuid1()
        self.offer_uuid = uuid.uuid1()
        self.is_anonymized = True
        self.save()

    def __str__(self):
        if self.producer_price_are_wo_vat:
            return "{} {}".format(self.short_profile_name, _("wo tax"))
        return self.short_profile_name

    class Meta:
        verbose_name = _("Producer")
        verbose_name_plural = _("Producers")
        ordering = (
            "-represent_this_buyinggroup",
            "short_profile_name",
        )
        indexes = [
            models.Index(
                fields=["-represent_this_buyinggroup", "short_profile_name"],
                name='producer_order_idx'),
        ]
Exemple #6
0
class Contract(TranslatableModel):
    translations = TranslatedFields(
        long_name=models.CharField(_("Long name"),
                                   max_length=100,
                                   default=EMPTY_STRING,
                                   blank=True,
                                   null=True),
        offer_description=HTMLField(
            _("Offer description"),
            configuration='CKEDITOR_SETTINGS_MODEL2',
            help_text=
            _("This message is send by mail to all customers when opening the order or on top "
              ),
            blank=True,
            default=EMPTY_STRING),
    )
    first_permanence_date = models.DateField(
        verbose_name=_("First permanence date"), db_index=True)
    last_permanence_date = models.DateField(
        verbose_name=_("Last permanence date"),
        null=True,
        blank=True,
        db_index=True)
    recurrences = RecurrenceField()
    producers = models.ManyToManyField('Producer',
                                       verbose_name=_("Producers"),
                                       related_name='contracts',
                                       blank=True)
    customers = models.ManyToManyField('Customer',
                                       verbose_name=_("Customers"),
                                       blank=True)
    picture2 = AjaxPictureField(verbose_name=_("Picture"),
                                null=True,
                                blank=True,
                                upload_to="contract",
                                size=SIZE_L)
    status = models.CharField(max_length=3,
                              choices=LUT_CONTRACT_STATUS,
                              default=CONTRACT_IN_WRITING,
                              verbose_name=_("status"))
    highest_status = models.CharField(max_length=3,
                                      choices=LUT_CONTRACT_STATUS,
                                      default=CONTRACT_IN_WRITING,
                                      verbose_name=_("Highest status"))
    all_dates = []
    dates_display = EMPTY_STRING

    @cached_property
    def get_producers(self):
        if self.status == CONTRACT_IN_WRITING:
            if len(self.producers.all()) > 0:
                changelist_url = urlresolvers.reverse(
                    'admin:repanier_product_changelist', )
                link = []
                for p in self.producers.all():
                    link.append(
                        '<a href="%s?producer=%d&commitment=%d">&nbsp;%s</a>' %
                        (changelist_url, p.id, self.id,
                         p.short_profile_name.replace(" ", "&nbsp;")))
                return mark_safe('<div class="wrap-text">%s</div>' %
                                 ", ".join(link))
            else:
                return mark_safe('<div class="wrap-text">%s</div>' %
                                 _("No offer"))
        else:
            return mark_safe('<div class="wrap-text">%s</div>' % ", ".join([
                p.short_profile_name.replace(" ", "&nbsp;")
                for p in self.producers.all()
            ]))

    get_producers.short_description = (_("Offers from"))

    @cached_property
    def get_dates(self):
        return self.dates_display

    get_dates.short_description = (_("Permanences"))
    get_dates.allow_tags = True

    def get_full_status_display(self):
        return self.get_status_display()

    get_full_status_display.short_description = (_("Contract status"))
    get_full_status_display.allow_tags = True

    def get_contract_admin_display(self):
        return "%s (%s)" % (self.long_name, self.dates_display)

    get_contract_admin_display.short_description = _("Commitments")
    get_contract_admin_display.allow_tags = False

    def __str__(self):
        return '%s' % self.long_name

    class Meta:
        verbose_name = _("Commitment")
        verbose_name_plural = _("Commitments")
Exemple #7
0
class Customer(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, db_index=True)
    login_attempt_counter = models.DecimalField(_("Login attempt counter"),
                                                default=DECIMAL_ZERO,
                                                max_digits=2,
                                                decimal_places=0)

    short_basket_name = models.CharField(_("Short name"),
                                         max_length=25,
                                         null=False,
                                         default=EMPTY_STRING,
                                         db_index=True,
                                         unique=True)
    long_basket_name = models.CharField(_("Long name"),
                                        max_length=100,
                                        null=True,
                                        default=EMPTY_STRING)
    email2 = models.EmailField(_("Secondary email"),
                               null=True,
                               blank=True,
                               default=EMPTY_STRING)
    language = models.CharField(max_length=5,
                                choices=settings.LANGUAGES,
                                default=settings.LANGUAGE_CODE,
                                verbose_name=_("Language"))

    picture = AjaxPictureField(verbose_name=_("Picture"),
                               null=True,
                               blank=True,
                               upload_to="customer",
                               size=SIZE_S)
    phone1 = models.CharField(_("Phone1"),
                              max_length=25,
                              null=True,
                              blank=True,
                              default=EMPTY_STRING)
    phone2 = models.CharField(_("Phone2"),
                              max_length=25,
                              null=True,
                              blank=True,
                              default=EMPTY_STRING)
    bank_account1 = models.CharField(_("Main bank account"),
                                     max_length=100,
                                     null=True,
                                     blank=True,
                                     default=EMPTY_STRING)
    bank_account2 = models.CharField(_("Secondary bank account"),
                                     max_length=100,
                                     null=True,
                                     blank=True,
                                     default=EMPTY_STRING)
    vat_id = models.CharField(_("VAT id"),
                              max_length=20,
                              null=True,
                              blank=True,
                              default=EMPTY_STRING)
    address = models.TextField(_("Address"),
                               null=True,
                               blank=True,
                               default=EMPTY_STRING)
    city = models.CharField(_("City"),
                            max_length=50,
                            null=True,
                            blank=True,
                            default=EMPTY_STRING)
    about_me = models.TextField(_("About me"),
                                null=True,
                                blank=True,
                                default=EMPTY_STRING)
    memo = models.TextField(_("Memo"),
                            null=True,
                            blank=True,
                            default=EMPTY_STRING)
    show_mails_to_members = models.BooleanField(
        _("Show my mail to other members"), default=False)
    show_phones_to_members = models.BooleanField(
        _("Show my phone to other members"), default=False)
    membership_fee_valid_until = models.DateField(
        _("Membership fee valid until"), default=datetime.date.today)
    # If this customer is member of a closed group, the customer.price_list_multiplier is not used
    # Invoices are sent to the consumer responsible of the group who is
    # also responsible for collecting the payments.
    # The LUT_DeliveryPoint.price_list_multiplier will be used when invoicing the consumer responsible
    # At this stage, the link between the customer invoice and this customer responsible is made with
    # CustomerInvoice.customer_charged
    price_list_multiplier = models.DecimalField(
        _("Coefficient applied to the producer tariff to calculate the consumer tariff"
          ),
        help_text=
        _("This multiplier is applied to each product ordered by this customer."
          ),
        default=DECIMAL_ONE,
        max_digits=5,
        decimal_places=4,
        blank=True,
        validators=[MinValueValidator(0)])

    password_reset_on = models.DateTimeField(_("Password reset on"),
                                             null=True,
                                             blank=True,
                                             default=None)
    date_balance = models.DateField(_("Date balance"),
                                    default=datetime.date.today)
    balance = ModelMoneyField(_("Balance"),
                              max_digits=8,
                              decimal_places=2,
                              default=DECIMAL_ZERO)
    # The initial balance is needed to compute the invoice control list
    initial_balance = ModelMoneyField(_("Initial balance"),
                                      max_digits=8,
                                      decimal_places=2,
                                      default=DECIMAL_ZERO)
    represent_this_buyinggroup = models.BooleanField(
        _("Represent_this_buyinggroup"), default=False)
    delivery_point = models.ForeignKey('LUT_DeliveryPoint',
                                       verbose_name=_("Delivery point"),
                                       blank=True,
                                       null=True,
                                       default=None)
    is_active = models.BooleanField(_("Active"), default=True)
    as_staff = models.ForeignKey('Staff', blank=True, null=True, default=None)
    # This indicate that the user record data have been replaced with anonymous data in application of GDPR
    is_anonymized = models.BooleanField(default=False)
    is_group = models.BooleanField(_("Group"), default=False)
    may_order = models.BooleanField(_("May order"), default=True)
    zero_waste = models.BooleanField(_("Zero waste"), default=False)
    valid_email = models.NullBooleanField(_("Valid email"), default=None)
    subscribe_to_email = models.BooleanField(
        _("Agree to receive unsolicited mails from this site"), default=True)
    preparation_order = models.IntegerField(null=True, blank=True, default=0)

    @classmethod
    def get_or_create_group(cls):
        customer_buyinggroup = Customer.objects.filter(
            represent_this_buyinggroup=True).order_by('?').first()
        if customer_buyinggroup is None:
            long_name = settings.REPANIER_SETTINGS_GROUP_NAME
            short_name = long_name[:25]
            user = User.objects.filter(
                username=short_name).order_by('?').first()
            if user is None:
                user = User.objects.create_user(
                    username=short_name,
                    email="{}{}".format(
                        long_name,
                        settings.REPANIER_SETTINGS_ALLOWED_MAIL_EXTENSION),
                    password=uuid.uuid1().hex,
                    first_name=EMPTY_STRING,
                    last_name=long_name)
            customer_buyinggroup = Customer.objects.create(
                user=user,
                short_basket_name=short_name,
                long_basket_name=long_name,
                phone1=settings.REPANIER_SETTINGS_COORDINATOR_PHONE,
                represent_this_buyinggroup=True)
        return customer_buyinggroup

    @classmethod
    def get_or_create_the_very_first_customer(cls):
        very_first_customer = Customer.objects.filter(
            represent_this_buyinggroup=False,
            is_active=True).order_by('id').first()
        if very_first_customer is None:
            long_name = settings.REPANIER_SETTINGS_COORDINATOR_NAME
            # short_name is the first word of long_name, limited to max. 25 characters
            short_name = long_name.split(None, 1)[0][:25]
            user = User.objects.filter(
                username=short_name).order_by('?').first()
            if user is None:
                user = User.objects.create_user(
                    username=short_name,
                    email=settings.REPANIER_SETTINGS_COORDINATOR_EMAIL,
                    password=uuid.uuid1().hex,
                    first_name=EMPTY_STRING,
                    last_name=long_name)
            very_first_customer = Customer.objects.create(
                user=user,
                short_basket_name=short_name,
                long_basket_name=long_name,
                phone1=settings.REPANIER_SETTINGS_COORDINATOR_PHONE,
                represent_this_buyinggroup=False)
        return very_first_customer

    def get_admin_date_balance(self):
        return timezone.now().date().strftime(settings.DJANGO_SETTINGS_DATE)

    get_admin_date_balance.short_description = (_("Date balance"))
    get_admin_date_balance.allow_tags = False

    def get_admin_date_joined(self):
        # New customer have no user during import of customers in admin.customer.CustomerResource
        try:
            return self.user.date_joined.strftime(
                settings.DJANGO_SETTINGS_DATE)
        except User.DoesNotExist:  # RelatedObjectDoesNotExist
            return EMPTY_STRING

    get_admin_date_joined.short_description = _("Date joined")
    get_admin_date_joined.allow_tags = False

    def get_admin_balance(self):
        return self.balance + self.get_bank_not_invoiced(
        ) - self.get_order_not_invoiced()

    get_admin_balance.short_description = (_("Balance"))
    get_admin_balance.allow_tags = False

    def get_order_not_invoiced(self):
        if settings.REPANIER_SETTINGS_MANAGE_ACCOUNTING:
            result_set = CustomerInvoice.objects.filter(
                customer_id=self.id,
                status__gte=PERMANENCE_OPENED,
                status__lte=PERMANENCE_SEND,
                customer_charged_id=self.id).order_by('?').aggregate(
                    Sum('total_price_with_tax'), Sum('delta_price_with_tax'),
                    Sum('delta_transport'))
            if result_set["total_price_with_tax__sum"] is not None:
                order_not_invoiced = RepanierMoney(
                    result_set["total_price_with_tax__sum"])
            else:
                order_not_invoiced = REPANIER_MONEY_ZERO
            if result_set["delta_price_with_tax__sum"] is not None:
                order_not_invoiced += RepanierMoney(
                    result_set["delta_price_with_tax__sum"])
            if result_set["delta_transport__sum"] is not None:
                order_not_invoiced += RepanierMoney(
                    result_set["delta_transport__sum"])
        else:
            order_not_invoiced = REPANIER_MONEY_ZERO
        return order_not_invoiced

    def get_bank_not_invoiced(self):
        if settings.REPANIER_SETTINGS_MANAGE_ACCOUNTING:
            result_set = BankAccount.objects.filter(
                customer_id=self.id,
                customer_invoice__isnull=True).order_by('?').aggregate(
                    Sum('bank_amount_in'), Sum('bank_amount_out'))
            if result_set["bank_amount_in__sum"] is not None:
                bank_in = RepanierMoney(result_set["bank_amount_in__sum"])
            else:
                bank_in = REPANIER_MONEY_ZERO
            if result_set["bank_amount_out__sum"] is not None:
                bank_out = RepanierMoney(result_set["bank_amount_out__sum"])
            else:
                bank_out = REPANIER_MONEY_ZERO
            bank_not_invoiced = bank_in - bank_out
        else:
            bank_not_invoiced = REPANIER_MONEY_ZERO
        return bank_not_invoiced

    def get_balance(self):
        last_customer_invoice = CustomerInvoice.objects.filter(
            customer_id=self.id,
            invoice_sort_order__isnull=False).order_by('?')
        balance = self.get_admin_balance()
        if last_customer_invoice.exists():
            if balance.amount >= 30:
                return '<a href="' + urlresolvers.reverse(
                    'customer_invoice_view', args=(0, )) + '?customer=' + str(
                        self.id) + '" class="btn" target="_blank" >' + (
                            "<span style=\"color:#32CD32\">{}</span>".format(
                                balance)) + '</a>'
            elif balance.amount >= -10:
                return '<a href="' + urlresolvers.reverse(
                    'customer_invoice_view', args=(0, )) + '?customer=' + str(
                        self.id) + '" class="btn" target="_blank" >' + (
                            "<span style=\"color:#696969\">{}</span>".format(
                                balance)) + '</a>'
            else:
                return '<a href="' + urlresolvers.reverse(
                    'customer_invoice_view', args=(0, )) + '?customer=' + str(
                        self.id) + '" class="btn" target="_blank" >' + (
                            "<span style=\"color:red\">{}</span>".format(
                                balance)) + '</a>'
        else:
            if balance.amount >= 30:
                return "<span style=\"color:#32CD32\">{}</span>".format(
                    balance)
            elif balance.amount >= -10:
                return "<span style=\"color:#696969\">{}</span>".format(
                    balance)
            else:
                return "<span style=\"color:red\">{}</span>".format(balance)

    get_balance.short_description = _("Balance")
    get_balance.allow_tags = True
    get_balance.admin_order_field = 'balance'

    def get_html_on_hold_movement(self,
                                  bank_not_invoiced=None,
                                  order_not_invoiced=None,
                                  total_price_with_tax=REPANIER_MONEY_ZERO):
        if settings.REPANIER_SETTINGS_MANAGE_ACCOUNTING:
            bank_not_invoiced = bank_not_invoiced if bank_not_invoiced is not None else self.get_bank_not_invoiced(
            )
            order_not_invoiced = order_not_invoiced if order_not_invoiced is not None else self.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
                        }
            else:
                customer_on_hold_movement = \
                    _(
                        'This balance does not take account of any unrecognized payments %(bank)s.') % {
                        'bank': bank_not_invoiced
                    }
            customer_on_hold_movement = mark_safe(customer_on_hold_movement)
        else:
            customer_on_hold_movement = EMPTY_STRING

        return customer_on_hold_movement

    def get_last_membership_fee(self):
        from repanier.models.purchase import Purchase

        last_membership_fee = Purchase.objects.filter(
            customer_id=self.id,
            offer_item__order_unit=PRODUCT_ORDER_UNIT_MEMBERSHIP_FEE).order_by(
                "-id")
        if last_membership_fee.exists():
            return last_membership_fee.first().selling_price

    get_last_membership_fee.short_description = _("Last membership fee")
    get_last_membership_fee.allow_tags = False

    def last_membership_fee_date(self):
        from repanier.models.purchase import Purchase

        last_membership_fee = Purchase.objects.filter(
            customer_id=self.id,
            offer_item__order_unit=PRODUCT_ORDER_UNIT_MEMBERSHIP_FEE).order_by(
                "-id").prefetch_related("customer_invoice")
        if last_membership_fee.exists():
            return last_membership_fee.first().customer_invoice.date_balance

    last_membership_fee_date.short_description = _("Last membership fee date")
    last_membership_fee_date.allow_tags = False

    def get_last_membership_fee_date(self):
        # Format it for the admin
        # Don't format it form import/export
        last_membership_fee_date = self.last_membership_fee_date()
        if last_membership_fee_date is not None:
            return last_membership_fee_date.strftime(
                settings.DJANGO_SETTINGS_DATE)
        return EMPTY_STRING

    get_last_membership_fee_date.short_description = _(
        "Last membership fee date")
    get_last_membership_fee_date.allow_tags = False

    def get_participation(self):
        now = timezone.now()
        return PermanenceBoard.objects.filter(
            customer_id=self.id,
            permanence_date__gte=now - datetime.timedelta(days=ONE_YEAR),
            permanence_date__lt=now,
            permanence_role__is_counted_as_participation=True).order_by(
                '?').count()

    get_participation.short_description = _("Participation")
    get_participation.allow_tags = False

    def get_purchase(self):
        now = timezone.now()
        # Do not count invoice having only products free of charge
        return CustomerInvoice.objects.filter(
            customer_id=self.id,
            total_price_with_tax__gt=DECIMAL_ZERO,
            date_balance__gte=now - datetime.timedelta(ONE_YEAR)).count()

    get_purchase.short_description = _("Purchase")
    get_purchase.allow_tags = False

    def my_order_confirmation_email_send_to(self):
        if self.email2:
            to_email = (self.user.email, self.email2)
        else:
            to_email = (self.user.email, )
        sent_to = ", ".join(to_email) if to_email is not None else EMPTY_STRING
        if settings.REPANIER_SETTINGS_CUSTOMER_MUST_CONFIRM_ORDER:
            msg_confirmation = _(
                "Order confirmed. An email containing this order summary has been sent to {}."
            ).format(sent_to)
        else:
            msg_confirmation = _(
                "An email containing this order summary has been sent to {}."
            ).format(sent_to)
        return msg_confirmation

    @property
    def who_is_who_display(self):
        return self.picture or self.show_mails_to_members or self.show_phones_to_members \
               or (self.about_me is not None and len(self.about_me.strip()) > 1)

    def get_html_unsubscribe_mail_footer(self):
        return mark_safe("<br><br><hr/><br><a href=\"{}\">{}</a>".format(
            self._get_unsubscribe_link(),
            _("Stop receiving unsolicited mails from {}").format(
                self._get_unsubscribe_site())))

    def _get_unsubscribe_link(self):
        customer_id, token = self.make_token().split(":", 1)
        return "https://{}{}".format(
            self._get_unsubscribe_site(),
            reverse('unsubscribe_view',
                    kwargs={
                        'customer_id': customer_id,
                        'token': token,
                    }))

    def _get_unsubscribe_site(self):
        return settings.ALLOWED_HOSTS[0]

    def make_token(self):
        return TimestampSigner().sign(self.id)

    def check_token(self, token):
        try:
            key = "{}:{}".format(self.id, token)
            TimestampSigner().unsign(key,
                                     max_age=60 * 60 * 48)  # Valid for 2 days
        except (BadSignature, SignatureExpired):
            return False
        return True

    def anonymize(self, also_group=False):
        if self.represent_this_buyinggroup:
            if not also_group:
                return
            self.short_basket_name = "{}-{}".format(_("GROUP"),
                                                    self.id).lower()
            self.long_basket_name = "{} {}".format(_("Group"), self.id)
        else:
            self.short_basket_name = "{}-{}".format(_("BASKET"),
                                                    self.id).lower()
            self.long_basket_name = "{} {}".format(_("Family"), self.id)
        self.email2 = EMPTY_STRING
        self.picture = EMPTY_STRING
        self.phone1 = EMPTY_STRING
        self.phone2 = EMPTY_STRING
        self.bank_account1 = EMPTY_STRING
        self.bank_account1 = EMPTY_STRING
        self.vat_id = EMPTY_STRING
        self.address = EMPTY_STRING
        self.about_me = EMPTY_STRING
        self.memo = EMPTY_STRING
        self.user.username = self.user.email = "{}@repanier.be".format(
            self.short_basket_name)
        self.user.first_name = EMPTY_STRING
        self.user.last_name = self.short_basket_name
        self.user.set_password(None)
        self.user.save()
        self.is_anonymized = True
        self.valid_email = False
        self.subscribe_to_email = False
        self.show_mails_to_members = False
        self.show_phones_to_members = False
        self.save()

    def __str__(self):
        if self.delivery_point is None:
            return self.short_basket_name
        else:
            return "{} - {}".format(self.delivery_point,
                                    self.short_basket_name)

    class Meta:
        verbose_name = _("Customer")
        verbose_name_plural = _("Customers")
        ordering = (
            "-represent_this_buyinggroup",
            "short_basket_name",
        )
        indexes = [
            models.Index(
                fields=["-represent_this_buyinggroup", "short_basket_name"],
                name='customer_order_idx'),
        ]
Exemple #8
0
class Contract(TranslatableModel):
    translations = TranslatedFields(long_name=models.CharField(
        _("Long name"),
        max_length=100,
        default=EMPTY_STRING,
        blank=True,
        null=True), )
    first_permanence_date = models.DateField(
        verbose_name=_("Date of the first permanence"), db_index=True)
    last_permanence_date = models.DateField(
        verbose_name=_("Last permanence date"),
        null=True,
        blank=True,
        db_index=True)
    recurrences = RecurrenceField()
    producers = models.ManyToManyField('Producer',
                                       verbose_name=_("Producers"),
                                       related_name='contracts',
                                       blank=True)
    customers = models.ManyToManyField('Customer',
                                       verbose_name=_("Customers"),
                                       blank=True)
    picture2 = AjaxPictureField(verbose_name=_("Picture"),
                                null=True,
                                blank=True,
                                upload_to="contract",
                                size=SIZE_L)
    is_active = models.BooleanField(_("Active"), default=True)
    permanences_dates = models.TextField(null=True, blank=True, default=None)

    @transaction.atomic()
    def get_or_create_offer_item(self, permanence, reset_add_2_stock=False):
        from repanier.models.offeritem import OfferItem, OfferItemWoReceiver

        # In case of contract
        # -1, generate eventually several offer's items if dates are flexible
        # -2, boxes may not be used in contracts
        OfferItemWoReceiver.objects.filter(permanence_id=permanence.id).update(
            may_order=False)
        for contract_content in ContractContent.objects.filter(
                contract_id=self.id,
                permanences_dates__isnull=False).order_by('?'):
            all_dates_str = sorted(
                list(
                    filter(
                        None,
                        contract_content.permanences_dates.split(
                            settings.DJANGO_SETTINGS_DATES_SEPARATOR))))
            if contract_content.flexible_dates:
                permanences_dates_order = 0
                for one_date_str in all_dates_str:
                    permanences_dates_order += 1
                    offer_item_qs = OfferItem.objects.filter(
                        permanence_id=permanence.id,
                        product_id=contract_content.product_id,
                        permanences_dates=one_date_str).order_by('?')
                    if not offer_item_qs.exists():
                        OfferItemWoReceiver.objects.create(
                            permanence_id=permanence.id,
                            product_id=contract_content.product_id,
                            producer_id=contract_content.product.producer_id,
                            contract_id=self.id,
                            permanences_dates=one_date_str,
                            not_permanences_dates=contract_content.
                            not_permanences_dates,
                            permanences_dates_counter=1,
                            permanences_dates_order=permanences_dates_order)
                        clean_offer_item(permanence,
                                         offer_item_qs,
                                         reset_add_2_stock=reset_add_2_stock)
                    else:
                        offer_item = offer_item_qs.first()
                        offer_item.contract_id = self.id
                        offer_item.permanences_dates_order = permanences_dates_order
                        offer_item.not_permanences_dates = contract_content.not_permanences_dates
                        if reset_add_2_stock:
                            offer_item.may_order = True
                        offer_item.save(update_fields=[
                            "contract", "may_order", "permanences_dates_order",
                            "not_permanences_dates"
                        ])
                        clean_offer_item(permanence,
                                         offer_item_qs,
                                         reset_add_2_stock=reset_add_2_stock)
            else:
                offer_item_qs = OfferItem.objects.filter(
                    permanence_id=permanence.id,
                    product_id=contract_content.product_id,
                    permanences_dates=contract_content.permanences_dates
                ).order_by('?')
                if not offer_item_qs.exists():
                    OfferItemWoReceiver.objects.create(
                        permanence_id=permanence.id,
                        product_id=contract_content.product_id,
                        producer_id=contract_content.product.producer_id,
                        contract_id=self.id,
                        permanences_dates=contract_content.permanences_dates,
                        not_permanences_dates=contract_content.
                        not_permanences_dates,
                        permanences_dates_counter=len(all_dates_str))
                    clean_offer_item(permanence,
                                     offer_item_qs,
                                     reset_add_2_stock=reset_add_2_stock)
                else:
                    offer_item = offer_item_qs.first()
                    offer_item.contract_id = self.id
                    offer_item.permanences_dates_order = 0
                    offer_item.not_permanences_dates = contract_content.not_permanences_dates
                    if reset_add_2_stock:
                        offer_item.may_order = True
                    offer_item.save(update_fields=[
                        "contract", "may_order", "permanences_dates_order",
                        "not_permanences_dates"
                    ])
                    clean_offer_item(permanence,
                                     offer_item_qs,
                                     reset_add_2_stock=reset_add_2_stock)

    @cached_property
    def get_producers(self):
        if len(self.producers.all()) > 0:
            changelist_url = urlresolvers.reverse(
                'admin:repanier_product_changelist', )
            link = []
            for p in self.producers.all():
                link.append(
                    "<a href=\"{}?producer={}&commitment={}\">&nbsp;{}&nbsp;{}</a>"
                    .format(changelist_url, p.id, self.id, LINK_UNICODE,
                            p.short_profile_name.replace(" ", "&nbsp;")))
            return mark_safe("<div class=\"wrap-text\">{}</div>".format(
                ", ".join(link)))
        else:
            return mark_safe("<div class=\"wrap-text\">{}</div>".format(
                _("No offer")))

    get_producers.short_description = (_("Offers from"))

    @cached_property
    def get_dates(self):
        if self.permanences_dates:
            all_dates_str = sorted(
                list(
                    filter(
                        None,
                        self.permanences_dates.split(
                            settings.DJANGO_SETTINGS_DATES_SEPARATOR))))
            all_dates = []
            for one_date_str in all_dates_str:
                one_date = parse_date(one_date_str)
                all_dates.append(one_date)
            return '{} : {}'.format(
                len(all_dates), ", ".join(
                    date.strftime(settings.DJANGO_SETTINGS_DAY_MONTH)
                    for date in all_dates))
        return EMPTY_STRING

    get_dates.short_description = (_("Permanences"))
    get_dates.allow_tags = True

    def get_contract_admin_display(self):
        return "{} ({})".format(
            self.safe_translation_getter('long_name', any_language=True),
            self.get_dates)

    get_contract_admin_display.short_description = _("Commitments")
    get_contract_admin_display.allow_tags = False

    def __str__(self):
        return "{}".format(
            self.safe_translation_getter('long_name', any_language=True))

    class Meta:
        verbose_name = _("Commitment")
        verbose_name_plural = _("Commitments")
Exemple #9
0
class Permanence(TranslatableModel):
    translations = TranslatedFields(
        short_name=models.CharField(
            _("offer name"),
            max_length=50, blank=True
        ),
        offer_description=HTMLField(
            _("offer_description"),
            configuration='CKEDITOR_SETTINGS_MODEL2',
            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"),
            configuration='CKEDITOR_SETTINGS_MODEL2',
            help_text=_(
                'This message is send by mail to all customers having bought something when closing the permanence.'),
            blank=True, default=EMPTY_STRING
        ),
    )

    status = models.CharField(
        _("Status"),
        max_length=3,
        choices=LUT_PERMANENCE_STATUS,
        default=PERMANENCE_PLANNED,
    )
    permanence_date = models.DateField(
        _("Date"), db_index=True
    )
    payment_date = models.DateField(
        _("Payment date"), blank=True, null=True, db_index=True
    )
    producers = models.ManyToManyField(
        'Producer',
        verbose_name=_("Producers"),
        blank=True
    )
    boxes = models.ManyToManyField(
        'Box',
        verbose_name=_('Boxes'),
        blank=True
    )
    contracts = models.ManyToManyField(
        'Contract',
        verbose_name=_("Commitments"),
        blank=True
    )
    # When master contract is defined, this permanence is used to let customer place order to the contract
    # This master contract will be the only one contract allowed for this permanence
    # This permanence will not be showed into "admin/permanence_in_preparation" nor "admin/ermanence_done"
    # but will be used in "admin/contract"
    master_contract = models.OneToOneField(
        'Contract',
        related_name='master_contract',
        verbose_name=_("Master contract"),
        on_delete=models.CASCADE,
        null=True, blank=True, default=None)

    # Calculated with Purchase
    total_purchase_with_tax = ModelMoneyField(
        _("Total amount"),
        help_text=_('Total purchase amount vat included'),
        default=DECIMAL_ZERO, max_digits=8, decimal_places=2)
    total_selling_with_tax = ModelMoneyField(
        _("Total amount"),
        help_text=_('Total purchase amount vat included'),
        default=DECIMAL_ZERO, max_digits=8, decimal_places=2)
    total_purchase_vat = ModelMoneyField(
        _("Total vat"),
        help_text=_('Vat part of the total purchased'),
        default=DECIMAL_ZERO, max_digits=9, decimal_places=4)
    total_selling_vat = ModelMoneyField(
        _("Total vat"),
        help_text=_('Vat part of the total purchased'),
        default=DECIMAL_ZERO, max_digits=9, decimal_places=4)

    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"),
    )
    master_permanence = models.ForeignKey(
        'Permanence',
        verbose_name=_("Master permanence"),
        related_name='child_permanence',
        blank=True, null=True, default=None,
        on_delete=models.PROTECT, db_index=True)
    invoice_sort_order = models.IntegerField(
        _("Invoice sort order"),
        default=None, blank=True, null=True)
    offer_description_on_home_page = models.BooleanField(
        _("Publish the offer description on the home page when the permanence is open"), default=True)
    picture = AjaxPictureField(
        verbose_name=_("picture"),
        null=True, blank=True,
        upload_to="permanence", size=SIZE_L)
    gauge = models.IntegerField(
        default=0, editable=False
    )

    @cached_property
    def get_producers(self):
        if self.status == PERMANENCE_PLANNED:
            if len(self.producers.all()) > 0:
                changelist_url = urlresolvers.reverse(
                    'admin:repanier_product_changelist',
                )
                link = []
                for p in self.producers.all():
                    link.append(
                        '<a href="%s?producer=%d">&nbsp;%s</a>' % (
                            changelist_url, p.id, p.short_profile_name))
                msg_html = '<div class="wrap-text">%s</div>' % ", ".join(link)
            else:
                msg_html = '<div class="wrap-text">%s</div>' % _("No offer")
        elif self.status == PERMANENCE_PRE_OPEN:
            msg_html = '<div class="wrap-text">%s</div>' % ", ".join([p.short_profile_name + " (" + p.phone1 + ")" for p in self.producers.all()])
        elif self.status in [PERMANENCE_OPENED, PERMANENCE_CLOSED]:
            close_offeritem_changelist_url = urlresolvers.reverse(
                'admin:repanier_offeritemclosed_changelist',
            )

            link = []
            for p in self.producers.all().only("id"):
                pi = 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) ' % (p.short_profile_name, pi.get_total_price_with_tax())).replace(
                            ' ', '&nbsp;')
                        offeritem_changelist_url = close_offeritem_changelist_url
                    else:
                        label = ('%s (%s) %s' % (
                        p.short_profile_name, pi.get_total_price_with_tax(), LOCK_UNICODE)).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(
                    '<a href="%s?permanence=%s&producer=%d">%s</a>' % (
                        offeritem_changelist_url, self.id, p.id, label))
            msg_html = '<div class="wrap-text">%s</div>' % ", ".join(link)

        elif self.status in [PERMANENCE_SEND, PERMANENCE_INVOICED, PERMANENCE_ARCHIVED]:
            send_offeritem_changelist_url = urlresolvers.reverse(
                'admin:repanier_offeritemsend_changelist',
            )
            send_customer_changelist_url = urlresolvers.reverse(
                'admin:repanier_customersend_changelist',
            )
            link = []
            at_least_one_permanence_send = False
            for pi in ProducerInvoice.objects.filter(permanence_id=self.id).select_related(
                    "producer").order_by('producer'):
                if pi.status == PERMANENCE_SEND:
                    at_least_one_permanence_send = True
                    if pi.producer.invoice_by_basket:
                        changelist_url = send_customer_changelist_url
                    else:
                        changelist_url = send_offeritem_changelist_url
                    # Important : no target="_blank"
                    label = '%s (%s) %s' % (
                    pi.producer.short_profile_name, pi.get_total_price_with_tax(), LOCK_UNICODE)
                    link.append(
                        '<a href="%s?permanence=%d&producer=%d">&nbsp;%s</a>' % (
                            changelist_url, self.id, pi.producer_id, label.replace(' ', '&nbsp;')
                        ))
                else:
                    if pi.invoice_reference:
                        if pi.to_be_invoiced_balance != DECIMAL_ZERO or pi.total_price_with_tax != DECIMAL_ZERO:
                            label = "%s (%s - %s)" % (
                                pi.producer.short_profile_name,
                                pi.to_be_invoiced_balance,
                                cap(pi.invoice_reference, 15)
                            )
                        else:
                            label = "%s (%s)" % (
                                pi.producer.short_profile_name,
                                cap(pi.invoice_reference, 15)
                            )
                    else:
                        if pi.to_be_invoiced_balance != DECIMAL_ZERO or pi.total_price_with_tax != DECIMAL_ZERO:
                            label = "%s (%s)" % (
                                pi.producer.short_profile_name,
                                pi.to_be_invoiced_balance
                            )
                        else:
                            # label = "%s" % (
                            #     pi.producer.short_profile_name
                            # )
                            continue
                    # 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
                    link.append(
                        '<a href="%s?producer=%d" target="_blank">%s</a>'
                        % (
                            urlresolvers.reverse('producer_invoice_view', args=(pi.id,)),
                            pi.producer_id,
                            label.replace(' ', '&nbsp;')))

            producers = ", ".join(link)
            if at_least_one_permanence_send:
                msg_html = '<div class="wrap-text">%s</div>' % producers
            else:
                msg_html = """
                    <div class="wrap-text"><button
                    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>
                    <div id="id_get_producers_%d" style="display:none;">%s</div></div>
                """ % (
                    self.id, _("Show"), _("Hide"), _("Show"), _("Show"), self.id, producers
                )
        else:
            msg_html = '<div class="wrap-text">%s</div>' % ", ".join([p.short_profile_name
                                   for p in
                                   Producer.objects.filter(
                                       producerinvoice__permanence_id=self.id).only(
                                       'short_profile_name')])
        return mark_safe(msg_html)

    get_producers.short_description = (_("Offers from"))
    # get_producers.allow_tags = True

    @cached_property
    def get_customers(self):
        if self.status in [
            PERMANENCE_OPENED, PERMANENCE_CLOSED, PERMANENCE_SEND
        ]:
            changelist_url = urlresolvers.reverse(
                'admin:repanier_purchase_changelist',
            )
            link = []
            delivery_save = None
            for ci in CustomerInvoice.objects.filter(permanence_id=self.id).select_related(
                "customer").order_by('delivery', 'customer'):
                if delivery_save != ci.delivery:
                    delivery_save = ci.delivery
                    if ci.delivery is not None:
                        link.append("<br/><b>%s</b>" % ci.delivery.get_delivery_display())
                    else:
                        link.append("<br/><br/>--")
                total_price_with_tax = ci.get_total_price_with_tax(customer_charged=True)
                # if ci.is_order_confirm_send:
                label = '%s%s (%s) %s%s' % (
                    "<b><i>" if ci.is_group else EMPTY_STRING,
                    ci.customer.short_basket_name,
                    "-" if ci.is_group or total_price_with_tax == DECIMAL_ZERO else total_price_with_tax,
                    ci.get_is_order_confirm_send_display(),
                    "</i></b>" if ci.is_group else EMPTY_STRING,
                )
                # else:
                #     label = '%s%s (%s) %s%s' % (
                #         "<b><i>" if ci.is_group else EMPTY_STRING,
                #         ci.customer.short_basket_name,
                #         "-" if ci.is_group or total_price_with_tax == DECIMAL_ZERO else total_price_with_tax,
                #         ci.get_is_order_confirm_send_display(),
                #         "</i></b>" if ci.is_group else EMPTY_STRING,
                #     )
                # Important : no target="_blank"
                link.append(
                    '<a href="%s?permanence=%d&customer=%d">%s</a>'
                    % (changelist_url, self.id, ci.customer_id, label.replace(' ', '&nbsp;')))
            customers = ", ".join(link)
        elif self.status in [PERMANENCE_INVOICED, PERMANENCE_ARCHIVED]:
            link = []
            delivery_save = None
            for ci in CustomerInvoice.objects.filter(permanence_id=self.id).select_related(
                "customer").order_by('delivery', 'customer'):
                if delivery_save != ci.delivery:
                    delivery_save = ci.delivery
                    if ci.delivery is not None:
                        link.append("<br/><b>%s</b>" % ci.delivery.get_delivery_display())
                    else:
                        link.append("<br/><br/>--")
                total_price_with_tax = ci.get_total_price_with_tax(customer_charged=True)
                label = "%s%s (%s) %s%s" % (
                    "<b><i>" if ci.is_group else EMPTY_STRING,
                    ci.customer.short_basket_name,
                    "-" if total_price_with_tax == DECIMAL_ZERO else total_price_with_tax,
                    ci.get_is_order_confirm_send_display(),
                    "</i></b>" if ci.is_group else EMPTY_STRING,
                )
                # 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
                link.append(
                    '<a href="%s?customer=%d" target="_blank">%s</a>'
                    % (
                        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.objects.filter(customerinvoice__permanence_id=self.id).only(
                                       'short_basket_name')])
        if len(customers) > 0:
            msg_html = """
                <div class="wrap-text"><button
                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>
                <div id="id_get_customers_%d" style="display:none;">%s</div></div>
            """ % (
                self.id, _("Show"), _("Hide"), _("Show"), _("Show"), self.id, customers
            )
            return mark_safe(msg_html)
        else:
            return mark_safe('<div class="wrap-text">%s</div>' % _("No purchase"))

    get_customers.short_description = (_("Purchases by"))
    # get_customers.allow_tags = True

    @cached_property
    def get_board(self):
        permanenceboard_set = 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 + \
                             '" target="_blank">' + r.short_name.replace(' ', '&nbsp;') + '</a>'
                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 + \
                             '" target="_blank">' + c.short_basket_name.replace(' ', '&nbsp;') + '</a>'
                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 = """
                <div class="wrap-text"><button
                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>
                <div id="id_get_board_%d" style="display:none;">%s</div></div>
            """ % (
                self.id, _("Show"), _("Hide"), _("Show"), _("Show"), self.id, board
            )
            return mark_safe(msg_html)
        else:
            return mark_safe('<div class="wrap-text">%s</div>' % _("No task"))

    get_board.short_description = (_("Tasks"))
    # 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):
        from repanier.models.purchase import Purchase

        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', 'payment_date'])
            else:
                self.save(update_fields=['status', 'is_updated_on', 'highest_status'])
            if new_status == PERMANENCE_WAIT_FOR_OPEN:
                for a_producer in self.producers.all():
                    # Create ProducerInvoice to be able to close those producer on demand
                    if not ProducerInvoice.objects.filter(
                            permanence_id=self.id,
                            producer_id=a_producer.id
                    ).order_by('?').exists():
                        ProducerInvoice.objects.create(
                            permanence_id=self.id,
                            producer_id=a_producer.id
                        )

            # self.with_delivery_point = DeliveryBoard.objects.filter(
            #     permanence_id=self.id
            # ).order_by('?').exists()
            if self.with_delivery_point:
                qs = 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)
            CustomerInvoice.objects.filter(
                permanence_id=self.id,
                delivery__isnull=True
            ).order_by('?').update(
                status=new_status
            )
            Purchase.objects.filter(
                permanence_id=self.id,
                customer_invoice__delivery__isnull=True
            ).order_by('?').update(
                status=new_status)
            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', 'highest_status', 'with_delivery_point'])
            menu_pool.clear()
            cache.clear()
        else:
            # /!\ If one delivery point has been closed, I may not close anymore by producer
            Purchase.objects.filter(permanence_id=self.id, producer__in=producers_id).order_by('?').update(
                status=new_status)
            ProducerInvoice.objects.filter(permanence_id=self.id, producer__in=producers_id).order_by(
                '?').update(status=new_status)

    def duplicate(self, dates):
        creation_counter = 0
        short_name = self.safe_translation_getter(
            'short_name', any_language=True, default=EMPTY_STRING
        )
        cur_language = translation.get_language()
        for date in dates:
            delta_days = (date - self.permanence_date).days
            # Mandatory because of Parler
            if short_name != EMPTY_STRING:
                already_exists = Permanence.objects.filter(
                    permanence_date=date,
                    translations__language_code=cur_language,
                    translations__short_name=short_name
                ).exists()
            else:
                already_exists = False
                for existing_permanence in Permanence.objects.filter(
                        permanence_date=date
                ):
                    try:
                        short_name = existing_permanence.short_name
                        already_exists = short_name == EMPTY_STRING
                    except TranslationDoesNotExist:
                        already_exists = True
                    if already_exists:
                        break
            if not already_exists:
                creation_counter += 1
                new_permanence = Permanence.objects.create(
                    permanence_date=date
                )
                self.duplicate_short_name(
                    new_permanence,
                    cur_language=translation.get_language(),
                )
                for permanence_board in PermanenceBoard.objects.filter(
                        permanence=self
                ):
                    PermanenceBoard.objects.create(
                        permanence=new_permanence,
                        permanence_role=permanence_board.permanence_role
                    )
                for delivery_board in DeliveryBoard.objects.filter(
                        permanence=self
                ):
                    if delivery_board.delivery_date is not None:
                        new_delivery_board = DeliveryBoard.objects.create(
                            permanence=new_permanence,
                            delivery_point=delivery_board.delivery_point,
                            delivery_date=delivery_board.delivery_date + datetime.timedelta(days=delta_days)
                        )
                    else:
                        new_delivery_board = DeliveryBoard.objects.create(
                            permanence=new_permanence,
                            delivery_point=delivery_board.delivery_point,
                        )
                    for language in settings.PARLER_LANGUAGES[settings.SITE_ID]:
                        language_code = language["code"]
                        translation.activate(language_code)
                        new_delivery_board.set_current_language(language_code)
                        delivery_board.set_current_language(language_code)
                        try:
                            new_delivery_board.delivery_comment = delivery_board.delivery_comment
                            new_delivery_board.save()
                        except TranslationDoesNotExist:
                            pass
                    translation.activate(cur_language)
                for a_producer in self.producers.all():
                    new_permanence.producers.add(a_producer)
        return creation_counter

    def duplicate_short_name(self, new_permanence, cur_language):
        for language in settings.PARLER_LANGUAGES[settings.SITE_ID]:
            language_code = language["code"]
            translation.activate(language_code)
            new_permanence.set_current_language(language_code)
            self.set_current_language(language_code)
            try:
                new_permanence.short_name = self.short_name
                new_permanence.save()
            except TranslationDoesNotExist:
                pass
        translation.activate(cur_language)
        return new_permanence

    def create_child(self, status):
        child_permanence = Permanence.objects.create(
            permanence_date=self.permanence_date,
            master_permanence_id=self.id,
            status=status
        )
        return self.duplicate_short_name(
            child_permanence,
            cur_language=translation.get_language(),
        )

    def recalculate_order_amount(self,
                                 offer_item_qs=None,
                                 re_init=False,
                                 send_to_producer=False):
        from repanier.models.purchase import Purchase
        getcontext().rounding = ROUND_HALF_UP

        if send_to_producer or re_init:
            assert offer_item_qs is None, 'offer_item_qs must be set to None when send_to_producer or re_init'
            ProducerInvoice.objects.filter(
                permanence_id=self.id
            ).update(
                total_price_with_tax=DECIMAL_ZERO,
                total_vat=DECIMAL_ZERO,
                total_deposit=DECIMAL_ZERO,
            )
            CustomerInvoice.objects.filter(
                permanence_id=self.id
            ).update(
                total_price_with_tax=DECIMAL_ZERO,
                total_vat=DECIMAL_ZERO,
                total_deposit=DECIMAL_ZERO
            )
            CustomerProducerInvoice.objects.filter(
                permanence_id=self.id
            ).update(
                total_purchase_with_tax=DECIMAL_ZERO,
                total_selling_with_tax=DECIMAL_ZERO
            )
            OfferItem.objects.filter(
                permanence_id=self.id
            ).update(
                quantity_invoiced=DECIMAL_ZERO,
                total_purchase_with_tax=DECIMAL_ZERO,
                total_selling_with_tax=DECIMAL_ZERO
            )
            self.total_purchase_with_tax=DECIMAL_ZERO
            self.total_selling_with_tax=DECIMAL_ZERO
            self.total_purchase_vat=DECIMAL_ZERO
            self.total_selling_vat=DECIMAL_ZERO
            for offer_item in OfferItem.objects.filter(
                    permanence_id=self.id,
                    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()

        if offer_item_qs is not None:
            purchase_set = Purchase.objects \
                .filter(permanence_id=self.id, offer_item__in=offer_item_qs) \
                .order_by('?')
        else:
            purchase_set = Purchase.objects \
                .filter(permanence_id=self.id) \
                .order_by('?')

        for a_purchase in purchase_set.select_related("offer_item"):
            # Recalculate 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 or re_init:
                a_purchase.previous_quantity_ordered = DECIMAL_ZERO
                a_purchase.previous_quantity_invoiced = DECIMAL_ZERO
                a_purchase.previous_purchase_price = DECIMAL_ZERO
                a_purchase.previous_selling_price = DECIMAL_ZERO
                a_purchase.previous_producer_vat = DECIMAL_ZERO
                a_purchase.previous_customer_vat = DECIMAL_ZERO
                a_purchase.previous_deposit = DECIMAL_ZERO
                if send_to_producer:
                    offer_item = a_purchase.offer_item
                    if offer_item.order_unit == PRODUCT_ORDER_UNIT_PC_KG:
                        a_purchase.quantity_invoiced = (a_purchase.quantity_ordered * offer_item.order_average_weight) \
                            .quantize(FOUR_DECIMALS)
                    else:
                        a_purchase.quantity_invoiced = a_purchase.quantity_ordered
            a_purchase.save()
        self.save()

    def recalculate_profit(self):
        from repanier.models.purchase import Purchase
        getcontext().rounding = ROUND_HALF_UP

        result_set = CustomerInvoice.objects.filter(
            permanence_id=self.id,
            is_group=True,
        ).order_by('?').aggregate(
            Sum('delta_price_with_tax'),
            Sum('delta_vat'),
            Sum('delta_transport')
        )
        if result_set["delta_price_with_tax__sum"] is not None:
            ci_sum_delta_price_with_tax = result_set["delta_price_with_tax__sum"]
        else:
            ci_sum_delta_price_with_tax = DECIMAL_ZERO
        if result_set["delta_vat__sum"] is not None:
            ci_sum_delta_vat = result_set["delta_vat__sum"]
        else:
            ci_sum_delta_vat = DECIMAL_ZERO
        if result_set["delta_transport__sum"] is not None:
            ci_sum_delta_transport = result_set["delta_transport__sum"]
        else:
            ci_sum_delta_transport = DECIMAL_ZERO

        result_set = Purchase.objects.filter(
            permanence_id=self.id,
            offer_item__price_list_multiplier__gte=DECIMAL_ONE
        ).order_by('?').aggregate(
            Sum('purchase_price'),
            Sum('selling_price'),
            Sum('producer_vat'),
            Sum('customer_vat'),
        )
        if result_set["purchase_price__sum"] is not None:
            purchase_price = result_set["purchase_price__sum"]
        else:
            purchase_price = DECIMAL_ZERO
        if result_set["selling_price__sum"] is not None:
            selling_price = result_set["selling_price__sum"]
        else:
            selling_price = DECIMAL_ZERO
        selling_price += ci_sum_delta_price_with_tax + ci_sum_delta_transport
        if result_set["producer_vat__sum"] is not None:
            producer_vat = result_set["producer_vat__sum"]
        else:
            producer_vat = DECIMAL_ZERO
        if result_set["customer_vat__sum"] is not None:
            customer_vat = result_set["customer_vat__sum"]
        else:
            customer_vat = DECIMAL_ZERO
        customer_vat += ci_sum_delta_vat
        self.total_purchase_with_tax = purchase_price
        self.total_selling_with_tax = selling_price
        self.total_purchase_vat = producer_vat
        self.total_selling_vat = customer_vat

        result_set = Purchase.objects.filter(
            permanence_id=self.id,
            offer_item__price_list_multiplier__lt=DECIMAL_ONE
        ).order_by('?').aggregate(
            Sum('selling_price'),
            Sum('customer_vat'),
        )
        if result_set["selling_price__sum"] is not None:
            selling_price = result_set["selling_price__sum"]
        else:
            selling_price = DECIMAL_ZERO
        if result_set["customer_vat__sum"] is not None:
            customer_vat = result_set["customer_vat__sum"]
        else:
            customer_vat = DECIMAL_ZERO
        self.total_purchase_with_tax += selling_price
        self.total_selling_with_tax += selling_price
        self.total_purchase_vat += customer_vat
        self.total_selling_vat += customer_vat

    @cached_property
    def get_new_products(self):
        assert self.status < PERMANENCE_SEND
        result = []
        for a_producer in self.producers.all():
            current_products = list(OfferItem.objects.filter(
                is_active=True,
                may_order=True,
                order_unit__lt=PRODUCT_ORDER_UNIT_DEPOSIT,  # Don't display technical products.
                permanence_id=self.id,
                producer=a_producer
            ).values_list(
                'product', flat=True
            ).order_by('?'))
            six_months_ago = timezone.now().date() - datetime.timedelta(days=6*30)
            previous_permanence = Permanence.objects.filter(
                status__gte=PERMANENCE_SEND,
                producers=a_producer,
                permanence_date__gte=six_months_ago
            ).order_by(
                "-permanence_date",
                "status"
            ).first()
            if previous_permanence is not None:
                previous_products = list(OfferItem.objects.filter(
                    is_active=True,
                    may_order=True,
                    order_unit__lt=PRODUCT_ORDER_UNIT_DEPOSIT,  # Don't display technical products.
                    permanence_id=previous_permanence.id,
                    producer=a_producer
                ).values_list(
                    'product', flat=True
                ).order_by('?'))
                new_products = [item for item in current_products if item not in previous_products]
            else:
                new_products = current_products

            qs = OfferItem.objects.filter(
                permanence_id=self.id,
                product__in=new_products,
                translations__language_code=translation.get_language()
            ).order_by(
                "translations__order_sort_order"
            )
            for o in qs:
                result.append('<li>%s, %s, %s</li>' % (
                    o.get_long_name(with_box_unicode=False),
                    o.producer.short_profile_name,
                    o.email_offer_price_with_vat,
                ))
        if result:
            return mark_safe('<ul>%s</ul>' % "".join(result))
        return EMPTY_STRING

    def get_full_status_display(self):
        need_to_refresh_status = self.status in [
            PERMANENCE_WAIT_FOR_PRE_OPEN,
            PERMANENCE_WAIT_FOR_OPEN,
            PERMANENCE_WAIT_FOR_CLOSED,
            PERMANENCE_WAIT_FOR_SEND,
            PERMANENCE_WAIT_FOR_INVOICED
        ]
        if self.with_delivery_point:
            status_list = []
            status = None
            status_counter = 0
            for delivery in DeliveryBoard.objects.filter(permanence_id=self.id).order_by("status", "id"):
                need_to_refresh_status |= delivery.status in [
                    PERMANENCE_WAIT_FOR_PRE_OPEN,
                    PERMANENCE_WAIT_FOR_OPEN,
                    PERMANENCE_WAIT_FOR_CLOSED,
                    PERMANENCE_WAIT_FOR_SEND,
                    PERMANENCE_WAIT_FOR_INVOICED
                ]
                if status != delivery.status:
                    status = delivery.status
                    status_counter += 1
                    status_list.append("<b>%s</b>" % delivery.get_status_display())
                status_list.append("- %s" % delivery.get_delivery_display(admin=True))
            # if status_counter > 1:
            message = "<br/>".join(status_list)
            # else:
            #     message = self.get_status_display()
        else:
            message = self.get_status_display()
        if need_to_refresh_status:
            url = urlresolvers.reverse(
                'display_status',
                args=(self.id,)
            )
            progress = "◤◥◢◣"[self.gauge] # "◴◷◶◵" "▛▜▟▙"
            self.gauge = (self.gauge + 1) % 4
            self.save(update_fields=['gauge'])
            msg_html = """
                    <div class="wrap-text" id="id_get_status_%d">
                    <script type="text/javascript">
                        window.setTimeout(function(){
                            django.jQuery.ajax({
                                url: '%s',
                                cache: false,
                                async: false,
                                success: function (result) {
                                    django.jQuery("#id_get_status_%d").html(result);
                                }
                            });
                        }, 500);
                    </script>
                    %s %s</div>
                """ % (
                self.id, url, self.id, progress, message
            )

        else:
            msg_html = '<div class="wrap-text">%s</div>' % message
        return mark_safe(msg_html)

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

    def get_permanence_display(self):
        short_name = self.safe_translation_getter(
            'short_name', any_language=True
        )
        if short_name:
            permanence_display = "%s" % short_name
        else:
            permanence_display = '%s%s' % (
                repanier.apps.REPANIER_SETTINGS_PERMANENCE_ON_NAME,
                self.permanence_date.strftime(settings.DJANGO_SETTINGS_DATE)
            )
        return permanence_display

    def get_permanence_admin_display(self):
        if self.status == PERMANENCE_INVOICED and self.total_selling_with_tax.amount != DECIMAL_ZERO:
            profit = self.total_selling_with_tax.amount - self.total_purchase_with_tax.amount
            # profit = self.total_selling_with_tax.amount - self.total_selling_vat.amount - self.total_purchase_with_tax.amount + self.total_purchase_vat.amount
            if profit != DECIMAL_ZERO:
                return '%s<br/>%s<br/>💶&nbsp;%s' % (
                    self.get_permanence_display(), self.total_selling_with_tax, RepanierMoney(profit)
                )
            return '%s<br/>%s' % (
                self.get_permanence_display(), self.total_selling_with_tax)
        else:
            return self.get_permanence_display()

    get_permanence_admin_display.short_description = _("Offers")
    get_permanence_admin_display.allow_tags = True

    def get_permanence_customer_display(self, with_status=True):
        if with_status:
            if self.with_delivery_point:
                if self.status == PERMANENCE_OPENED:
                    deliveries_count = 0
                else:
                    deliveries_qs = 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:
                    return "%s - %s" % (self.get_permanence_display(), self.get_status_display())
                else:
                    return "%s - %s" % (self.get_permanence_display(), _('orders closed'))
        return self.get_permanence_display()

    def __str__(self):
        return self.get_permanence_display()

    class Meta:
        verbose_name = _('order')
        verbose_name_plural = _('orders')