33"""
44
55import datetime
6+ import math
67import numbers
78
89from django .conf import settings
@@ -1172,13 +1173,13 @@ class LivelihoodActivity(common_models.Model):
11721173 wealth_group = models .ForeignKey (WealthGroup , on_delete = models .CASCADE , help_text = _ ("Wealth Group" ))
11731174
11741175 # Also used for the quantity received for the PaymentInKind and ReliefGiftsOther Livelihood Strategies
1175- quantity_produced = models .PositiveIntegerField (blank = True , null = True , verbose_name = _ ("Quantity Produced" ))
1176- quantity_purchased = models .PositiveIntegerField (blank = True , null = True , verbose_name = _ ("Quantity Purchased" ))
1177- quantity_sold = models .PositiveIntegerField (blank = True , null = True , verbose_name = _ ("Quantity Sold/Exchanged" ))
1178- quantity_other_uses = models .PositiveIntegerField (blank = True , null = True , verbose_name = _ ("Quantity Other Uses" ))
1176+ quantity_produced = models .FloatField (blank = True , null = True , verbose_name = _ ("Quantity Produced" ))
1177+ quantity_purchased = models .FloatField (blank = True , null = True , verbose_name = _ ("Quantity Purchased" ))
1178+ quantity_sold = models .FloatField (blank = True , null = True , verbose_name = _ ("Quantity Sold/Exchanged" ))
1179+ quantity_other_uses = models .FloatField (blank = True , null = True , verbose_name = _ ("Quantity Other Uses" ))
11791180 # Can normally be calculated / validated as `quantity_produced + quantity_purchased - quantity_sold - quantity_other_uses` # NOQA: E501
11801181 # but there are exceptions, such as MilkProduction which also stores MilkProduction.quantity_butter_production
1181- quantity_consumed = models .PositiveIntegerField (blank = True , null = True , verbose_name = _ ("Quantity Consumed" ))
1182+ quantity_consumed = models .FloatField (blank = True , null = True , verbose_name = _ ("Quantity Consumed" ))
11821183
11831184 price = models .FloatField (
11841185 blank = True ,
@@ -1356,19 +1357,17 @@ def validate_quantity_consumed(self):
13561357 )
13571358
13581359 # Check if the actual quantity_consumed matches the expected quantity_consumed
1359- if self .quantity_consumed and self .quantity_consumed != expected_quantity_consumed :
1360+ if self .quantity_consumed and not math . isclose ( self .quantity_consumed , expected_quantity_consumed ) :
13601361 if quantity_butter_production :
13611362 message = "Quantity consumed for Milk Production must be quantity produced + quantity purchased - quantity sold - quantity used for butter production - quantity used for other things" # NOQA: E501
13621363 else :
13631364 message = "Quantity consumed for a Livelihood Activity must be quantity produced + quantity purchased - quantity sold - quantity used for other things" # NOQA: E501
13641365 raise ValidationError (_ (message ))
13651366
13661367 def validate_income (self ):
1367- income = self .income or 0
1368- quantity_sold = self .quantity_sold or 0
1369- price = self .price or 0
1370- if self .income and income != quantity_sold * price :
1371- raise ValidationError (_ ("Income for a Livelihood Activity must be quantity sold multiplied by price" ))
1368+ if self .income and self .quantity_sold is not None and self .price is not None :
1369+ if not math .isclose (self .income , self .quantity_sold * self .price ):
1370+ raise ValidationError (_ ("Income for a Livelihood Activity must be quantity sold multiplied by price" ))
13721371
13731372 def validate_expenditure (self ):
13741373 """
@@ -1386,7 +1385,7 @@ def validate_expenditure(self):
13861385 price = self .price or 0
13871386 expenditure = self .expenditure or 0
13881387
1389- if self .expenditure and expenditure != quantity_produced * price :
1388+ if self .expenditure and not math . isclose ( expenditure , quantity_produced * price ) :
13901389 raise ValidationError (
13911390 _ ("Expenditure for a Livelihood Activity must be quantity produced multiplied by price" )
13921391 )
@@ -1409,9 +1408,9 @@ def validate_kcals_consumed(self):
14091408 "product" : self .livelihood_strategy .product ,
14101409 }
14111410 )
1412- if (
1413- self .kcals_consumed
1414- != self .quantity_consumed * conversion_factor * self .livelihood_strategy .product .kcals_per_unit
1411+ if not math . isclose (
1412+ self .kcals_consumed ,
1413+ self .quantity_consumed * conversion_factor * self .livelihood_strategy .product .kcals_per_unit ,
14151414 ):
14161415 raise ValidationError (
14171416 _ (
@@ -1637,7 +1636,7 @@ class MilkType(models.TextChoices):
16371636 # https://unstats.un.org/unsd/classifications/Econ/Detail/EN/1074/22110. That LivelihoodActivity would have a
16381637 # `quantity_produced` equal to the amount of whole milk that was used for ButterProduction, and then the
16391638 # quantity_sold and the quantity_consumed could be tracked separately.
1640- quantity_butter_production = models .PositiveIntegerField (
1639+ quantity_butter_production = models .FloatField (
16411640 blank = True , null = True , verbose_name = _ ("Quantity used for Butter Production" )
16421641 ) # NOQA: E501
16431642 type_of_milk_consumed = models .CharField (
@@ -1736,7 +1735,7 @@ def clean(self):
17361735
17371736 def validate_quantity_produced (self ):
17381737 if self .quantity_produced is not None and self .animals_slaughtered and self .carcass_weight is not None :
1739- if self .quantity_produced != self .animals_slaughtered * self .carcass_weight :
1738+ if not math . isclose ( self .quantity_produced , self .animals_slaughtered * self .carcass_weight ) :
17401739 raise ValidationError (
17411740 _ (
17421741 "Quantity Produced for a Meat Production must be animals slaughtered multiplied by carcass weight"
@@ -1789,7 +1788,7 @@ class FoodPurchase(LivelihoodActivity):
17891788 # NIO93 Row B422 tia = 2.5kg
17901789
17911790 # unit_multiple has names like wt_of_measure in the BSS.
1792- unit_multiple = models .PositiveSmallIntegerField (
1791+ unit_multiple = models .FloatField (
17931792 blank = True ,
17941793 null = True ,
17951794 verbose_name = _ ("Unit Multiple" ),
@@ -1818,7 +1817,9 @@ def validate_quantity_purchased(self):
18181817 and self .times_per_month is not None
18191818 and self .months_per_year is not None
18201819 ):
1821- if self .quantity_purchased != self .unit_multiple * self .times_per_month * self .months_per_year :
1820+ if not math .isclose (
1821+ self .quantity_purchased , self .unit_multiple * self .times_per_month * self .months_per_year
1822+ ):
18221823 raise ValidationError (
18231824 _ (
18241825 "Quantity purchased for a Food Purchase must be purchase amount * purchases per month * months per year" # NOQA: E501
@@ -1830,7 +1831,7 @@ def validate_expenditure(self):
18301831 price = self .price or 0
18311832 expenditure = self .expenditure or 0
18321833
1833- if self .expenditure and expenditure != quantity_purchased * price :
1834+ if self .expenditure and not math . isclose ( expenditure , quantity_purchased * price ) :
18341835 raise ValidationError (_ ("Expenditure for a Food Purchase must be quantity purchased multiplied by price" ))
18351836
18361837 class Meta :
@@ -1912,9 +1913,9 @@ def validate_quantity_produced(self):
19121913 and self .times_per_month is not None
19131914 and self .months_per_year is not None
19141915 ):
1915- if (
1916- self .quantity_produced
1917- != self .payment_per_time * self .people_per_household * self .times_per_month * self .months_per_year
1916+ if not math . isclose (
1917+ self .quantity_produced ,
1918+ self .payment_per_time * self .people_per_household * self .times_per_month * self .months_per_year ,
19181919 ):
19191920 raise ValidationError (
19201921 _ (
@@ -1937,7 +1938,7 @@ class ReliefGiftOther(LivelihoodActivity):
19371938
19381939 # Production calculation /validation is `unit_of_measure * unit_multiple * times_per_year`
19391940 # Also used for the number of children receiving school meals.
1940- unit_multiple = models .PositiveSmallIntegerField (
1941+ unit_multiple = models .FloatField (
19411942 blank = True ,
19421943 null = True ,
19431944 verbose_name = _ ("Unit Multiple" ),
@@ -1963,7 +1964,7 @@ class ReliefGiftOther(LivelihoodActivity):
19631964
19641965 def validate_quantity_produced (self ):
19651966 if self .quantity_produced is not None and self .unit_multiple is not None and self .times_per_year is not None :
1966- if self .quantity_produced != self .unit_multiple * self .times_per_year :
1967+ if not math . isclose ( self .quantity_produced , self .unit_multiple * self .times_per_year ) :
19671968 raise ValidationError (
19681969 _ ("Quantity produced for Relief, Gifts, Other must be amount received * times per year" )
19691970 )
@@ -2072,17 +2073,17 @@ def validate_income(self):
20722073 and self .times_per_month is not None
20732074 and self .months_per_year is not None
20742075 ):
2075- if (
2076- self .income
2077- != self .payment_per_time * self .people_per_household * self .times_per_month * self .months_per_year
2076+ if not math . isclose (
2077+ self .income ,
2078+ self .payment_per_time * self .people_per_household * self .times_per_month * self .months_per_year ,
20782079 ):
20792080 raise ValidationError (
20802081 _ (
20812082 "Quantity produced for Other Cash Income must be payment per time * number of people * labor per month * months per year" # NOQA: E501
20822083 )
20832084 )
2084- if self .income is not None and self .payment_per_time is not None and self .times_per_year is not None :
2085- if self .income != self .payment_per_time * self .times_per_year :
2085+ elif self .income is not None and self .payment_per_time is not None and self .times_per_year is not None :
2086+ if not math . isclose ( self .income , self .payment_per_time * self .times_per_year ) :
20862087 raise ValidationError (_ ("Income for 'Other Cash Income' must be payment per time * times per year" ))
20872088
20882089 def calculate_fields (self ):
@@ -2106,7 +2107,7 @@ class OtherPurchase(LivelihoodActivity):
21062107 # individual fields must be nullable
21072108 # Do we need this, or can we use combined units of measure like FDW, e.g. 5kg
21082109 # NIO93 Row B422 tia = 2.5kg
2109- unit_multiple = models .PositiveSmallIntegerField (
2110+ unit_multiple = models .FloatField (
21102111 blank = True ,
21112112 null = True ,
21122113 verbose_name = _ ("Unit Multiple" ),
@@ -2132,7 +2133,7 @@ def validate_expenditure(self):
21322133 errors = []
21332134 if self .times_per_month is not None and self .months_per_year is not None :
21342135 expected_times_per_year = self .times_per_month * self .months_per_year
2135- if self .times_per_year is not None and self .times_per_year != expected_times_per_year :
2136+ if self .times_per_year is not None and not math . isclose ( self .times_per_year , expected_times_per_year ) :
21362137 errors .append (
21372138 _ (
21382139 "Times per year must be times per month * months per year. Expected: %(expected)s, Found: %(found)s"
@@ -2144,7 +2145,7 @@ def validate_expenditure(self):
21442145 )
21452146 if self .price is not None and self .unit_multiple is not None and self .times_per_year is not None :
21462147 expected_expenditure = self .price * self .unit_multiple * self .times_per_year
2147- if self .expenditure is not None and self .expenditure != expected_expenditure :
2148+ if self .expenditure is not None and not math . isclose ( self .expenditure , expected_expenditure ) :
21482149 errors .append (
21492150 _ (
21502151 "Expenditure for Other Purchases must be price * unit multiple * purchases per year. Expected: %(expected)s, Found: %(found)s"
0 commit comments