class LUT_ProductionMode(MPTTModel, TranslatableModel): parent = TreeForeignKey('self', null=True, blank=True, related_name='children') translations = TranslatedFields( short_name=models.CharField(_("Short name"), max_length=50, default=EMPTY_STRING), description=HTMLField(_("Description"), configuration='CKEDITOR_SETTINGS_MODEL2', blank=True, default=EMPTY_STRING), ) picture2 = AjaxPictureField(verbose_name=_("Picture"), null=True, blank=True, upload_to="label", size=SIZE_XS) is_active = models.BooleanField(_("Active"), default=True) objects = LUT_ProductionModeManager() def __str__(self): # return self.short_name return self.safe_translation_getter('short_name', any_language=True, default=EMPTY_STRING) class Meta: verbose_name = _("Production mode") verbose_name_plural = _("Production modes")
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
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
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"], ]
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\"> {}</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'), ]
class Contract(TranslatableModel): translations = TranslatedFields( long_name=models.CharField(_("Long name"), max_length=100, default=EMPTY_STRING, blank=True, null=True), offer_description=HTMLField( _("Offer description"), configuration='CKEDITOR_SETTINGS_MODEL2', help_text= _("This message is send by mail to all customers when opening the order or on top " ), blank=True, default=EMPTY_STRING), ) first_permanence_date = models.DateField( verbose_name=_("First permanence date"), db_index=True) last_permanence_date = models.DateField( verbose_name=_("Last permanence date"), null=True, blank=True, db_index=True) recurrences = RecurrenceField() producers = models.ManyToManyField('Producer', verbose_name=_("Producers"), related_name='contracts', blank=True) customers = models.ManyToManyField('Customer', verbose_name=_("Customers"), blank=True) picture2 = AjaxPictureField(verbose_name=_("Picture"), null=True, blank=True, upload_to="contract", size=SIZE_L) status = models.CharField(max_length=3, choices=LUT_CONTRACT_STATUS, default=CONTRACT_IN_WRITING, verbose_name=_("status")) highest_status = models.CharField(max_length=3, choices=LUT_CONTRACT_STATUS, default=CONTRACT_IN_WRITING, verbose_name=_("Highest status")) all_dates = [] dates_display = EMPTY_STRING @cached_property def get_producers(self): if self.status == CONTRACT_IN_WRITING: if len(self.producers.all()) > 0: changelist_url = urlresolvers.reverse( 'admin:repanier_product_changelist', ) link = [] for p in self.producers.all(): link.append( '<a href="%s?producer=%d&commitment=%d"> %s</a>' % (changelist_url, p.id, self.id, p.short_profile_name.replace(" ", " "))) return mark_safe('<div class="wrap-text">%s</div>' % ", ".join(link)) else: return mark_safe('<div class="wrap-text">%s</div>' % _("No offer")) else: return mark_safe('<div class="wrap-text">%s</div>' % ", ".join([ p.short_profile_name.replace(" ", " ") for p in self.producers.all() ])) get_producers.short_description = (_("Offers from")) @cached_property def get_dates(self): return self.dates_display get_dates.short_description = (_("Permanences")) get_dates.allow_tags = True def get_full_status_display(self): return self.get_status_display() get_full_status_display.short_description = (_("Contract status")) get_full_status_display.allow_tags = True def get_contract_admin_display(self): return "%s (%s)" % (self.long_name, self.dates_display) get_contract_admin_display.short_description = _("Commitments") get_contract_admin_display.allow_tags = False def __str__(self): return '%s' % self.long_name class Meta: verbose_name = _("Commitment") verbose_name_plural = _("Commitments")
class Customer(models.Model): user = models.OneToOneField(settings.AUTH_USER_MODEL, db_index=True) login_attempt_counter = models.DecimalField(_("Login attempt counter"), default=DECIMAL_ZERO, max_digits=2, decimal_places=0) short_basket_name = models.CharField(_("Short name"), max_length=25, null=False, default=EMPTY_STRING, db_index=True, unique=True) long_basket_name = models.CharField(_("Long name"), max_length=100, null=True, default=EMPTY_STRING) email2 = models.EmailField(_("Secondary email"), null=True, blank=True, default=EMPTY_STRING) language = models.CharField(max_length=5, choices=settings.LANGUAGES, default=settings.LANGUAGE_CODE, verbose_name=_("Language")) picture = AjaxPictureField(verbose_name=_("Picture"), null=True, blank=True, upload_to="customer", size=SIZE_S) phone1 = models.CharField(_("Phone1"), max_length=25, null=True, blank=True, default=EMPTY_STRING) phone2 = models.CharField(_("Phone2"), max_length=25, null=True, blank=True, default=EMPTY_STRING) bank_account1 = models.CharField(_("Main bank account"), max_length=100, null=True, blank=True, default=EMPTY_STRING) bank_account2 = models.CharField(_("Secondary bank account"), max_length=100, null=True, blank=True, default=EMPTY_STRING) vat_id = models.CharField(_("VAT id"), max_length=20, null=True, blank=True, default=EMPTY_STRING) address = models.TextField(_("Address"), null=True, blank=True, default=EMPTY_STRING) city = models.CharField(_("City"), max_length=50, null=True, blank=True, default=EMPTY_STRING) about_me = models.TextField(_("About me"), null=True, blank=True, default=EMPTY_STRING) memo = models.TextField(_("Memo"), null=True, blank=True, default=EMPTY_STRING) show_mails_to_members = models.BooleanField( _("Show my mail to other members"), default=False) show_phones_to_members = models.BooleanField( _("Show my phone to other members"), default=False) membership_fee_valid_until = models.DateField( _("Membership fee valid until"), default=datetime.date.today) # If this customer is member of a closed group, the customer.price_list_multiplier is not used # Invoices are sent to the consumer responsible of the group who is # also responsible for collecting the payments. # The LUT_DeliveryPoint.price_list_multiplier will be used when invoicing the consumer responsible # At this stage, the link between the customer invoice and this customer responsible is made with # CustomerInvoice.customer_charged price_list_multiplier = models.DecimalField( _("Coefficient applied to the producer tariff to calculate the consumer tariff" ), help_text= _("This multiplier is applied to each product ordered by this customer." ), default=DECIMAL_ONE, max_digits=5, decimal_places=4, blank=True, validators=[MinValueValidator(0)]) password_reset_on = models.DateTimeField(_("Password reset on"), null=True, blank=True, default=None) date_balance = models.DateField(_("Date balance"), default=datetime.date.today) balance = ModelMoneyField(_("Balance"), max_digits=8, decimal_places=2, default=DECIMAL_ZERO) # The initial balance is needed to compute the invoice control list initial_balance = ModelMoneyField(_("Initial balance"), max_digits=8, decimal_places=2, default=DECIMAL_ZERO) represent_this_buyinggroup = models.BooleanField( _("Represent_this_buyinggroup"), default=False) delivery_point = models.ForeignKey('LUT_DeliveryPoint', verbose_name=_("Delivery point"), blank=True, null=True, default=None) is_active = models.BooleanField(_("Active"), default=True) as_staff = models.ForeignKey('Staff', blank=True, null=True, default=None) # This indicate that the user record data have been replaced with anonymous data in application of GDPR is_anonymized = models.BooleanField(default=False) is_group = models.BooleanField(_("Group"), default=False) may_order = models.BooleanField(_("May order"), default=True) zero_waste = models.BooleanField(_("Zero waste"), default=False) valid_email = models.NullBooleanField(_("Valid email"), default=None) subscribe_to_email = models.BooleanField( _("Agree to receive unsolicited mails from this site"), default=True) preparation_order = models.IntegerField(null=True, blank=True, default=0) @classmethod def get_or_create_group(cls): customer_buyinggroup = Customer.objects.filter( represent_this_buyinggroup=True).order_by('?').first() if customer_buyinggroup is None: long_name = settings.REPANIER_SETTINGS_GROUP_NAME short_name = long_name[:25] user = User.objects.filter( username=short_name).order_by('?').first() if user is None: user = User.objects.create_user( username=short_name, email="{}{}".format( long_name, settings.REPANIER_SETTINGS_ALLOWED_MAIL_EXTENSION), password=uuid.uuid1().hex, first_name=EMPTY_STRING, last_name=long_name) customer_buyinggroup = Customer.objects.create( user=user, short_basket_name=short_name, long_basket_name=long_name, phone1=settings.REPANIER_SETTINGS_COORDINATOR_PHONE, represent_this_buyinggroup=True) return customer_buyinggroup @classmethod def get_or_create_the_very_first_customer(cls): very_first_customer = Customer.objects.filter( represent_this_buyinggroup=False, is_active=True).order_by('id').first() if very_first_customer is None: long_name = settings.REPANIER_SETTINGS_COORDINATOR_NAME # short_name is the first word of long_name, limited to max. 25 characters short_name = long_name.split(None, 1)[0][:25] user = User.objects.filter( username=short_name).order_by('?').first() if user is None: user = User.objects.create_user( username=short_name, email=settings.REPANIER_SETTINGS_COORDINATOR_EMAIL, password=uuid.uuid1().hex, first_name=EMPTY_STRING, last_name=long_name) very_first_customer = Customer.objects.create( user=user, short_basket_name=short_name, long_basket_name=long_name, phone1=settings.REPANIER_SETTINGS_COORDINATOR_PHONE, represent_this_buyinggroup=False) return very_first_customer def get_admin_date_balance(self): return timezone.now().date().strftime(settings.DJANGO_SETTINGS_DATE) get_admin_date_balance.short_description = (_("Date balance")) get_admin_date_balance.allow_tags = False def get_admin_date_joined(self): # New customer have no user during import of customers in admin.customer.CustomerResource try: return self.user.date_joined.strftime( settings.DJANGO_SETTINGS_DATE) except User.DoesNotExist: # RelatedObjectDoesNotExist return EMPTY_STRING get_admin_date_joined.short_description = _("Date joined") get_admin_date_joined.allow_tags = False def get_admin_balance(self): return self.balance + self.get_bank_not_invoiced( ) - self.get_order_not_invoiced() get_admin_balance.short_description = (_("Balance")) get_admin_balance.allow_tags = False def get_order_not_invoiced(self): if settings.REPANIER_SETTINGS_MANAGE_ACCOUNTING: result_set = CustomerInvoice.objects.filter( customer_id=self.id, status__gte=PERMANENCE_OPENED, status__lte=PERMANENCE_SEND, customer_charged_id=self.id).order_by('?').aggregate( Sum('total_price_with_tax'), Sum('delta_price_with_tax'), Sum('delta_transport')) if result_set["total_price_with_tax__sum"] is not None: order_not_invoiced = RepanierMoney( result_set["total_price_with_tax__sum"]) else: order_not_invoiced = REPANIER_MONEY_ZERO if result_set["delta_price_with_tax__sum"] is not None: order_not_invoiced += RepanierMoney( result_set["delta_price_with_tax__sum"]) if result_set["delta_transport__sum"] is not None: order_not_invoiced += RepanierMoney( result_set["delta_transport__sum"]) else: order_not_invoiced = REPANIER_MONEY_ZERO return order_not_invoiced def get_bank_not_invoiced(self): if settings.REPANIER_SETTINGS_MANAGE_ACCOUNTING: result_set = BankAccount.objects.filter( customer_id=self.id, customer_invoice__isnull=True).order_by('?').aggregate( Sum('bank_amount_in'), Sum('bank_amount_out')) if result_set["bank_amount_in__sum"] is not None: bank_in = RepanierMoney(result_set["bank_amount_in__sum"]) else: bank_in = REPANIER_MONEY_ZERO if result_set["bank_amount_out__sum"] is not None: bank_out = RepanierMoney(result_set["bank_amount_out__sum"]) else: bank_out = REPANIER_MONEY_ZERO bank_not_invoiced = bank_in - bank_out else: bank_not_invoiced = REPANIER_MONEY_ZERO return bank_not_invoiced def get_balance(self): last_customer_invoice = CustomerInvoice.objects.filter( customer_id=self.id, invoice_sort_order__isnull=False).order_by('?') balance = self.get_admin_balance() if last_customer_invoice.exists(): if balance.amount >= 30: return '<a href="' + urlresolvers.reverse( 'customer_invoice_view', args=(0, )) + '?customer=' + str( self.id) + '" class="btn" target="_blank" >' + ( "<span style=\"color:#32CD32\">{}</span>".format( balance)) + '</a>' elif balance.amount >= -10: return '<a href="' + urlresolvers.reverse( 'customer_invoice_view', args=(0, )) + '?customer=' + str( self.id) + '" class="btn" target="_blank" >' + ( "<span style=\"color:#696969\">{}</span>".format( balance)) + '</a>' else: return '<a href="' + urlresolvers.reverse( 'customer_invoice_view', args=(0, )) + '?customer=' + str( self.id) + '" class="btn" target="_blank" >' + ( "<span style=\"color:red\">{}</span>".format( balance)) + '</a>' else: if balance.amount >= 30: return "<span style=\"color:#32CD32\">{}</span>".format( balance) elif balance.amount >= -10: return "<span style=\"color:#696969\">{}</span>".format( balance) else: return "<span style=\"color:red\">{}</span>".format(balance) get_balance.short_description = _("Balance") get_balance.allow_tags = True get_balance.admin_order_field = 'balance' def get_html_on_hold_movement(self, bank_not_invoiced=None, order_not_invoiced=None, total_price_with_tax=REPANIER_MONEY_ZERO): if settings.REPANIER_SETTINGS_MANAGE_ACCOUNTING: bank_not_invoiced = bank_not_invoiced if bank_not_invoiced is not None else self.get_bank_not_invoiced( ) order_not_invoiced = order_not_invoiced if order_not_invoiced is not None else self.get_order_not_invoiced( ) other_order_not_invoiced = order_not_invoiced - total_price_with_tax else: bank_not_invoiced = REPANIER_MONEY_ZERO other_order_not_invoiced = REPANIER_MONEY_ZERO if other_order_not_invoiced.amount != DECIMAL_ZERO or bank_not_invoiced.amount != DECIMAL_ZERO: if other_order_not_invoiced.amount != DECIMAL_ZERO: if bank_not_invoiced.amount == DECIMAL_ZERO: customer_on_hold_movement = \ _('This balance does not take account of any unbilled sales %(other_order)s.') % { 'other_order': other_order_not_invoiced } else: customer_on_hold_movement = \ _( 'This balance does not take account of any unrecognized payments %(bank)s and any unbilled order %(other_order)s.') \ % { 'bank': bank_not_invoiced, 'other_order': other_order_not_invoiced } else: customer_on_hold_movement = \ _( 'This balance does not take account of any unrecognized payments %(bank)s.') % { 'bank': bank_not_invoiced } customer_on_hold_movement = mark_safe(customer_on_hold_movement) else: customer_on_hold_movement = EMPTY_STRING return customer_on_hold_movement def get_last_membership_fee(self): from repanier.models.purchase import Purchase last_membership_fee = Purchase.objects.filter( customer_id=self.id, offer_item__order_unit=PRODUCT_ORDER_UNIT_MEMBERSHIP_FEE).order_by( "-id") if last_membership_fee.exists(): return last_membership_fee.first().selling_price get_last_membership_fee.short_description = _("Last membership fee") get_last_membership_fee.allow_tags = False def last_membership_fee_date(self): from repanier.models.purchase import Purchase last_membership_fee = Purchase.objects.filter( customer_id=self.id, offer_item__order_unit=PRODUCT_ORDER_UNIT_MEMBERSHIP_FEE).order_by( "-id").prefetch_related("customer_invoice") if last_membership_fee.exists(): return last_membership_fee.first().customer_invoice.date_balance last_membership_fee_date.short_description = _("Last membership fee date") last_membership_fee_date.allow_tags = False def get_last_membership_fee_date(self): # Format it for the admin # Don't format it form import/export last_membership_fee_date = self.last_membership_fee_date() if last_membership_fee_date is not None: return last_membership_fee_date.strftime( settings.DJANGO_SETTINGS_DATE) return EMPTY_STRING get_last_membership_fee_date.short_description = _( "Last membership fee date") get_last_membership_fee_date.allow_tags = False def get_participation(self): now = timezone.now() return PermanenceBoard.objects.filter( customer_id=self.id, permanence_date__gte=now - datetime.timedelta(days=ONE_YEAR), permanence_date__lt=now, permanence_role__is_counted_as_participation=True).order_by( '?').count() get_participation.short_description = _("Participation") get_participation.allow_tags = False def get_purchase(self): now = timezone.now() # Do not count invoice having only products free of charge return CustomerInvoice.objects.filter( customer_id=self.id, total_price_with_tax__gt=DECIMAL_ZERO, date_balance__gte=now - datetime.timedelta(ONE_YEAR)).count() get_purchase.short_description = _("Purchase") get_purchase.allow_tags = False def my_order_confirmation_email_send_to(self): if self.email2: to_email = (self.user.email, self.email2) else: to_email = (self.user.email, ) sent_to = ", ".join(to_email) if to_email is not None else EMPTY_STRING if settings.REPANIER_SETTINGS_CUSTOMER_MUST_CONFIRM_ORDER: msg_confirmation = _( "Order confirmed. An email containing this order summary has been sent to {}." ).format(sent_to) else: msg_confirmation = _( "An email containing this order summary has been sent to {}." ).format(sent_to) return msg_confirmation @property def who_is_who_display(self): return self.picture or self.show_mails_to_members or self.show_phones_to_members \ or (self.about_me is not None and len(self.about_me.strip()) > 1) def get_html_unsubscribe_mail_footer(self): return mark_safe("<br><br><hr/><br><a href=\"{}\">{}</a>".format( self._get_unsubscribe_link(), _("Stop receiving unsolicited mails from {}").format( self._get_unsubscribe_site()))) def _get_unsubscribe_link(self): customer_id, token = self.make_token().split(":", 1) return "https://{}{}".format( self._get_unsubscribe_site(), reverse('unsubscribe_view', kwargs={ 'customer_id': customer_id, 'token': token, })) def _get_unsubscribe_site(self): return settings.ALLOWED_HOSTS[0] def make_token(self): return TimestampSigner().sign(self.id) def check_token(self, token): try: key = "{}:{}".format(self.id, token) TimestampSigner().unsign(key, max_age=60 * 60 * 48) # Valid for 2 days except (BadSignature, SignatureExpired): return False return True def anonymize(self, also_group=False): if self.represent_this_buyinggroup: if not also_group: return self.short_basket_name = "{}-{}".format(_("GROUP"), self.id).lower() self.long_basket_name = "{} {}".format(_("Group"), self.id) else: self.short_basket_name = "{}-{}".format(_("BASKET"), self.id).lower() self.long_basket_name = "{} {}".format(_("Family"), self.id) self.email2 = EMPTY_STRING self.picture = EMPTY_STRING self.phone1 = EMPTY_STRING self.phone2 = EMPTY_STRING self.bank_account1 = EMPTY_STRING self.bank_account1 = EMPTY_STRING self.vat_id = EMPTY_STRING self.address = EMPTY_STRING self.about_me = EMPTY_STRING self.memo = EMPTY_STRING self.user.username = self.user.email = "{}@repanier.be".format( self.short_basket_name) self.user.first_name = EMPTY_STRING self.user.last_name = self.short_basket_name self.user.set_password(None) self.user.save() self.is_anonymized = True self.valid_email = False self.subscribe_to_email = False self.show_mails_to_members = False self.show_phones_to_members = False self.save() def __str__(self): if self.delivery_point is None: return self.short_basket_name else: return "{} - {}".format(self.delivery_point, self.short_basket_name) class Meta: verbose_name = _("Customer") verbose_name_plural = _("Customers") ordering = ( "-represent_this_buyinggroup", "short_basket_name", ) indexes = [ models.Index( fields=["-represent_this_buyinggroup", "short_basket_name"], name='customer_order_idx'), ]
class Contract(TranslatableModel): translations = TranslatedFields(long_name=models.CharField( _("Long name"), max_length=100, default=EMPTY_STRING, blank=True, null=True), ) first_permanence_date = models.DateField( verbose_name=_("Date of the first permanence"), db_index=True) last_permanence_date = models.DateField( verbose_name=_("Last permanence date"), null=True, blank=True, db_index=True) recurrences = RecurrenceField() producers = models.ManyToManyField('Producer', verbose_name=_("Producers"), related_name='contracts', blank=True) customers = models.ManyToManyField('Customer', verbose_name=_("Customers"), blank=True) picture2 = AjaxPictureField(verbose_name=_("Picture"), null=True, blank=True, upload_to="contract", size=SIZE_L) is_active = models.BooleanField(_("Active"), default=True) permanences_dates = models.TextField(null=True, blank=True, default=None) @transaction.atomic() def get_or_create_offer_item(self, permanence, reset_add_2_stock=False): from repanier.models.offeritem import OfferItem, OfferItemWoReceiver # In case of contract # -1, generate eventually several offer's items if dates are flexible # -2, boxes may not be used in contracts OfferItemWoReceiver.objects.filter(permanence_id=permanence.id).update( may_order=False) for contract_content in ContractContent.objects.filter( contract_id=self.id, permanences_dates__isnull=False).order_by('?'): all_dates_str = sorted( list( filter( None, contract_content.permanences_dates.split( settings.DJANGO_SETTINGS_DATES_SEPARATOR)))) if contract_content.flexible_dates: permanences_dates_order = 0 for one_date_str in all_dates_str: permanences_dates_order += 1 offer_item_qs = OfferItem.objects.filter( permanence_id=permanence.id, product_id=contract_content.product_id, permanences_dates=one_date_str).order_by('?') if not offer_item_qs.exists(): OfferItemWoReceiver.objects.create( permanence_id=permanence.id, product_id=contract_content.product_id, producer_id=contract_content.product.producer_id, contract_id=self.id, permanences_dates=one_date_str, not_permanences_dates=contract_content. not_permanences_dates, permanences_dates_counter=1, permanences_dates_order=permanences_dates_order) clean_offer_item(permanence, offer_item_qs, reset_add_2_stock=reset_add_2_stock) else: offer_item = offer_item_qs.first() offer_item.contract_id = self.id offer_item.permanences_dates_order = permanences_dates_order offer_item.not_permanences_dates = contract_content.not_permanences_dates if reset_add_2_stock: offer_item.may_order = True offer_item.save(update_fields=[ "contract", "may_order", "permanences_dates_order", "not_permanences_dates" ]) clean_offer_item(permanence, offer_item_qs, reset_add_2_stock=reset_add_2_stock) else: offer_item_qs = OfferItem.objects.filter( permanence_id=permanence.id, product_id=contract_content.product_id, permanences_dates=contract_content.permanences_dates ).order_by('?') if not offer_item_qs.exists(): OfferItemWoReceiver.objects.create( permanence_id=permanence.id, product_id=contract_content.product_id, producer_id=contract_content.product.producer_id, contract_id=self.id, permanences_dates=contract_content.permanences_dates, not_permanences_dates=contract_content. not_permanences_dates, permanences_dates_counter=len(all_dates_str)) clean_offer_item(permanence, offer_item_qs, reset_add_2_stock=reset_add_2_stock) else: offer_item = offer_item_qs.first() offer_item.contract_id = self.id offer_item.permanences_dates_order = 0 offer_item.not_permanences_dates = contract_content.not_permanences_dates if reset_add_2_stock: offer_item.may_order = True offer_item.save(update_fields=[ "contract", "may_order", "permanences_dates_order", "not_permanences_dates" ]) clean_offer_item(permanence, offer_item_qs, reset_add_2_stock=reset_add_2_stock) @cached_property def get_producers(self): if len(self.producers.all()) > 0: changelist_url = urlresolvers.reverse( 'admin:repanier_product_changelist', ) link = [] for p in self.producers.all(): link.append( "<a href=\"{}?producer={}&commitment={}\"> {} {}</a>" .format(changelist_url, p.id, self.id, LINK_UNICODE, p.short_profile_name.replace(" ", " "))) return mark_safe("<div class=\"wrap-text\">{}</div>".format( ", ".join(link))) else: return mark_safe("<div class=\"wrap-text\">{}</div>".format( _("No offer"))) get_producers.short_description = (_("Offers from")) @cached_property def get_dates(self): if self.permanences_dates: all_dates_str = sorted( list( filter( None, self.permanences_dates.split( settings.DJANGO_SETTINGS_DATES_SEPARATOR)))) all_dates = [] for one_date_str in all_dates_str: one_date = parse_date(one_date_str) all_dates.append(one_date) return '{} : {}'.format( len(all_dates), ", ".join( date.strftime(settings.DJANGO_SETTINGS_DAY_MONTH) for date in all_dates)) return EMPTY_STRING get_dates.short_description = (_("Permanences")) get_dates.allow_tags = True def get_contract_admin_display(self): return "{} ({})".format( self.safe_translation_getter('long_name', any_language=True), self.get_dates) get_contract_admin_display.short_description = _("Commitments") get_contract_admin_display.allow_tags = False def __str__(self): return "{}".format( self.safe_translation_getter('long_name', any_language=True)) class Meta: verbose_name = _("Commitment") verbose_name_plural = _("Commitments")
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"> %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( ' ', ' ') 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(' ', ' ') offeritem_changelist_url = close_offeritem_changelist_url else: label = ('%s ' % (p.short_profile_name,)).replace(' ', ' ') 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"> %s</a>' % ( changelist_url, self.id, pi.producer_id, label.replace(' ', ' ') )) 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(' ', ' '))) 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(' ', ' '))) 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(' ', ' ') ) ) 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(' ', ' ') + '</a>' c_link = EMPTY_STRING c = permanenceboard_row.customer if c: c_url = urlresolvers.reverse( 'admin:repanier_customer_change', args=(c.id,) ) c_link = ' -> <a href="' + c_url + \ '" target="_blank">' + c.short_basket_name.replace(' ', ' ') + '</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/>💶 %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')