Describe the bug
The fix for #7665 (commit 79c3a4f) correctly made the loan-preparation quantity math null-safe, but it also added an early return that disables the availability business rule for all new rows, in specifyweb/backend/businessrules/rules/interaction_rules.py → loanprep_quantity_must_be_lte_availability:
@orm_signal_handler('pre_save', 'Loanpreparation')
def loanprep_quantity_must_be_lte_availability(ipreparation):
if ipreparation.id is None: # added by the #7665 fix
return
...
The rule no longer runs on INSERT, so the server no longer enforces availability when a loan preparation is created — a specimen can be placed on two open loans at once. Over-loan prevention now depends entirely on the frontend InteractionDialog availability pre-query. Any creation path that does not go through that filter inserts an over-loan silently:
- "Add Unassociated Item" on a loan
- the REST API (
POST /api/specify/loanpreparation/)
- WorkBench loan uploads
The sibling rules in the same file — giftprep_quantity_must_be_lte_availability and exchangeoutprep_quantity_must_be_lte_availability — do not early-return on insert; they validate creation. Loans are the only interaction missing insert-time enforcement.
To Reproduce
- Find a preparation fully out on an open loan (e.g.
countAmt = 1, on loan A, unresolved).
- Add that same preparation to loan B via "Add Unassociated Item" (or POST a
loanpreparation row directly).
- Result: HTTP 201, no warning — the single-count preparation is now unresolved-out on two open loans at once.
get_availability already returns 0 (or negative) for such a prep, so the check would catch it if it ran on insert.
Expected behavior
Creating a loan preparation whose quantity - quantityresolved exceeds the preparation's availability should raise a BusinessRuleException, the same way gift and exchange-out preparations are validated on creation.
Impact
At the California Academy of Sciences we found 76 preparations currently out on two or more open loans in a single collection. Most originate from a Sp6→Sp7 conversion, but we also reproduced it live: a curator added already-out lots to a new loan through the UI with no warning.
Proposed fix (narrow)
Keep the null-safe quantity handling from the #7665 fix (int(x or 0) already treats an empty Quantity Resolved as 0), but remove the if ipreparation.id is None: return so the availability check also runs on insert — matching the gift/exchange rules. This restores over-loan protection without reintroducing the #7665 WorkBench failure, since #7665 was a TypeError from int(None) and the null-safe ints alone fix that.
PR with fix and regression tests to follow.
Crash Report
N/A — the bug is the absence of an error.
- OS: any
- Browser: any
- Specify 7 Version: any version including commit 79c3a4f (after v7.10.x)
- Database Name: casiz (CAS Invertebrate Zoology)
Reported By
California Academy of Sciences
Describe the bug
The fix for #7665 (commit 79c3a4f) correctly made the loan-preparation quantity math null-safe, but it also added an early return that disables the availability business rule for all new rows, in
specifyweb/backend/businessrules/rules/interaction_rules.py→loanprep_quantity_must_be_lte_availability:The rule no longer runs on INSERT, so the server no longer enforces availability when a loan preparation is created — a specimen can be placed on two open loans at once. Over-loan prevention now depends entirely on the frontend InteractionDialog availability pre-query. Any creation path that does not go through that filter inserts an over-loan silently:
POST /api/specify/loanpreparation/)The sibling rules in the same file —
giftprep_quantity_must_be_lte_availabilityandexchangeoutprep_quantity_must_be_lte_availability— do not early-return on insert; they validate creation. Loans are the only interaction missing insert-time enforcement.To Reproduce
countAmt = 1, on loan A, unresolved).loanpreparationrow directly).get_availabilityalready returns 0 (or negative) for such a prep, so the check would catch it if it ran on insert.Expected behavior
Creating a loan preparation whose
quantity - quantityresolvedexceeds the preparation's availability should raise a BusinessRuleException, the same way gift and exchange-out preparations are validated on creation.Impact
At the California Academy of Sciences we found 76 preparations currently out on two or more open loans in a single collection. Most originate from a Sp6→Sp7 conversion, but we also reproduced it live: a curator added already-out lots to a new loan through the UI with no warning.
Proposed fix (narrow)
Keep the null-safe quantity handling from the #7665 fix (
int(x or 0)already treats an empty Quantity Resolved as 0), but remove theif ipreparation.id is None: returnso the availability check also runs on insert — matching the gift/exchange rules. This restores over-loan protection without reintroducing the #7665 WorkBench failure, since #7665 was aTypeErrorfromint(None)and the null-safe ints alone fix that.PR with fix and regression tests to follow.
Crash Report
N/A — the bug is the absence of an error.
Reported By
California Academy of Sciences