Hello,
I have the following code:
models.py:
class Ingredient(models.Model):
"""to create ingredients whether they are food, packaging or any other category,
then to define their real cost according to a yield"""
ingredient_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=60, unique=False, verbose_name='nom')
category = models.ForeignKey(IngredientCategory, on_delete=models.CASCADE, verbose_name='catégorie')
purchase_unit = models.CharField(max_length=6, choices=UNIT, verbose_name="unité d'achat")
use_unit = models.CharField(max_length=6, choices=UNIT, verbose_name="unité d'utilisation")
stock_unit = models.CharField(max_length=6, choices=UNIT, verbose_name="unité de stockage")
negociated_price = models.DecimalField(max_digits=7, decimal_places=3, verbose_name='prix négocié')
net_weight = models.DecimalField(max_digits=6, decimal_places=3, verbose_name='poids net ou volume unitaire')
number_of_packs_per_purchase_unit = models.DecimalField(max_digits=3, decimal_places=0,
verbose_name='nombre de paquets par colis',
default=1,
help_text='nombre de pièces par carton')
number_of_number_pieces_per_pack = models.DecimalField(max_digits=3, decimal_places=0, default=1,
verbose_name='nombre de pièces par paquet',
help_text='nombre de pièce par paquet')
ingredient_yield = models.DecimalField(max_digits=3, decimal_places=2, default=1, verbose_name='rendement',
help_text='ex: pour 93% mettre 0.93')
# nested fields
price_per_kg_ou_l = models.DecimalField(max_digits=7, decimal_places=3, blank=True, null=True,
verbose_name='prix au kg ou l')
net_weight_after_yield = models.DecimalField(max_digits=6, decimal_places=3, blank=True, null=True,
verbose_name='poids ou volume après rendement')
price_per_kg_ou_l_after_yield = models.DecimalField(max_digits=7, decimal_places=3, blank=True, null=True,
verbose_name='prix de revient au kg ou l')
price_per_piece = models.DecimalField(max_digits=6, decimal_places=3, blank=True, null=True,
verbose_name='prix par pièce')
@property
def last_invoiced_price(self):
pass
if not self.ingredient_id:
return 0
else:
if not self.receptioningredients_set.values('invoiced_price'):
return self.negociated_price
else:
dict_1 = self.receptioningredients_set.values('invoiced_price').latest('reception_date')
return dict_1['invoiced_price']
@property
def _price_per_kg_ou_l(self):
"""to calculate the price per kg or litre of the ingredient"""
if self.purchase_unit == 'litre' or self.purchase_unit == 'kg':
return self.last_invoiced_price
else:
return self.last_invoiced_price / self.net_weight
@property
def _net_weight_after_yield(self):
"""to calculate the wheight after a yield """
return round(self.net_weight * self.ingredient_yield, 3)
@property
def _price_per_kg_ou_l_after_yield(self):
"""to calculate the price per kg after a yield"""
return round(self._price_per_kg_ou_l / self.ingredient_yield, 3)
@property
def _price_per_piece(self):
"""to calculate the price per piece"""
if self.purchase_unit == 'colis':
return self.last_invoiced_price / (self.number_of_packs_per_purchase_unit * self.number_of_number_pieces_per_pack)
elif self.purchase_unit == 'pièce':
return self.last_invoiced_price
elif self.purchase_unit == 'paquet':
return self.last_invoiced_price / self.number_of_number_pieces_per_pack
else:
return self.last_invoiced_price * self.net_weight / (self.number_of_packs_per_purchase_unit * self.number_of_number_pieces_per_pack)
def save(self, *args, **kwargs):
self.price_per_kg_or_l = self._price_per_kg_ou_l
self.net_weight_after_yield = self._net_weight_after_yield
self.price_per_kg_ou_l_after_yield = self._price_per_kg_ou_l_after_yield
self.price_per_piece = self._price_per_piece
super().save(*args, **kwargs)
def __str__(self):
return self.name
class Meta:
verbose_name = 'ingrédient'
verbose_name_plural = 'c - ingrédients'
class Reception(models.Model):
reception_id = models.AutoField(primary_key=True)
date = models.DateField()
created_on = models.DateField(auto_now_add=True)
updated_on = models.DateField(auto_now=True)
supplier = models.ForeignKey(Supplier, on_delete=models.PROTECT, verbose_name='fournisseur')
def get_total_amount(self):
if not self.reception_id:
pass
else:
dict_1 = self.receptioningredients_set.aggregate(result=Sum('invoiced_amount'))
return dict_1['result']
get_total_amount.short_description = 'montant'
class Meta:
verbose_name = 'réception'
verbose_name_plural = 'd - réceptions'
def __str__(self):
return 'réception n° {}'.format(self.reception_id)
class ReceptionIngredients(models.Model):
reception = models.ForeignKey(Reception, on_delete=models.PROTECT, verbose_name='réception')
ingredient = models.ForeignKey(Ingredient, on_delete=models.PROTECT, verbose_name='ingrédient')
tracability_number = models.CharField(max_length=60, blank=True, null=True, verbose_name='n° de lot')
best_before = models.DateField(blank=True, null=True, verbose_name='DLC / DLUO')
invoiced_quantity = models.DecimalField(max_digits=9, decimal_places=3, blank=True, null=True,
verbose_name='quantité_facturée')
invoiced_price = models.DecimalField(max_digits=9, decimal_places=3, blank=True, null=True,
verbose_name='prix_facturé')
invoiced_amount = models.DecimalField(max_digits=9, decimal_places=3, blank=True, null=True, editable=False,
verbose_name='montant_facturé')
is_closed = models.BooleanField(default=False, verbose_name='clôturé')
reception_date = models.DateField(null=True, blank=True, editable=False, verbose_name='date de réception')
class Meta:
verbose_name = 'ingrédient'
verbose_name_plural = 'e - ingrédients reçus'
@property
def get_invoiced_amount(self):
return self.invoiced_price * self.invoiced_quantity
def save(self, *args, **kwargs):
self.invoiced_amount = self.get_invoiced_amount
self.reception_date = self.reception.date
super().save(*args, **kwargs)
class Recipe(models.Model):
"""gives a name and describes the recipe and calculate its cost"""
recipe_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=60, verbose_name='nom')
# nested in lines
recipe_weight = models.DecimalField(max_digits=6, decimal_places=3, null=True, blank=True,
verbose_name='poids ou volume')
recipe_price = models.DecimalField(max_digits=6, decimal_places=3, null=True, blank=True,
verbose_name='prix de revient')
recipe_price_per_kg = models.DecimalField(max_digits=6, decimal_places=3, null=True, blank=True,
verbose_name='prix de revient au kg ou l')
@property
def _recipe_weight(self):
if self.recipeingredient_set.count() == 0:
return 0
else:
dict_1 = self.recipeingredient_set.aggregate(recipe_weight=Sum('quantity'))
return round(dict_1['recipe_weight'] * self.recipe_yield, 3)
@property
def _weight_per_serving(self):
if self.recipeingredient_set.count() == 0:
return 0
else:
return round(self._recipe_weight / self.number_of_serving, 3)
@property
def _recipe_price(self):
if self.recipeingredient_set.count() == 0:
return 0
else:
dict_1 = self.recipeingredient_set.aggregate(recipe_price=Sum('ingredient_price'))
return round(dict_1['recipe_price'], 3)
@property
def _price_per_serving(self):
if self.recipeingredient_set.count() == 0:
return 0
else:
return round(self._recipe_price / self.number_of_serving, 3)
def __str__(self):
return self.name
class Meta:
verbose_name = 'recette'
verbose_name_plural = 'b - recettes'
class RecipeIngredient(models.Model):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, verbose_name='recette')
ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE)
quantity = models.DecimalField(max_digits=6, decimal_places=3, verbose_name='quantité')
unit = models.CharField(max_length=10, editable=False, null=True, blank=True, verbose_name='unité')
# nested in lines
ingredient_price = models.DecimalField(max_digits=6, decimal_places=3, blank=True, null=True,
verbose_name="prix total de l'ingrédient")
quantity_per_serving = models.DecimalField(max_digits=6, decimal_places=3, blank=True, null=True,
verbose_name="quantité d'ingrédient par part servie")
price_per_serving = models.DecimalField(max_digits=6, decimal_places=3, blank=True, null=True,
verbose_name="prix de l'ingrédient par part servie")
production = models.OneToOneField(Production, on_delete=models.CASCADE, blank=True, null=True, editable=False)
allergen = models.CharField(max_length=60, blank=True, null=True, editable=False, verbose_name='allergène(s)')
@property
def get_last_invoiced_price(self):
pass
if not self.ingredient.ingredient_id:
return 0
else:
if not self.ingredient.receptioningredients_set.values('invoiced_price'):
return self.ingredient.negociated_price
else:
dict_1 = self.ingredient.receptioningredients_set.values('invoiced_price').latest('reception_date')
return dict_1['invoiced_price']
@property
def _ingredient_price(self):
if self.ingredient.use_unit == 'kg':
return round(self.get_last_invoiced_price / (self.ingredient.net_weight * self.ingredient.ingredient_yield) * self.quantity, 3)
elif self.ingredient.use_unit == 'litre':
return round(self.get_last_invoiced_price / (self.ingredient.net_weight * self.ingredient.ingredient_yield) * self.quantity, 3)
elif self.ingredient.use_unit == 'colis':
return round(self.get_last_invoiced_price * self.quantity, 3)
elif self.ingredient.use_unit == 'paquet':
return round(self.get_last_invoiced_price, 3)
else:
return round(self.ingredient.price_per_piece * self.quantity, 3)
def save(self, *args, **kwargs):
self.unit = self.ingredient.use_unit
self.ingredient_price = self._ingredient_price
super().save(*args, **kwargs)
class Meta:
verbose_name = 'ingrédient'
verbose_name_plural = 'ingrédients'
def __str__(self):
return '{}'.format(self.ingredient)
as you can see I managed to calculate the last invoiced price almost everywhere, everything is working fine.
The only problem I have is that in the model Recipe in order to calculate the total price of the recipe I add the last invoiced price multiplied by the quantity (which in itself has several if/elif).
It means that in the model RecipeIngredient :
-_ingredient_price is always accurate whenever I receive a new ingredient
- ingredient_price is not accurate so in the model Recipe the price of the recipe is wrong
I tried to update ingredient_price but it is not working:
in admin.py:
class RecipeIngredientAdmin(admin.ModelAdmin):
list_display = ['recipe', 'production', 'ingredient', 'get_allergen_list', 'allergen',
'get_last_invoiced_price', 'ingredient_price', '_ingredient_price']
actions = ['update_price']
def update_price(obj, request, queryset):
for obj in obj.queryset.all():
queryset.bulk_update(obj, ['ingredient_price'], batch_size=None)
I there any other way (like aggregating all these formulas in the model Recipe)?