# -*- coding: utf-8
from __future__ import unicode_literals
import copy
from django.conf import settings
from django.core.validators import MinValueValidator
from django.db import models
from django.db.models import F
from django.db.models.signals import pre_save, post_init
from django.dispatch import receiver
from django.utils.encoding import python_2_unicode_compatible
from django.utils.formats import number_format
from django.utils.translation import ugettext_lazy as _
from djangocms_text_ckeditor.fields import HTMLField
from parler.models import TranslatableModel, TranslatedFields
import invoice
from repanier.apps import REPANIER_SETTINGS_PERMANENCE_NAME
from repanier.const import *
from repanier.fields.RepanierMoneyField import ModelMoneyField, RepanierMoney
from repanier.picture.const import SIZE_M
from repanier.picture.fields import AjaxPictureField
from repanier.tools import get_display
@python_2_unicode_compatible
class OfferItem(TranslatableModel):
translations = TranslatedFields(
long_name=models.CharField(_("long_name"), max_length=100,
default=EMPTY_STRING, blank=True, null=True),
cache_part_a=HTMLField(configuration='CKEDITOR_SETTINGS_MODEL2', default=EMPTY_STRING, blank=True),
cache_part_b=HTMLField(configuration='CKEDITOR_SETTINGS_MODEL2', default=EMPTY_STRING, blank=True),
cache_part_e=HTMLField(configuration='CKEDITOR_SETTINGS_MODEL2', 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
)
# Important : Check select_related
product = models.ForeignKey(
'Product', verbose_name=_("product"), on_delete=models.PROTECT)
picture2 = AjaxPictureField(
verbose_name=_("picture"),
null=True, blank=True,
upload_to="product", size=SIZE_M)
reference = models.CharField(
_("reference"), max_length=36, blank=True, null=True)
department_for_customer = models.ForeignKey(
'LUT_DepartmentForCustomer',
verbose_name=_("department_for_customer"), blank=True, null=True, on_delete=models.PROTECT)
producer = models.ForeignKey(
'Producer', verbose_name=_("producer"), on_delete=models.PROTECT)
order_unit = models.CharField(
max_length=3,
choices=LUT_PRODUCT_ORDER_UNIT,
default=PRODUCT_ORDER_UNIT_PC,
verbose_name=_("order unit"))
wrapped = models.BooleanField(_('Individually wrapped by the producer'),
default=False)
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)])
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'))
producer_unit_price = ModelMoneyField(
_("producer unit price"),
help_text=_('producer unit price (/piece or /kg or /l), including vat, without deposit'),
default=DECIMAL_ZERO, max_digits=8, decimal_places=2)
customer_unit_price = ModelMoneyField(
_("customer unit price"),
help_text=_('(/piece or /kg or /l), including vat, without deposit'),
default=DECIMAL_ZERO, max_digits=8, decimal_places=2)
producer_price_are_wo_vat = models.BooleanField(_("producer price are wo vat"), default=False)
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 unit price'),
default=DECIMAL_ZERO, max_digits=8, decimal_places=2)
vat_level = models.CharField(
max_length=3,
choices=LUT_ALL_VAT, # settings.LUT_VAT,
default=settings.DICT_VAT_DEFAULT,
verbose_name=_("tax"))
# 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)
is_box = models.BooleanField(_("is_box"), default=False)
is_box_content = models.BooleanField(_("is_box_content"), 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)
limit_order_quantity_to_stock = models.BooleanField(_("limit maximum order qty of the group to stock qty"),
default=False)
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)
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)
stock = models.DecimalField(
_("Current stock"),
default=DECIMAL_ZERO, max_digits=9, decimal_places=3,
validators=[MinValueValidator(0)])
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)
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_ZERO, max_digits=6, decimal_places=3)
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_ZERO, max_digits=6, decimal_places=3)
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=DECIMAL_ZERO, max_digits=6, decimal_places=3)
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)
def get_producer(self):
return self.producer.short_profile_name
get_producer.short_description = (_("producers"))
get_producer.allow_tags = False
def get_product(self):
return self.product.long_name
get_product.short_description = (_("products"))
get_product.allow_tags = False
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 _("%(qty)s") % {'qty': number_format(invoiced_qty, 4)}
else:
return _("%(qty)s + 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 _("%(price)s") % {'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'
@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
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
@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 get_like(self, user):
return '' % (
EMPTY_STRING if self.product.likes.filter(id=user.id).only("id").exists() else "-empty", self.id)
def get_qty_display(self, is_quantity_invoiced=False, box_unicode=BOX_UNICODE):
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 = get_display(
qty=1,
order_average_weight=self.order_average_weight,
order_unit=PRODUCT_ORDER_UNIT_KG,
for_customer=False,
without_price_display=True
)
else:
qty_display = get_display(
qty=1,
order_average_weight=self.order_average_weight,
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, box_unicode=BOX_UNICODE):
qty_display = self.get_qty_display(is_quantity_invoiced, box_unicode)
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_order_name(self, is_quantity_invoiced=False, box_unicode=BOX_UNICODE):
return '%s %s' % (self.long_name, self.get_qty_display(is_quantity_invoiced, box_unicode))
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, box_unicode=BOX_UNICODE):
return '%s %s' % (self.long_name, self.get_qty_and_price_display(is_quantity_invoiced, customer_price, box_unicode))
get_long_name.short_description = (_("long_name"))
get_long_name.allow_tags = False
get_long_name.admin_order_field = 'translations__long_name'
def __str__(self):
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"]
]
@receiver(post_init, sender=OfferItem)
def offer_item_post_init(sender, **kwargs):
offer_item = kwargs["instance"]
if offer_item.id is None:
offer_item.previous_add_2_stock = DECIMAL_ZERO
offer_item.previous_producer_unit_price = DECIMAL_ZERO
offer_item.previous_unit_deposit = DECIMAL_ZERO
else:
offer_item.previous_add_2_stock = offer_item.add_2_stock
offer_item.previous_producer_unit_price = offer_item.producer_unit_price.amount
offer_item.previous_unit_deposit = offer_item.unit_deposit.amount
@receiver(pre_save, sender=OfferItem)
def offer_item_pre_save(sender, **kwargs):
offer_item = kwargs["instance"]
if offer_item.manage_replenishment:
if (offer_item.previous_add_2_stock != offer_item.add_2_stock or
offer_item.previous_producer_unit_price != offer_item.producer_unit_price.amount or
offer_item.previous_unit_deposit != offer_item.unit_deposit.amount
):
previous_producer_price = ((offer_item.previous_producer_unit_price +
offer_item.previous_unit_deposit) * offer_item.previous_add_2_stock)
producer_price = ((offer_item.producer_unit_price.amount +
offer_item.unit_deposit.amount) * offer_item.add_2_stock)
delta_add_2_stock_invoiced = offer_item.add_2_stock - offer_item.previous_add_2_stock
delta_producer_price = producer_price - previous_producer_price
invoice.ProducerInvoice.objects.filter(
producer_id=offer_item.producer_id,
permanence_id=offer_item.permanence_id
).update(
total_price_with_tax=F('total_price_with_tax') +
delta_producer_price
)
offer_item.quantity_invoiced += delta_add_2_stock_invoiced
offer_item.total_purchase_with_tax.amount += delta_producer_price
# Do not do it twice
offer_item.previous_add_2_stock = offer_item.add_2_stock
offer_item.previous_producer_unit_price = offer_item.producer_unit_price.amount
offer_item.previous_unit_deposit = offer_item.unit_deposit.amount
@python_2_unicode_compatible
class OfferItemSend(OfferItem):
def __str__(self):
return '%s, %s' % (self.producer.short_profile_name, self.get_long_name(is_quantity_invoiced=True))
class Meta:
proxy = True
verbose_name = _("offer's item")
verbose_name_plural = _("offer's items")
@receiver(post_init, sender=OfferItemSend)
def offer_item_send_post_init(sender, **kwargs):
offer_item_post_init(sender, **kwargs)
@receiver(pre_save, sender=OfferItemSend)
def offer_item_send_pre_save(sender, **kwargs):
offer_item_pre_save(sender, **kwargs)
@python_2_unicode_compatible
class OfferItemClosed(OfferItem):
def __str__(self):
return '%s, %s' % (self.producer.short_profile_name, self.get_long_name(is_quantity_invoiced=True))
class Meta:
proxy = True
verbose_name = _("offer's item")
verbose_name_plural = _("offer's items")
@receiver(post_init, sender=OfferItemClosed)
def offer_item_closed_post_init(sender, **kwargs):
offer_item_post_init(sender, **kwargs)
@receiver(pre_save, sender=OfferItemClosed)
def offer_item_closed_pre_save(sender, **kwargs):
offer_item_pre_save(sender, **kwargs)