Example #1
0
class CustomerProducerInvoice(models.Model):
    customer = models.ForeignKey(
        'Customer', verbose_name=_("Customer"),
        on_delete=models.PROTECT)
    producer = models.ForeignKey(
        'Producer', verbose_name=_("Producer"),
        on_delete=models.PROTECT)
    permanence = models.ForeignKey(
        'Permanence', verbose_name=_('Order'), on_delete=models.PROTECT,
        db_index=True)
    # Calculated with Purchase
    total_purchase_with_tax = ModelMoneyField(
        _("Producer amount invoiced"),
        help_text=_('Total selling amount vat included'),
        default=DECIMAL_ZERO, max_digits=8, decimal_places=2)
    # Calculated with Purchase
    total_selling_with_tax = ModelMoneyField(
        _("Invoiced to the consumer including tax"),
        help_text=_('Total selling amount vat included'),
        default=DECIMAL_ZERO, max_digits=8, decimal_places=2)

    def get_html_producer_price_purchased(self):
        if self.total_purchase_with_tax != DECIMAL_ZERO:
            return mark_safe(_("<b>%(price)s</b>") % {'price': self.total_purchase_with_tax})
        return EMPTY_STRING

    get_html_producer_price_purchased.short_description = (_("Producer amount invoiced"))
    get_html_producer_price_purchased.allow_tags = True
    get_html_producer_price_purchased.admin_order_field = 'total_purchase_with_tax'

    def __str__(self):
        return "{}, {}".format(self.producer, self.customer)

    class Meta:
        unique_together = ("permanence", "customer", "producer",)
Example #2
0
class LUT_DeliveryPoint(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),
    )
    is_active = models.BooleanField(_("Active"), default=True)
    customer_responsible = models.ForeignKey(
        'Customer', verbose_name=_("Customer responsible"),
        help_text=_("Invoices are sent to this customer who is responsible for collecting the payments."),
        blank=True, null=True, default=None)
    inform_customer_responsible = models.BooleanField(_("Inform the group of orders placed by its members"), default=False)
    transport = ModelMoneyField(
        _("Delivery point shipping cost"),
        # help_text=_("This amount is added once for groups with entitled customer or at each customer for open groups."),
        default=DECIMAL_ZERO, blank=True, max_digits=5, decimal_places=2,
        validators=[MinValueValidator(0)])
    min_transport = ModelMoneyField(
        _("Minium order amount for free shipping cost"),
        # help_text=_("This is the minimum order amount to avoid shipping cost."),
        default=DECIMAL_ZERO, blank=True, max_digits=5, decimal_places=2,
        validators=[MinValueValidator(0)])

    objects = LUT_DeliveryPointManager()

    def __str__(self):
        if self.customer_responsible is not None:
            return "[{}]".format(self.customer_responsible.short_basket_name)
        else:
            return self.safe_translation_getter('short_name', any_language=True, default=EMPTY_STRING)

    class Meta:
        verbose_name = _("Delivery point")
        verbose_name_plural = _("Deliveries points")
Example #3
0
class Invoice(models.Model):
    permanence = models.ForeignKey(
        'Permanence', verbose_name=_('Order'),
        on_delete=models.PROTECT, db_index=True)
    status = models.CharField(
        max_length=3,
        choices=LUT_PERMANENCE_STATUS,
        default=PERMANENCE_PLANNED,
        verbose_name=_("Status"))
    date_previous_balance = models.DateField(
        _("Date previous balance"), default=datetime.date.today)
    previous_balance = ModelMoneyField(
        _("Previous balance"), max_digits=8, decimal_places=2, default=DECIMAL_ZERO)
    # Calculated with Purchase
    total_price_with_tax = ModelMoneyField(
        _("Invoiced TVAC"),
        default=DECIMAL_ZERO, max_digits=8, decimal_places=2)
    delta_price_with_tax = ModelMoneyField(
        _("Total amount"),
        help_text=_('Purchase to add amount vat included'),
        default=DECIMAL_ZERO, max_digits=8, decimal_places=2)
    delta_transport = ModelMoneyField(
        _("Delivery point shipping cost"),
        help_text=_("Transport to add"),
        default=DECIMAL_ZERO, max_digits=5, decimal_places=2,
        validators=[MinValueValidator(0)])
    total_vat = ModelMoneyField(
        _("VAT"),
        default=DECIMAL_ZERO, max_digits=9, decimal_places=4)
    delta_vat = ModelMoneyField(
        _("VAT to add"),
        default=DECIMAL_ZERO, max_digits=9, decimal_places=4)
    total_deposit = ModelMoneyField(
        _("Deposit"),
        help_text=_('Surcharge'),
        default=DECIMAL_ZERO, max_digits=8, decimal_places=2)
    bank_amount_in = ModelMoneyField(
        _("Cash in"), help_text=_('Payment on the account'),
        max_digits=8, decimal_places=2, default=DECIMAL_ZERO)
    bank_amount_out = ModelMoneyField(
        _("Cash out"), help_text=_('Payment from the account'),
        max_digits=8, decimal_places=2, default=DECIMAL_ZERO)
    date_balance = models.DateField(
        _("Date balance"), default=datetime.date.today)
    balance = ModelMoneyField(
        _("Balance"),
        max_digits=8, decimal_places=2, default=DECIMAL_ZERO)

    def get_delta_price_with_tax(self):
        return self.delta_price_with_tax.amount

    def get_abs_delta_price_with_tax(self):
        return abs(self.delta_price_with_tax.amount)

    def __str__(self):
        return _("Invoice")

    class Meta:
        abstract = True
Example #4
0
class BoxContent(models.Model):
    box = models.ForeignKey(
        "Box",
        verbose_name=_("Box"),
        null=True,
        blank=True,
        db_index=True,
        on_delete=models.PROTECT,
    )
    product = models.ForeignKey(
        "Product",
        verbose_name=_("Product"),
        related_name="box_content",
        null=True,
        blank=True,
        db_index=True,
        on_delete=models.PROTECT,
    )
    content_quantity = models.DecimalField(
        _("Fixed quantity per unit"),
        default=DECIMAL_ZERO,
        max_digits=6,
        decimal_places=3,
        validators=[MinValueValidator(0)],
    )
    calculated_customer_content_price = ModelMoneyField(
        _("Calculated consumer tariff"),
        default=DECIMAL_ZERO,
        max_digits=8,
        decimal_places=2,
    )
    calculated_content_deposit = ModelMoneyField(
        _("Content deposit"),
        help_text=_("Surcharge"),
        default=DECIMAL_ZERO,
        max_digits=8,
        decimal_places=2,
    )

    def get_calculated_customer_content_price(self):
        # workaround for a display problem with Money field in the admin list_display
        return self.calculated_customer_content_price + self.calculated_content_deposit

    get_calculated_customer_content_price.short_description = _(
        "Calculated consumer tariff")

    def __str__(self):
        return EMPTY_STRING

    class Meta:
        verbose_name = _("Box content")
        verbose_name_plural = _("Boxes content")
        unique_together = (("box", "product"), )
        index_together = [["product", "box"]]
Example #5
0
class BoxContent(models.Model):
    box = models.ForeignKey('Box',
                            verbose_name=_("box"),
                            null=True,
                            blank=True,
                            db_index=True,
                            on_delete=models.PROTECT)
    product = models.ForeignKey('Product',
                                verbose_name=_("product"),
                                related_name='box_content',
                                null=True,
                                blank=True,
                                db_index=True,
                                on_delete=models.PROTECT)
    content_quantity = models.DecimalField(_("fixed content quantity"),
                                           default=DECIMAL_ZERO,
                                           max_digits=6,
                                           decimal_places=3,
                                           validators=[MinValueValidator(0)])
    calculated_customer_content_price = ModelMoneyField(
        _("customer content price"),
        default=DECIMAL_ZERO,
        max_digits=8,
        decimal_places=2)
    calculated_content_deposit = ModelMoneyField(
        _("content deposit"),
        help_text=_('deposit to add to the original content price'),
        default=DECIMAL_ZERO,
        max_digits=8,
        decimal_places=2)

    def get_calculated_customer_content_price(self):
        # workaround for a display problem with Money field in the admin list_display
        return self.calculated_customer_content_price + self.calculated_content_deposit

    get_calculated_customer_content_price.short_description = (
        _("customer content price"))
    get_calculated_customer_content_price.allow_tags = False

    def __str__(self):
        return EMPTY_STRING

    class Meta:
        verbose_name = _("box content")
        verbose_name_plural = _("boxes content")
        unique_together = (
            "box",
            "product",
        )
        index_together = [
            ["product", "box"],
        ]
Example #6
0
class CustomerProducerInvoice(models.Model):
    customer = models.ForeignKey(
        "Customer", verbose_name=_("Customer"), on_delete=models.PROTECT
    )
    producer = models.ForeignKey(
        "Producer", verbose_name=_("Producer"), on_delete=models.PROTECT
    )
    permanence = models.ForeignKey(
        "Permanence", verbose_name=_("Order"), on_delete=models.PROTECT, db_index=True
    )
    # Calculated with Purchase
    total_purchase_with_tax = ModelMoneyField(
        _("Producer amount invoiced"),
        help_text=_("Total selling amount vat included"),
        default=DECIMAL_ZERO,
        max_digits=8,
        decimal_places=2,
    )
    # Calculated with Purchase
    total_selling_with_tax = ModelMoneyField(
        _("Invoiced to the consumer w TVA"),
        help_text=_("Total selling amount vat included"),
        default=DECIMAL_ZERO,
        max_digits=8,
        decimal_places=2,
    )

    def get_html_producer_price_purchased(self):
        if self.total_purchase_with_tax != DECIMAL_ZERO:
            return format_html("<b>{}</b>", self.total_purchase_with_tax)
        return EMPTY_STRING

    get_html_producer_price_purchased.short_description = _("Producer amount invoiced")
    get_html_producer_price_purchased.admin_order_field = "total_purchase_with_tax"

    def __str__(self):
        return "{}, {}".format(self.producer, self.customer)

    class Meta:
        unique_together = (("permanence", "customer", "producer"),)
Example #7
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'),
        ]
Example #8
0
class CustomerInvoice(Invoice):
    customer = models.ForeignKey(
        'Customer', verbose_name=_("customer"),
        on_delete=models.PROTECT)
    customer_charged = models.ForeignKey(
        'Customer', verbose_name=_("customer"), related_name='invoices_paid', blank=True, null=True,
        on_delete=models.PROTECT, db_index=True)
    delivery = models.ForeignKey(
        'DeliveryBoard', verbose_name=_("delivery board"),
        null=True, blank=True, default=None,
        on_delete=models.PROTECT)
    # IMPORTANT: default = True -> for the order form, to display nothing at the begin of the order
    # is_order_confirm_send and total_price_with_tax = 0 --> display nothing
    # otherwise display
    # - send a mail with the order to me
    # - confirm the order (if REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS) and send a mail with the order to me
    # - mail send to XYZ
    # - order confirmed (if REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS) and mail send to XYZ
    is_order_confirm_send = models.BooleanField(_("is_order_confirm_send"), choices=LUT_CONFIRM, default=False)
    invoice_sort_order = models.IntegerField(
        _("invoice sort order"),
        default=None, blank=True, null=True, db_index=True)
    price_list_multiplier = models.DecimalField(
        _("Delivery point price list multiplier"),
        help_text=_("This multiplier is applied once for groups with entitled customer or at each customer invoice for open groups."),
        default=DECIMAL_ONE, max_digits=5, decimal_places=4, blank=True,
        validators=[MinValueValidator(0)])
    transport = ModelMoneyField(
        _("Delivery point shipping cost"),
        help_text=_("This amount is added once for groups with entitled customer or at each customer for open groups."),
        default=DECIMAL_ZERO, max_digits=5, decimal_places=2,
        validators=[MinValueValidator(0)])
    min_transport = ModelMoneyField(
        _("Minium order amount for free shipping cost"),
        help_text=_("This is the minimum order amount to avoid shipping cost."),
        default=DECIMAL_ZERO, max_digits=5, decimal_places=2,
        validators=[MinValueValidator(0)])
    master_permanence = models.ForeignKey(
        'Permanence', verbose_name=_("master permanence"),
        related_name='child_customer_invoice',
        blank=True, null=True, default=None,
        on_delete=models.PROTECT, db_index=True)
    is_group = models.BooleanField(_("is a group"), default=False)

    def get_abs_delta_vat(self):
        return abs(self.delta_vat)

    def get_total_price_with_tax(self, customer_charged=False):
        if self.customer_id == self.customer_charged_id:
            return self.total_price_with_tax + self.delta_price_with_tax + self.delta_transport
        else:
            if self.status < PERMANENCE_INVOICED or not customer_charged:
                return self.total_price_with_tax
            else:
                return self.customer_charged # if self.total_price_with_tax != DECIMAL_ZERO else RepanierMoney()

    def get_total_price_wo_tax(self):
        return self.get_total_price_with_tax() - self.get_total_tax()

    def get_total_tax(self):
        # round to 2 decimals
        return RepanierMoney(self.total_vat.amount + self.delta_vat.amount)

    @transaction.atomic
    def set_delivery(self, delivery):
        # May not use delivery_id because it won't reload customer_invoice.delivery
        # Important
        # If it's an invoice of a member of a group :
        #   self.customer_charged_id != self.customer_id
        #   self.customer_charged_id == owner of the group
        #   price_list_multiplier = DECIMAL_ONE
        # Else :
        #   self.customer_charged_id = self.customer_id
        #   price_list_multiplier may vary
        from repanier.apps import REPANIER_SETTINGS_TRANSPORT, REPANIER_SETTINGS_MIN_TRANSPORT
        if delivery is None:
            if self.permanence.with_delivery_point:
                # If the customer is member of a group set the group as default delivery point
                delivery_point = self.customer.delivery_point
                delivery = DeliveryBoard.objects.filter(
                    delivery_point=delivery_point,
                    permanence=self.permanence
                ).order_by('?').first()
            else:
                delivery_point = None
        else:
            delivery_point = delivery.delivery_point
        self.delivery = delivery

        if delivery_point is None:
            self.customer_charged = self.customer
            self.price_list_multiplier = DECIMAL_ONE
            self.transport = REPANIER_SETTINGS_TRANSPORT
            self.min_transport = REPANIER_SETTINGS_MIN_TRANSPORT
        else:
            customer_responsible = delivery_point.customer_responsible
            if customer_responsible is None:
                self.customer_charged = self.customer
                self.price_list_multiplier = DECIMAL_ONE
                self.transport = delivery_point.transport
                self.min_transport = delivery_point.min_transport
            else:
                self.customer_charged = customer_responsible
                self.price_list_multiplier = DECIMAL_ONE
                self.transport = REPANIER_MONEY_ZERO
                self.min_transport = REPANIER_MONEY_ZERO
                if self.customer_id != customer_responsible.id:
                    customer_invoice_charged = CustomerInvoice.objects.filter(
                        permanence_id=self.permanence_id,
                        customer_id=customer_responsible.id
                    ).order_by('?')
                    if not customer_invoice_charged.exists():
                        CustomerInvoice.objects.create(
                            permanence_id=self.permanence_id,
                            customer_id=customer_responsible.id,
                            status=self.status,
                            customer_charged_id=customer_responsible.id,
                            price_list_multiplier=customer_responsible.price_list_multiplier,
                            transport=delivery_point.transport,
                            min_transport=delivery_point.min_transport,
                            is_order_confirm_send=True,
                            is_group=True,
                            delivery=delivery
                        )

    def my_order_confirmation(self, permanence, is_basket=False,
                              basket_message=EMPTY_STRING, to_json=None):
        from repanier.apps import REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS

        if permanence.with_delivery_point:
            if self.delivery is not None:
                label = self.delivery.get_delivery_customer_display()
                delivery_id = self.delivery_id
            else:
                delivery_id = 0

                if self.customer.delivery_point is not None:
                    qs = DeliveryBoard.objects.filter(
                        Q(
                            permanence_id=permanence.id,
                            delivery_point_id=self.customer.delivery_point_id,
                            status=PERMANENCE_OPENED
                        ) | Q(
                            permanence_id=permanence.id,
                            delivery_point__customer_responsible__isnull=False,
                            status=PERMANENCE_OPENED
                        )
                    ).order_by('?')
                else:
                    qs = DeliveryBoard.objects.filter(
                        permanence_id=permanence.id,
                        delivery_point__customer_responsible__isnull=False,
                        status=PERMANENCE_OPENED
                    ).order_by('?')
                if qs.exists():
                    label = "%s" % _('Please, select a delivery point')
                    CustomerInvoice.objects.filter(
                        permanence_id=permanence.id,
                        customer_id=self.customer_id).order_by('?').update(
                        status=PERMANENCE_OPENED)
                else:
                    label = "%s" % _('No delivery point is open for you. You can not place order.')
                    # IMPORTANT :
                    # 1 / This prohibit to place an order into the customer UI
                    # 2 / task_order.close_send_order will delete any CLOSED orders without any delivery point
                    CustomerInvoice.objects.filter(
                        permanence_id=permanence.id,
                        customer_id=self.customer_id
                    ).order_by('?').update(
                        status=PERMANENCE_CLOSED)
            if self.customer_id != self.customer_charged_id:
                msg_price = msg_transport = EMPTY_STRING
            else:
                if self.transport.amount <= DECIMAL_ZERO:
                    transport = False
                    msg_transport = EMPTY_STRING
                else:
                    transport = True
                    if self.min_transport.amount > DECIMAL_ZERO:
                        msg_transport = "%s<br/>" % \
                                        _(
                                            'The shipping costs for this delivery point amount to %(transport)s for orders of less than %(min_transport)s.') % {
                                            'transport'    : self.transport,
                                            'min_transport': self.min_transport
                                        }
                    else:
                        msg_transport = "%s<br/>" % \
                                        _(
                                            'The shipping costs for this delivery point amount to %(transport)s.') % {
                                            'transport': self.transport,
                                        }
                if self.price_list_multiplier == DECIMAL_ONE:
                    msg_price = EMPTY_STRING
                else:
                    if transport:
                        if self.price_list_multiplier > DECIMAL_ONE:
                            msg_price = "%s<br/>" % \
                                        _(
                                            'A price increase of %(increase)s %% of the total invoiced is due for this delivery point. This does not apply to the cost of transport which is fixed.') % {
                                            'increase': number_format(
                                                (self.price_list_multiplier - DECIMAL_ONE) * 100, 2)
                                        }
                        else:
                            msg_price = "%s<br/>" % \
                                        _(
                                            'A price decrease of %(decrease)s %% of the total invoiced is given for this delivery point. This does not apply to the cost of transport which is fixed.') % {
                                            'decrease': number_format(
                                                (DECIMAL_ONE - self.price_list_multiplier) * 100, 2)
                                        }
                    else:
                        if self.price_list_multiplier > DECIMAL_ONE:
                            msg_price = "%s<br/>" % \
                                        _(
                                            'A price increase of %(increase)s %% of the total invoiced is due for this delivery point.') % {
                                            'increase': number_format(
                                                (self.price_list_multiplier - DECIMAL_ONE) * 100, 2)
                                        }
                        else:
                            msg_price = "%s<br/>" % \
                                        _(
                                            'A price decrease of %(decrease)s %% of the total invoiced is given for this delivery point.') % {
                                            'decrease': number_format(
                                                (DECIMAL_ONE - self.price_list_multiplier) * 100, 2)
                                        }

            msg_delivery = '%s<b><i><select name="delivery" id="delivery" onmouseover="show_select_delivery_list_ajax(%d)" onchange="delivery_ajax(%d)" class="form-control"><option value="%d" selected>%s</option></select></i></b><br/>%s%s' % (
                _("Delivery point"),
                delivery_id,
                delivery_id,
                delivery_id,
                label,
                msg_transport,
                msg_price
            )
        else:
            msg_delivery = EMPTY_STRING
        msg_confirmation1 = EMPTY_STRING
        if not is_basket and not REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS:
            # or customer_invoice.total_price_with_tax.amount != DECIMAL_ZERO:
            # If apps.REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS is True,
            # then permanence.with_delivery_point is also True
            msg_html = EMPTY_STRING
        else:
            if self.is_order_confirm_send:
                msg_confirmation2 = self.customer.my_order_confirmation_email_send_to()
                msg_html = """
                <div class="row">
                <div class="panel panel-default">
                <div class="panel-heading">
                %s
                <p><font color="#51a351">%s</font><p/>
                %s
                </div>
                </div>
                </div>
                 """ % (msg_delivery, msg_confirmation2, basket_message)
            else:
                msg_html = None
                btn_disabled = EMPTY_STRING if permanence.status == PERMANENCE_OPENED else "disabled"
                msg_confirmation2 = EMPTY_STRING
                if REPANIER_SETTINGS_CUSTOMERS_MUST_CONFIRM_ORDERS:
                    if is_basket:
                        if self.status == PERMANENCE_OPENED:
                            if (permanence.with_delivery_point and self.delivery is None) \
                                    or self.total_price_with_tax == DECIMAL_ZERO:
                                btn_disabled = "disabled"
                            msg_confirmation1 = '<font color="red">%s</font><br/>' % _(
                                "An unconfirmed order will be canceled.")
                            msg_confirmation2 = '<span class="glyphicon glyphicon-floppy-disk"></span>&nbsp;&nbsp;%s' % _(
                                "Confirm this order and receive an email containing its summary.")
                    else:
                        href = urlresolvers.reverse(
                            'order_view', args=(permanence.id,)
                        )
                        if self.status == PERMANENCE_OPENED:
                            msg_confirmation1 = '<font color="red">%s</font><br/>' % _(
                                "An unconfirmed order will be canceled.")
                            msg_confirmation2 = _("Verify my order content before validating it.")
                            msg_html = """
                                <div class="row">
                                <div class="panel panel-default">
                                <div class="panel-heading">
                                %s
                                %s
                                <a href="%s?is_basket=yes" class="btn btn-info" %s>%s</a>
                                </div>
                                </div>
                                </div>
                                 """ % (msg_delivery, msg_confirmation1, href, btn_disabled, msg_confirmation2)
                else:
                    if is_basket:
                        msg_confirmation2 = _("Receive an email containing this order summary.")
                    elif permanence.with_delivery_point:
                        msg_html = """
                            <div class="row">
                            <div class="panel panel-default">
                            <div class="panel-heading">
                            %s
                            </div>
                            </div>
                            </div>
                             """ % msg_delivery
                    else:
                        msg_html = EMPTY_STRING
                if msg_html is None:
                    if msg_confirmation2 == EMPTY_STRING:
                        msg_html = """
                        <div class="row">
                        <div class="panel panel-default">
                        <div class="panel-heading">
                        %s
                        <div class="clearfix"></div>
                        %s
                        </div>
                        </div>
                        </div>
                         """ % (msg_delivery, basket_message)
                    else:
                        msg_html = """
                        <div class="row">
                        <div class="panel panel-default">
                        <div class="panel-heading">
                        %s
                        %s
                        <button id="btn_confirm_order" class="btn btn-info" %s onclick="btn_receive_order_email();">%s</button>
                        <div class="clearfix"></div>
                        %s
                        </div>
                        </div>
                        </div>
                         """ % (msg_delivery, msg_confirmation1, btn_disabled, msg_confirmation2, basket_message)
        if to_json is not None:
            option_dict = {'id': "#span_btn_confirm_order", 'html': msg_html}
            to_json.append(option_dict)

    @transaction.atomic
    def confirm_order(self):
        from repanier.models.purchase import Purchase

        Purchase.objects.filter(
            customer_invoice__id=self.id
        ).update(quantity_confirmed=F('quantity_ordered'))
        self.calculate_and_save_delta_buyinggroup(confirm_order=True)
        self.is_order_confirm_send = True

    def calculate_and_save_delta_buyinggroup(self, confirm_order=False):
        previous_delta_price_with_tax = self.delta_price_with_tax.amount
        previous_delta_vat = self.delta_vat.amount
        previous_delta_transport = self.delta_transport.amount

        self.calculate_delta_price(confirm_order)
        self.calculate_delta_transport()

        if previous_delta_price_with_tax != self.delta_price_with_tax.amount or previous_delta_vat != self.delta_vat.amount or previous_delta_transport != self.delta_transport.amount:
            producer_invoice_buyinggroup = ProducerInvoice.objects.filter(
                producer__represent_this_buyinggroup=True,
                permanence_id=self.permanence_id,
            ).order_by('?').first()
            if producer_invoice_buyinggroup is None:
                # producer_buyinggroup = Producer.objects.filter(
                #     represent_this_buyinggroup=True
                # ).order_by('?').first()

                producer_invoice_buyinggroup = ProducerInvoice.objects.create(
                    producer_id=REPANIER_SETTINGS_GROUP_PRODUCER_ID,
                    permanence_id=self.permanence_id,
                    status=self.permanence.status
                )
            producer_invoice_buyinggroup.delta_price_with_tax.amount += self.delta_price_with_tax.amount - previous_delta_price_with_tax
            producer_invoice_buyinggroup.delta_vat.amount += self.delta_vat.amount - previous_delta_vat
            producer_invoice_buyinggroup.delta_transport.amount += self.delta_transport.amount - previous_delta_transport

            producer_invoice_buyinggroup.save()

    def calculate_delta_price(self, confirm_order=False):
        from repanier.models.purchase import Purchase
        getcontext().rounding = ROUND_HALF_UP

        self.delta_price_with_tax.amount = DECIMAL_ZERO
        self.delta_vat.amount = DECIMAL_ZERO

        # Important
        # Si c'est une facture du membre d'un groupe :
        #   self.customer_charged_id == purchase.customer_charged_id != self.customer_id
        #   self.customer_charged_id == purchase.customer_charged_id == owner of the group
        #   self.price_list_multiplier = DECIMAL_ONE
        # Si c'est une facture lambda ou d'un groupe :
        #   self.customer_charged_id == purchase.customer_charged_id = self.customer_id
        #   self.price_list_multiplier may vary
        if self.customer_id == self.customer_charged_id:

            if self.price_list_multiplier != DECIMAL_ONE:
                result_set = Purchase.objects.filter(
                    permanence_id=self.permanence_id,
                    customer_invoice__customer_charged_id=self.customer_id,
                    is_resale_price_fixed=False
                ).order_by('?').aggregate(
                    Sum('customer_vat'),
                    Sum('deposit'),
                    Sum('selling_price')
                )

                if result_set["customer_vat__sum"] is not None:
                    total_vat = result_set["customer_vat__sum"]
                else:
                    total_vat = DECIMAL_ZERO
                if result_set["deposit__sum"] is not None:
                    total_deposit = result_set["deposit__sum"]
                else:
                    total_deposit = DECIMAL_ZERO
                if result_set["selling_price__sum"] is not None:
                    total_price_with_tax = result_set["selling_price__sum"]
                else:
                    total_price_with_tax = DECIMAL_ZERO

                total_price_with_tax_wo_deposit = total_price_with_tax - total_deposit
                self.delta_price_with_tax.amount = -(
                    total_price_with_tax_wo_deposit - (
                        total_price_with_tax_wo_deposit * self.price_list_multiplier
                    ).quantize(TWO_DECIMALS)
                )
                self.delta_vat.amount = -(
                    total_vat - (
                        total_vat * self.price_list_multiplier
                    ).quantize(FOUR_DECIMALS)
                )

            result_set = Purchase.objects.filter(
                permanence_id=self.permanence_id,
                customer_invoice__customer_charged_id=self.customer_id,
            ).order_by('?').aggregate(
                Sum('customer_vat'),
                Sum('deposit'),
                Sum('selling_price')
            )
        else:
            result_set = Purchase.objects.filter(
                permanence_id=self.permanence_id,
                customer_id=self.customer_id,
            ).order_by('?').aggregate(
                Sum('customer_vat'),
                Sum('deposit'),
                Sum('selling_price')
            )
        if result_set["customer_vat__sum"] is not None:
            self.total_vat.amount = result_set["customer_vat__sum"]
        else:
            self.total_vat.amount = DECIMAL_ZERO
        if result_set["deposit__sum"] is not None:
            self.total_deposit.amount = result_set["deposit__sum"]
        else:
            self.total_deposit.amount = DECIMAL_ZERO
        if result_set["selling_price__sum"] is not None:
            self.total_price_with_tax.amount = result_set["selling_price__sum"]
        else:
            self.total_price_with_tax.amount = DECIMAL_ZERO

    def calculate_delta_transport(self):

        self.delta_transport.amount = DECIMAL_ZERO
        if self.master_permanence_id is None and self.transport.amount != DECIMAL_ZERO:
            # Calculate transport only on master customer invoice
            # But take into account the children customer invoices
            result_set = CustomerInvoice.objects.filter(
                master_permanence_id=self.permanence_id
            ).order_by('?').aggregate(
                Sum('total_price_with_tax'),
                Sum('delta_price_with_tax')
            )
            if result_set["total_price_with_tax__sum"] is not None:
                sum_total_price_with_tax = result_set["total_price_with_tax__sum"]
            else:
                sum_total_price_with_tax = DECIMAL_ZERO
            if result_set["delta_price_with_tax__sum"] is not None:
                sum_delta_price_with_tax = result_set["delta_price_with_tax__sum"]
            else:
                sum_delta_price_with_tax = DECIMAL_ZERO

            sum_total_price_with_tax += self.total_price_with_tax.amount
            sum_delta_price_with_tax += self.delta_price_with_tax.amount

            total_price_with_tax = sum_total_price_with_tax + sum_delta_price_with_tax
            if total_price_with_tax != DECIMAL_ZERO:
                if self.min_transport.amount == DECIMAL_ZERO:
                    self.delta_transport.amount = self.transport.amount
                elif total_price_with_tax < self.min_transport.amount:
                    self.delta_transport.amount = min(
                        self.min_transport.amount - total_price_with_tax,
                        self.transport.amount
                    )

    def cancel_confirm_order(self):
        if self.is_order_confirm_send:
            # Change of confirmation status
            self.is_order_confirm_send = False
            return True
        else:
            # No change of confirmation status
            return False

    def create_child(self, new_permanence):
        if self.customer_id != self.customer_charged_id:
            # TODO : Créer la customer invoice du groupe
            customer_invoice = CustomerInvoice.objects.filter(
                permanence_id=self.permanence_id,
                customer_id=self.customer_charged_id
            ).only("id").order_by('?')
            if not customer_invoice.exists():
                customer_invoice = CustomerInvoice.objects.create(
                    permanence_id=self.permanence_id,
                    customer_id=self.customer_charged_id,
                    customer_charged_id=self.customer_charged_id,
                    status=self.status
                )
                customer_invoice.set_delivery(delivery=None)
                customer_invoice.save()
        return CustomerInvoice.objects.create(
            permanence_id=new_permanence.id,
            customer_id=self.customer_id,
            master_permanence_id=self.permanence_id,
            customer_charged_id=self.customer_charged_id,
            status=self.status
        )

    def delete_if_unconfirmed(self, permanence):
        if not self.is_order_confirm_send:
            from repanier.email.email_order import export_order_2_1_customer
            from repanier.models.purchase import Purchase

            filename = "{0}-{1}.xlsx".format(
                slugify(_("Canceled order")),
                slugify(permanence)
            )
            sender_email, sender_function, signature, cc_email_staff = get_signature(
                is_reply_to_order_email=True)
            export_order_2_1_customer(
                self.customer, filename, permanence, sender_email,
                sender_function, signature,
                cancel_order=True
            )
            purchase_qs = Purchase.objects.filter(
                customer_invoice_id=self.id,
                is_box_content=False,
            ).order_by('?')
            for a_purchase in purchase_qs.select_related("customer"):
                create_or_update_one_cart_item(
                    customer=a_purchase.customer,
                    offer_item_id=a_purchase.offer_item_id,
                    q_order=DECIMAL_ZERO,
                    batch_job=True,
                    comment=_("Cancelled qty : %s") % number_format(a_purchase.quantity_ordered, 4)
                )

    def __str__(self):
        return '%s, %s' % (self.customer, self.permanence)

    class Meta:
        verbose_name = _("customer invoice")
        verbose_name_plural = _("customers invoices")
        unique_together = ("permanence", "customer",)
Example #9
0
class ProducerInvoice(Invoice):
    producer = models.ForeignKey(
        'Producer', verbose_name=_("producer"),
        # related_name='producer_invoice',
        on_delete=models.PROTECT)

    delta_stock_with_tax = ModelMoneyField(
        _("Total stock"),
        help_text=_('stock taken amount vat included'),
        default=DECIMAL_ZERO, max_digits=8, decimal_places=2)

    delta_stock_vat = ModelMoneyField(
        _("Total stock vat"),
        help_text=_('vat to add'),
        default=DECIMAL_ZERO, max_digits=9, decimal_places=4)
    delta_deposit = ModelMoneyField(
        _("deposit"),
        help_text=_('deposit to add'),
        default=DECIMAL_ZERO, max_digits=8, decimal_places=2)
    delta_stock_deposit = ModelMoneyField(
        _("deposit"),
        help_text=_('deposit to add'),
        default=DECIMAL_ZERO, max_digits=8, decimal_places=2)

    to_be_paid = models.BooleanField(_("to be paid"), choices=LUT_BANK_NOTE, default=False)
    calculated_invoiced_balance = ModelMoneyField(
        _("calculated balance to be invoiced"), max_digits=8, decimal_places=2, default=DECIMAL_ZERO)
    to_be_invoiced_balance = ModelMoneyField(
        _("balance to be invoiced"), max_digits=8, decimal_places=2, default=DECIMAL_ZERO)
    invoice_sort_order = models.IntegerField(
        _("invoice sort order"),
        default=None, blank=True, null=True, db_index=True)
    invoice_reference = models.CharField(
        _("invoice reference"), max_length=100, null=True, blank=True)

    def get_negative_previous_balance(self):
        return - self.previous_balance

    def get_negative_balance(self):
        return - self.balance

    def get_total_price_with_tax(self):
        return self.total_price_with_tax + self.delta_price_with_tax + self.delta_transport + self.delta_stock_with_tax

    def get_total_vat(self):
        return self.total_vat + self.delta_stock_vat

    def get_total_deposit(self):
        return self.total_deposit + self.delta_stock_deposit

    def get_order_json(self, to_json):
        a_producer = self.producer
        if a_producer.minimum_order_value.amount > DECIMAL_ZERO:
            ratio = self.total_price_with_tax.amount / a_producer.minimum_order_value.amount
            if ratio >= DECIMAL_ONE:
                ratio = 100
            else:
                ratio *= 100
            option_dict = {'id'  : "#order_procent%d" % a_producer.id,
                           'html': "%s%%" % number_format(ratio, 0)}
            to_json.append(option_dict)
        if self.status != PERMANENCE_OPENED:
            option_dict = {'id'  : "#order_closed%d" % a_producer.id,
                           'html': '&nbsp;<span class="glyphicon glyphicon-ban-circle" aria-hidden="true"></span>'}
            to_json.append(option_dict)
        return

    def __str__(self):
        return '%s, %s' % (self.producer, self.permanence)

    class Meta:
        verbose_name = _("producer invoice")
        verbose_name_plural = _("producers invoices")
        unique_together = ("permanence", "producer",)
Example #10
0
class Invoice(models.Model):
    permanence = models.ForeignKey(
        'Permanence', verbose_name=permanence_verbose_name(),
        on_delete=models.PROTECT, db_index=True)
    status = models.CharField(
        max_length=3,
        choices=LUT_PERMANENCE_STATUS,
        default=PERMANENCE_PLANNED,
        verbose_name=_("invoice_status"))
    date_previous_balance = models.DateField(
        _("date_previous_balance"), default=datetime.date.today)
    previous_balance = ModelMoneyField(
        _("previous_balance"), max_digits=8, decimal_places=2, default=DECIMAL_ZERO)
    # Calculated with Purchase
    total_price_with_tax = ModelMoneyField(
        _("Total amount"),
        help_text=_('Total purchase amount vat included'),
        default=DECIMAL_ZERO, max_digits=8, decimal_places=2)
    delta_price_with_tax = ModelMoneyField(
        _("Total amount"),
        help_text=_('purchase to add amount vat included'),
        default=DECIMAL_ZERO, max_digits=8, decimal_places=2)
    delta_transport = ModelMoneyField(
        _("Delivery point transport"),
        help_text=_("transport to add"),
        default=DECIMAL_ZERO, max_digits=5, decimal_places=2,
        validators=[MinValueValidator(0)])
    total_vat = ModelMoneyField(
        _("Total vat"),
        help_text=_('Vat part of the total purchased'),
        default=DECIMAL_ZERO, max_digits=9, decimal_places=4)
    delta_vat = ModelMoneyField(
        _("Total vat"),
        help_text=_('vat to add'),
        default=DECIMAL_ZERO, max_digits=9, decimal_places=4)
    total_deposit = ModelMoneyField(
        _("deposit"),
        help_text=_('deposit to add to the original unit price'),
        default=DECIMAL_ZERO, max_digits=8, decimal_places=2)
    bank_amount_in = ModelMoneyField(
        _("bank_amount_in"), help_text=_('payment_on_the_account'),
        max_digits=8, decimal_places=2, default=DECIMAL_ZERO)
    bank_amount_out = ModelMoneyField(
        _("bank_amount_out"), help_text=_('payment_from_the_account'),
        max_digits=8, decimal_places=2, default=DECIMAL_ZERO)
    date_balance = models.DateField(
        _("date_balance"), default=datetime.date.today)
    balance = ModelMoneyField(
        _("balance"),
        max_digits=8, decimal_places=2, default=DECIMAL_ZERO)

    def get_delta_price_with_tax(self):
        return self.delta_price_with_tax.amount

    def get_abs_delta_price_with_tax(self):
        return abs(self.delta_price_with_tax.amount)

    def __str__(self):
        return _("Invoice")

    class Meta:
        abstract = True
Example #11
0
class Configuration(TranslatableModel):
    group_name = models.CharField(_("group name"),
                                  max_length=50,
                                  default=EMPTY_STRING)
    test_mode = models.BooleanField(_("test mode"), default=False)
    login_attempt_counter = models.DecimalField(_("login attempt counter"),
                                                default=DECIMAL_ZERO,
                                                max_digits=2,
                                                decimal_places=0)
    password_reset_on = models.DateTimeField(_("password_reset_on"),
                                             null=True,
                                             blank=True,
                                             default=None)
    name = models.CharField(max_length=3,
                            choices=LUT_PERMANENCE_NAME,
                            default=PERMANENCE_NAME_PERMANENCE,
                            verbose_name=_("offers name"))
    currency = models.CharField(max_length=3,
                                choices=LUT_CURRENCY,
                                default=CURRENCY_EUR,
                                verbose_name=_("currency"))
    max_week_wo_participation = models.DecimalField(
        _("display a pop up on the order form after this max week wo participation"
          ),
        help_text=_("0 mean : never display a pop up."),
        default=DECIMAL_ZERO,
        max_digits=2,
        decimal_places=0,
        validators=[MinValueValidator(0)])
    send_opening_mail_to_customer = models.BooleanField(
        _("send opening mail to customers"), default=True)
    send_abstract_order_mail_to_customer = models.BooleanField(
        _("send abstract order mail to customers"), default=False)
    send_order_mail_to_customer = models.BooleanField(
        _("send order mail to customers"), default=True)
    send_cancel_order_mail_to_customer = models.BooleanField(
        _("send cancel order mail to customers"), default=True)
    send_abstract_order_mail_to_producer = models.BooleanField(
        _("send abstract order mail to producers"), default=False)
    send_order_mail_to_producer = models.BooleanField(
        _("send order mail to producers"), default=True)
    send_order_mail_to_board = models.BooleanField(
        _("send order mail to board"), default=True)
    send_invoice_mail_to_customer = models.BooleanField(
        _("send invoice mail to customers"), default=True)
    send_invoice_mail_to_producer = models.BooleanField(
        _("send invoice mail to producers"), default=False)
    invoice = models.BooleanField(_("activate invoice"), default=True)
    close_wo_sending = models.BooleanField(_("close wo sending"),
                                           default=False)
    display_anonymous_order_form = models.BooleanField(
        _("display anonymous order form"), default=True)
    display_producer_on_order_form = models.BooleanField(
        _("display producers on order form"), default=True)
    display_who_is_who = models.BooleanField(_("display who is who"),
                                             default=True)
    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)
    page_break_on_customer_check = models.BooleanField(
        _("page break on customer check"), default=False)
    sms_gateway_mail = models.EmailField(
        _("sms gateway email"),
        help_text=
        _("To actually send sms, use for e.g. on a GSM : https://play.google.com/store/apps/details?id=eu.apksoft.android.smsgateway"
          ),
        max_length=50,
        null=True,
        blank=True,
        default=EMPTY_STRING)
    customers_must_confirm_orders = models.BooleanField(
        _("customers must confirm orders"), default=False)
    membership_fee = ModelMoneyField(_("membership fee"),
                                     default=DECIMAL_ZERO,
                                     max_digits=8,
                                     decimal_places=2)
    membership_fee_duration = models.DecimalField(
        _("membership fee duration"),
        help_text=_("number of month(s). 0 mean : no membership fee."),
        default=DECIMAL_ZERO,
        max_digits=3,
        decimal_places=0,
        validators=[MinValueValidator(0)])
    home_site = models.URLField(_("home site"),
                                null=True,
                                blank=True,
                                default=EMPTY_STRING)
    permanence_of_last_cancelled_invoice = models.ForeignKey(
        'Permanence', on_delete=models.PROTECT, blank=True, null=True)
    transport = ModelMoneyField(
        _("Shipping cost"),
        help_text=_("This amount is added to order less than min_transport."),
        default=DECIMAL_ZERO,
        max_digits=5,
        decimal_places=2,
        validators=[MinValueValidator(0)])
    min_transport = ModelMoneyField(
        _("Minium order amount for free shipping cost"),
        help_text=_(
            "This is the minimum order amount to avoid shipping cost."),
        default=DECIMAL_ZERO,
        max_digits=5,
        decimal_places=2,
        validators=[MinValueValidator(0)])
    notification_is_public = models.BooleanField(
        _("the notification is public"), default=False)
    email_is_custom = models.BooleanField(_("Email is customised"),
                                          default=False)
    email_host = models.CharField(
        _("email host"),
        help_text=
        _("For @gmail.com, see: https://mail.google.com/mail/u/0/#settings/fwdandpop and activate POP"
          ),
        max_length=50,
        null=True,
        blank=True,
        default="smtp.gmail.com")
    email_port = models.IntegerField(
        _("email port"),
        help_text=_("Usually 587 for @gmail.com, otherwise 25"),
        blank=True,
        null=True,
        default=587)
    email_use_tls = models.BooleanField(
        _("email use tls"),
        help_text=_("TLS is used otherwise SSL is used"),
        default=True)
    email_host_user = models.EmailField(
        _("email host user"),
        help_text=_("For @gmail.com : [email protected]"),
        max_length=50,
        null=True,
        blank=True,
        default="*****@*****.**")
    email_host_password = models.CharField(
        _("email host password"),
        help_text=
        _("For @gmail.com, you must generate an application password, see: https://security.google.com/settings/security/apppasswords"
          ),
        max_length=25,
        null=True,
        blank=True,
        default=EMPTY_STRING)
    translations = TranslatedFields(
        group_label=models.CharField(_("group label"),
                                     max_length=100,
                                     default=EMPTY_STRING,
                                     blank=True),
        how_to_register=HTMLField(_("how to register"),
                                  help_text=EMPTY_STRING,
                                  configuration='CKEDITOR_SETTINGS_MODEL2',
                                  default=EMPTY_STRING,
                                  blank=True),
        notification=HTMLField(_("notification"),
                               help_text=EMPTY_STRING,
                               configuration='CKEDITOR_SETTINGS_MODEL2',
                               default=EMPTY_STRING,
                               blank=True),
        offer_customer_mail=HTMLField(_("offer customer mail"),
                                      help_text=EMPTY_STRING,
                                      configuration='CKEDITOR_SETTINGS_MODEL2',
                                      default="""
                                      Bonjour,<br />
                                      <br />
                                      Les commandes de la {{ permanence_link }} sont maintenant ouvertes auprès de : {{ offer_producer }}.<br />
                                      {% if offer_description %}{{ offer_description }}<br />
                                      {% endif %}
                                      {% if offer_recent_detail %}<br />Nouveauté(s) :<br />
                                      {{ offer_recent_detail }}{% endif %}<br />
                                      <br />
                                      {{ signature }}
                                      """,
                                      blank=True),
        offer_producer_mail=HTMLField(_("offer producer mail"),
                                      help_text=EMPTY_STRING,
                                      configuration='CKEDITOR_SETTINGS_MODEL2',
                                      default="""
                                      Cher/Chère {{ long_profile_name }},<br />
                                      <br />
                                      {% if offer_description != "" %}Voici l'annonce consommateur :<br />
                                      {{ offer_description }}<br />
                                      <br />
                                      {% endif %} Veuillez vérifier votre <strong>{{ offer_link }}</strong>.<br />
                                      <br />
                                      {{ signature }}
                                      """,
                                      blank=True),
        order_customer_mail=HTMLField(_("order customer mail"),
                                      help_text=EMPTY_STRING,
                                      configuration='CKEDITOR_SETTINGS_MODEL2',
                                      default="""
                                      Bonjour {{ long_basket_name }},<br />
                                      <br />
                                      En pièce jointe vous trouverez le montant de votre panier {{ short_basket_name }} de la {{ permanence_link }}.<br />
                                      <br />
                                      {{ last_balance }}<br />
                                      {{ order_amount }}<br />
                                      {% if on_hold_movement %}{{ on_hold_movement }}<br />
                                      {% endif %} {% if payment_needed %}{{ payment_needed }}<br />
                                      {% endif %}<br />
                                      <br />
                                      {{ signature }}
                                      """,
                                      blank=True),
        cancel_order_customer_mail=HTMLField(
            _("cancelled order customer mail"),
            help_text=EMPTY_STRING,
            configuration='CKEDITOR_SETTINGS_MODEL2',
            default="""
                                      Bonjour {{ long_basket_name }},<br />
                                      <br />
                                      La commande ci-jointe de votre panier {{ short_basket_name }} de la {{ permanence_link }} <b>a été annulée</b> car vous ne l'avez pas confirmée.<br />
                                      <br />
                                      {{ signature }}
                                      """,
            blank=True),
        order_staff_mail=HTMLField(_("order staff mail"),
                                   help_text=EMPTY_STRING,
                                   configuration='CKEDITOR_SETTINGS_MODEL2',
                                   default="""
                                   Cher/Chère membre de l'équipe de préparation,<br/>
                                   <br/>
                                   En pièce jointe vous trouverez la liste de préparation pour la {{ permanence_link }}.<br/>
                                   <br/>
                                   L'équipe de préparation est composée de :<br/>
                                   {{ board_composition }}<br/>
                                   ou de<br/>
                                   {{ board_composition_and_description }}<br/>
                                   <br/>
                                   {{ signature }}
                                   """,
                                   blank=True),
        order_producer_mail=HTMLField(_("order producer mail"),
                                      help_text=EMPTY_STRING,
                                      configuration='CKEDITOR_SETTINGS_MODEL2',
                                      default="""
                                      Cher/Chère {{ name }},<br />
                                      <br />
                                      {% if order_empty %}Le groupe ne vous a rien acheté pour la {{ permanence_link }}.{% else %}En pièce jointe, vous trouverez la commande du groupe pour la {{ permanence }}.{% if duplicate %}<br />
                                      <strong>ATTENTION </strong>: La commande est présente en deux exemplaires. Le premier exemplaire est classé par produit et le duplicata est classé par panier.{% else %}{% endif %}{% endif %}<br />
                                      <br />
                                      {{ signature }}
                                      """,
                                      blank=True),
        invoice_customer_mail=HTMLField(
            _("invoice customer mail"),
            help_text=EMPTY_STRING,
            configuration='CKEDITOR_SETTINGS_MODEL2',
            default="""
                                        Bonjour {{ name }},<br/>
                                        <br/>
                                        En cliquant sur ce lien vous trouverez votre facture pour la {{ permanence_link }}.{% if invoice_description %}<br/>
                                        <br/>
                                        {{ invoice_description }}{% endif %}
                                        <br />
                                        {{ order_amount }}<br />
                                        {{ last_balance_link }}<br />
                                        {% if payment_needed %}{{ payment_needed }}<br />
                                        {% endif %}<br />
                                        <br />
                                        {{ signature }}
                                        """,
            blank=True),
        invoice_producer_mail=HTMLField(
            _("invoice producer mail"),
            help_text=EMPTY_STRING,
            configuration='CKEDITOR_SETTINGS_MODEL2',
            default="""
                                        Cher/Chère {{ profile_name }},<br/>
                                        <br/>
                                        En cliquant sur ce lien vous trouverez le détail de notre paiement pour la {{ permanence_link }}.<br/>
                                        <br/>
                                        {{ signature }}
                                        """,
            blank=True),
    )

    def clean(self):
        try:
            template = Template(self.offer_customer_mail)
        except Exception as error_str:
            raise ValidationError(
                mark_safe("%s : %s" % (self.offer_customer_mail, error_str)))
        try:
            template = Template(self.offer_producer_mail)
        except Exception as error_str:
            raise ValidationError(
                mark_safe("%s : %s" % (self.offer_producer_mail, error_str)))
        try:
            template = Template(self.order_customer_mail)
        except Exception as error_str:
            raise ValidationError(
                mark_safe("%s : %s" % (self.order_customer_mail, error_str)))
        try:
            template = Template(self.order_staff_mail)
        except Exception as error_str:
            raise ValidationError(
                mark_safe("%s : %s" % (self.order_staff_mail, error_str)))
        try:
            template = Template(self.order_producer_mail)
        except Exception as error_str:
            raise ValidationError(
                mark_safe("%s : %s" % (self.order_producer_mail, error_str)))
        try:
            template = Template(self.invoice_customer_mail)
        except Exception as error_str:
            raise ValidationError(
                mark_safe("%s : %s" % (self.invoice_customer_mail, error_str)))
        try:
            template = Template(self.invoice_producer_mail)
        except Exception as error_str:
            raise ValidationError(
                mark_safe("%s : %s" % (self.invoice_producer_mail, error_str)))

    def __str__(self):
        return EMPTY_STRING

    class Meta:
        verbose_name = _("configuration")
        verbose_name_plural = _("configurations")
Example #12
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
Example #13
0
class Configuration(TranslatableModel):
    group_name = models.CharField(
        _("Name of the group"),
        max_length=50,
        default=settings.REPANIER_SETTINGS_GROUP_NAME,
    )
    login_attempt_counter = models.DecimalField(
        _("Login attempt counter"), default=DECIMAL_ZERO, max_digits=2, decimal_places=0
    )
    password_reset_on = models.DateTimeField(
        _("Password reset on"), null=True, blank=True, default=None
    )
    name = models.CharField(
        max_length=3,
        choices=LUT_PERMANENCE_NAME,
        default=PERMANENCE_NAME_PERMANENCE,
        verbose_name=_("Offers name"),
    )
    currency = models.CharField(
        max_length=3,
        choices=LUT_CURRENCY,
        default=CURRENCY_EUR,
        verbose_name=_("Currency"),
    )
    max_week_wo_participation = models.DecimalField(
        _("Alert the customer after this number of weeks without participation"),
        help_text=_("0 mean : never display a pop up."),
        default=DECIMAL_ZERO,
        max_digits=2,
        decimal_places=0,
        validators=[MinValueValidator(0)],
    )
    send_abstract_order_mail_to_customer = models.BooleanField(
        _("Send abstract order mail to customers"), default=False
    )
    send_order_mail_to_board = models.BooleanField(
        _("Send an order distribution email to members registered for a task"),
        default=True,
    )
    send_invoice_mail_to_customer = models.BooleanField(
        _("Send invoice mail to customers"), default=True
    )
    send_invoice_mail_to_producer = models.BooleanField(
        _("Send invoice mail to producers"), default=False
    )
    invoice = models.BooleanField(_("Enable accounting module"), default=True)
    display_anonymous_order_form = models.BooleanField(
        _("Allow the anonymous visitor to see the customer order screen"), default=True
    )
    display_who_is_who = models.BooleanField(
        _('Display the "who\'s who"'), default=True
    )
    xlsx_portrait = models.BooleanField(
        _("Always generate XLSX files in portrait mode"), default=False
    )
    bank_account = models.CharField(
        _("Bank account"), max_length=100, blank=True, default=EMPTY_STRING
    )
    vat_id = models.CharField(
        _("VAT id"), max_length=20, blank=True, default=EMPTY_STRING
    )
    page_break_on_customer_check = models.BooleanField(
        _("Page break on customer check"), default=False
    )
    membership_fee = ModelMoneyField(
        _("Membership fee"), default=DECIMAL_ZERO, max_digits=8, decimal_places=2
    )
    membership_fee_duration = models.DecimalField(
        _("Membership fee duration"),
        help_text=_("Number of month(s). 0 mean : no membership fee."),
        default=DECIMAL_ZERO,
        max_digits=3,
        decimal_places=0,
        validators=[MinValueValidator(0)],
    )
    home_site = models.URLField(_("Home site"), blank=True, default="/")
    permanence_of_last_cancelled_invoice = models.ForeignKey(
        "Permanence", on_delete=models.PROTECT, blank=True, null=True
    )
    translations = TranslatedFields(
        group_label=models.CharField(
            _("Label to mention on the invoices of the group"),
            max_length=100,
            default=EMPTY_STRING,
            blank=True,
        ),
        how_to_register=HTMLField(
            _("How to register"),
            help_text=EMPTY_STRING,
            configuration="CKEDITOR_SETTINGS_MODEL2",
            default=EMPTY_STRING,
            blank=True,
        ),
        offer_customer_mail=HTMLField(
            _(
                "Contents of the order opening email sent to consumers authorized to order"
            ),
            help_text=EMPTY_STRING,
            configuration="CKEDITOR_SETTINGS_MODEL2",
            default=EMPTY_STRING,
            blank=True,
        ),
        order_customer_mail=HTMLField(
            _(
                "Content of the order confirmation email sent to the consumers concerned"
            ),
            help_text=EMPTY_STRING,
            configuration="CKEDITOR_SETTINGS_MODEL2",
            default=EMPTY_STRING,
            blank=True,
        ),
        cancel_order_customer_mail=HTMLField(
            _(
                "Content of the email in case of cancellation of the order sent to the consumers concerned"
            ),
            help_text=EMPTY_STRING,
            configuration="CKEDITOR_SETTINGS_MODEL2",
            default=EMPTY_STRING,
            blank=True,
        ),
        order_staff_mail=HTMLField(
            _(
                "Content of the order distribution email sent to the members enrolled to a task"
            ),
            help_text=EMPTY_STRING,
            configuration="CKEDITOR_SETTINGS_MODEL2",
            default=EMPTY_STRING,
            blank=True,
        ),
        order_producer_mail=HTMLField(
            _(
                "Content of the order confirmation email sent to the producers concerned"
            ),
            help_text=EMPTY_STRING,
            configuration="CKEDITOR_SETTINGS_MODEL2",
            default=EMPTY_STRING,
            blank=True,
        ),
        invoice_customer_mail=HTMLField(
            _(
                "Content of the invoice confirmation email sent to the customers concerned"
            ),
            help_text=EMPTY_STRING,
            configuration="CKEDITOR_SETTINGS_MODEL2",
            default=EMPTY_STRING,
            blank=True,
        ),
        invoice_producer_mail=HTMLField(
            _(
                "Content of the payment confirmation email sent to the producers concerned"
            ),
            help_text=EMPTY_STRING,
            configuration="CKEDITOR_SETTINGS_MODEL2",
            default=EMPTY_STRING,
            blank=True,
        ),
    )

    def clean(self):
        try:
            template = Template(self.offer_customer_mail)
        except Exception as error_str:
            raise ValidationError(
                mark_safe("{} : {}".format(self.offer_customer_mail, error_str))
            )
        try:
            template = Template(self.order_customer_mail)
        except Exception as error_str:
            raise ValidationError(
                mark_safe("{} : {}".format(self.order_customer_mail, error_str))
            )
        try:
            template = Template(self.order_staff_mail)
        except Exception as error_str:
            raise ValidationError(
                mark_safe("{} : {}".format(self.order_staff_mail, error_str))
            )
        try:
            template = Template(self.order_producer_mail)
        except Exception as error_str:
            raise ValidationError(
                mark_safe("{} : {}".format(self.order_producer_mail, error_str))
            )
        if settings.REPANIER_SETTINGS_MANAGE_ACCOUNTING:
            try:
                template = Template(self.invoice_customer_mail)
            except Exception as error_str:
                raise ValidationError(
                    mark_safe("{} : {}".format(self.invoice_customer_mail, error_str))
                )
            try:
                template = Template(self.invoice_producer_mail)
            except Exception as error_str:
                raise ValidationError(
                    mark_safe("{} : {}".format(self.invoice_producer_mail, error_str))
                )

    @classmethod
    def init_repanier(cls):
        from repanier.const import DECIMAL_ONE, PERMANENCE_NAME_PERMANENCE, CURRENCY_EUR
        from repanier.models.producer import Producer
        from repanier.models.bankaccount import BankAccount
        from repanier.models.staff import Staff
        from repanier.models.customer import Customer
        from repanier.models.lut import LUT_DepartmentForCustomer

        logger.debug("######## start of init_repanier")

        # Create the configuration record managed via the admin UI
        config = Configuration.objects.filter(id=DECIMAL_ONE).first()
        if config is not None:
            return config
        site = Site.objects.get_current()
        if site is not None:
            site.name = settings.REPANIER_SETTINGS_GROUP_NAME
            site.domain = settings.ALLOWED_HOSTS[0]
            site.save()
        config = Configuration.objects.create(
            group_name=settings.REPANIER_SETTINGS_GROUP_NAME,
            name=PERMANENCE_NAME_PERMANENCE,
            bank_account="BE99 9999 9999 9999",
            currency=CURRENCY_EUR,
        )
        config.init_email()
        config.save()

        # Create firsts users
        Producer.get_or_create_group()
        customer_buyinggroup = Customer.get_or_create_group()
        very_first_customer = Customer.get_or_create_the_very_first_customer()

        BankAccount.open_account(
            customer_buyinggroup=customer_buyinggroup,
            very_first_customer=very_first_customer,
        )
        very_first_customer = Customer.get_or_create_the_very_first_customer()
        coordinator = Staff.get_or_create_any_coordinator()
        Staff.get_or_create_order_responsible()
        Staff.get_or_create_invoice_responsible()
        # Create and publish first web page
        if not coordinator.is_webmaster:
            # This should not be the case...
            return

        from cms.models import StaticPlaceholder
        from cms.constants import X_FRAME_OPTIONS_DENY
        from cms import api

        page = api.create_page(
            title=_("Home"),
            soft_root=False,
            template=settings.CMS_TEMPLATE_HOME,
            language=settings.LANGUAGE_CODE,
            published=True,
            parent=None,
            xframe_options=X_FRAME_OPTIONS_DENY,
            in_navigation=True,
        )
        try:
            # New in CMS 3.5
            page.set_as_homepage()
        except:
            pass

        placeholder = page.placeholders.get(slot="home-hero")
        api.add_plugin(
            placeholder=placeholder,
            plugin_type="TextPlugin",
            language=settings.LANGUAGE_CODE,
            body=settings.CMS_TEMPLATE_HOME_HERO,
        )
        static_placeholder = StaticPlaceholder(
            code="footer",
            # site_id=1
        )
        static_placeholder.save()
        api.add_plugin(
            placeholder=static_placeholder.draft,
            plugin_type="TextPlugin",
            language=settings.LANGUAGE_CODE,
            body="hello world footer",
        )
        static_placeholder.publish(
            request=None, language=settings.LANGUAGE_CODE, force=True
        )
        api.publish_page(
            page=page,
            user=coordinator.customer_responsible.user,
            language=settings.LANGUAGE_CODE,
        )

        if LUT_DepartmentForCustomer.objects.count() == 0:
            # Generate a template of LUT_DepartmentForCustomer
            parent = LUT_DepartmentForCustomer.objects.create(short_name=_("Vegetable"))
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Basket of vegetables"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Salad"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Tomato"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Potato"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Green"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Cabbage"), parent=parent
            )
            parent = LUT_DepartmentForCustomer.objects.create(short_name=_("Fruit"))
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Basket of fruits"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Apple"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Pear"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Plum"), parent=parent
            )
            parent = LUT_DepartmentForCustomer.objects.create(short_name=_("Bakery"))
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Flour"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Bread"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Pastry"), parent=parent
            )
            parent = LUT_DepartmentForCustomer.objects.create(short_name=_("Butchery"))
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Delicatessen"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Chicken"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Pork"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Beef"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Beef and pork"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Veal"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Lamb"), parent=parent
            )
            parent = LUT_DepartmentForCustomer.objects.create(short_name=_("Grocery"))
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Takeaway"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Pasta"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Chocolate"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(short_name=_("Oil"), parent=parent)
            LUT_DepartmentForCustomer.objects.create(short_name=_("Egg"), parent=parent)
            LUT_DepartmentForCustomer.objects.create(short_name=_("Jam"), parent=parent)
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Cookie"), parent=parent
            )
            parent = LUT_DepartmentForCustomer.objects.create(short_name=_("Creamery"))
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Dairy"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Cow cheese"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Goat cheese"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Sheep cheese"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Mixed cheese"), parent=parent
            )
            parent = LUT_DepartmentForCustomer.objects.create(short_name=_("Icecream"))
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Cup of icecream"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Icecream per liter"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Icecream in frisco"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Icecream cake"), parent=parent
            )
            parent = LUT_DepartmentForCustomer.objects.create(short_name=_("Sorbet"))
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Cup of sorbet"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Sorbet per liter"), parent=parent
            )
            parent = LUT_DepartmentForCustomer.objects.create(short_name=_("Drink"))
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Juice"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Coffee"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(short_name=_("Tea"), parent=parent)
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Herbal tea"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Wine"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Aperitif"), parent=parent
            )
            LUT_DepartmentForCustomer.objects.create(
                short_name=_("Liqueurs"), parent=parent
            )
            parent = LUT_DepartmentForCustomer.objects.create(short_name=_("Hygiene"))
            parent = LUT_DepartmentForCustomer.objects.create(short_name=_("Deposit"))
            parent = LUT_DepartmentForCustomer.objects.create(
                short_name=_("Subscription")
            )

        logger.debug("######## end of init_repanier")

        return config

    def init_email(self):
        for language in settings.PARLER_LANGUAGES[settings.SITE_ID]:
            language_code = language["code"]
            self.set_current_language(language_code)
            try:
                self.offer_customer_mail = """
                    Bonjour,<br />
                    <br />
                    Les commandes de la {{ permanence_link }} sont maintenant ouvertes auprès de : {{ offer_producer }}.<br />
                    {% if offer_description %}<br />{{ offer_description }}<br />
                    {% endif %} {% if offer_recent_detail %}<br />
                    Nouveauté(s) :<br />
                    {{ offer_recent_detail }}{% endif %}<br />
                    <br />
                    {{ signature }}
                    """
                self.order_customer_mail = """
                    Bonjour {{ long_basket_name }},<br>
                    <br>
                    En pièce jointe vous trouverez le montant de votre panier {{ short_basket_name }} de la {{ permanence_link }}.<br>
                    <br>
                    {{ last_balance }}<br>
                    {{ order_amount }}<br>
                    {% if on_hold_movement %}{{ on_hold_movement }}<br>
                    {% endif %} {% if payment_needed %}{{ payment_needed }}<br>
                    {% endif %}<br>
                    <br>
                    {{ signature }}
                    """
                self.cancel_order_customer_mail = """
                    Bonjour {{ long_basket_name }},<br>
                    <br>
                    La commande ci-jointe de votre panier {{ short_basket_name }} de la {{ permanence_link }} <b>a été annulée</b> car vous ne l'avez pas confirmée.<br>
                    <br>
                    {{ signature }}
                    """
                self.order_staff_mail = """
                    Cher/Chère membre de l'équipe de préparation,<br>
                    <br>
                    En pièce jointe vous trouverez la liste de préparation pour la {{ permanence_link }}.<br>
                    <br>
                    L'équipe de préparation est composée de :<br>
                    {{ board_composition_and_description }}<br>
                    <br>
                    {{ signature }}
                    """
                self.order_producer_mail = """
                    Cher/Chère {{ name }},<br>
                    <br>
                    {% if order_empty %}Le groupe ne vous a rien acheté pour la {{ permanence_link }}.{% else %}En pièce jointe, vous trouverez la commande du groupe pour la {{ permanence }}.{% if duplicate %}<br>
                    <strong>ATTENTION </strong>: La commande est présente en deux exemplaires. Le premier exemplaire est classé par produit et le duplicata est classé par panier.{% else %}{% endif %}{% endif %}<br>
                    <br>
                    {{ signature }}
                    """
                self.invoice_customer_mail = """
                    Bonjour {{ name }},<br>
                    <br>
                    En cliquant sur ce lien vous trouverez votre facture pour la {{ permanence_link }}.{% if invoice_description %}<br>
                    <br>
                    {{ invoice_description }}{% endif %}
                    <br>
                    {{ order_amount }}<br>
                    {{ last_balance_link }}<br>
                    {% if payment_needed %}{{ payment_needed }}<br>
                    {% endif %}<br>
                    <br>
                    {{ signature }}
                    """
                self.invoice_producer_mail = """
                    Cher/Chère {{ profile_name }},<br>
                    <br>
                    En cliquant sur ce lien vous trouverez le détail de notre paiement pour la {{ permanence_link }}.<br>
                    <br>
                    {{ signature }}
                    """
                self.save_translations()
            except TranslationDoesNotExist:
                pass

    def __str__(self):
        return self.group_name

    class Meta:
        verbose_name = _("Configuration")
        verbose_name_plural = _("Configurations")
Example #14
0
class LUT_DeliveryPoint(MPTTModel, TranslatableModel):
    parent = TreeForeignKey("self",
                            null=True,
                            blank=True,
                            related_name="children",
                            on_delete=models.CASCADE)
    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,
        ),
    )
    is_active = models.BooleanField(_("Active"), default=True)
    # A delivery point may have a customer who is responsible to pay
    # for all the customers who have selected this delivery point
    # Such delivery point represent a closed group of customers.
    customer_responsible = models.ForeignKey(
        "Customer",
        verbose_name=_("Customer responsible"),
        help_text=
        _("Invoices are sent to this customer who is responsible for collecting the payments."
          ),
        blank=True,
        null=True,
        default=None,
        on_delete=models.CASCADE,
    )
    # Does the customer responsible of this delivery point be informed of
    # each individual order for this delivery point ?
    inform_customer_responsible = models.BooleanField(
        _("Inform the group of orders placed by its members"), default=False)
    transport = ModelMoneyField(
        _("Delivery point shipping cost"),
        default=DECIMAL_ZERO,
        blank=True,
        max_digits=5,
        decimal_places=2,
        validators=[MinValueValidator(0)],
    )
    min_transport = ModelMoneyField(
        _("Minimum order amount for free shipping cost"),
        # help_text=_("This is the minimum order amount to avoid shipping cost."),
        default=DECIMAL_ZERO,
        blank=True,
        max_digits=5,
        decimal_places=2,
        validators=[MinValueValidator(0)],
    )

    objects = LUT_DeliveryPointManager()

    def __str__(self):
        if self.customer_responsible is not None:
            return "[{}]".format(self.customer_responsible.short_basket_name)
        else:
            return self.safe_translation_getter("short_name",
                                                any_language=True,
                                                default=EMPTY_STRING)

    class Meta:
        verbose_name = _("Delivery point")
        verbose_name_plural = _("Deliveries points")
Example #15
0
class Customer(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL,
                                db_index=True,
                                on_delete=models.CASCADE)
    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,
        blank=False,
        default=EMPTY_STRING,
        db_index=True,
        unique=True,
    )
    long_basket_name = models.CharField(_("Long name"),
                                        max_length=100,
                                        blank=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 = RepanierPictureField(
        verbose_name=_("Picture"),
        null=True,
        blank=True,
        upload_to="customer",
        size=SIZE_S,
    )
    phone1 = models.CharField(_("Phone1"),
                              max_length=25,
                              blank=True,
                              default=EMPTY_STRING)
    phone2 = models.CharField(_("Phone2"),
                              max_length=25,
                              blank=True,
                              default=EMPTY_STRING)
    bank_account1 = models.CharField(_("Main bank account"),
                                     max_length=100,
                                     blank=True,
                                     default=EMPTY_STRING)
    bank_account2 = models.CharField(_("Secondary bank account"),
                                     max_length=100,
                                     blank=True,
                                     default=EMPTY_STRING)
    vat_id = models.CharField(_("VAT id"),
                              max_length=20,
                              blank=True,
                              default=EMPTY_STRING)
    address = models.TextField(_("Address"), blank=True, default=EMPTY_STRING)
    city = models.CharField(_("City"),
                            max_length=50,
                            blank=True,
                            default=EMPTY_STRING)
    about_me = models.TextField(_("About me"),
                                blank=True,
                                default=EMPTY_STRING)
    memo = models.TextField(_("Memo"), blank=True, default=EMPTY_STRING)
    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,
        on_delete=models.CASCADE,
    )
    is_active = models.BooleanField(_("Active"), default=True)
    as_staff = models.ForeignKey("Staff",
                                 blank=True,
                                 null=True,
                                 default=None,
                                 on_delete=models.CASCADE)
    # 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 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=settings.DEFAULT_FROM_EMAIL,
                    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

    @classmethod
    def get_customer_from_valid_email(cls, email_address):
        # try to find a customer based on user__email or customer__email2
        customer = (Customer.objects.filter(
            Q(user__email=email_address) | Q(email2=email_address)).exclude(
                valid_email=False).order_by("?").first())
        return customer

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

    get_admin_date_balance.short_description = _("Date balance")

    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")

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

    get_admin_balance.short_description = _("Balance")

    def get_phone1(self, prefix=EMPTY_STRING, postfix=EMPTY_STRING):
        # return ", phone1" if prefix = ", "
        # return " (phone1)" if prefix = " (" and postfix = ")"
        if not self.phone1:
            return EMPTY_STRING
        return "{}{}{}".format(prefix, self.phone1, postfix)

    def get_phone2(self):
        return self.phone2 or EMPTY_STRING

    def get_phones(self, sep=", "):
        return (sep.join([self.phone1, self.phone2, EMPTY_STRING])
                if self.phone2 else sep.join([self.phone1, EMPTY_STRING]))

    def get_email1(self, prefix=EMPTY_STRING):
        if not self.user.email:
            return EMPTY_STRING
        return "{}{}".format(prefix, self.user.email)

    def get_emails(self, sep="; "):
        return (sep.join([self.user.email, self.email2, EMPTY_STRING])
                if self.email2 else sep.join([self.user.email, EMPTY_STRING]))

    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(
                total_price=Sum(
                    "total_price_with_tax",
                    output_field=DecimalField(max_digits=8,
                                              decimal_places=2,
                                              default=DECIMAL_ZERO),
                ),
                delta_price=Sum(
                    "delta_price_with_tax",
                    output_field=DecimalField(max_digits=8,
                                              decimal_places=2,
                                              default=DECIMAL_ZERO),
                ),
                delta_transport=Sum(
                    "delta_transport",
                    output_field=DecimalField(max_digits=5,
                                              decimal_places=2,
                                              default=DECIMAL_ZERO),
                ),
            ))
            total_price = (result_set["total_price"]
                           if result_set["total_price"] is not None else
                           DECIMAL_ZERO)
            delta_price = (result_set["delta_price"]
                           if result_set["delta_price"] is not None else
                           DECIMAL_ZERO)
            delta_transport = (result_set["delta_transport"]
                               if result_set["delta_transport"] is not None
                               else DECIMAL_ZERO)
            order_not_invoiced = RepanierMoney(total_price + delta_price +
                                               delta_transport)
        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(
                    bank_in=Sum(
                        "bank_amount_in",
                        output_field=DecimalField(max_digits=8,
                                                  decimal_places=2,
                                                  default=DECIMAL_ZERO),
                    ),
                    bank_out=Sum(
                        "bank_amount_out",
                        output_field=DecimalField(max_digits=8,
                                                  decimal_places=2,
                                                  default=DECIMAL_ZERO),
                    ),
                ))
            bank_in = (result_set["bank_in"]
                       if result_set["bank_in"] is not None else DECIMAL_ZERO)
            bank_out = (result_set["bank_out"] if result_set["bank_out"]
                        is not None else DECIMAL_ZERO)
            bank_not_invoiced = bank_in - bank_out
        else:
            bank_not_invoiced = DECIMAL_ZERO
        return RepanierMoney(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 format_html(
                    '<a href="{}?customer={}" class="btn" target="_blank" ><span style="color:#32CD32">{}</span></a>',
                    reverse("customer_invoice_view", args=(0, )),
                    str(self.id),
                    balance,
                )
            elif balance.amount >= -10:
                return format_html(
                    '<a href="{}?customer={}" class="btn" target="_blank" ><span style="color:#696969">{}</span></a>',
                    reverse("customer_invoice_view", args=(0, )),
                    str(self.id),
                    balance,
                )
            else:
                return format_html(
                    '<a href="{}?customer={}" class="btn" target="_blank" ><span style="color:red">{}</span></a>',
                    reverse("customer_invoice_view", args=(0, )),
                    str(self.id),
                    balance,
                )
        else:
            if balance.amount >= 30:
                return format_html('<span style="color:#32CD32">{}</span>',
                                   balance)
            elif balance.amount >= -10:
                return format_html('<span style="color:#696969">{}</span>',
                                   balance)
            else:
                return format_html('<span style="color:red">{}</span>',
                                   balance)

    get_balance.short_description = _("Balance")
    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")

    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")

    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")

    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")

    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")

    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

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

    def get_html_list_unsubscribe(self):
        return mark_safe("<{}>".format(self._get_unsubscribe_link()))

    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
                    }),
        )

    @staticmethod
    def _get_unsubscribe_site():
        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.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",
            )
        ]
Example #16
0
class ProducerInvoice(Invoice):
    producer = models.ForeignKey(
        "Producer",
        verbose_name=_("Producer"),
        # related_name='producer_invoice',
        on_delete=models.PROTECT,
    )

    delta_stock_with_tax = ModelMoneyField(
        _("Amount deducted from the stock"),
        default=DECIMAL_ZERO,
        max_digits=8,
        decimal_places=2,
    )

    delta_stock_vat = ModelMoneyField(
        _("Total VAT deducted from the stock"),
        default=DECIMAL_ZERO,
        max_digits=9,
        decimal_places=4,
    )
    delta_deposit = ModelMoneyField(
        _("Deposit"),
        help_text=_("+ Deposit"),
        default=DECIMAL_ZERO,
        max_digits=8,
        decimal_places=2,
    )
    delta_stock_deposit = ModelMoneyField(
        _("Deposit"),
        help_text=_("+ Deposit"),
        default=DECIMAL_ZERO,
        max_digits=8,
        decimal_places=2,
    )

    to_be_paid = models.BooleanField(
        _("To be paid"), choices=LUT_BANK_NOTE, default=False
    )
    calculated_invoiced_balance = ModelMoneyField(
        _("Amount due to the producer as calculated by Repanier"),
        max_digits=8,
        decimal_places=2,
        default=DECIMAL_ZERO,
    )
    to_be_invoiced_balance = ModelMoneyField(
        _("Amount claimed by the producer"),
        max_digits=8,
        decimal_places=2,
        default=DECIMAL_ZERO,
    )
    invoice_sort_order = models.IntegerField(
        _("Invoice sort order"), default=None, blank=True, null=True, db_index=True
    )
    invoice_reference = models.CharField(
        _("Invoice reference"), max_length=100, blank=True, default=EMPTY_STRING
    )

    def get_negative_previous_balance(self):
        return -self.previous_balance

    def get_negative_balance(self):
        return -self.balance

    def get_total_price_with_tax(self):
        return (
            self.total_price_with_tax
            + self.delta_price_with_tax
            + self.delta_transport
            + self.delta_stock_with_tax
        )

    def get_total_vat(self):
        return self.total_vat + self.delta_stock_vat

    def get_total_deposit(self):
        return self.total_deposit + self.delta_stock_deposit

    def get_order_json(self):
        a_producer = self.producer
        json_dict = {}
        if a_producer.minimum_order_value.amount > DECIMAL_ZERO:
            ratio = (
                self.total_price_with_tax.amount / a_producer.minimum_order_value.amount
            )
            if ratio >= DECIMAL_ONE:
                ratio = 100
            else:
                ratio *= 100
            json_dict["#order_procent{}".format(a_producer.id)] = "{}%".format(
                number_format(ratio, 0)
            )
        return json_dict

    def __str__(self):
        return "{}, {}".format(self.producer, self.permanence)

    class Meta:
        verbose_name = _("Producer invoice")
        verbose_name_plural = _("Producers invoices")
        unique_together = (("permanence", "producer"),)
Example #17
0
class BankAccount(models.Model):
    permanence = models.ForeignKey(
        'Permanence',
        verbose_name=REPANIER_SETTINGS_PERMANENCE_NAME,
        on_delete=models.PROTECT,
        blank=True,
        null=True)
    producer = models.ForeignKey('Producer',
                                 verbose_name=_("producer"),
                                 on_delete=models.PROTECT,
                                 blank=True,
                                 null=True)
    customer = models.ForeignKey('Customer',
                                 verbose_name=_("customer"),
                                 on_delete=models.PROTECT,
                                 blank=True,
                                 null=True)
    operation_date = models.DateField(_("operation_date"), db_index=True)
    operation_comment = models.CharField(_("operation_comment"),
                                         max_length=100,
                                         null=True,
                                         blank=True)
    operation_status = models.CharField(max_length=3,
                                        choices=LUT_BANK_TOTAL,
                                        default=BANK_NOT_LATEST_TOTAL,
                                        verbose_name=_("Bank balance status"),
                                        db_index=True)
    bank_amount_in = ModelMoneyField(_("bank_amount_in"),
                                     help_text=_('payment_on_the_account'),
                                     max_digits=8,
                                     decimal_places=2,
                                     default=DECIMAL_ZERO,
                                     validators=[MinValueValidator(0)])
    bank_amount_out = ModelMoneyField(_("bank_amount_out"),
                                      help_text=_('payment_from_the_account'),
                                      max_digits=8,
                                      decimal_places=2,
                                      default=DECIMAL_ZERO,
                                      validators=[MinValueValidator(0)])
    producer_invoice = models.ForeignKey('ProducerInvoice',
                                         verbose_name=_("producer_invoice"),
                                         blank=True,
                                         null=True,
                                         on_delete=models.PROTECT,
                                         db_index=True)
    customer_invoice = models.ForeignKey('CustomerInvoice',
                                         verbose_name=_("customer_invoice"),
                                         blank=True,
                                         null=True,
                                         on_delete=models.PROTECT,
                                         db_index=True)
    is_updated_on = models.DateTimeField(_("is_updated_on"), auto_now=True)

    def get_bank_amount_in(self):
        if self.operation_status in [BANK_PROFIT, BANK_TAX]:
            return "<i>%s</i>" % (self.bank_amount_in
                                  if self.bank_amount_in.amount != DECIMAL_ZERO
                                  else EMPTY_STRING)
        else:
            return self.bank_amount_in if self.bank_amount_in.amount != DECIMAL_ZERO else EMPTY_STRING

    get_bank_amount_in.short_description = (_("bank_amount_in"))
    get_bank_amount_in.allow_tags = True
    get_bank_amount_in.admin_order_field = 'bank_amount_in'

    def get_bank_amount_out(self):
        if self.operation_status in [BANK_PROFIT, BANK_TAX]:
            return "<i>%s</i>" % (self.bank_amount_out if
                                  self.bank_amount_out.amount != DECIMAL_ZERO
                                  else EMPTY_STRING)
        else:
            return self.bank_amount_out if self.bank_amount_out.amount != DECIMAL_ZERO else EMPTY_STRING

    get_bank_amount_out.short_description = (_("bank_amount_out"))
    get_bank_amount_out.allow_tags = True
    get_bank_amount_out.admin_order_field = 'bank_amount_out'

    def get_producer(self):
        if self.producer is not None:
            return self.producer.short_profile_name
        else:
            if self.customer is None:
                # This is a total, show it
                from repanier.apps import REPANIER_SETTINGS_GROUP_NAME
                if self.operation_status == BANK_LATEST_TOTAL:
                    return "<b>%s</b>" % "=== %s" % REPANIER_SETTINGS_GROUP_NAME
                else:
                    return "<b>%s</b>" % "--- %s" % REPANIER_SETTINGS_GROUP_NAME
            return EMPTY_STRING

    get_producer.short_description = (_("producer"))
    get_producer.allow_tags = True
    get_producer.admin_order_field = 'producer'

    def get_customer(self):
        if self.customer is not None:
            return self.customer.short_basket_name
        else:
            if self.producer is None:
                # This is a total, show it
                from repanier.apps import REPANIER_SETTINGS_BANK_ACCOUNT
                if self.operation_status == BANK_LATEST_TOTAL:

                    if REPANIER_SETTINGS_BANK_ACCOUNT is not None:
                        return "<b>%s</b>" % REPANIER_SETTINGS_BANK_ACCOUNT
                    else:
                        return "<b>%s</b>" % "=============="
                else:
                    if REPANIER_SETTINGS_BANK_ACCOUNT is not None:
                        return "<b>%s</b>" % REPANIER_SETTINGS_BANK_ACCOUNT
                    else:
                        return "<b>%s</b>" % "--------------"
            return EMPTY_STRING

    get_customer.short_description = (_("customer"))
    get_customer.allow_tags = True
    get_customer.admin_order_field = 'customer'

    class Meta:
        verbose_name = _("bank account movement")
        verbose_name_plural = _("bank account movements")
        ordering = ('-operation_date', '-id')
        index_together = [
            ['operation_date', 'id'],
            ['customer_invoice', 'operation_date', 'id'],
            ['producer_invoice', 'operation_date', 'operation_date', 'id'],
            ['permanence', 'customer', 'producer', 'operation_date', 'id'],
        ]
Example #18
0
class LUT_DeliveryPoint(MPTTModel, TranslatableModel):
    parent = TreeForeignKey('self',
                            null=True,
                            blank=True,
                            related_name='children')
    translations = TranslatedFields(
        short_name=models.CharField(_("Short name"),
                                    max_length=50,
                                    db_index=True,
                                    unique=True,
                                    default=EMPTY_STRING),
        description=HTMLField(_("description"),
                              configuration='CKEDITOR_SETTINGS_MODEL2',
                              blank=True,
                              default=EMPTY_STRING),
    )
    is_active = models.BooleanField(_("is_active"), default=True)
    customer_responsible = models.ForeignKey(
        'Customer',
        verbose_name=_("customer_responsible"),
        help_text=
        _("Invoices are sent to this consumer who is responsible for collecting the payments."
          ),
        on_delete=models.PROTECT,
        blank=True,
        null=True,
        default=None)
    inform_customer_responsible = models.BooleanField(
        _("inform_customer_responsible"), default=False)
    # closed_group = models.BooleanField(_("with entitled customer"), default=False)
    # price_list_multiplier = models.DecimalField(
    #     _("Delivery point price list multiplier"),
    #     help_text=_("This multiplier is applied once for groups with entitled customer."),
    #     default=DECIMAL_ONE, max_digits=5, decimal_places=4, blank=True,
    #     validators=[MinValueValidator(0)])
    transport = ModelMoneyField(
        _("Delivery point transport"),
        # help_text=_("This amount is added once for groups with entitled customer or at each customer for open groups."),
        default=DECIMAL_ZERO,
        blank=True,
        max_digits=5,
        decimal_places=2,
        validators=[MinValueValidator(0)])
    min_transport = ModelMoneyField(
        _("Minium order amount for free shipping cost"),
        # help_text=_("This is the minimum order amount to avoid shipping cost."),
        default=DECIMAL_ZERO,
        blank=True,
        max_digits=5,
        decimal_places=2,
        validators=[MinValueValidator(0)])

    objects = LUT_DeliveryPointManager()

    def __str__(self):
        if self.customer_responsible:
            return "[%s] %s" % (_("Group"),
                                self.customer_responsible.short_basket_name)
        else:
            return self.safe_translation_getter('short_name',
                                                any_language=True,
                                                default=EMPTY_STRING)

    class Meta:
        verbose_name = _("delivery point")
        verbose_name_plural = _("deliveries points")
Example #19
0
class Purchase(models.Model):
    permanence = models.ForeignKey(
        'Permanence',
        verbose_name=repanier.apps.REPANIER_SETTINGS_PERMANENCE_NAME,
        on_delete=models.PROTECT,
        db_index=True)
    status = models.CharField(max_length=3,
                              choices=LUT_PERMANENCE_STATUS,
                              default=PERMANENCE_PLANNED,
                              verbose_name=_("Invoice status"))
    offer_item = models.ForeignKey('OfferItem',
                                   verbose_name=_("Offer item"),
                                   on_delete=models.PROTECT)
    producer = models.ForeignKey('Producer',
                                 verbose_name=_("Producer"),
                                 on_delete=models.PROTECT)
    customer = models.ForeignKey('Customer',
                                 verbose_name=_("Customer"),
                                 on_delete=models.PROTECT,
                                 db_index=True)
    customer_producer_invoice = models.ForeignKey('CustomerProducerInvoice',
                                                  on_delete=models.PROTECT,
                                                  db_index=True)
    producer_invoice = models.ForeignKey('ProducerInvoice',
                                         verbose_name=_("Producer invoice"),
                                         on_delete=models.PROTECT,
                                         db_index=True)
    customer_invoice = models.ForeignKey('CustomerInvoice',
                                         verbose_name=_("Customer invoice"),
                                         on_delete=models.PROTECT,
                                         db_index=True)

    is_box = models.BooleanField(default=False)
    is_box_content = models.BooleanField(default=False)

    quantity_ordered = models.DecimalField(_("Quantity ordered"),
                                           max_digits=9,
                                           decimal_places=4,
                                           default=DECIMAL_ZERO)
    quantity_contracted = models.DecimalField(_("Quantity contracted"),
                                              max_digits=9,
                                              decimal_places=4,
                                              default=DECIMAL_ZERO)
    quantity_confirmed = models.DecimalField(_("Quantity confirmed"),
                                             max_digits=9,
                                             decimal_places=4,
                                             default=DECIMAL_ZERO)
    # 0 if this is not a KG product -> the preparation list for this product will be produced by family
    # qty if not -> the preparation list for this product will be produced by qty then by family
    quantity_for_preparation_sort_order = models.DecimalField(
        _("Quantity for preparation order_by"),
        max_digits=9,
        decimal_places=4,
        default=DECIMAL_ZERO)
    # If Permanence.status < SEND this is the order quantity
    # During sending the orders to the producer this become the invoiced quantity
    # via tools.recalculate_order_amount(..., send_to_producer=True)
    quantity_invoiced = models.DecimalField(_("Quantity invoiced"),
                                            max_digits=9,
                                            decimal_places=4,
                                            default=DECIMAL_ZERO)
    purchase_price = ModelMoneyField(_("Producer row price"),
                                     max_digits=8,
                                     decimal_places=2,
                                     default=DECIMAL_ZERO)
    selling_price = ModelMoneyField(_("Customer row price"),
                                    max_digits=8,
                                    decimal_places=2,
                                    default=DECIMAL_ZERO)

    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)
    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)])

    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(
        _("Customer prices are set by the producer"), default=False)
    comment = models.CharField(_("Comment"),
                               max_length=100,
                               default=EMPTY_STRING,
                               blank=True,
                               null=True)
    is_updated_on = models.DateTimeField(_("Updated on"),
                                         auto_now=True,
                                         db_index=True)

    def get_customer_unit_price(self):
        offer_item = self.offer_item
        if self.price_list_multiplier == DECIMAL_ONE:
            return offer_item.customer_unit_price.amount
        else:
            return (offer_item.customer_unit_price.amount *
                    self.price_list_multiplier).quantize(TWO_DECIMALS)

    get_customer_unit_price.short_description = (_("Customer unit price"))

    def get_unit_deposit(self):
        return self.offer_item.unit_deposit.amount

    def get_customer_unit_vat(self):
        offer_item = self.offer_item
        if self.price_list_multiplier == DECIMAL_ONE:
            return offer_item.customer_vat.amount
        else:
            return (offer_item.customer_vat.amount *
                    self.price_list_multiplier).quantize(FOUR_DECIMALS)

    def get_producer_unit_vat(self):
        offer_item = self.offer_item
        if offer_item.manage_production:
            return self.get_customer_unit_vat()
        return offer_item.producer_vat.amount

    def get_selling_price(self):
        # workaround for a display problem with Money field in the admin list_display
        return self.selling_price

    get_selling_price.short_description = (_("Customer row price"))

    def get_producer_unit_price(self):
        offer_item = self.offer_item
        if offer_item.manage_production:
            return self.get_customer_unit_price()
        return offer_item.producer_unit_price.amount

    get_producer_unit_price.short_description = (_("Producer unit price"))

    def get_html_producer_unit_price(self):
        if self.offer_item is not None:
            return mark_safe(
                _("<b>%(price)s</b>") %
                {'price': self.get_producer_unit_price()})
        return EMPTY_STRING

    get_html_producer_unit_price.short_description = (_("Producer unit price"))
    get_html_producer_unit_price.allow_tags = True

    def get_html_unit_deposit(self):
        if self.offer_item is not None:
            return mark_safe(
                _("<b>%(price)s</b>") %
                {'price': self.offer_item.unit_deposit})
        return EMPTY_STRING

    get_html_unit_deposit.short_description = (_("Deposit"))

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

    get_permanence_display.short_description = (_("Permanence"))

    def get_delivery_display(self):
        if self.customer_invoice is not None and self.customer_invoice.delivery is not None:
            return self.customer_invoice.delivery.get_delivery_display(br=True)
        return EMPTY_STRING

    get_delivery_display.short_description = (_("Delivery point"))

    def get_quantity(self):
        if self.status < PERMANENCE_WAIT_FOR_SEND:
            return self.quantity_ordered
        else:
            return self.quantity_invoiced

    get_quantity.short_description = (_("Quantity invoiced"))

    def get_producer_quantity(self):
        if self.status < PERMANENCE_WAIT_FOR_SEND:
            return self.quantity_ordered
        else:
            offer_item = self.offer_item
            if offer_item.order_unit == PRODUCT_ORDER_UNIT_PC_KG:
                if offer_item.order_average_weight != 0:
                    return (self.quantity_invoiced /
                            offer_item.order_average_weight
                            ).quantize(FOUR_DECIMALS)
            return self.quantity_invoiced

    def get_long_name(self, customer_price=True):
        return self.offer_item.get_long_name(customer_price=customer_price)

    def set_comment(self, comment):
        if comment:
            if self.comment:
                self.comment = cap("{}, {}".format(self.comment, comment), 100)
            else:
                self.comment = cap(comment, 100)

    @transaction.atomic
    def save(self, *args, **kwargs):
        if not self.pk:
            # This code only happens if the objects is not in the database yet.
            # Otherwise it would have pk
            customer_invoice = CustomerInvoice.objects.filter(
                permanence_id=self.permanence_id,
                customer_id=self.customer_id).only("id").order_by('?').first()
            if customer_invoice is None:
                customer_invoice = CustomerInvoice.objects.create(
                    permanence_id=self.permanence_id,
                    customer_id=self.customer_id,
                    customer_charged_id=self.customer_id,
                    status=self.status)
                customer_invoice.set_delivery(delivery=None)
                customer_invoice.save()
            self.customer_invoice = customer_invoice
            producer_invoice = ProducerInvoice.objects.filter(
                permanence_id=self.permanence_id,
                producer_id=self.producer_id).only("id").order_by('?').first()
            if producer_invoice is None:
                producer_invoice = ProducerInvoice.objects.create(
                    permanence_id=self.permanence_id,
                    producer_id=self.producer_id,
                    status=self.status)
            self.producer_invoice = producer_invoice
            customer_producer_invoice = CustomerProducerInvoice.objects.filter(
                permanence_id=self.permanence_id,
                customer_id=self.customer_id,
                producer_id=self.producer_id).only("id").order_by('?').first()
            if customer_producer_invoice is None:
                customer_producer_invoice = CustomerProducerInvoice.objects.create(
                    permanence_id=self.permanence_id,
                    customer_id=self.customer_id,
                    producer_id=self.producer_id,
                )
            self.customer_producer_invoice = customer_producer_invoice
        super(Purchase, self).save(*args, **kwargs)

    @transaction.atomic
    def save_box(self):
        if self.offer_item.is_box:
            for content in BoxContent.objects.filter(
                    box_id=self.offer_item.product_id).order_by('?'):
                content_offer_item = content.product.get_or_create_offer_item(
                    self.permanence)
                # Select one purchase
                content_purchase = Purchase.objects.filter(
                    customer_id=self.customer_id,
                    offer_item_id=content_offer_item.id,
                    is_box_content=True).order_by('?').first()
                if content_purchase is None:
                    content_purchase = Purchase.objects.create(
                        permanence=self.permanence,
                        offer_item=content_offer_item,
                        producer=self.producer,
                        customer=self.customer,
                        quantity_ordered=self.quantity_ordered *
                        content.content_quantity,
                        quantity_invoiced=self.quantity_invoiced *
                        content.content_quantity,
                        is_box_content=True,
                        status=self.status)
                else:
                    content_purchase.status = self.status
                    content_purchase.quantity_ordered = self.quantity_ordered * content.content_quantity
                    content_purchase.quantity_invoiced = self.quantity_invoiced * content.content_quantity
                    content_purchase.save()
                content_purchase.permanence.producers.add(
                    content_offer_item.producer)

    def __str__(self):
        # Use to not display label (inline_admin_form.original) into the inline form (tabular.html)
        return EMPTY_STRING

    class Meta:
        verbose_name = _("Purchase")
        verbose_name_plural = _("Purchases")
        # ordering = ("permanence", "customer", "offer_item", "is_box_content")
        unique_together = ("customer", "offer_item", "is_box_content")
        index_together = [["permanence", "customer_invoice"]]
Example #20
0
class OfferItem(Item):
    translations = TranslatedFields(
        long_name=models.CharField(_("Long name"),
                                   max_length=100,
                                   default=EMPTY_STRING,
                                   blank=True),
        cache_part_a=HTMLField(default=EMPTY_STRING, blank=True),
        cache_part_b=HTMLField(default=EMPTY_STRING, blank=True),
        # Language dependant customer sort order for optimization
        order_sort_order=models.IntegerField(default=0, db_index=True),
        # Language dependant preparation sort order for optimization
        preparation_sort_order=models.IntegerField(default=0, db_index=True),
        # Language dependant producer sort order for optimization
        producer_sort_order=models.IntegerField(default=0, db_index=True),
    )
    permanence = models.ForeignKey(
        "Permanence",
        verbose_name=REPANIER_SETTINGS_PERMANENCE_NAME,
        on_delete=models.PROTECT,
        db_index=True,
    )
    product = models.ForeignKey("Product",
                                verbose_name=_("Product"),
                                on_delete=models.PROTECT)
    # is a box content
    is_box_content = models.BooleanField(default=False)

    producer_price_are_wo_vat = models.BooleanField(
        _("Producer price are without vat"), 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_ZERO,
        max_digits=5,
        decimal_places=4,
        validators=[MinValueValidator(0)],
    )
    is_resale_price_fixed = models.BooleanField(
        _("The resale price is fixed (boxes, deposit)"), default=False)

    # Calculated with Purchase : Total producer purchase price vat included
    total_purchase_with_tax = ModelMoneyField(
        _("Producer amount invoiced"),
        default=DECIMAL_ZERO,
        max_digits=8,
        decimal_places=2,
    )
    # Calculated with Purchase : Total customer selling price vat included
    total_selling_with_tax = ModelMoneyField(
        _("Invoiced to the consumer w TVA"),
        default=DECIMAL_ZERO,
        max_digits=8,
        decimal_places=2,
    )

    # Calculated with Purchase : Quantity invoiced to all customers
    # If Permanence.status < SEND this is the order quantity
    # During sending the orders to the producer this become the invoiced quantity
    # via permanence.recalculate_order_amount(..., send_to_producer=True)
    quantity_invoiced = models.DecimalField(_("Qty invoiced"),
                                            max_digits=9,
                                            decimal_places=4,
                                            default=DECIMAL_ZERO)
    use_order_unit_converted = models.BooleanField(default=False)

    may_order = models.BooleanField(_("May order"), default=True)
    manage_production = models.BooleanField(default=False)

    def get_vat_level(self):
        return self.get_vat_level_display()

    get_vat_level.short_description = _("VAT rate")
    get_vat_level.admin_order_field = "vat_level"

    def get_producer_qty_stock_invoiced(self):
        # Return quantity to buy to the producer and stock used to deliver the invoiced quantity
        if self.quantity_invoiced > DECIMAL_ZERO:
            return self.quantity_invoiced, DECIMAL_ZERO, self.quantity_invoiced
        return DECIMAL_ZERO, DECIMAL_ZERO, DECIMAL_ZERO

    def get_html_producer_qty_stock_invoiced(self):
        invoiced_qty, taken_from_stock, customer_qty = (
            self.get_producer_qty_stock_invoiced())
        if invoiced_qty == DECIMAL_ZERO:
            if taken_from_stock == DECIMAL_ZERO:
                return EMPTY_STRING
            else:
                return mark_safe(
                    _("stock %(stock)s") %
                    {"stock": number_format(taken_from_stock, 4)})
        else:
            if taken_from_stock == DECIMAL_ZERO:
                return mark_safe(
                    _("<b>%(qty)s</b>") %
                    {"qty": number_format(invoiced_qty, 4)})
            else:
                return mark_safe(
                    _("<b>%(qty)s</b> + stock %(stock)s") % {
                        "qty": number_format(invoiced_qty, 4),
                        "stock": number_format(taken_from_stock, 4),
                    })

    get_html_producer_qty_stock_invoiced.short_description = _(
        "Qty invoiced by the producer")
    get_html_producer_qty_stock_invoiced.admin_order_field = "quantity_invoiced"

    def get_producer_qty_invoiced(self):
        invoiced_qty, taken_from_stock, customer_qty = (
            self.get_producer_qty_stock_invoiced())
        return invoiced_qty

    def get_producer_unit_price_invoiced(self):
        if self.producer_unit_price.amount > self.customer_unit_price.amount:
            return self.customer_unit_price
        else:
            return self.producer_unit_price

    def get_producer_row_price_invoiced(self):
        if self.producer_unit_price.amount > self.customer_unit_price.amount:
            return self.total_selling_with_tax
        else:
            return self.total_purchase_with_tax

    def get_html_producer_price_purchased(self):
        price = self.total_purchase_with_tax
        if price != DECIMAL_ZERO:
            return mark_safe(_("<b>%(price)s</b>") % {"price": price})
        return EMPTY_STRING

    get_html_producer_price_purchased.short_description = _(
        "Producer amount invoiced")
    get_html_producer_price_purchased.admin_order_field = "total_purchase_with_tax"

    def get_html_like(self, user):
        if settings.REPANIER_SETTINGS_TEMPLATE == "bs3":
            return mark_safe(
                '<span class="glyphicon glyphicon-heart{}" onclick="like_ajax({});return false;"></span>'
                .format(
                    EMPTY_STRING if self.product.likes.filter(
                        id=user.id).only("id").exists() else "-empty",
                    self.id,
                ))
        else:
            return mark_safe(
                '<span class="fa{} fa-heart" onclick="like_ajax({});return false;"></span>'
                .format(
                    "s" if self.product.likes.filter(
                        id=user.id).only("id").exists() else "r",
                    self.id,
                ))

    def get_order_name(self):
        qty_display = self.get_qty_display()
        if qty_display:
            return "{} {}".format(
                self.safe_translation_getter("long_name", any_language=True),
                qty_display,
            )
        return "{}".format(
            self.safe_translation_getter("long_name", any_language=True))

    def get_qty_display(self):
        if self.is_box:
            # To avoid unicode error in email_offer.send_open_order
            qty_display = BOX_UNICODE
        else:
            if self.use_order_unit_converted:
                # The only conversion done in permanence concerns PRODUCT_ORDER_UNIT_PC_KG
                # so we are sure that 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_long_name(self, customer_price=True, is_html=False):
        return super(OfferItem,
                     self).get_long_name(customer_price=customer_price)

    def get_html_long_name(self):
        return mark_safe(self.get_long_name(is_html=True))

    def get_long_name_with_producer(self, is_html=False):
        return super(OfferItem, self).get_long_name_with_producer()

    def get_html_long_name_with_producer(self):
        return mark_safe(self.get_long_name_with_producer(is_html=True))

    get_html_long_name_with_producer.short_description = _("Offer items")
    get_html_long_name_with_producer.admin_order_field = "translations__long_name"

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

    class Meta:
        verbose_name = _("Offer item")
        verbose_name_plural = _("Offer items")
        unique_together = ("permanence", "product")
Example #21
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"],
        ]
Example #22
0
class Producer(models.Model):
    short_profile_name = models.CharField(
        _("Short name"),
        max_length=25,
        blank=False,
        default=EMPTY_STRING,
        db_index=True,
        unique=True,
    )
    long_profile_name = models.CharField(_("Long name"),
                                         max_length=100,
                                         blank=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 = RepanierPictureField(
        verbose_name=_("Picture"),
        null=True,
        blank=True,
        upload_to="producer",
        size=SIZE_L,
    )
    phone1 = models.CharField(_("Phone1"),
                              max_length=25,
                              blank=True,
                              default=EMPTY_STRING)
    phone2 = models.CharField(_("Phone2"),
                              max_length=25,
                              blank=True,
                              default=EMPTY_STRING)
    bank_account = models.CharField(_("Bank account"),
                                    max_length=100,
                                    blank=True,
                                    default=EMPTY_STRING)
    vat_id = models.CharField(_("VAT id"),
                              max_length=20,
                              blank=True,
                              default=EMPTY_STRING)
    fax = models.CharField(_("Fax"),
                           max_length=100,
                           blank=True,
                           default=EMPTY_STRING)
    address = models.TextField(_("Address"), blank=True, default=EMPTY_STRING)
    city = models.CharField(_("City"),
                            max_length=50,
                            blank=True,
                            default=EMPTY_STRING)
    memo = models.TextField(_("Memo"), 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,
                            default=EMPTY_STRING,
                            db_index=True)
    offer_uuid = models.CharField("uuid",
                                  max_length=36,
                                  default=EMPTY_STRING,
                                  db_index=True)
    offer_filled = models.BooleanField(_("Offer filled"), default=False)
    invoice_by_basket = models.BooleanField(_("Invoice by basket"),
                                            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)],
    )
    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_phone1(self, prefix=EMPTY_STRING):
        # return ", phone1" if prefix = ", "
        if not self.phone1:
            return EMPTY_STRING
        return "{}{}".format(prefix, self.phone1)

    def get_phone2(self):
        return self.phone2 or EMPTY_STRING

    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 = reverse(
                "admin:repanier_product_changelist")
            link = '<a href="{}?is_active__exact=1&producer={}" class="repanier-a-info">&nbsp;{}</a>'.format(
                changeproductslist_url, str(self.id), _("Products"))
            return format_html(link)

        return EMPTY_STRING

    get_products.short_description = EMPTY_STRING

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

    get_admin_date_balance.short_description = _("Date balance")

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

    get_admin_balance.short_description = _("Balance")

    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(
                total_price_with_tax=Sum(
                    "total_price_with_tax",
                    output_field=DecimalField(max_digits=8,
                                              decimal_places=2,
                                              default=DECIMAL_ZERO),
                ),
                delta_price_with_tax=Sum(
                    "delta_price_with_tax",
                    output_field=DecimalField(max_digits=8,
                                              decimal_places=2,
                                              default=DECIMAL_ZERO),
                ),
                delta_transport=Sum(
                    "delta_transport",
                    output_field=DecimalField(max_digits=5,
                                              decimal_places=2,
                                              default=DECIMAL_ZERO),
                ),
            ))
            if result_set["total_price_with_tax"] is not None:
                order_not_invoiced = RepanierMoney(
                    result_set["total_price_with_tax"])
            else:
                order_not_invoiced = REPANIER_MONEY_ZERO
            if result_set["delta_price_with_tax"] is not None:
                order_not_invoiced += RepanierMoney(
                    result_set["delta_price_with_tax"])
            if result_set["delta_transport"] is not None:
                order_not_invoiced += RepanierMoney(
                    result_set["delta_transport"])
        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(
                    bank_amount_in=Sum(
                        "bank_amount_in",
                        output_field=DecimalField(max_digits=8,
                                                  decimal_places=2,
                                                  default=DECIMAL_ZERO),
                    ),
                    bank_amount_out=Sum(
                        "bank_amount_out",
                        output_field=DecimalField(max_digits=8,
                                                  decimal_places=2,
                                                  default=DECIMAL_ZERO),
                    ),
                ))

            total_bank_amount_in = (result_set["bank_amount_in"]
                                    if result_set["bank_amount_in"] is not None
                                    else DECIMAL_ZERO)
            total_bank_amount_out = (result_set["bank_amount_out"]
                                     if result_set["bank_amount_out"]
                                     is not None else DECIMAL_ZERO)
            bank_not_invoiced = RepanierMoney(total_bank_amount_out -
                                              total_bank_amount_in)
        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()
        # 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(total_selling_price_with_tax=Sum(
                "total_selling_with_tax",
                output_field=DecimalField(
                    max_digits=8, decimal_places=2, default=DECIMAL_ZERO),
            )))

        payment_needed = (result_set["total_selling_price_with_tax"]
                          if result_set["total_selling_price_with_tax"]
                          is not None else 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(total_purchase_price_with_tax=Sum(
                "total_purchase_with_tax",
                output_field=DecimalField(
                    max_digits=8, decimal_places=2, default=DECIMAL_ZERO),
            )))

        if result_set["total_purchase_price_with_tax"] is not None:
            payment_needed += result_set["total_purchase_price_with_tax"]

        calculated_invoiced_balance = self.balance - bank_not_invoiced + payment_needed
        return calculated_invoiced_balance

    get_calculated_invoiced_balance.short_description = _("Balance")

    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 balance.amount < 0:
            color = "#298A08"
        elif balance.amount == 0:
            color = "#32CD32"
        elif balance.amount > 30:
            color = "red"
        else:
            color = "#696969"

        if last_producer_invoice_set.exists():
            return format_html(
                '<a href="{}?producer={}" class="repanier-a-info" target="_blank"><span style="color:{}">{}</span></a>',
                reverse("producer_invoice_view", args=(0, )),
                str(self.id),
                color,
                -balance,
            )
        else:
            return format_html('<span style="color:{}">{}</span>', color,
                               -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:
            total_price_with_tax = producer_last_invoice.get_total_price_with_tax(
            )
            if total_price_with_tax < DECIMAL_ZERO:
                return format_html(
                    '<span style="color:#298A08">{}</span>',
                    number_format(total_price_with_tax, 2),
                )
            elif total_price_with_tax == DECIMAL_ZERO:
                return format_html(
                    '<span style="color:#32CD32">{}</span>',
                    number_format(total_price_with_tax, 2),
                )
            elif total_price_with_tax > 30:
                return format_html(
                    '<span style="color:red">{}</span>',
                    number_format(total_price_with_tax, 2),
                )
            else:
                return format_html(
                    '<span style="color:#696969">{}</span>',
                    number_format(total_price_with_tax, 2),
                )
        else:
            return format_html('<span style="color:#32CD32">{}</span>',
                               number_format(0, 2))

    get_last_invoice.short_description = _("Last invoice")

    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 VAT"))
        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",
            )
        ]
Example #23
0
class ProducerInvoice(Invoice):
    producer = models.ForeignKey(
        'Producer',
        verbose_name=_("Producer"),
        # related_name='producer_invoice',
        on_delete=models.PROTECT)

    delta_stock_with_tax = ModelMoneyField(_("Amount deducted from the stock"),
                                           default=DECIMAL_ZERO,
                                           max_digits=8,
                                           decimal_places=2)

    delta_stock_vat = ModelMoneyField(_("Total VAT deducted from the stock"),
                                      default=DECIMAL_ZERO,
                                      max_digits=9,
                                      decimal_places=4)
    delta_deposit = ModelMoneyField(_("Deposit"),
                                    help_text=_('+ Deposit'),
                                    default=DECIMAL_ZERO,
                                    max_digits=8,
                                    decimal_places=2)
    delta_stock_deposit = ModelMoneyField(_("Deposit"),
                                          help_text=_('+ Deposit'),
                                          default=DECIMAL_ZERO,
                                          max_digits=8,
                                          decimal_places=2)

    to_be_paid = models.BooleanField(_("To be paid"),
                                     choices=LUT_BANK_NOTE,
                                     default=False)
    calculated_invoiced_balance = ModelMoneyField(
        _("Amount due to the producer as calculated by Repanier"),
        max_digits=8,
        decimal_places=2,
        default=DECIMAL_ZERO)
    to_be_invoiced_balance = ModelMoneyField(
        _("Amount claimed by the producer"),
        max_digits=8,
        decimal_places=2,
        default=DECIMAL_ZERO)
    invoice_sort_order = models.IntegerField(_("Invoice sort order"),
                                             default=None,
                                             blank=True,
                                             null=True,
                                             db_index=True)
    invoice_reference = models.CharField(_("Invoice reference"),
                                         max_length=100,
                                         null=True,
                                         blank=True)

    def get_negative_previous_balance(self):
        return -self.previous_balance

    def get_negative_balance(self):
        return -self.balance

    def get_total_price_with_tax(self):
        return self.total_price_with_tax + self.delta_price_with_tax + self.delta_transport + self.delta_stock_with_tax

    def get_total_vat(self):
        return self.total_vat + self.delta_stock_vat

    def get_total_deposit(self):
        return self.total_deposit + self.delta_stock_deposit

    def get_order_json(self):
        a_producer = self.producer
        json_dict = {}
        if a_producer.minimum_order_value.amount > DECIMAL_ZERO:
            ratio = self.total_price_with_tax.amount / a_producer.minimum_order_value.amount
            if ratio >= DECIMAL_ONE:
                ratio = 100
            else:
                ratio *= 100
            json_dict["#order_procent{}".format(a_producer.id)] = "{}%".format(
                number_format(ratio, 0))
        if self.status != PERMANENCE_OPENED:
            json_dict["#order_closed{}".format(a_producer.id)] = mark_safe(
                "&nbsp;<span class=\"glyphicon glyphicon-ban-circle\" aria-hidden=\"true\"></span>"
            )
        return json_dict

    def __str__(self):
        return "{}, {}".format(self.producer, self.permanence)

    class Meta:
        verbose_name = _("Producer invoice")
        verbose_name_plural = _("Producers invoices")
        unique_together = (
            "permanence",
            "producer",
        )
Example #24
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')
Example #25
0
class Configuration(TranslatableModel):
    group_name = models.CharField(_("Name of the group"),
                                  max_length=50,
                                  default=EMPTY_STRING)
    login_attempt_counter = models.DecimalField(_("Login attempt counter"),
                                                default=DECIMAL_ZERO,
                                                max_digits=2,
                                                decimal_places=0)
    password_reset_on = models.DateTimeField(_("Password reset on"),
                                             null=True,
                                             blank=True,
                                             default=None)
    name = models.CharField(max_length=3,
                            choices=LUT_PERMANENCE_NAME,
                            default=PERMANENCE_NAME_PERMANENCE,
                            verbose_name=_("Offers name"))
    currency = models.CharField(max_length=3,
                                choices=LUT_CURRENCY,
                                default=CURRENCY_EUR,
                                verbose_name=_("Currency"))
    max_week_wo_participation = models.DecimalField(
        _("Alert the customer after this number of weeks without participation"
          ),
        help_text=_("0 mean : never display a pop up."),
        default=DECIMAL_ZERO,
        max_digits=2,
        decimal_places=0,
        validators=[MinValueValidator(0)])
    send_abstract_order_mail_to_customer = models.BooleanField(
        _("Send abstract order mail to customers"), default=False)
    send_order_mail_to_board = models.BooleanField(
        _("Send an order distribution email to members registered for a task"),
        default=True)
    send_invoice_mail_to_customer = models.BooleanField(
        _("Send invoice mail to customers"), default=True)
    send_invoice_mail_to_producer = models.BooleanField(
        _("Send invoice mail to producers"), default=False)
    invoice = models.BooleanField(_("Enable accounting module"), default=True)
    display_anonymous_order_form = models.BooleanField(
        _("Allow the anonymous visitor to see the customer order screen"),
        default=True)
    display_who_is_who = models.BooleanField(_("Display the \"who's who\""),
                                             default=True)
    xlsx_portrait = models.BooleanField(
        _("Always generate XLSX files in portrait mode"), default=False)
    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)
    page_break_on_customer_check = models.BooleanField(
        _("Page break on customer check"), default=False)
    sms_gateway_mail = models.EmailField(
        _("Sms gateway email"),
        help_text=
        _("To actually send sms, use for e.g. on a GSM : https://play.google.com/store/apps/details?id=eu.apksoft.android.smsgateway"
          ),
        max_length=50,
        null=True,
        blank=True,
        default=EMPTY_STRING)
    membership_fee = ModelMoneyField(_("Membership fee"),
                                     default=DECIMAL_ZERO,
                                     max_digits=8,
                                     decimal_places=2)
    membership_fee_duration = models.DecimalField(
        _("Membership fee duration"),
        help_text=_("Number of month(s). 0 mean : no membership fee."),
        default=DECIMAL_ZERO,
        max_digits=3,
        decimal_places=0,
        validators=[MinValueValidator(0)])
    home_site = models.URLField(_("Home site"),
                                null=True,
                                blank=True,
                                default=EMPTY_STRING)
    permanence_of_last_cancelled_invoice = models.ForeignKey(
        'Permanence', on_delete=models.PROTECT, blank=True, null=True)
    transport = ModelMoneyField(
        _("Shipping cost"),
        help_text=_("This amount is added to order less than min_transport."),
        default=DECIMAL_ZERO,
        max_digits=5,
        decimal_places=2,
        validators=[MinValueValidator(0)])
    min_transport = ModelMoneyField(
        _("Minium order amount for free shipping cost"),
        help_text=_(
            "This is the minimum order amount to avoid shipping cost."),
        default=DECIMAL_ZERO,
        max_digits=5,
        decimal_places=2,
        validators=[MinValueValidator(0)])
    email_is_custom = models.BooleanField(_("Email is customised"),
                                          default=False)
    email_host = models.CharField(
        _("Email host"),
        help_text=
        _("For @gmail.com, see: https://mail.google.com/mail/u/0/#settings/fwdandpop and activate POP"
          ),
        max_length=50,
        null=True,
        blank=True,
        default="smtp.gmail.com")
    email_port = models.IntegerField(
        _("Email port"),
        help_text=_("Usually 587 for @gmail.com, otherwise 25"),
        blank=True,
        null=True,
        default=587)
    email_use_tls = models.BooleanField(
        _("Email use tls"),
        help_text=_("TLS is used otherwise SSL is used"),
        default=True)
    email_host_user = models.EmailField(_("Email host user"),
                                        help_text=settings.DEFAULT_FROM_EMAIL,
                                        max_length=50,
                                        null=True,
                                        blank=True,
                                        default=settings.DEFAULT_FROM_EMAIL)
    email_host_password = models.CharField(
        _("Email host password"),
        help_text=
        _("For @gmail.com, you must generate an application password, see: https://security.google.com/settings/security/apppasswords"
          ),
        max_length=25,
        null=True,
        blank=True,
        default=EMPTY_STRING)
    db_version = models.PositiveSmallIntegerField(default=0)
    translations = TranslatedFields(
        group_label=models.CharField(
            _("Label to mention on the invoices of the group"),
            max_length=100,
            default=EMPTY_STRING,
            blank=True),
        how_to_register=HTMLField(_("How to register"),
                                  help_text=EMPTY_STRING,
                                  configuration='CKEDITOR_SETTINGS_MODEL2',
                                  default=EMPTY_STRING,
                                  blank=True),
        offer_customer_mail=HTMLField(_(
            "Contents of the order opening email sent to consumers authorized to order"
        ),
                                      help_text=EMPTY_STRING,
                                      configuration='CKEDITOR_SETTINGS_MODEL2',
                                      default=EMPTY_STRING,
                                      blank=True),
        offer_producer_mail=HTMLField(_("Email content"),
                                      help_text=EMPTY_STRING,
                                      configuration='CKEDITOR_SETTINGS_MODEL2',
                                      default=EMPTY_STRING,
                                      blank=True),
        order_customer_mail=HTMLField(_(
            "Content of the order confirmation email sent to the consumers concerned"
        ),
                                      help_text=EMPTY_STRING,
                                      configuration='CKEDITOR_SETTINGS_MODEL2',
                                      default=EMPTY_STRING,
                                      blank=True),
        cancel_order_customer_mail=HTMLField(
            _("Content of the email in case of cancellation of the order sent to the consumers concerned"
              ),
            help_text=EMPTY_STRING,
            configuration='CKEDITOR_SETTINGS_MODEL2',
            default=EMPTY_STRING,
            blank=True),
        order_staff_mail=HTMLField(_(
            "Content of the order distribution email sent to the members enrolled to a task"
        ),
                                   help_text=EMPTY_STRING,
                                   configuration='CKEDITOR_SETTINGS_MODEL2',
                                   default=EMPTY_STRING,
                                   blank=True),
        order_producer_mail=HTMLField(_(
            "Content of the order confirmation email sent to the producers concerned"
        ),
                                      help_text=EMPTY_STRING,
                                      configuration='CKEDITOR_SETTINGS_MODEL2',
                                      default=EMPTY_STRING,
                                      blank=True),
        invoice_customer_mail=HTMLField(
            _("Content of the invoice confirmation email sent to the customers concerned"
              ),
            help_text=EMPTY_STRING,
            configuration='CKEDITOR_SETTINGS_MODEL2',
            default=EMPTY_STRING,
            blank=True),
        invoice_producer_mail=HTMLField(
            _("Content of the payment confirmation email sent to the producers concerned"
              ),
            help_text=EMPTY_STRING,
            configuration='CKEDITOR_SETTINGS_MODEL2',
            default=EMPTY_STRING,
            blank=True),
    )

    def clean(self):
        try:
            template = Template(self.offer_customer_mail)
        except Exception as error_str:
            raise ValidationError(
                mark_safe("{} : {}".format(self.offer_customer_mail,
                                           error_str)))
        try:
            template = Template(self.offer_producer_mail)
        except Exception as error_str:
            raise ValidationError(
                mark_safe("{} : {}".format(self.offer_producer_mail,
                                           error_str)))
        try:
            template = Template(self.order_customer_mail)
        except Exception as error_str:
            raise ValidationError(
                mark_safe("{} : {}".format(self.order_customer_mail,
                                           error_str)))
        try:
            template = Template(self.order_staff_mail)
        except Exception as error_str:
            raise ValidationError(
                mark_safe("{} : {}".format(self.order_staff_mail, error_str)))
        try:
            template = Template(self.order_producer_mail)
        except Exception as error_str:
            raise ValidationError(
                mark_safe("{} : {}".format(self.order_producer_mail,
                                           error_str)))
        if settings.REPANIER_SETTINGS_MANAGE_ACCOUNTING:
            try:
                template = Template(self.invoice_customer_mail)
            except Exception as error_str:
                raise ValidationError(
                    mark_safe("{} : {}".format(self.invoice_customer_mail,
                                               error_str)))
            try:
                template = Template(self.invoice_producer_mail)
            except Exception as error_str:
                raise ValidationError(
                    mark_safe("{} : {}".format(self.invoice_producer_mail,
                                               error_str)))

    @classmethod
    def init_repanier(cls):
        from repanier.const import DECIMAL_ONE, PERMANENCE_NAME_PERMANENCE, CURRENCY_EUR
        from repanier.models.producer import Producer
        from repanier.models.bankaccount import BankAccount
        from repanier.models.staff import Staff
        from repanier.models.customer import Customer

        # Create the configuration record managed via the admin UI
        config = Configuration.objects.filter(id=DECIMAL_ONE).first()
        if config is not None:
            return config
        group_name = settings.REPANIER_SETTINGS_GROUP_NAME
        site = Site.objects.get_current()
        if site is not None:
            site.name = group_name
            site.domain = group_name
            site.save()
        config = Configuration.objects.create(
            group_name=group_name,
            name=PERMANENCE_NAME_PERMANENCE,
            bank_account="BE99 9999 9999 9999",
            currency=CURRENCY_EUR)
        config.init_email()
        config.save()

        # Create firsts users
        Producer.get_or_create_group()
        customer_buyinggroup = Customer.get_or_create_group()
        very_first_customer = Customer.get_or_create_the_very_first_customer()

        BankAccount.open_account(customer_buyinggroup=customer_buyinggroup,
                                 very_first_customer=very_first_customer)

        coordinator = Staff.get_or_create_any_coordinator()
        Staff.get_or_create_order_responsible()
        Staff.get_or_create_invoice_responsible()
        # Create and publish first web page
        if not coordinator.is_webmaster:
            # This should not be the case...
            return

        from cms.models import StaticPlaceholder
        from cms.constants import X_FRAME_OPTIONS_DENY
        from cms import api
        page = api.create_page(title=_("Home"),
                               soft_root=False,
                               template=settings.CMS_TEMPLATE_HOME,
                               language=settings.LANGUAGE_CODE,
                               published=True,
                               parent=None,
                               xframe_options=X_FRAME_OPTIONS_DENY,
                               in_navigation=True)
        try:
            # New in CMS 3.5
            page.set_as_homepage()
        except:
            pass

        placeholder = page.placeholders.get(slot="home-hero")
        api.add_plugin(placeholder=placeholder,
                       plugin_type='TextPlugin',
                       language=settings.LANGUAGE_CODE,
                       body=settings.CMS_TEMPLATE_HOME_HERO)
        placeholder = page.placeholders.get(slot="home-col-1")
        api.add_plugin(placeholder=placeholder,
                       plugin_type='TextPlugin',
                       language=settings.LANGUAGE_CODE,
                       body=settings.CMS_TEMPLATE_HOME_COL_1)
        placeholder = page.placeholders.get(slot="home-col-2")
        api.add_plugin(placeholder=placeholder,
                       plugin_type='TextPlugin',
                       language=settings.LANGUAGE_CODE,
                       body=settings.CMS_TEMPLATE_HOME_COL_2)
        placeholder = page.placeholders.get(slot="home-col-3")
        api.add_plugin(placeholder=placeholder,
                       plugin_type='TextPlugin',
                       language=settings.LANGUAGE_CODE,
                       body=settings.CMS_TEMPLATE_HOME_COL_3)
        static_placeholder = StaticPlaceholder(code="footer",
                                               # site_id=1
                                               )
        static_placeholder.save()
        api.add_plugin(placeholder=static_placeholder.draft,
                       plugin_type='TextPlugin',
                       language=settings.LANGUAGE_CODE,
                       body='hello world footer')
        static_placeholder.publish(request=None,
                                   language=settings.LANGUAGE_CODE,
                                   force=True)
        api.publish_page(page=page,
                         user=coordinator.user,
                         language=settings.LANGUAGE_CODE)

        return config

    def init_email(self):
        for language in settings.PARLER_LANGUAGES[settings.SITE_ID]:
            language_code = language["code"]
            self.set_current_language(language_code)
            try:
                self.offer_customer_mail = """
                    Bonjour,<br />
                    <br />
                    Les commandes de la {{ permanence_link }} sont maintenant ouvertes auprès de : {{ offer_producer }}.<br />
                    {% if offer_description %}<br />{{ offer_description }}<br />
                    {% endif %} {% if offer_recent_detail %}<br />
                    Nouveauté(s) :<br />
                    {{ offer_recent_detail }}{% endif %}<br />
                    <br />
                    {{ signature }}
                    """
                self.offer_producer_mail = """
                    Cher/Chère {{ long_profile_name }},<br>
                    <br>
                    {% if offer_description != "" %}Voici l'annonce consommateur :<br>
                    {{ offer_description }}<br>
                    <br>
                    {% endif %} Veuillez vérifier votre <strong>{{ offer_link }}</strong>.<br>
                    <br>
                    {{ signature }}
                    """
                self.order_customer_mail = """
                    Bonjour {{ long_basket_name }},<br>
                    <br>
                    En pièce jointe vous trouverez le montant de votre panier {{ short_basket_name }} de la {{ permanence_link }}.<br>
                    <br>
                    {{ last_balance }}<br>
                    {{ order_amount }}<br>
                    {% if on_hold_movement %}{{ on_hold_movement }}<br>
                    {% endif %} {% if payment_needed %}{{ payment_needed }}<br>
                    {% endif %}<br>
                    <br>
                    {{ signature }}
                    """
                self.cancel_order_customer_mail = """
                    Bonjour {{ long_basket_name }},<br>
                    <br>
                    La commande ci-jointe de votre panier {{ short_basket_name }} de la {{ permanence_link }} <b>a été annulée</b> car vous ne l'avez pas confirmée.<br>
                    <br>
                    {{ signature }}
                    """
                self.order_staff_mail = """
                    Cher/Chère membre de l'équipe de préparation,<br>
                    <br>
                    En pièce jointe vous trouverez la liste de préparation pour la {{ permanence_link }}.<br>
                    <br>
                    L'équipe de préparation est composée de :<br>
                    {{ board_composition_and_description }}<br>
                    <br>
                    {{ signature }}
                    """
                self.order_producer_mail = """
                    Cher/Chère {{ name }},<br>
                    <br>
                    {% if order_empty %}Le groupe ne vous a rien acheté pour la {{ permanence_link }}.{% else %}En pièce jointe, vous trouverez la commande du groupe pour la {{ permanence }}.{% if duplicate %}<br>
                    <strong>ATTENTION </strong>: La commande est présente en deux exemplaires. Le premier exemplaire est classé par produit et le duplicata est classé par panier.{% else %}{% endif %}{% endif %}<br>
                    <br>
                    {{ signature }}
                    """
                self.invoice_customer_mail = """
                    Bonjour {{ name }},<br>
                    <br>
                    En cliquant sur ce lien vous trouverez votre facture pour la {{ permanence_link }}.{% if invoice_description %}<br>
                    <br>
                    {{ invoice_description }}{% endif %}
                    <br>
                    {{ order_amount }}<br>
                    {{ last_balance_link }}<br>
                    {% if payment_needed %}{{ payment_needed }}<br>
                    {% endif %}<br>
                    <br>
                    {{ signature }}
                    """
                self.invoice_producer_mail = """
                    Cher/Chère {{ profile_name }},<br>
                    <br>
                    En cliquant sur ce lien vous trouverez le détail de notre paiement pour la {{ permanence_link }}.<br>
                    <br>
                    {{ signature }}
                    """
                self.save_translations()
            except TranslationDoesNotExist:
                pass

    def upgrade_db(self):
        if self.db_version == 0:
            from repanier.models import Product, OfferItemWoReceiver, BankAccount, Permanence, Staff
            # Staff.objects.rebuild()
            Product.objects.filter(is_box=True).order_by('?').update(
                limit_order_quantity_to_stock=True)
            OfferItemWoReceiver.objects.filter(
                permanence__status__gte=PERMANENCE_SEND,
                order_unit=PRODUCT_ORDER_UNIT_PC_KG).order_by('?').update(
                    use_order_unit_converted=True)
            for bank_account in BankAccount.objects.filter(
                    permanence__isnull=False,
                    producer__isnull=True,
                    customer__isnull=True).order_by('?').only(
                        "id", "permanence_id"):
                Permanence.objects.filter(
                    id=bank_account.permanence_id,
                    invoice_sort_order__isnull=True).order_by('?').update(
                        invoice_sort_order=bank_account.id)
            for permanence in Permanence.objects.filter(
                    status__in=[PERMANENCE_CANCELLED, PERMANENCE_ARCHIVED],
                    invoice_sort_order__isnull=True).order_by('?'):
                bank_account = BankAccount.get_closest_to(
                    permanence.permanence_date)
                if bank_account is not None:
                    permanence.invoice_sort_order = bank_account.id
                    permanence.save(update_fields=['invoice_sort_order'])
            Staff.objects.order_by('?').update(
                is_order_manager=F('is_reply_to_order_email'),
                is_invoice_manager=F('is_reply_to_invoice_email'),
                is_order_referent=F('is_contributor'))
            self.db_version = 1
        if self.db_version == 1:
            for user in User.objects.filter(is_staff=False).order_by('?'):
                user.first_name = EMPTY_STRING
                user.last_name = user.username[:30]
                user.save()
            for user in User.objects.filter(is_staff=True,
                                            is_superuser=False).order_by('?'):
                user.first_name = EMPTY_STRING
                user.last_name = user.email[:30]
                user.save()
            self.db_version = 2
        if self.db_version == 2:
            from repanier.models import Staff
            Staff.objects.order_by('?').update(
                is_repanier_admin=F('is_coordinator'), )
            Staff.objects.filter(is_repanier_admin=True).order_by('?').update(
                can_be_contacted=True, )
            Staff.objects.filter(is_order_manager=True).order_by('?').update(
                can_be_contacted=True, )
            Staff.objects.filter(is_invoice_manager=True).order_by('?').update(
                can_be_contacted=True, )
            Staff.objects.filter(
                is_invoice_referent=True).order_by('?').update(
                    is_invoice_manager=True, )
            Staff.objects.filter(is_order_referent=True).order_by('?').update(
                is_order_manager=True, )
            self.db_version = 3

    def __str__(self):
        return self.group_name

    class Meta:
        verbose_name = _("Configuration")
        verbose_name_plural = _("Configurations")
Example #26
0
class CustomerInvoice(Invoice):
    customer = models.ForeignKey('Customer',
                                 verbose_name=_("Customer"),
                                 on_delete=models.PROTECT)
    customer_charged = models.ForeignKey('Customer',
                                         verbose_name=_("Customer"),
                                         related_name='invoices_paid',
                                         blank=True,
                                         null=True,
                                         on_delete=models.PROTECT,
                                         db_index=True)
    delivery = models.ForeignKey('DeliveryBoard',
                                 verbose_name=_("Delivery board"),
                                 null=True,
                                 blank=True,
                                 default=None,
                                 on_delete=models.PROTECT)
    # IMPORTANT: default = True -> for the order form, to display nothing at the begin of the order
    # is_order_confirm_send and total_price_with_tax = 0 --> display nothing
    # otherwise display
    # - send a mail with the order to me
    # - confirm the order (if REPANIER_SETTINGS_CUSTOMER_MUST_CONFIRM_ORDER) and send a mail with the order to me
    # - mail send to XYZ
    # - order confirmed (if REPANIER_SETTINGS_CUSTOMER_MUST_CONFIRM_ORDER) and mail send to XYZ
    is_order_confirm_send = models.BooleanField(
        _("Confirmation of the order send"),
        choices=settings.LUT_CONFIRM,
        default=False)
    invoice_sort_order = models.IntegerField(_("Invoice sort order"),
                                             default=None,
                                             blank=True,
                                             null=True,
                                             db_index=True)
    price_list_multiplier = models.DecimalField(
        _("Delivery point coefficient applied to the producer tariff to calculate the consumer tariff"
          ),
        help_text=
        _("This multiplier is applied once for groups with entitled customer or at each customer invoice for open groups."
          ),
        default=DECIMAL_ONE,
        max_digits=5,
        decimal_places=4,
        blank=True,
        validators=[MinValueValidator(0)])
    transport = ModelMoneyField(
        _("Delivery point shipping cost"),
        help_text=
        _("This amount is added once for groups with entitled customer or at each customer for open groups."
          ),
        default=DECIMAL_ZERO,
        max_digits=5,
        decimal_places=2,
        validators=[MinValueValidator(0)])
    min_transport = ModelMoneyField(
        _("Minium order amount for free shipping cost"),
        help_text=_(
            "This is the minimum order amount to avoid shipping cost."),
        default=DECIMAL_ZERO,
        max_digits=5,
        decimal_places=2,
        validators=[MinValueValidator(0)])
    master_permanence = models.ForeignKey(
        'Permanence',
        verbose_name=_("Master permanence"),
        related_name='child_customer_invoice',
        blank=True,
        null=True,
        default=None,
        on_delete=models.PROTECT,
        db_index=True)
    is_group = models.BooleanField(_("Group"), default=False)

    def get_abs_delta_vat(self):
        return abs(self.delta_vat)

    def get_total_price_with_tax(self, customer_charged=False):
        if self.customer_id == self.customer_charged_id:
            return self.total_price_with_tax + self.delta_price_with_tax + self.delta_transport
        else:
            if self.status < PERMANENCE_INVOICED or not customer_charged:
                return self.total_price_with_tax
            else:
                return self.customer_charged  # if self.total_price_with_tax != DECIMAL_ZERO else RepanierMoney()

    def get_total_price_wo_tax(self):
        return self.get_total_price_with_tax() - self.get_total_tax()

    def get_total_tax(self):
        # round to 2 decimals
        return RepanierMoney(self.total_vat.amount + self.delta_vat.amount)

    @property
    def has_purchase(self):
        if self.total_price_with_tax.amount != DECIMAL_ZERO or self.is_order_confirm_send:
            return True

        from repanier.models.purchase import PurchaseWoReceiver

        result = False
        result_set = PurchaseWoReceiver.objects.filter(
            permanence_id=self.permanence_id,
            customer_invoice_id=self.id).order_by('?').aggregate(
                Sum('quantity_ordered'),
                Sum('quantity_invoiced'),
            )
        if result_set["quantity_ordered__sum"] is not None:
            sum_quantity_ordered = result_set["quantity_ordered__sum"]
            if sum_quantity_ordered != DECIMAL_ZERO:
                result = True
        if result_set["quantity_invoiced__sum"] is not None:
            sum_quantity_invoiced = result_set["quantity_invoiced__sum"]
            if sum_quantity_invoiced != DECIMAL_ZERO:
                result = True
        return result

    @transaction.atomic
    def set_delivery(self, delivery):
        # May not use delivery_id because it won't reload customer_invoice.delivery
        # Important
        # If it's an invoice of a member of a group :
        #   self.customer_charged_id != self.customer_id
        #   self.customer_charged_id == owner of the group
        #   price_list_multiplier = DECIMAL_ONE
        # Else :
        #   self.customer_charged_id = self.customer_id
        #   price_list_multiplier may vary
        from repanier.apps import REPANIER_SETTINGS_TRANSPORT, REPANIER_SETTINGS_MIN_TRANSPORT
        if delivery is None:
            if self.permanence.with_delivery_point:
                # If the customer is member of a group set the group as default delivery point
                delivery_point = self.customer.delivery_point
                delivery = DeliveryBoard.objects.filter(
                    delivery_point=delivery_point,
                    permanence=self.permanence).order_by('?').first()
            else:
                delivery_point = None
        else:
            delivery_point = delivery.delivery_point
        self.delivery = delivery

        if delivery_point is None:
            self.customer_charged = self.customer
            self.price_list_multiplier = DECIMAL_ONE
            self.transport = REPANIER_SETTINGS_TRANSPORT
            self.min_transport = REPANIER_SETTINGS_MIN_TRANSPORT
        else:
            customer_responsible = delivery_point.customer_responsible
            if customer_responsible is None:
                self.customer_charged = self.customer
                self.price_list_multiplier = DECIMAL_ONE
                self.transport = delivery_point.transport
                self.min_transport = delivery_point.min_transport
            else:
                self.customer_charged = customer_responsible
                self.price_list_multiplier = DECIMAL_ONE
                self.transport = REPANIER_MONEY_ZERO
                self.min_transport = REPANIER_MONEY_ZERO
                if self.customer_id != customer_responsible.id:
                    customer_invoice_charged = CustomerInvoice.objects.filter(
                        permanence_id=self.permanence_id,
                        customer_id=customer_responsible.id).order_by('?')
                    if not customer_invoice_charged.exists():
                        CustomerInvoice.objects.create(
                            permanence_id=self.permanence_id,
                            customer_id=customer_responsible.id,
                            status=self.status,
                            customer_charged_id=customer_responsible.id,
                            price_list_multiplier=customer_responsible.
                            price_list_multiplier,
                            transport=delivery_point.transport,
                            min_transport=delivery_point.min_transport,
                            is_order_confirm_send=True,
                            is_group=True,
                            delivery=delivery)

    def get_html_my_order_confirmation(self,
                                       permanence,
                                       is_basket=False,
                                       basket_message=EMPTY_STRING):

        if permanence.with_delivery_point:
            if self.delivery is not None:
                label = self.delivery.get_delivery_customer_display()
                delivery_id = self.delivery_id
            else:
                delivery_id = 0

                if self.customer.delivery_point is not None:
                    qs = DeliveryBoard.objects.filter(
                        Q(permanence_id=permanence.id,
                          delivery_point_id=self.customer.delivery_point_id,
                          status=PERMANENCE_OPENED)
                        | Q(permanence_id=permanence.id,
                            delivery_point__customer_responsible__isnull=True,
                            status=PERMANENCE_OPENED)).order_by('?')
                else:
                    qs = DeliveryBoard.objects.filter(
                        permanence_id=permanence.id,
                        delivery_point__customer_responsible__isnull=True,
                        status=PERMANENCE_OPENED).order_by('?')
                if qs.exists():
                    label = "{}".format(_('Please, select a delivery point'))
                    CustomerInvoice.objects.filter(
                        permanence_id=permanence.id,
                        customer_id=self.customer_id).order_by('?').update(
                            status=PERMANENCE_OPENED)
                else:
                    label = "{}".format(
                        _('No delivery point is open for you. You can not place order.'
                          ))
                    # IMPORTANT :
                    # 1 / This prohibit to place an order into the customer UI
                    # 2 / task_order.close_send_order will delete any CLOSED orders without any delivery point
                    CustomerInvoice.objects.filter(
                        permanence_id=permanence.id,
                        customer_id=self.customer_id).order_by('?').update(
                            status=PERMANENCE_CLOSED)
            if self.customer_id != self.customer_charged_id:
                msg_price = msg_transport = EMPTY_STRING
            else:
                if self.transport.amount <= DECIMAL_ZERO:
                    transport = False
                    msg_transport = EMPTY_STRING
                else:
                    transport = True
                    if self.min_transport.amount > DECIMAL_ZERO:
                        msg_transport = "{}<br>".format(
                            _('The shipping costs for this delivery point amount to %(transport)s for orders of less than %(min_transport)s.'
                              ) % {
                                  'transport': self.transport,
                                  'min_transport': self.min_transport
                              })
                    else:
                        msg_transport = "{}<br>".format(
                            _('The shipping costs for this delivery point amount to %(transport)s.'
                              ) % {
                                  'transport': self.transport,
                              })
                if self.price_list_multiplier == DECIMAL_ONE:
                    msg_price = EMPTY_STRING
                else:
                    if transport:
                        if self.price_list_multiplier > DECIMAL_ONE:
                            msg_price = "{}<br>".format(
                                _('In addition, a surcharge of %(increase)s %% is applied to the billed total. It does not apply to deposits or fees.'
                                  ) % {
                                      'increase':
                                      number_format(
                                          (self.price_list_multiplier -
                                           DECIMAL_ONE) * 100, 2)
                                  })
                        else:
                            msg_price = "{}<br>".format(
                                _('In addition a reduction of %(decrease)s %% is applied to the billed total. It does not apply to deposits or fees.'
                                  ) %
                                {
                                    'decrease':
                                    number_format(
                                        (DECIMAL_ONE -
                                         self.price_list_multiplier) * 100, 2)
                                })
                    else:
                        if self.price_list_multiplier > DECIMAL_ONE:
                            msg_price = "{}<br>".format(
                                _('For this delivery point, an overload of %(increase)s %% is applied to the billed total (out of deposit).'
                                  ) % {
                                      'increase':
                                      number_format(
                                          (self.price_list_multiplier -
                                           DECIMAL_ONE) * 100, 2)
                                  })
                        else:
                            msg_price = "{}<br>".format(
                                _('For this delivery point, a reduction of %(decrease)s %% is applied to the invoiced total (out of deposit).'
                                  ) %
                                {
                                    'decrease':
                                    number_format(
                                        (DECIMAL_ONE -
                                         self.price_list_multiplier) * 100, 2)
                                })

            msg_delivery = """
            {}<b><i>
            <select name=\"delivery\" id=\"delivery\" onmouseover=\"show_select_delivery_list_ajax({})\" onchange=\"delivery_ajax()\" class=\"form-control\">
            <option value=\"{}\" selected>{}</option>
            </select>
            </i></b><br>{}{}
            """.format(_("Delivery point"), delivery_id, delivery_id, label,
                       msg_transport, msg_price)
        else:
            msg_delivery = EMPTY_STRING
        msg_confirmation1 = EMPTY_STRING
        if not is_basket and not settings.REPANIER_SETTINGS_CUSTOMER_MUST_CONFIRM_ORDER:
            # or customer_invoice.total_price_with_tax.amount != DECIMAL_ZERO:
            # If REPANIER_SETTINGS_CUSTOMER_MUST_CONFIRM_ORDER,
            # then permanence.with_delivery_point is also True
            msg_html = EMPTY_STRING
        else:
            if self.is_order_confirm_send:
                msg_confirmation2 = self.customer.my_order_confirmation_email_send_to(
                )
                msg_html = """
                <div class="row">
                <div class="panel panel-default">
                <div class="panel-heading">
                {}
                <p><font color="#51a351">{}</font><p/>
                {}
                </div>
                </div>
                </div>
                 """.format(msg_delivery, msg_confirmation2, basket_message)
            else:
                msg_html = None
                btn_disabled = EMPTY_STRING if permanence.status == PERMANENCE_OPENED else "disabled"
                msg_confirmation2 = EMPTY_STRING
                if settings.REPANIER_SETTINGS_CUSTOMER_MUST_CONFIRM_ORDER:
                    if is_basket:
                        if self.status == PERMANENCE_OPENED:
                            if (permanence.with_delivery_point and self.delivery is None) \
                                    or not self.has_purchase:
                                btn_disabled = "disabled"
                            msg_confirmation1 = "<span style=\"color: red; \">{}</span><br>".format(
                                _("⚠ Unconfirmed orders will be canceled."))
                            msg_confirmation2 = "<span class=\"glyphicon glyphicon-floppy-disk\"></span>&nbsp;&nbsp;{}".format(
                                _("Confirm this order and receive an email containing its summary."
                                  ))
                    else:
                        href = reverse('order_view', args=(permanence.id, ))
                        if self.status == PERMANENCE_OPENED:
                            msg_confirmation1 = "<span style=\"color: red; \">{}</span><br>".format(
                                _("⚠ Unconfirmed orders will be canceled."))
                            msg_confirmation2 = _(
                                "➜ Go to the confirmation step of my order.")
                            msg_html = """
                                <div class="row">
                                <div class="panel panel-default">
                                <div class="panel-heading">
                                {}
                                {}
                                <a href="{}?is_basket=yes" class="btn btn-info" {}>{}</a>
                                </div>
                                </div>
                                </div>
                                 """.format(msg_delivery, msg_confirmation1,
                                            href, btn_disabled,
                                            msg_confirmation2)
                else:
                    if is_basket:
                        msg_confirmation2 = _(
                            "Receive an email containing this order summary.")
                    elif permanence.with_delivery_point:
                        msg_html = """
                            <div class="row">
                            <div class="panel panel-default">
                            <div class="panel-heading">
                            {}
                            </div>
                            </div>
                            </div>
                             """.format(msg_delivery)
                    else:
                        msg_html = EMPTY_STRING
                if msg_html is None:
                    if msg_confirmation2 == EMPTY_STRING:
                        msg_html = """
                        <div class="row">
                        <div class="panel panel-default">
                        <div class="panel-heading">
                        {}
                        <div class="clearfix"></div>
                        {}
                        </div>
                        </div>
                        </div>
                         """.format(msg_delivery, basket_message)
                    else:
                        msg_html = """
                        <div class="row">
                        <div class="panel panel-default">
                        <div class="panel-heading">
                        {}
                        {}
                        <button id="btn_confirm_order" class="btn btn-info" {} onclick="btn_receive_order_email();">{}</button>
                        <div class="clearfix"></div>
                        {}
                        </div>
                        </div>
                        </div>
                         """.format(msg_delivery, msg_confirmation1,
                                    btn_disabled, msg_confirmation2,
                                    basket_message)
        return {"#span_btn_confirm_order": mark_safe(msg_html)}

    @transaction.atomic
    def confirm_order(self):
        from repanier.models.purchase import Purchase

        Purchase.objects.filter(customer_invoice__id=self.id).update(
            quantity_confirmed=F('quantity_ordered'))
        self.calculate_and_save_delta_buyinggroup()
        self.is_order_confirm_send = True

    def calculate_and_save_delta_buyinggroup(self):
        previous_delta_price_with_tax = self.delta_price_with_tax.amount
        previous_delta_vat = self.delta_vat.amount
        previous_delta_transport = self.delta_transport.amount

        self.calculate_delta_price()
        self.calculate_delta_transport()

        if previous_delta_price_with_tax != self.delta_price_with_tax.amount or previous_delta_vat != self.delta_vat.amount or previous_delta_transport != self.delta_transport.amount:
            producer_invoice_buyinggroup = ProducerInvoice.objects.filter(
                producer__represent_this_buyinggroup=True,
                permanence_id=self.permanence_id,
            ).order_by('?').first()
            if producer_invoice_buyinggroup is None:
                from repanier.models.producer import Producer
                producer_invoice_buyinggroup = ProducerInvoice.objects.create(
                    producer=Producer.get_or_create_group(),
                    permanence_id=self.permanence_id,
                    status=self.permanence.status)
            producer_invoice_buyinggroup.delta_price_with_tax.amount += self.delta_price_with_tax.amount - previous_delta_price_with_tax
            producer_invoice_buyinggroup.delta_vat.amount += self.delta_vat.amount - previous_delta_vat
            producer_invoice_buyinggroup.delta_transport.amount += self.delta_transport.amount - previous_delta_transport

            producer_invoice_buyinggroup.save()

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

        self.delta_price_with_tax.amount = DECIMAL_ZERO
        self.delta_vat.amount = DECIMAL_ZERO

        if self.customer_id == self.customer_charged_id:
            # It's an invoice of a group, or of a customer who is not member of a group :
            #   self.customer_charged_id = self.customer_id
            #   self.price_list_multiplier may vary
            if self.price_list_multiplier != DECIMAL_ONE:
                result_set = Purchase.objects.filter(
                    permanence_id=self.permanence_id,
                    customer_invoice__customer_charged_id=self.customer_id,
                    is_resale_price_fixed=False).order_by('?').aggregate(
                        Sum('customer_vat'), Sum('deposit'),
                        Sum('selling_price'))

                if result_set["customer_vat__sum"] is not None:
                    total_vat = result_set["customer_vat__sum"]
                else:
                    total_vat = DECIMAL_ZERO
                if result_set["deposit__sum"] is not None:
                    total_deposit = result_set["deposit__sum"]
                else:
                    total_deposit = DECIMAL_ZERO
                if result_set["selling_price__sum"] is not None:
                    total_price_with_tax = result_set["selling_price__sum"]
                else:
                    total_price_with_tax = DECIMAL_ZERO

                total_price_with_tax_wo_deposit = total_price_with_tax - total_deposit
                self.delta_price_with_tax.amount = (
                    (total_price_with_tax_wo_deposit *
                     self.price_list_multiplier).quantize(TWO_DECIMALS) -
                    total_price_with_tax_wo_deposit)
                self.delta_vat.amount = -(
                    (total_vat * self.price_list_multiplier
                     ).quantize(FOUR_DECIMALS) - total_vat)

            result_set = Purchase.objects.filter(
                permanence_id=self.permanence_id,
                customer_invoice__customer_charged_id=self.customer_id,
            ).order_by('?').aggregate(Sum('customer_vat'), Sum('deposit'),
                                      Sum('selling_price'))
        else:
            # It's an invoice of a member of a group
            #   self.customer_charged_id != self.customer_id
            #   self.customer_charged_id == owner of the group
            #   assertion : self.price_list_multiplier always == DECIMAL_ONE
            result_set = Purchase.objects.filter(
                permanence_id=self.permanence_id,
                customer_id=self.customer_id,
            ).order_by('?').aggregate(Sum('customer_vat'), Sum('deposit'),
                                      Sum('selling_price'))
        if result_set["customer_vat__sum"] is not None:
            self.total_vat.amount = result_set["customer_vat__sum"]
        else:
            self.total_vat.amount = DECIMAL_ZERO
        if result_set["deposit__sum"] is not None:
            self.total_deposit.amount = result_set["deposit__sum"]
        else:
            self.total_deposit.amount = DECIMAL_ZERO
        if result_set["selling_price__sum"] is not None:
            self.total_price_with_tax.amount = result_set["selling_price__sum"]
        else:
            self.total_price_with_tax.amount = DECIMAL_ZERO
        if settings.REPANIER_SETTINGS_ROUND_INVOICES:
            total_price = self.total_price_with_tax.amount + self.delta_price_with_tax.amount
            total_price_gov_be = round_gov_be(total_price)
            self.delta_price_with_tax.amount += (total_price_gov_be -
                                                 total_price)

    def calculate_delta_transport(self):

        self.delta_transport.amount = DECIMAL_ZERO
        if self.master_permanence_id is None and self.transport.amount != DECIMAL_ZERO:
            # Calculate transport only on master customer invoice
            # But take into account the children customer invoices
            result_set = CustomerInvoice.objects.filter(
                master_permanence_id=self.permanence_id).order_by(
                    '?').aggregate(Sum('total_price_with_tax'),
                                   Sum('delta_price_with_tax'))
            if result_set["total_price_with_tax__sum"] is not None:
                sum_total_price_with_tax = result_set[
                    "total_price_with_tax__sum"]
            else:
                sum_total_price_with_tax = DECIMAL_ZERO
            if result_set["delta_price_with_tax__sum"] is not None:
                sum_delta_price_with_tax = result_set[
                    "delta_price_with_tax__sum"]
            else:
                sum_delta_price_with_tax = DECIMAL_ZERO

            sum_total_price_with_tax += self.total_price_with_tax.amount
            sum_delta_price_with_tax += self.delta_price_with_tax.amount

            total_price_with_tax = sum_total_price_with_tax + sum_delta_price_with_tax
            if total_price_with_tax != DECIMAL_ZERO:
                if self.min_transport.amount == DECIMAL_ZERO:
                    self.delta_transport.amount = self.transport.amount
                elif total_price_with_tax < self.min_transport.amount:
                    self.delta_transport.amount = min(
                        self.min_transport.amount - total_price_with_tax,
                        self.transport.amount)

    def cancel_confirm_order(self):
        if self.is_order_confirm_send:
            # Change of confirmation status
            self.is_order_confirm_send = False
            return True
        else:
            # No change of confirmation status
            return False

    def create_child(self, new_permanence):
        if self.customer_id != self.customer_charged_id:
            # TODO : Créer la customer invoice du groupe
            customer_invoice = CustomerInvoice.objects.filter(
                permanence_id=self.permanence_id,
                customer_id=self.customer_charged_id).only("id").order_by('?')
            if not customer_invoice.exists():
                customer_invoice = CustomerInvoice.objects.create(
                    permanence_id=self.permanence_id,
                    customer_id=self.customer_charged_id,
                    customer_charged_id=self.customer_charged_id,
                    status=self.status)
                customer_invoice.set_delivery(delivery=None)
                customer_invoice.save()
        return CustomerInvoice.objects.create(
            permanence_id=new_permanence.id,
            customer_id=self.customer_id,
            master_permanence_id=self.permanence_id,
            customer_charged_id=self.customer_charged_id,
            status=self.status)

    def cancel_if_unconfirmed(self, permanence):
        if settings.REPANIER_SETTINGS_CUSTOMER_MUST_CONFIRM_ORDER \
                and not self.is_order_confirm_send \
                and self.has_purchase:
            from repanier.email.email_order import export_order_2_1_customer
            from repanier.models.purchase import Purchase

            filename = "{0}-{1}.xlsx".format(_("Canceled order"), permanence)

            export_order_2_1_customer(self.customer,
                                      filename,
                                      permanence,
                                      cancel_order=True)
            purchase_qs = Purchase.objects.filter(
                customer_invoice_id=self.id,
                is_box_content=False,
            ).order_by('?')
            for a_purchase in purchase_qs.select_related("customer"):
                create_or_update_one_cart_item(
                    customer=a_purchase.customer,
                    offer_item_id=a_purchase.offer_item_id,
                    q_order=DECIMAL_ZERO,
                    batch_job=True,
                    comment=_("Qty not confirmed : {}").format(
                        number_format(a_purchase.quantity_ordered, 4)))

    def __str__(self):
        return "{}, {}".format(self.customer, self.permanence)

    class Meta:
        verbose_name = _("Customer invoice")
        verbose_name_plural = _("Customers invoices")
        unique_together = (
            "permanence",
            "customer",
        )
Example #27
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
Example #28
0
class BankAccount(models.Model):
    permanence = models.ForeignKey(
        "Permanence",
        verbose_name=REPANIER_SETTINGS_PERMANENCE_NAME,
        on_delete=models.PROTECT,
        blank=True,
        null=True,
    )
    producer = models.ForeignKey(
        "Producer",
        verbose_name=_("Producer"),
        on_delete=models.PROTECT,
        blank=True,
        null=True,
    )
    customer = models.ForeignKey(
        "Customer",
        verbose_name=_("Customer"),
        on_delete=models.PROTECT,
        blank=True,
        null=True,
    )
    operation_date = models.DateField(_("Operation date"), db_index=True)
    operation_comment = models.CharField(
        _("Operation comment"), max_length=100, blank=True, default=EMPTY_STRING
    )
    operation_status = models.CharField(
        max_length=3,
        choices=LUT_BANK_TOTAL,
        default=BANK_NOT_LATEST_TOTAL,
        verbose_name=_("Account balance status"),
        db_index=True,
    )
    bank_amount_in = ModelMoneyField(
        _("Cash in"),
        help_text=_("Payment on the account"),
        max_digits=8,
        decimal_places=2,
        default=DECIMAL_ZERO,
        validators=[MinValueValidator(0)],
    )
    bank_amount_out = ModelMoneyField(
        _("Cash out"),
        help_text=_("Payment from the account"),
        max_digits=8,
        decimal_places=2,
        default=DECIMAL_ZERO,
        validators=[MinValueValidator(0)],
    )
    producer_invoice = models.ForeignKey(
        "ProducerInvoice",
        verbose_name=_("Producer invoice"),
        blank=True,
        null=True,
        on_delete=models.PROTECT,
        db_index=True,
    )
    customer_invoice = models.ForeignKey(
        "CustomerInvoice",
        verbose_name=_("Customer invoice"),
        blank=True,
        null=True,
        on_delete=models.PROTECT,
        db_index=True,
    )
    is_updated_on = models.DateTimeField(_("Updated on"), auto_now=True)

    @classmethod
    def open_account(cls, customer_buyinggroup, very_first_customer):
        bank_account = BankAccount.objects.filter().order_by("?")
        if not bank_account.exists():
            BankAccount.objects.create(
                operation_status=BANK_LATEST_TOTAL,
                operation_date=timezone.now().date(),
                operation_comment=_("Account opening"),
            )
            # Create this also prevent the deletion of the customer representing the buying group
            BankAccount.objects.create(
                operation_date=timezone.now().date(),
                customer=customer_buyinggroup,
                operation_comment=_("Initial balance"),
            )
            # Create this also prevent the deletion of the very first customer
            BankAccount.objects.create(
                operation_date=timezone.now().date(),
                customer=very_first_customer,
                operation_comment=_("Initial balance"),
            )

    @classmethod
    def get_closest_to(cls, target):
        # https://stackoverflow.com/questions/15855715/filter-on-datetime-closest-to-the-given-datetime
        # https://www.vinta.com.br/blog/2017/advanced-django-querying-sorting-events-date/
        # Get closest bank_account (sub-)total from target date
        qs = cls.objects.filter(producer__isnull=True, customer__isnull=True).order_by(
            "?"
        )
        closest_greater_qs = qs.filter(operation_date__gt=target).order_by(
            "operation_date"
        )
        closest_less_qs = qs.filter(operation_date__lt=target).order_by(
            "-operation_date"
        )

        closest_greater = closest_greater_qs.first()
        if closest_greater is None:
            closest_greater = closest_less_qs.first()

        closest_less = closest_less_qs.first()
        if closest_less is None:
            closest_less = closest_greater_qs.first()

        if closest_greater is not None and closest_less is not None:
            if (
                closest_greater.operation_date - target
                > target - closest_less.operation_date
            ):
                return closest_less
            else:
                return closest_greater

    def get_bank_amount_in(self):
        if self.operation_status in [BANK_PROFIT, BANK_TAX]:
            return format_html(
                "<i>{}</i>",
                self.bank_amount_in
                if self.bank_amount_in.amount != DECIMAL_ZERO
                else EMPTY_STRING,
            )
        else:
            return (
                self.bank_amount_in
                if self.bank_amount_in.amount != DECIMAL_ZERO
                else EMPTY_STRING
            )

    get_bank_amount_in.short_description = _("Cash in")
    get_bank_amount_in.admin_order_field = "bank_amount_in"

    def get_bank_amount_out(self):
        if self.operation_status in [BANK_PROFIT, BANK_TAX]:
            return format_html(
                "<i>{}</i>",
                self.bank_amount_out
                if self.bank_amount_out.amount != DECIMAL_ZERO
                else EMPTY_STRING,
            )
        else:
            return (
                self.bank_amount_out
                if self.bank_amount_out.amount != DECIMAL_ZERO
                else EMPTY_STRING
            )

    get_bank_amount_out.short_description = _("Cash out")
    get_bank_amount_out.admin_order_field = "bank_amount_out"

    def get_producer(self):
        if self.producer is not None:
            return self.producer.short_profile_name
        else:
            if self.customer is None:
                # This is a total, show it
                if self.operation_status == BANK_LATEST_TOTAL:
                    return format_html(
                        "<b>=== {}</b>", settings.REPANIER_SETTINGS_GROUP_NAME
                    )
                else:
                    return format_html(
                        "<b>--- {}</b>", settings.REPANIER_SETTINGS_GROUP_NAME
                    )
            return EMPTY_STRING

    get_producer.short_description = _("Producer")
    get_producer.admin_order_field = "producer"

    def get_customer(self):
        if self.customer is not None:
            return self.customer.short_basket_name
        else:
            if self.producer is None:
                # This is a total, show it
                from repanier.apps import REPANIER_SETTINGS_BANK_ACCOUNT

                if self.operation_status == BANK_LATEST_TOTAL:

                    if REPANIER_SETTINGS_BANK_ACCOUNT is not None:
                        return format_html("<b>{}</b>", REPANIER_SETTINGS_BANK_ACCOUNT)
                    else:
                        return format_html("<b>{}</b>", "==============")
                else:
                    if REPANIER_SETTINGS_BANK_ACCOUNT is not None:
                        return format_html("<b>{}</b>", REPANIER_SETTINGS_BANK_ACCOUNT)
                    else:
                        return format_html("<b>{}</b>", "--------------")
            return EMPTY_STRING

    get_customer.short_description = _("Customer")
    get_customer.admin_order_field = "customer"

    class Meta:
        verbose_name = _("Bank account transaction")
        verbose_name_plural = _("Bank account transactions")
        ordering = ("-operation_date", "-id")
        index_together = [
            ["operation_date", "id"],
            ["customer_invoice", "operation_date", "id"],
            ["producer_invoice", "operation_date", "operation_date", "id"],
            ["permanence", "customer", "producer", "operation_date", "id"],
        ]
Example #29
0
class OfferItem(Item):
    translations = TranslatedFields(
        long_name=models.CharField(_("Long name"), max_length=100,
                                   default=EMPTY_STRING, blank=True, null=True),
        cache_part_a=HTMLField(default=EMPTY_STRING, blank=True),
        cache_part_b=HTMLField(default=EMPTY_STRING, blank=True),
        # Language dependant customer sort order for optimization
        order_sort_order=models.IntegerField(default=0, db_index=True),
        # Language dependant preparation sort order for optimization
        preparation_sort_order=models.IntegerField(default=0, db_index=True),
        # Language dependant producer sort order for optimization
        producer_sort_order=models.IntegerField(default=0, db_index=True)
    )
    permanence = models.ForeignKey(
        'Permanence',
        verbose_name=REPANIER_SETTINGS_PERMANENCE_NAME,
        on_delete=models.PROTECT,
        db_index=True
    )
    product = models.ForeignKey(
        'Product',
        verbose_name=_("Product"),
        on_delete=models.PROTECT)
    # is a box or a contract content
    is_box_content = models.BooleanField(default=False)

    producer_price_are_wo_vat = models.BooleanField(_("Producer price are without vat"), 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_ZERO, max_digits=5, decimal_places=4,
        validators=[MinValueValidator(0)])
    is_resale_price_fixed = models.BooleanField(
        _("The resale price is set by the producer"),
        default=False)

    # Calculated with Purchase : Total producer purchase price vat included
    total_purchase_with_tax = ModelMoneyField(
        _("Producer amount invoiced"),
        default=DECIMAL_ZERO, max_digits=8, decimal_places=2)
    # Calculated with Purchase : Total customer selling price vat included
    total_selling_with_tax = ModelMoneyField(
        _("Invoiced to the consumer including tax"),
        default=DECIMAL_ZERO, max_digits=8, decimal_places=2)

    # Calculated with Purchase : Quantity invoiced to all customers
    # If Permanence.status < SEND this is the order quantity
    # During sending the orders to the producer this become the invoiced quantity
    # via permanence.recalculate_order_amount(..., send_to_producer=True)
    quantity_invoiced = models.DecimalField(
        _("Qty invoiced"),
        max_digits=9, decimal_places=4, default=DECIMAL_ZERO)
    use_order_unit_converted = models.BooleanField(default=False)

    may_order = models.BooleanField(_("May order"), default=True)
    manage_replenishment = models.BooleanField(_("Manage replenishment"), default=False)
    manage_production = models.BooleanField(default=False)
    producer_pre_opening = models.BooleanField(_("Pre-open the orders"), default=False)

    add_2_stock = models.DecimalField(
        _("Additional"),
        default=DECIMAL_ZERO, max_digits=9, decimal_places=4)
    new_stock = models.DecimalField(
        _("Remaining stock"),
        default=None, max_digits=9, decimal_places=3, null=True)
    contract = models.ForeignKey(
        'Contract',
        verbose_name=_("Commitment"),
        on_delete=models.PROTECT,
        null=True, blank=True, default=None
    )
    permanences_dates = models.TextField(
        null=True, blank=True, default=None)
    # Opposite of permaneces_date used to know when the related product is not into offer
    not_permanences_dates = models.TextField(
        null=True, blank=True, default=None)
    # Number of permanences where this product is placed.
    # Used to compute the price during order phase
    permanences_dates_counter = models.IntegerField(
        null=True, blank=True, default=1)
    # Important : permanences_dates_order is used to
    # group together offer item's of the same product of a contract
    # with different purchases dates on the order form
    # 0   : No group needed
    # 1   : Master of a group
    # > 1 : Displayed with the master of the group
    permanences_dates_order = models.IntegerField(default=0)

    def get_vat_level(self):
        return self.get_vat_level_display()

    get_vat_level.short_description = (_("VAT level"))
    get_vat_level.admin_order_field = 'vat_level'

    def get_producer_qty_stock_invoiced(self):
        # Return quantity to buy to the producer and stock used to deliver the invoiced quantity
        if self.quantity_invoiced > DECIMAL_ZERO:
            if self.manage_replenishment:
                # if RepanierSettings.producer_pre_opening then the stock is the max available qty by the producer,
                # not into our stock
                quantity_for_customer = self.quantity_invoiced - self.add_2_stock
                if self.stock == DECIMAL_ZERO:
                    return self.quantity_invoiced, DECIMAL_ZERO, quantity_for_customer
                else:
                    delta = (quantity_for_customer - self.stock).quantize(FOUR_DECIMALS)
                    if delta <= DECIMAL_ZERO:
                        # i.e. quantity_for_customer <= self.stock
                        return self.add_2_stock, quantity_for_customer, quantity_for_customer
                    else:
                        return delta + self.add_2_stock, self.stock, quantity_for_customer
            else:
                return self.quantity_invoiced, DECIMAL_ZERO, self.quantity_invoiced
        return DECIMAL_ZERO, DECIMAL_ZERO, DECIMAL_ZERO

    def get_html_producer_qty_stock_invoiced(self):
        invoiced_qty, taken_from_stock, customer_qty = self.get_producer_qty_stock_invoiced()
        if invoiced_qty == DECIMAL_ZERO:
            if taken_from_stock == DECIMAL_ZERO:
                return EMPTY_STRING
            else:
                return mark_safe(_("Stock %(stock)s") % {'stock': number_format(taken_from_stock, 4)})
        else:
            if taken_from_stock == DECIMAL_ZERO:
                return mark_safe(_("<b>%(qty)s</b>") % {'qty': number_format(invoiced_qty, 4)})
            else:
                return mark_safe(_("<b>%(qty)s</b> + stock %(stock)s") % {'qty': number_format(invoiced_qty, 4),
                                                                          'stock': number_format(taken_from_stock, 4)})

    get_html_producer_qty_stock_invoiced.short_description = (_("Qty invoiced by the producer"))
    get_html_producer_qty_stock_invoiced.admin_order_field = 'quantity_invoiced'

    def get_producer_qty_invoiced(self):
        invoiced_qty, taken_from_stock, customer_qty = self.get_producer_qty_stock_invoiced()
        return invoiced_qty

    def get_producer_unit_price_invoiced(self):
        if self.producer_unit_price.amount > self.customer_unit_price.amount:
            return self.customer_unit_price
        else:
            return self.producer_unit_price

    def get_producer_row_price_invoiced(self):
        if self.manage_replenishment:
            if self.producer_unit_price.amount > self.customer_unit_price.amount:
                return RepanierMoney(
                    (self.customer_unit_price.amount + self.unit_deposit.amount) * self.get_producer_qty_invoiced(), 2)
            else:
                return RepanierMoney(
                    (self.producer_unit_price.amount + self.unit_deposit.amount) * self.get_producer_qty_invoiced(), 2)
        else:
            if self.producer_unit_price.amount > self.customer_unit_price.amount:
                return self.total_selling_with_tax
            else:
                return self.total_purchase_with_tax

    def get_html_producer_price_purchased(self):
        if self.manage_replenishment:
            invoiced_qty, taken_from_stock, customer_qty = self.get_producer_qty_stock_invoiced()
            price = RepanierMoney(
                ((self.producer_unit_price.amount + self.unit_deposit.amount) * invoiced_qty).quantize(TWO_DECIMALS))
        else:
            price = self.total_purchase_with_tax
        if price != DECIMAL_ZERO:
            return mark_safe(_("<b>%(price)s</b>") % {'price': price})
        return EMPTY_STRING

    get_html_producer_price_purchased.short_description = (_("Producer amount invoiced"))
    get_html_producer_price_purchased.admin_order_field = 'total_purchase_with_tax'

    def get_html_like(self, user):
        return mark_safe("<span class=\"glyphicon glyphicon-heart{}\" onclick=\"like_ajax({});return false;\"></span>".format(
            EMPTY_STRING if self.product.likes.filter(id=user.id).only("id").exists() else "-empty", self.id))

    @cached_property
    def get_not_permanences_dates(self):
        if self.not_permanences_dates:
            all_dates_str = sorted(
                list(filter(None, self.not_permanences_dates.split(settings.DJANGO_SETTINGS_DATES_SEPARATOR))))
            all_days = []
            for one_date_str in all_dates_str:
                one_date = parse_date(one_date_str)
                all_days.append(one_date.strftime(settings.DJANGO_SETTINGS_DAY_MONTH))
            return ", ".join(all_days)
        return EMPTY_STRING

    @cached_property
    def get_html_permanences_dates(self):
        if self.permanences_dates:
            all_dates_str = sorted(
                list(filter(None, self.permanences_dates.split(settings.DJANGO_SETTINGS_DATES_SEPARATOR))))
            all_days = []
            month_save = None
            for one_date_str in all_dates_str:
                one_date = parse_date(one_date_str)
                if month_save != one_date.month:
                    if month_save is not None:
                        new_line = "<br>"
                    else:
                        new_line = EMPTY_STRING
                    month_save = one_date.month
                else:
                    new_line = EMPTY_STRING
                all_days.append("{}{}".format(new_line, one_date.strftime(settings.DJANGO_SETTINGS_DAY_MONTH)))
            return mark_safe(", ".join(all_days))
        return EMPTY_STRING

    @cached_property
    def get_permanences_dates(self):
        if self.permanences_dates:
            all_dates_str = sorted(
                list(filter(None, self.permanences_dates.split(settings.DJANGO_SETTINGS_DATES_SEPARATOR))))
            all_days = []
            # https://stackoverflow.com/questions/3845423/remove-empty-strings-from-a-list-of-strings
            # -> list(filter(None, all_dates_str))
            for one_date_str in all_dates_str:
                one_date = parse_date(one_date_str)
                all_days.append("{}".format(one_date.strftime(settings.DJANGO_SETTINGS_DAY_MONTH)))
            return ", ".join(all_days)
        return EMPTY_STRING

    def get_order_name(self):
        qty_display = self.get_qty_display()
        if qty_display:
            return "{} {}".format(self.safe_translation_getter('long_name', any_language=True), qty_display)
        return "{}".format(self.safe_translation_getter('long_name', any_language=True))

    def get_qty_display(self):
        if self.is_box:
            # To avoid unicode error in email_offer.send_open_order
            qty_display = BOX_UNICODE
        else:
            if self.use_order_unit_converted:
                # The only conversion done in permanence concerns PRODUCT_ORDER_UNIT_PC_KG
                # so we are sure that 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_long_name(self, customer_price=True, is_html=False):
        if self.permanences_dates:
            new_line = "<br>" if is_html else "\n"
            return "{}{}{}".format(
                super(OfferItem, self).get_long_name(customer_price=customer_price),
                new_line,
                self.get_permanences_dates
            )
        else:
            return super(OfferItem, self).get_long_name(customer_price=customer_price)

    def get_html_long_name(self):
        return mark_safe(self.get_long_name(is_html=True))

    def get_long_name_with_producer(self, is_html=False):
        if self.permanences_dates:
            return "{}, {}".format(
                self.producer.short_profile_name,
                self.get_long_name(customer_price=True, is_html=is_html)
            )
        else:
            return super(OfferItem, self).get_long_name_with_producer()

    def get_html_long_name_with_producer(self):
        return mark_safe(self.get_long_name_with_producer(is_html=True))

    get_html_long_name_with_producer.short_description = (_("Offer items"))
    get_html_long_name_with_producer.allow_tags = True
    get_html_long_name_with_producer.admin_order_field = 'translations__long_name'

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

    class Meta:
        verbose_name = _("Offer item")
        verbose_name_plural = _("Offer items")
        unique_together = ("permanence", "product", "permanences_dates")
Example #30
0
class OfferItem(Item):
    translations = TranslatedFields(
        long_name=models.CharField(_("long_name"),
                                   max_length=100,
                                   default=EMPTY_STRING,
                                   blank=True,
                                   null=True),
        cache_part_a=HTMLField(default=EMPTY_STRING, blank=True),
        cache_part_b=HTMLField(default=EMPTY_STRING, blank=True),
        order_sort_order=models.IntegerField(
            _("customer sort order for optimization"),
            default=0,
            db_index=True),
        preparation_sort_order=models.IntegerField(
            _("preparation sort order for optimization"),
            default=0,
            db_index=True),
        producer_sort_order=models.IntegerField(
            _("producer sort order for optimization"),
            default=0,
            db_index=True))
    permanence = models.ForeignKey(
        'Permanence',
        verbose_name=REPANIER_SETTINGS_PERMANENCE_NAME,
        on_delete=models.PROTECT,
        db_index=True)
    product = models.ForeignKey('Product',
                                verbose_name=_("product"),
                                on_delete=models.PROTECT)
    # is a box or a contract content
    is_box_content = models.BooleanField(_("is a box content"), default=False)

    producer_price_are_wo_vat = models.BooleanField(
        _("producer price are wo vat"), default=False)
    price_list_multiplier = models.DecimalField(
        _("price_list_multiplier"),
        help_text=
        _("This multiplier is applied to each price automatically imported/pushed."
          ),
        default=DECIMAL_ZERO,
        max_digits=5,
        decimal_places=4,
        validators=[MinValueValidator(0)])
    is_resale_price_fixed = models.BooleanField(
        _("the resale price is set by the producer"), default=False)

    # Calculated with Purchase
    total_purchase_with_tax = ModelMoneyField(
        _("producer amount invoiced"),
        help_text=_('Total purchase amount vat included'),
        default=DECIMAL_ZERO,
        max_digits=8,
        decimal_places=2)
    # Calculated with Purchase
    total_selling_with_tax = ModelMoneyField(
        _("customer amount invoiced"),
        help_text=_('Total selling amount vat included'),
        default=DECIMAL_ZERO,
        max_digits=8,
        decimal_places=2)

    # Calculated with Purchase.
    # If Permanence.status < SEND this is the order quantity
    # During sending the orders to the producer this become the invoiced quantity
    # via tools.recalculate_order_amount(..., send_to_producer=True)
    quantity_invoiced = models.DecimalField(
        _("quantity invoiced"),
        help_text=_('quantity invoiced to our customer'),
        max_digits=9,
        decimal_places=4,
        default=DECIMAL_ZERO)

    may_order = models.BooleanField(_("may_order"), default=True)

    manage_replenishment = models.BooleanField(_("manage stock"),
                                               default=False)
    manage_production = models.BooleanField(_("manage production"),
                                            default=False)
    producer_pre_opening = models.BooleanField(_("producer pre-opening"),
                                               default=False)

    add_2_stock = models.DecimalField(_("Add 2 stock"),
                                      default=DECIMAL_ZERO,
                                      max_digits=9,
                                      decimal_places=4)
    new_stock = models.DecimalField(_("Final stock"),
                                    default=None,
                                    max_digits=9,
                                    decimal_places=3,
                                    null=True)

    def get_vat_level(self):
        return self.get_vat_level_display()

    get_vat_level.short_description = EMPTY_STRING
    get_vat_level.allow_tags = False
    get_vat_level.admin_order_field = 'vat_level'

    def get_producer_qty_stock_invoiced(self):
        # Return quantity to buy to the producer and stock used to deliver the invoiced quantity
        if self.quantity_invoiced > DECIMAL_ZERO:
            if self.manage_replenishment:
                # if RepanierSettings.producer_pre_opening then the stock is the max available qty by the producer,
                # not into our stock
                quantity_for_customer = self.quantity_invoiced - self.add_2_stock
                if self.stock == DECIMAL_ZERO:
                    return self.quantity_invoiced, DECIMAL_ZERO, quantity_for_customer
                else:
                    delta = (quantity_for_customer -
                             self.stock).quantize(FOUR_DECIMALS)
                    if delta <= DECIMAL_ZERO:
                        # i.e. quantity_for_customer <= self.stock
                        return self.add_2_stock, quantity_for_customer, quantity_for_customer
                    else:
                        return delta + self.add_2_stock, self.stock, quantity_for_customer
            else:
                return self.quantity_invoiced, DECIMAL_ZERO, self.quantity_invoiced
        return DECIMAL_ZERO, DECIMAL_ZERO, DECIMAL_ZERO

    def get_html_producer_qty_stock_invoiced(self):
        invoiced_qty, taken_from_stock, customer_qty = self.get_producer_qty_stock_invoiced(
        )
        if invoiced_qty == DECIMAL_ZERO:
            if taken_from_stock == DECIMAL_ZERO:
                return EMPTY_STRING
            else:
                return _("stock %(stock)s") % {
                    'stock': number_format(taken_from_stock, 4)
                }
        else:
            if taken_from_stock == DECIMAL_ZERO:
                return _("<b>%(qty)s</b>") % {
                    'qty': number_format(invoiced_qty, 4)
                }
            else:
                return _("<b>%(qty)s</b> + stock %(stock)s") % {
                    'qty': number_format(invoiced_qty, 4),
                    'stock': number_format(taken_from_stock, 4)
                }

    get_html_producer_qty_stock_invoiced.short_description = (
        _("quantity invoiced by the producer"))
    get_html_producer_qty_stock_invoiced.allow_tags = True
    get_html_producer_qty_stock_invoiced.admin_order_field = 'quantity_invoiced'

    def get_producer_qty_invoiced(self):
        invoiced_qty, taken_from_stock, customer_qty = self.get_producer_qty_stock_invoiced(
        )
        return invoiced_qty

    def get_producer_unit_price_invoiced(self):
        if self.producer_unit_price.amount > self.customer_unit_price.amount:
            return self.customer_unit_price
        else:
            return self.producer_unit_price

    def get_producer_row_price_invoiced(self):
        if self.manage_replenishment:
            if self.producer_unit_price.amount > self.customer_unit_price.amount:
                return RepanierMoney((self.customer_unit_price.amount +
                                      self.unit_deposit.amount) *
                                     self.get_producer_qty_invoiced(), 2)
            else:
                return RepanierMoney((self.producer_unit_price.amount +
                                      self.unit_deposit.amount) *
                                     self.get_producer_qty_invoiced(), 2)
        else:
            if self.producer_unit_price.amount > self.customer_unit_price.amount:
                return self.total_selling_with_tax
            else:
                return self.total_purchase_with_tax

    def get_html_producer_price_purchased(self):
        if self.manage_replenishment:
            invoiced_qty, taken_from_stock, customer_qty = self.get_producer_qty_stock_invoiced(
            )
            price = RepanierMoney(
                ((self.producer_unit_price.amount + self.unit_deposit.amount) *
                 invoiced_qty).quantize(TWO_DECIMALS))
        else:
            price = self.total_purchase_with_tax
        # price = self.total_purchase_with_tax
        if price != DECIMAL_ZERO:
            return _("<b>%(price)s</b>") % {'price': price}
        return EMPTY_STRING

    get_html_producer_price_purchased.short_description = (
        _("producer amount invoiced"))
    get_html_producer_price_purchased.allow_tags = True
    get_html_producer_price_purchased.admin_order_field = 'total_purchase_with_tax'

    def get_like(self, user):
        return '<span class="glyphicon glyphicon-heart%s" onclick="like_ajax(%d);return false;"></span>' % (
            EMPTY_STRING if self.product.likes.filter(
                id=user.id).only("id").exists() else "-empty", self.id)

    def __str__(self):
        return super(OfferItem, self).display()
        # return '%s, %s' % (self.producer.short_profile_name, self.get_long_name())

    class Meta:
        verbose_name = _("offer's item")
        verbose_name_plural = _("offer's items")
        unique_together = (
            "permanence",
            "product",
        )
        index_together = [["id", "order_unit"]]