diff --git a/specifyweb/backend/businessrules/rules/interaction_rules.py b/specifyweb/backend/businessrules/rules/interaction_rules.py index 23b79bb375f..9eea53ea0f8 100644 --- a/specifyweb/backend/businessrules/rules/interaction_rules.py +++ b/specifyweb/backend/businessrules/rules/interaction_rules.py @@ -6,7 +6,7 @@ def get_availability(prep, iprepid, iprepid_fld): args = [prep.id] sql = """ - select p.countAmt - coalesce(sum(lp.quantity-lp.quantityresolved),0) - coalesce(sum(gp.quantity),0) - coalesce(sum(ep.quantity),0) + select p.countAmt - coalesce(sum(coalesce(lp.quantity,0)-coalesce(lp.quantityresolved,0)),0) - coalesce(sum(gp.quantity),0) - coalesce(sum(ep.quantity),0) from preparation p left join loanpreparation lp on lp.preparationid = p.preparationid left join giftpreparation gp on gp.preparationid = p.preparationid @@ -29,9 +29,6 @@ def get_availability(prep, iprepid, iprepid_fld): @orm_signal_handler('pre_save', 'Loanpreparation') def loanprep_quantity_must_be_lte_availability(ipreparation): - if ipreparation.id is None: - return - if ipreparation.preparation is not None: available = get_availability( ipreparation.preparation, ipreparation.id, "loanpreparationid") or 0 diff --git a/specifyweb/backend/businessrules/tests/test_loanpreparation.py b/specifyweb/backend/businessrules/tests/test_loanpreparation.py new file mode 100644 index 00000000000..f44280833df --- /dev/null +++ b/specifyweb/backend/businessrules/tests/test_loanpreparation.py @@ -0,0 +1,104 @@ +from specifyweb.specify import models +from specifyweb.specify.tests.test_api import ApiTests +from ..exceptions import BusinessRuleException + + +class LoanPreparationTests(ApiTests): + def setUp(self): + super().setUp() + self.preptype = models.Preptype.objects.create( + name='testPrepType', + isloanable=True, + collection=self.collection, + ) + self.preparation = models.Preparation.objects.create( + collectionobject=self.collectionobjects[0], + preptype=self.preptype, + countamt=1, + ) + self.loan_a = models.Loan.objects.create( + loannumber='1', + discipline=self.discipline) + self.loan_b = models.Loan.objects.create( + loannumber='2', + discipline=self.discipline) + + def test_insert_cannot_exceed_availability(self): + models.Loanpreparation.objects.create( + loan=self.loan_a, + discipline=self.discipline, + preparation=self.preparation, + quantity=1, + quantityresolved=0) + + with self.assertRaises(BusinessRuleException): + models.Loanpreparation.objects.create( + loan=self.loan_b, + discipline=self.discipline, + preparation=self.preparation, + quantity=1, + quantityresolved=0) + + def test_insert_within_availability(self): + self.preparation.countamt = 2 + self.preparation.save() + + models.Loanpreparation.objects.create( + loan=self.loan_a, + discipline=self.discipline, + preparation=self.preparation, + quantity=1, + quantityresolved=0) + + models.Loanpreparation.objects.create( + loan=self.loan_b, + discipline=self.discipline, + preparation=self.preparation, + quantity=1, + quantityresolved=0) + + def test_resolved_loan_frees_availability(self): + models.Loanpreparation.objects.create( + loan=self.loan_a, + discipline=self.discipline, + preparation=self.preparation, + quantity=1, + quantityresolved=1, + isresolved=True) + + models.Loanpreparation.objects.create( + loan=self.loan_b, + discipline=self.discipline, + preparation=self.preparation, + quantity=1, + quantityresolved=0) + + def test_null_quantityresolved_treated_as_zero(self): + # A null Quantity Resolved must not crash the rule (#7665)... + models.Loanpreparation.objects.create( + loan=self.loan_a, + discipline=self.discipline, + preparation=self.preparation, + quantity=1, + quantityresolved=None) + + # ...and the loan still counts as fully unresolved. + with self.assertRaises(BusinessRuleException): + models.Loanpreparation.objects.create( + loan=self.loan_b, + discipline=self.discipline, + preparation=self.preparation, + quantity=1, + quantityresolved=None) + + def test_update_cannot_exceed_availability(self): + lp = models.Loanpreparation.objects.create( + loan=self.loan_a, + discipline=self.discipline, + preparation=self.preparation, + quantity=1, + quantityresolved=0) + + lp.quantity = 2 + with self.assertRaises(BusinessRuleException): + lp.save()