Skip to content

Commit edaad72

Browse files
authored
Merge pull request #291 from American-Institutes-for-Research/HEA-1094/fix_FoodPurchase_and_OtherPurchase_validations
Update validate_quantity_purchased and validate_quantity_produced met…
2 parents b971234 + b99e6c7 commit edaad72

3 files changed

Lines changed: 137 additions & 38 deletions

File tree

apps/baseline/models.py

Lines changed: 83 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2474,20 +2474,27 @@ class FoodPurchase(LivelihoodActivity):
24742474
help_text=_("Number of times in a year that the purchase is made"),
24752475
)
24762476

2477+
def calculate_fields(self):
2478+
if self.times_per_year is None and self.times_per_month is not None and self.months_per_year is not None:
2479+
self.times_per_year = self.times_per_month * self.months_per_year
2480+
if self.quantity_purchased is None and self.unit_multiple is not None and self.times_per_year is not None:
2481+
self.quantity_purchased = self.unit_multiple * self.times_per_year
2482+
super().calculate_fields()
2483+
24772484
def validate_quantity_purchased(self):
2478-
if (
2479-
self.quantity_purchased is not None
2480-
and self.unit_multiple is not None
2481-
and self.times_per_month is not None
2482-
and self.months_per_year is not None
2483-
):
2484-
if not math.isclose(
2485-
self.quantity_purchased, self.unit_multiple * self.times_per_month * self.months_per_year
2486-
):
2485+
if self.times_per_month is not None and self.months_per_year is not None:
2486+
expected_times_per_year = self.times_per_month * self.months_per_year
2487+
if self.times_per_year is not None and not math.isclose(self.times_per_year, expected_times_per_year):
24872488
raise ValidationError(
24882489
_(
2489-
"Quantity purchased for a Food Purchase must be purchase amount * purchases per month * months per year" # NOQA: E501
2490+
"Times per year must be times per month * months per year. Expected: %(expected)s, Found: %(found)s" # NOQA: E501
24902491
)
2492+
% {"expected": expected_times_per_year, "found": self.times_per_year}
2493+
)
2494+
if self.quantity_purchased is not None and self.unit_multiple is not None and self.times_per_year is not None:
2495+
if not math.isclose(self.quantity_purchased, self.unit_multiple * self.times_per_year):
2496+
raise ValidationError(
2497+
_("Quantity purchased for a Food Purchase must be purchase amount * purchases per year")
24912498
)
24922499

24932500
def validate_expenditure(self):
@@ -2569,21 +2576,41 @@ def clean(self):
25692576
)
25702577
super().clean()
25712578

2579+
def calculate_fields(self):
2580+
if self.times_per_year is None and self.times_per_month is not None and self.months_per_year is not None:
2581+
self.times_per_year = self.times_per_month * self.months_per_year
2582+
if (
2583+
self.quantity_produced is None
2584+
and self.payment_per_time is not None
2585+
and self.people_per_household is not None
2586+
and self.times_per_year is not None
2587+
):
2588+
self.quantity_produced = self.payment_per_time * self.people_per_household * self.times_per_year
2589+
super().calculate_fields()
2590+
25722591
def validate_quantity_produced(self):
2592+
if self.times_per_month is not None and self.months_per_year is not None:
2593+
expected_times_per_year = self.times_per_month * self.months_per_year
2594+
if self.times_per_year is not None and not math.isclose(self.times_per_year, expected_times_per_year):
2595+
raise ValidationError(
2596+
_(
2597+
"Times per year must be times per month * months per year. Expected: %(expected)s, Found: %(found)s" # NOQA: E501
2598+
)
2599+
% {"expected": expected_times_per_year, "found": self.times_per_year}
2600+
)
25732601
if (
25742602
self.quantity_produced is not None
25752603
and self.payment_per_time is not None
25762604
and self.people_per_household is not None
2577-
and self.times_per_month is not None
2578-
and self.months_per_year is not None
2605+
and self.times_per_year is not None
25792606
):
25802607
if not math.isclose(
25812608
self.quantity_produced,
2582-
self.payment_per_time * self.people_per_household * self.times_per_month * self.months_per_year,
2609+
self.payment_per_time * self.people_per_household * self.times_per_year,
25832610
):
25842611
raise ValidationError(
25852612
_(
2586-
"Quantity produced for Payment In Kind must be payment per time * number of people * labor per month * months per year" # NOQA: E501
2613+
"Quantity produced for Payment In Kind must be payment per time * number of people * times per year" # NOQA: E501
25872614
)
25882615
)
25892616

@@ -2626,7 +2653,23 @@ class ReliefGiftOther(LivelihoodActivity):
26262653
help_text=_("Number of times in a year that the item is received"),
26272654
)
26282655

2656+
def calculate_fields(self):
2657+
if self.times_per_year is None and self.times_per_month is not None and self.months_per_year is not None:
2658+
self.times_per_year = self.times_per_month * self.months_per_year
2659+
if self.quantity_produced is None and self.unit_multiple is not None and self.times_per_year is not None:
2660+
self.quantity_produced = self.unit_multiple * self.times_per_year
2661+
super().calculate_fields()
2662+
26292663
def validate_quantity_produced(self):
2664+
if self.times_per_month is not None and self.months_per_year is not None:
2665+
expected_times_per_year = self.times_per_month * self.months_per_year
2666+
if self.times_per_year is not None and not math.isclose(self.times_per_year, expected_times_per_year):
2667+
raise ValidationError(
2668+
_(
2669+
"Times per year must be times per month * months per year. Expected: %(expected)s, Found: %(found)s" # NOQA: E501
2670+
)
2671+
% {"expected": expected_times_per_year, "found": self.times_per_year}
2672+
)
26302673
if self.quantity_produced is not None and self.unit_multiple is not None and self.times_per_year is not None:
26312674
if not math.isclose(self.quantity_produced, self.unit_multiple * self.times_per_year):
26322675
raise ValidationError(
@@ -2733,26 +2776,31 @@ def clean(self):
27332776
def validate_income(self):
27342777
if (
27352778
self.people_per_household is not None
2736-
and self.income is not None
2737-
and self.payment_per_time is not None
27382779
and self.times_per_month is not None
27392780
and self.months_per_year is not None
27402781
):
2741-
if not math.isclose(
2742-
self.income,
2743-
self.payment_per_time * self.people_per_household * self.times_per_month * self.months_per_year,
2744-
):
2782+
expected_times_per_year = self.people_per_household * self.times_per_month * self.months_per_year
2783+
if self.times_per_year is not None and not math.isclose(self.times_per_year, expected_times_per_year):
27452784
raise ValidationError(
27462785
_(
2747-
"Quantity produced for Other Cash Income must be payment per time * number of people * labor per month * months per year" # NOQA: E501
2786+
"Times per year must be people per household * times per month * months per year. Expected: %(expected)s, Found: %(found)s" # NOQA: E501
27482787
)
2788+
% {"expected": expected_times_per_year, "found": self.times_per_year}
27492789
)
2750-
elif self.income is not None and self.payment_per_time is not None and self.times_per_year is not None:
2790+
if self.income is not None and self.payment_per_time is not None and self.times_per_year is not None:
27512791
if not math.isclose(self.income, self.payment_per_time * self.times_per_year):
27522792
raise ValidationError(_("Income for 'Other Cash Income' must be payment per time * times per year"))
27532793

27542794
def calculate_fields(self):
2755-
self.times_per_year = self.people_per_household * self.times_per_month * self.months_per_year
2795+
if (
2796+
self.times_per_year is None
2797+
and self.people_per_household is not None
2798+
and self.times_per_month is not None
2799+
and self.months_per_year is not None
2800+
):
2801+
self.times_per_year = self.people_per_household * self.times_per_month * self.months_per_year
2802+
if self.income is None and self.payment_per_time is not None and self.times_per_year is not None:
2803+
self.income = self.payment_per_time * self.times_per_year
27562804
super().calculate_fields()
27572805

27582806
class Meta:
@@ -2796,6 +2844,18 @@ class OtherPurchase(LivelihoodActivity):
27962844
help_text=_("Number of times in a year that the product is purchased"),
27972845
)
27982846

2847+
def calculate_fields(self):
2848+
if self.times_per_year is None and self.times_per_month is not None and self.months_per_year is not None:
2849+
self.times_per_year = self.times_per_month * self.months_per_year
2850+
if (
2851+
self.expenditure is None
2852+
and self.price is not None
2853+
and self.unit_multiple is not None
2854+
and self.times_per_year is not None
2855+
):
2856+
self.expenditure = self.price * self.unit_multiple * self.times_per_year
2857+
super().calculate_fields()
2858+
27992859
def validate_expenditure(self):
28002860
errors = []
28012861
if self.times_per_month is not None and self.months_per_year is not None:

apps/baseline/tests/factories.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ class Meta:
664664
people_per_household = fuzzy.FuzzyInteger(1, 30)
665665
times_per_month = fuzzy.FuzzyInteger(1, 40)
666666
months_per_year = fuzzy.FuzzyInteger(1, 12)
667-
times_per_year = factory.LazyAttribute(lambda o: o.times_per_month * o.months_per_year)
667+
times_per_year = factory.LazyAttribute(lambda o: o.people_per_household * o.times_per_month * o.months_per_year)
668668

669669

670670
class OtherPurchaseFactory(LivelihoodActivityFactory):

apps/baseline/tests/test_models.py

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -288,19 +288,37 @@ def setUpTestData(cls):
288288
unit_multiple=None,
289289
times_per_month=None,
290290
months_per_year=None,
291+
times_per_year=None,
291292
quantity_purchased=None,
292293
)
294+
# Correct via times_per_month/months_per_year path (times_per_year matches)
293295
cls.foodpurchase2 = FoodPurchase(
294296
unit_multiple=2,
295297
times_per_month=5,
296298
months_per_year=12,
297-
quantity_purchased=120,
299+
times_per_year=60, # 5 * 12 = 60
300+
quantity_purchased=120, # 2 * 60 = 120
298301
)
299-
# Incorrect: 2 * 5 * 12 = 120
302+
# Incorrect quantity_purchased: 2 * 60 = 120, not 100
300303
cls.foodpurchase3 = FoodPurchase(
301304
unit_multiple=2,
302305
times_per_month=5,
303306
months_per_year=12,
307+
times_per_year=60,
308+
quantity_purchased=100,
309+
)
310+
# Correct via times_per_year only (no times_per_month/months_per_year)
311+
cls.foodpurchase4 = FoodPurchase(
312+
unit_multiple=3,
313+
times_per_year=10,
314+
quantity_purchased=30, # 3 * 10 = 30
315+
)
316+
# Inconsistent times_per_year vs times_per_month * months_per_year
317+
cls.foodpurchase5 = FoodPurchase(
318+
unit_multiple=2,
319+
times_per_month=5,
320+
months_per_year=12,
321+
times_per_year=50, # Incorrect: should be 60
304322
quantity_purchased=100,
305323
)
306324

@@ -310,11 +328,16 @@ def test_validate_quantity_purchased(self):
310328
"""
311329
# Missing data should not raise ValidationError
312330
self.foodpurchase1.validate_quantity_purchased()
313-
# Expected consistant values, should not raise
331+
# Correct via times_per_month/months_per_year (times_per_year set consistently)
314332
self.foodpurchase2.validate_quantity_purchased()
315-
# Incorrect: 2 * 5 * 12 = 120
333+
# Incorrect quantity_purchased
316334
with conditional_logging():
317335
self.assertRaises(ValidationError, self.foodpurchase3.validate_quantity_purchased)
336+
# Correct via times_per_year only
337+
self.foodpurchase4.validate_quantity_purchased()
338+
# Inconsistent times_per_year
339+
with self.assertRaises(ValidationError):
340+
self.foodpurchase5.validate_quantity_purchased()
318341

319342

320343
class PaymentInKindTestCase(TestCase):
@@ -326,21 +349,26 @@ def setUpTestData(cls):
326349
people_per_household=None,
327350
times_per_month=None,
328351
months_per_year=None,
352+
times_per_year=None,
329353
quantity_produced=None,
330354
)
355+
# Correct: times_per_year = 5 * 12 = 60; quantity_produced = 10 * 2 * 60 = 1200
331356
cls.paymentinkind2 = PaymentInKind(
332357
payment_per_time=10,
333358
people_per_household=2,
334359
times_per_month=5,
335360
months_per_year=12,
336-
quantity_produced=1200, # 10 * 2 * 5 * 12 = 1200
361+
times_per_year=60,
362+
quantity_produced=1200,
337363
)
364+
# Incorrect quantity_produced: 10 * 2 * 60 = 1200, not 1000
338365
cls.paymentinkind3 = PaymentInKind(
339366
payment_per_time=10,
340367
people_per_household=2,
341368
times_per_month=5,
342369
months_per_year=12,
343-
quantity_produced=1000, # Incorrect: should be 1200
370+
times_per_year=60,
371+
quantity_produced=1000,
344372
)
345373

346374
def test_validate_quantity_produced(self):
@@ -350,10 +378,10 @@ def test_validate_quantity_produced(self):
350378
# Missing data should not raise ValidationError
351379
self.paymentinkind1.validate_quantity_produced()
352380

353-
# Expected consistent values, should not raise ValidationError
381+
# Correct (times_per_year consistent with times_per_month * months_per_year)
354382
self.paymentinkind2.validate_quantity_produced()
355383

356-
# Incorrect: 10 * 2 * 5 * 12 = 1200
384+
# Incorrect quantity_produced
357385
with self.assertRaises(ValidationError):
358386
self.paymentinkind3.validate_quantity_produced()
359387

@@ -377,18 +405,22 @@ def setUpTestData(cls):
377405
# Create different instances without saving
378406
cls.reliefgift1 = ReliefGiftOther(
379407
unit_multiple=None,
408+
times_per_month=None,
409+
months_per_year=None,
380410
times_per_year=None,
381411
quantity_produced=None,
382412
)
413+
# Correct via times_per_year only
383414
cls.reliefgift2 = ReliefGiftOther(
384415
unit_multiple=10,
385416
times_per_year=12,
386417
quantity_produced=120, # 10 * 12 = 120
387418
)
419+
# Incorrect quantity_produced
388420
cls.reliefgift3 = ReliefGiftOther(
389421
unit_multiple=10,
390422
times_per_year=12,
391-
quantity_produced=100, # Incorrect: should be 10 * 12 = 120
423+
quantity_produced=100, # Incorrect: should be 120
392424
)
393425

394426
def test_validate_quantity_produced(self):
@@ -398,10 +430,10 @@ def test_validate_quantity_produced(self):
398430
# Missing data should not raise ValidationError
399431
self.reliefgift1.validate_quantity_produced()
400432

401-
# Expected consistent values, should not raise ValidationError
433+
# Correct via times_per_year only
402434
self.reliefgift2.validate_quantity_produced()
403435

404-
# Incorrect: 10 * 12 = 120
436+
# Incorrect quantity_produced
405437
with self.assertRaises(ValidationError):
406438
self.reliefgift3.validate_quantity_produced()
407439

@@ -415,27 +447,34 @@ def setUpTestData(cls):
415447
people_per_household=None,
416448
times_per_month=None,
417449
months_per_year=None,
450+
times_per_year=None,
418451
income=None,
419452
)
453+
# Correct: times_per_year = 2 * 5 * 12 = 120; income = 100 * 120 = 12000
420454
cls.othercashincome2 = OtherCashIncome(
421455
payment_per_time=100,
422456
people_per_household=2,
423457
times_per_month=5,
424458
months_per_year=12,
425-
income=12000, # 100 * 2 * 5 * 12 = 12000
459+
times_per_year=120,
460+
income=12000,
426461
)
462+
# Incorrect income: 100 * 120 = 12000, not 10000
427463
cls.othercashincome3 = OtherCashIncome(
428464
payment_per_time=100,
429465
people_per_household=2,
430466
times_per_month=5,
431467
months_per_year=12,
432-
income=10000, # Incorrect: should be 12000
468+
times_per_year=120,
469+
income=10000,
433470
)
471+
# Correct via times_per_year only (no people_per_household/times_per_month)
434472
cls.othercashincome4 = OtherCashIncome(
435473
payment_per_time=100,
436474
times_per_year=24,
437475
income=2400, # 100 * 24 = 2400
438476
)
477+
# Incorrect income via times_per_year only
439478
cls.othercashincome5 = OtherCashIncome(
440479
payment_per_time=100,
441480
times_per_year=24,
@@ -448,7 +487,7 @@ def test_validate_income(self):
448487
# Correct data should not raise ValidationError
449488
self.othercashincome2.validate_income()
450489
self.othercashincome4.validate_income()
451-
# Incorrect data should raise ValidationError.
490+
# Incorrect income should raise ValidationError
452491
with self.assertRaises(ValidationError):
453492
self.othercashincome3.validate_income()
454493
with self.assertRaises(ValidationError):

0 commit comments

Comments
 (0)