From 37057ade8bcf4ffc3106e84cecc61eb200886fc6 Mon Sep 17 00:00:00 2001 From: Matt Blenkinsop Date: Mon, 22 Jan 2024 15:54:41 +0000 Subject: [PATCH] Bug 28924: Add can_borrow and is_patron_inside_charge_limits methods MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This patch adds two new methods. can_borrow is a top-level method for determining whether a patron is able to borrow. Currently this logic is repeataed in multiple methods and shuld be more DRY. This bug is specific to patron charge limits so that is the focus but the method can be developed in follow-up bugs to cover all areas. The is_patron_inside_charge_limits method is now to be used where any of the three noissuescharge sysprefs are used. It will look at the patron category based on either a borrowernumber or patron object and return a hash detailing the charges, the charge limit and whether the patron is over the limit Test plan: 1. Choose a patron and note their patron category 2. Create a manual invoice on that patron for 6.00 3. Try to check an item out to the patron and it will show a message saying that checkout is blocked because the fine balance is over the limit. 4. This is because the default global value for the ‘noissuescharge’ is 5.00 and we are now over that limit 5. Navigate to Administration > Patron categories and click to edit the category relevant to your patron 6. At the bottom of the form will be fields called No Issues Charge, No Issues Charge Guarantees and No Issues Charge Guarantors With Guarantees. Set those fields to 7 7. Now try and checkout again - this time you will be allowed to checkout as the category level limit is higher than the fine we set. 8. Choose a different patron (patron 2) with a category that is different to the first patron (patron 1). 9. Repeat steps 2 and 3. This time checkout will be blocked again because the limit for this patron category is still the global value of 5.00 10. Choose a third patron with a category of child or similar (patron 3) 11. Make patron 1 a guarantor for patron 3 12. Edit patron 3’s patron category and set the limit for No Issues Charge, No Issues Charge Guarantees and No Issues Charge Guarantors With Guarantees to 5. 13. Try to check out to patron 3 and it will be blocked due to the fines on patron 1’s account 14. Try to checkout to patron 1 - it should still be possible 15. Add a fine to patron 3’s account for 2.00 16. Try to check out to patron 1 - the checkout will be blocked due to the total of 8 now owed by the patron and its guarantees Sponsored-by: Cuyahoga County Public Library Signed-off-by: David Nind Signed-off-by: Nick Clemens Signed-off-by: Katrin Fischer --- Koha/Patron.pm | 100 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/Koha/Patron.pm b/Koha/Patron.pm index 98dd05a4bb..35fad91346 100644 --- a/Koha/Patron.pm +++ b/Koha/Patron.pm @@ -2901,6 +2901,106 @@ sub consent { : Koha::Patron::Consent->new( { borrowernumber => $self->borrowernumber, type => $type } ); } + +=head3 can_borrow + +my $patron_borrowing_status = $patron->can_borrow( { patron => $patron } ); + +This method determines whether a borrower is able to borrow based on various parameters. +- Debarrments +- Expiry +- Charges + +If any blockers are found, these are returned in a hash + +=cut + +sub can_borrow { + my ( $self, $args ) = @_; + + my $patron = $args->{patron}; + my $status = { can_borrow => 1 }; + + $status->{debarred} = 1 if $patron->debarred; + $status->{expired} = 1 if $patron->is_expired; + $status->{can_borrow} = 0 if $status->{debarred} || $status->{expired}; + + # Patron charges + my $patron_charge_limits = $patron->is_patron_inside_charge_limits( { patron => $patron } ); + %$status = ( %$status, %$patron_charge_limits ); + $status->{can_borrow} = 0 + if $patron_charge_limits->{noissuescharge}->{overlimit} + || $patron_charge_limits->{NoIssuesChargeGuarantees}->{overlimit} + || $patron_charge_limits->{NoIssuesChargeGuarantorsWithGuarantees}->{overlimit}; + + return $status; +} + +=head3 is_patron_inside_charge_limits + +my $patron_charge_limits = $patron->is_patron_inside_charge_limits( { patron => $patron } ); + +Checks the current account balance for a patron and any guarantors/guarantees and compares it with any charge limits in place +Takes into account patron category level charge limits in the first instance and defaults to global sysprefs if not set + +=cut + +sub is_patron_inside_charge_limits { + my ( $self, $args ) = @_; + + my $borrowernumber = $args->{borrowernumber}; + my $patron = $args->{patron} || Koha::Patrons->find( { borrowernumber => $borrowernumber } ); + my $patron_category = $patron->category; + my $patron_charge_limits = {}; + + my $no_issues_charge = $patron_category->noissuescharge || C4::Context->preference('noissuescharge') || 0; + my $no_issues_charge_guarantees = + $patron_category->noissueschargeguarantees || C4::Context->preference('NoIssuesChargeGuarantees') || 0; + my $no_issues_charge_guarantors_with_guarantees = + $patron_category->noissueschargeguarantorswithguarantees + || C4::Context->preference('NoIssuesChargeGuarantorsWithGuarantees') + || 0; + + my $non_issues_charges = $patron->account->non_issues_charges; + my $guarantees_non_issues_charges = 0; + my $guarantors_non_issues_charges = 0; + + # Check the debt of this patrons guarantees + if ( defined $no_issues_charge_guarantees ) { + my @guarantees = map { $_->guarantee } $patron->guarantee_relationships->as_list; + foreach my $g (@guarantees) { + $guarantees_non_issues_charges += $g->account->non_issues_charges; + } + } + + # Check the debt of this patrons guarantors *and* the guarantees of those guarantors + if ( defined $no_issues_charge_guarantors_with_guarantees ) { + $guarantors_non_issues_charges = $patron->relationships_debt( + { include_guarantors => 1, only_this_guarantor => 0, include_this_patron => 1 } ); + } + + # Return hash for each charge limit - limit, charge, overlimit + $patron_charge_limits->{noissuescharge} = + { limit => $no_issues_charge, charge => $non_issues_charges, overlimit => 0 }; + $patron_charge_limits->{noissuescharge}->{overlimit} = 1 + if $no_issues_charge && $non_issues_charges > $no_issues_charge; + + $patron_charge_limits->{NoIssuesChargeGuarantees} = + { limit => $no_issues_charge_guarantees, charge => $guarantees_non_issues_charges, overlimit => 0 }; + $patron_charge_limits->{NoIssuesChargeGuarantees}->{overlimit} = 1 + if $no_issues_charge_guarantees && $guarantees_non_issues_charges > $no_issues_charge_guarantees; + + $patron_charge_limits->{NoIssuesChargeGuarantorsWithGuarantees} = { + limit => $no_issues_charge_guarantors_with_guarantees, charge => $guarantors_non_issues_charges, + overlimit => 0 + }; + $patron_charge_limits->{NoIssuesChargeGuarantorsWithGuarantees}->{overlimit} = 1 + if $no_issues_charge_guarantors_with_guarantees + && $guarantors_non_issues_charges > $no_issues_charge_guarantors_with_guarantees; + + return $patron_charge_limits; +} + =head2 Internal methods =head3 _type -- 2.39.5