@@ -1012,8 +1012,12 @@ def clean(self):
10121012 """
10131013 if self .strategy_type in self .REQUIRES_PRODUCT and not self .product :
10141014 raise ValidationError (_ ("A %s Livelihood Strategy must have a Product" % self .strategy_type ))
1015- if self .strategy_type in [ "MilkProduction" , "ButterProduction" ] and not self .season :
1015+ if self .strategy_type in self . REQUIRES_SEASON and not self .season :
10161016 raise ValidationError (_ ("A %s Livelihood Strategy must have a Season" % self .strategy_type ))
1017+ # All strategies require either a product or an additional identifier to distinguish them
1018+ if not self .product and not self .additional_identifier :
1019+ raise ValidationError (_ ("A Livelihood Strategy must have either a Product or an Additional Identifier" ))
1020+
10171021 super ().clean ()
10181022
10191023 def save (self , * args , ** kwargs ):
@@ -1239,6 +1243,18 @@ def validate_quantity_produced(self):
12391243 """
12401244 pass
12411245
1246+ def validate_quantity_purchased (self ):
1247+ """
1248+ Validate the quantity_purchased.
1249+
1250+ In most LivelihoodActivity subclasses the quantity_purchased is not used
1251+ and this validation passes.
1252+
1253+ However, FoodPurchase has additional fields that allow the quantity_purchased
1254+ to be validated. This method is overwritten in that subclass.
1255+ """
1256+ pass
1257+
12421258 def validate_quantity_consumed (self ):
12431259 # Default to 0 if any of the quantities are None
12441260 quantity_produced = self .quantity_produced or 0
@@ -1347,6 +1363,7 @@ def clean(self):
13471363 self .validate_livelihood_zone_baseline ()
13481364 self .validate_strategy_type ()
13491365 self .validate_quantity_produced ()
1366+ self .validate_quantity_purchased ()
13501367 self .validate_quantity_consumed ()
13511368 self .validate_income ()
13521369 self .validate_expenditure ()
@@ -1505,7 +1522,12 @@ class MilkType(models.TextChoices):
15051522 WHOLE = "whole" , _ ("whole" )
15061523
15071524 # Production calculation /validation is `lactation days * daily_production`
1508- milking_animals = models .PositiveSmallIntegerField (verbose_name = _ ("Number of milking animals" ))
1525+ # Although logically we can't have a MilkProduction without milking animals,
1526+ # the BSS may contain records with 0 production and a blank milking_animals
1527+ # field, particularly in Season 2 in a Baseline, or in a Response scenario
1528+ milking_animals = models .PositiveSmallIntegerField (
1529+ blank = True , null = True , verbose_name = _ ("Number of milking animals" )
1530+ )
15091531 lactation_days = models .PositiveSmallIntegerField (
15101532 blank = True , null = True , verbose_name = _ ("Average number of days of lactation" )
15111533 )
@@ -1551,9 +1573,13 @@ def validate_quantity_produced(self):
15511573
15521574 def clean (self ):
15531575 super ().clean ()
1554- if self .milking_animals and not self .lactation_days :
1576+ # Note that we check that lactation_days and daily_production are not None
1577+ # because 0 is a valid value for both fields. It is possible to have a
1578+ # non-zero milking_animals but zero lactation_days or daily_production,
1579+ # particularly in Season 2 in a Baseline, or in a Response scenario.
1580+ if self .milking_animals and self .lactation_days is None :
15551581 raise ValidationError (_ ("Lactation days must be provided if there are milking animals" ))
1556- if self .milking_animals and not self .daily_production :
1582+ if self .milking_animals and self .daily_production is None :
15571583 raise ValidationError (_ ("Daily production must be provided if there are milking animals" ))
15581584
15591585 class Meta :
@@ -1618,7 +1644,7 @@ class MeatProduction(LivelihoodActivity):
16181644 carcass_weight = models .FloatField (verbose_name = _ ("Carcass weight per animal" ))
16191645
16201646 def validate_quantity_produced (self ):
1621- if self .quantity_produced != self .animals_slaughtered * self .carcass_weight :
1647+ if self .quantity_produced and self . quantity_produced != self .animals_slaughtered * self .carcass_weight :
16221648 raise ValidationError (
16231649 _ ("Quantity Produced for a Meat Production must be animals slaughtered multiplied by carcass weight" )
16241650 )
@@ -1691,20 +1717,28 @@ class FoodPurchase(LivelihoodActivity):
16911717 help_text = _ ("Number of times in a year that the purchase is made" ),
16921718 )
16931719
1694- def validate_quantity_produced (self ):
1720+ def validate_quantity_purchased (self ):
16951721 if (
1696- self .quantity_produced is not None
1722+ self .quantity_purchased is not None
16971723 and self .unit_multiple is not None
16981724 and self .times_per_month is not None
16991725 and self .months_per_year is not None
17001726 ):
1701- if self .quantity_produced != self .unit_multiple * self .times_per_month * self .months_per_year :
1727+ if self .quantity_purchased != self .unit_multiple * self .times_per_month * self .months_per_year :
17021728 raise ValidationError (
17031729 _ (
1704- "Quantity produced for a Food Purchase must be purchase amount * purchases per month * months per year" # NOQA: E501
1730+ "Quantity purchased for a Food Purchase must be purchase amount * purchases per month * months per year" # NOQA: E501
17051731 )
17061732 )
17071733
1734+ def validate_expenditure (self ):
1735+ quantity_purchased = self .quantity_purchased or 0
1736+ price = self .price or 0
1737+ expenditure = self .expenditure or 0
1738+
1739+ if self .expenditure and expenditure != quantity_purchased * price :
1740+ raise ValidationError (_ ("Expenditure for a Food Purchase must be quantity purchased multiplied by price" ))
1741+
17081742 class Meta :
17091743 verbose_name = LivelihoodStrategyType .FOOD_PURCHASE .label
17101744 verbose_name_plural = _ ("Food Purchases" )
0 commit comments