From 1d8f322bae82a81bc6ed2c762d4008ac5ad562b8 Mon Sep 17 00:00:00 2001 From: Agustin Moyano Date: Thu, 5 Sep 2019 14:13:27 -0300 Subject: [PATCH] Bug 19618: Add ability to place holds for members of a club in intranet This patch adds the ability to place a hold for each member of a club in random order. To test: 1) apply this patch 2) create 2 clubs, (club names should have some part in common, and some part different for each other) 3) in one of them add at least 6 members 4) enter patron clubs management and click on "Actions" button SUCCESS.1 => club with members has a new action called "search to hold" 5) click on search to hold SUCCESS.2 => in the list of bilios there appears an action called "Place hold for " 6) click on "Place hold for " SUCCESS.3 => a new window appears where you can select pickup location (defaults to club's library, if any), and the list of members. 7) go back to the list of bilios in the catalog and click on "Forget " 8) click on "Holds" action of any biblio SUCCESS.4 => a search box appears with two tabs: Patrons and Clubs 9) click on Clubs tab and search by the common part of clubs names SUCCESS.5 => a list of clubs that matches the search appears. If you click on any of them, the same page as SUCCESS.3 appears. 10) go back to the search box in SUCCESS.4 and search by the different part of the name. SUCCESS.6 => because there is only one club that matches search criteria, the same page as SUCCESS.3 appears; 11) Sign off --- Koha/Club/Enrollment.pm | 16 ++ catalogue/search.pl | 8 + .../prog/en/includes/clubs-table.inc | 111 ++++++++ .../prog/en/modules/catalogue/results.tt | 37 ++- .../prog/en/modules/clubs/clubs.tt | 95 +------ .../prog/en/modules/members/moremember.tt | 2 +- .../prog/en/modules/reserve/request.tt | 268 ++++++++++++++++-- reserve/request.pl | 73 ++++- 8 files changed, 492 insertions(+), 118 deletions(-) create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/clubs-table.inc diff --git a/Koha/Club/Enrollment.pm b/Koha/Club/Enrollment.pm index 168f55bca6..2b4e627a77 100644 --- a/Koha/Club/Enrollment.pm +++ b/Koha/Club/Enrollment.pm @@ -24,6 +24,8 @@ use Carp; use Koha::Database; use Koha::Clubs; use Koha::Patrons; +use Koha::DateUtils qw(dt_from_string); +use DateTime; use base qw(Koha::Object); @@ -71,6 +73,20 @@ sub patron { return scalar Koha::Patrons->find( $self->borrowernumber() ); } +=head3 is_canceled +Determines if enrollment is canceled +=cut + +sub is_canceled { + my ( $self ) = @_; + + return 0 unless $self->date_canceled; + my $today = dt_from_string; + my $date_canceled = dt_from_string( $self->date_canceled ); + + return DateTime->compare($date_canceled, $today) < 1; +} + =head3 type =cut diff --git a/catalogue/search.pl b/catalogue/search.pl index a3a7015efc..8f460de8e3 100755 --- a/catalogue/search.pl +++ b/catalogue/search.pl @@ -203,6 +203,14 @@ if($cgi->cookie("holdfor")){ ); } +if($cgi->cookie("holdforclub")){ + my $holdfor_club = Koha::Clubs->find( $cgi->cookie("holdforclub") ); + $template->param( + holdforclub => $cgi->cookie("holdforclub"), + holdforclub_name => $holdfor_club->name, + ); +} + # get biblionumbers stored in the cart my @cart_list; diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/clubs-table.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/clubs-table.inc new file mode 100644 index 0000000000..62027b1a38 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/clubs-table.inc @@ -0,0 +1,111 @@ + + + + + + + [% UNLESS destination == 'holds' %] + + + [% END %] + + + + + [% UNLESS destination == 'holds' %] + + [% END %] + + + + [% FOREACH c IN clubs %] + [% IF destination == 'holds' %] + [% IF multi_hold %] + [% SET data_url = "/cgi-bin/koha/reserve/request.pl?club=" _ c.id _ "&multi_hold=1&biblionumbers=" _ biblionumbers %] + [% ELSE %] + [% SET data_url = "/cgi-bin/koha/reserve/request.pl?club=" _ c.id _ "&biblionumber=" _ biblionumber %] + [% END %] + + [% ELSE %] + + [% END %] + + + + [% UNLESS destination == 'holds' %] + + + [% END %] + + + + + [% UNLESS destination == 'holds' %] + + [% END %] + + [% END %] + +
NameTemplateDescriptionPublic enrollmentEmail requiredLibraryStart dateEnd dateEnrolled patrons 
[% c.name | html %][% c.club_template.name | html %][% c.description | html %] + [% IF c.club_template.is_enrollable_from_opac %] + Yes + [% ELSE %] + No + [% END %] + + [% IF c.club_template.is_email_required %] + Yes + [% ELSE %] + No + [% END %] + [% Branches.GetName( c.branchcode ) | html %] + [% IF c.date_start %] + [% c.date_start | $KohaDates %] + [% END %] + + [% IF c.date_end %] + [% c.date_end | $KohaDates %] + [% END %] + + [% c.club_enrollments.count | html %] + + +
\ No newline at end of file diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/results.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/results.tt index c8af35f986..3d9bbe9f24 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/results.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/results.tt @@ -123,7 +123,7 @@ [% END # /IF virtualshelves %] [% IF ( CAN_user_reserveforothers_place_holds && DisplayMultiPlaceHold ) %] - [% IF ( holdfor ) %] + [% IF ( holdfor or holdforclub ) %] [% ELSE %] @@ -422,6 +429,7 @@ [% IF CAN_user_reserveforothers_place_holds %] Holds ([% Biblio.HoldsCount( SEARCH_RESULT.biblionumber ) | html %]) [% IF ( holdfor ) %] | Place hold for [% holdfor_firstname | html %] [% holdfor_surname | html %] ([% holdfor_cardnumber | html %])[% END %] + [% IF ( holdforclub ) %] | Place hold for [% holdforclub_name | html %][% END %] [% ELSE %] Holds ([% Biblio.HoldsCount( SEARCH_RESULT.biblionumber ) | html %]) [% END %] @@ -617,6 +625,7 @@ + @@ -713,6 +722,7 @@ $("#searchheader").on("click",".placehold", function(){ $("#holdFor").val(""); + $("#holdForClub").val(""); placeHold(); $(".btn-group").removeClass("open"); return false; @@ -724,8 +734,14 @@ return false; }); - $("#forgetholdfor").click(function(){ - forgetPatron(); + $(".placeholdforclub").click(function(){ + holdForClub(); + $(".btn-group").removeClass("open"); + return false; + }); + + $("#forgetholdfor, #forgetholdforclub").click(function(){ + forgetPatronAndClub(); $(".btn-group").removeClass("open"); return false; }); @@ -884,8 +900,9 @@ return false; } - function forgetPatron(){ + function forgetPatronAndClub(){ $.removeCookie("holdfor", { path: '/' }); + $.removeCookie("holdforclub", { path: '/' }); $(".holdforlink").remove(); $("#placeholdc").html(" "+_("Place hold")+""); } @@ -936,6 +953,7 @@ function holdfor(){ $("#holdFor").val(""); + $("#holdForClub").val(""); placeHold(); } @@ -943,6 +961,11 @@ $("#holdFor").val("[% holdfor_cardnumber | html %]"); placeHold(); } + + function holdForClub() { + $("#holdForClub").val("[% holdforclub | html %]"); + placeHold(); + } [% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/clubs/clubs.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/clubs/clubs.tt index 42d50ff41c..e4176a9da0 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/clubs/clubs.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/clubs/clubs.tt @@ -120,93 +120,7 @@ [% END %] [% IF clubs %] - - - - - - - - - - - - - - - - - [% FOREACH c IN clubs %] - - - - - - - - - - - - - [% END %] - -
NameTemplateDescriptionPublic enrollmentEmail requiredLibraryStart dateEnd dateEnrolled patrons 
[% c.name | html %][% c.club_template.name | html %][% c.description | html %] - [% IF c.club_template.is_enrollable_from_opac %] - Yes - [% ELSE %] - No - [% END %] - - [% IF c.club_template.is_email_required %] - Yes - [% ELSE %] - No - [% END %] - [% Branches.GetName( c.branchcode ) | html %] - [% IF c.date_start %] - [% c.date_start | $KohaDates %] - [% END %] - - [% IF c.date_end %] - [% c.date_end | $KohaDates %] - [% END %] - - [% c.club_enrollments.count | html %] - - -
+ [% INCLUDE 'clubs-table.inc' %] [% ELSE %] [% IF club_templates %]
No clubs defined.
@@ -282,6 +196,13 @@ }); } } + + function SearchToHold(club_id) { + var date = new Date(); + date.setTime(date.getTime() + (10 * 60 * 1000)); + $.cookie("holdforclub", club_id, { path: "/", expires: date }); + location.href="/cgi-bin/koha/catalogue/search.pl"; + } [% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt index 95ad755bca..597de84b19 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt @@ -861,7 +861,7 @@ if ( $('#clubs-tab').length ) { $('#clubs-tab-link').on('click', function() { $('#clubs-tab').text(_("Loading...")); - $('#clubs-tab').load('/cgi-bin/koha/clubs/patron-clubs-tab.pl?borrowernumber=[% borrowernumber | html %]'); + $('#clubs-tab').load('/cgi-bin/koha/clubs/patron-clubs-tab.pl?borrowernumber=[% patron.borrowernumber | html %]'); }); } diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt index 91e4ebb6b5..5b0c482c20 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt @@ -73,7 +73,7 @@

Confirm holds

[% END %] - [% UNLESS patron OR patron.borrowernumber OR noitems %] + [% UNLESS club OR patron OR patron.borrowernumber OR noitems %] [% IF ( messageborrower ) %]

Patron not found

@@ -81,30 +81,224 @@
[% END %] -
-
- [% UNLESS borrowers %] - -
Enter patron card number or partial name:
- - - [% IF multi_hold %] - - - [% ELSE %] - - [% END %] - [% ELSE %] + [% IF ( messageclub ) %] +
+

Club not found

+

No club with this name, please, try another

+
+ [% END %] +
+ +
+ +
+
+ +
Enter patron card number or partial name:
+ + + [% IF multi_hold %] + + + [% ELSE %] + + [% END %] + + [% IF ( multi_hold ) %] + + + [% END %] + +
+
+
+
Enter club id or partial name:
+ + + [% IF multi_hold %] + + + [% ELSE %] + + [% END %] + + [% IF ( multi_hold ) %] + + + [% END %] +
+
+
+
+

+ [% IF borrowers %] [% INCLUDE 'circ-patron-search-results.inc' destination = "holds" %] + [% ELSIF clubs %] + [% INCLUDE 'clubs-table.inc' destination = "holds" %] [% END %] -

+

+
+ [% ELSIF club %] +
+
+
+ Hold details +
- [% IF ( multi_hold ) %] - - - [% END %] -
+ [% IF ( multi_hold ) %] + + + + + [% FOREACH biblioloo IN biblioloop %] + + + [% END %] + [% ELSE %] + + + + [% END # /IF multi_hold %] +
    +
  1. + Club: [% club.name | html %] +
  2. +
  3. + Description: [% club.description | html %] +
  4. +
  5. + + +
  6. +
+

Members

+
    + [% FOREACH member IN members %] + [% SET patron = member.patron %] +
  1. + + [% IF member.exceeded_maxreserves %] +
    + + Too many holds: Patron can only place a maximum of [% maxreserves | html %] total holds. +
    + [% END %] + [% IF ( member.expiry ) %] +
    + + Account has expired +
    + [% END %] + [% IF patron.is_debarred %] +
    + + Patron has restrictions +
    + [% END %] + [% IF amount_outstanding && Koha.Preference('maxoutstanding') && amount_outstanding > Koha.Preference('maxoutstanding') %] +
    + + Patron has outstanding fines: [% member.amount_outstanding | $Price %] +
    + [% END %] + [% IF ( member.diffbranch ) %] +
    + + Pickup library is different. Patron's home library: ([% Branches.GetName(patron.branchcode) | html %] / [% patron.branchcode | html %] ) +
    + [% END %] +
  2. + [% END %] +
+ [% UNLESS ( multi_hold ) %] +
+ +
+ [% ELSE %] + + + + + [% UNLESS ( item_level_itypes ) %] + + [% END %] + + + + [% FOREACH biblioloo IN biblioloop %] + [% IF ( biblioloo.warn ) %] + + [% ELSE %] + + [% END %] + + [% END %] + + [% UNLESS ( item_level_itypes ) %] + + [% END %] + + + + [% END # /FOREACH biblioloo %] +
 TitleItem typePriorityInformation
+ [% UNLESS ( biblioloo.warn ) %] + + +
    +
  • + [% biblioloo.title | html %] +
  • + [% IF ( biblioloo.publicationyear ) %] +
  • + Publication year: [% biblioloo.publicationyear | html %] +
  • + [% END %] +
+ [% IF ( biblioloo.warn ) %] + + [% END %] +
+ [% biblioloo.itypename | html %] + [% biblioloo.rank | html %] + [% IF ( biblioloo.checked_previously ) %] + Patron has previously checked out this title
+ [% END %] + [% IF ( biblioloo.alreadyres ) %] +
    + [% ELSE %] + [% IF ( biblioloo.none_avail ) %] +
      + [% END %] + [% END %] + + [% IF ( biblioloo.alreadyres ) %] +
    • + [% patron.firstname | html %] [% patron.surname | html %] + already has a hold on this item +
    • + [% END %] + [% IF ( biblioloo.none_avail ) %] +
    • No items are available to be placed on hold
    • + [% END %] + + [% IF ( biblioloo.alreadyres ) %] +
    + [% ELSE %] + [% IF ( biblioloo.none_avail ) %] +
+ [% END %] + [% END %] +
+ [% END %] + +
[% ELSIF NOT noitems # /UNLESS patron %] [% IF ( checked_previously && !multi_hold ) %] @@ -183,7 +377,7 @@
Hold details -
+ @@ -752,6 +946,11 @@ columns_settings_borrowers_table = [% ColumnsSettings.GetColumns( 'circ', 'circulation', 'table_borrowers', 'json' ) | $raw %] $(document).ready(function() { + [% UNLESS clubs %] + $('#circ_holds_select').tabs({active: 0}); + [% ELSE %] + $('#circ_holds_select').tabs({active: 1}); + [% END %] function ToggleHoldsToPlace() { if ( $("#requestany").prop('checked') ) { $("#holds_to_place_count").prop('disabled', false); @@ -793,6 +992,31 @@ "margin-right":"0em" }); + $("#club-request-form").on("submit", function() { + let $t = $(this); + $('.clubalert').addClass('hide'); + let options = { + url: $t.attr('action'), + method: $t.attr('method').toUpperCase(), + contentType: 'application/json', + data: JSON.stringify({ + biblio_id: biblionumber, + pickup_library_id: $('select[name="pickup"]').val() + }) + }; + if($('input[name="checkitem"]:checked').length) + options.data.item_id = $('input[name="checkitem"]:checked').val(); + $.ajax(options) + .then(function(result) { + let url = 'request.pl?biblionumber='+biblionumber+($('input[name="multi_hold"]').length && $('input[name="multi_hold"]').val()?'&multi_hold=1':''); + document.location = url; + }) + .fail(function(err) { + $('.clubalert').removeClass('hide').html(err.responseJSON.error); + }); + return false; + }); + [% UNLESS ( multi_hold ) %] $("#hold-request-form").on("submit", function(){ return check(); diff --git a/reserve/request.pl b/reserve/request.pl index 49ea545826..c7832434a3 100755 --- a/reserve/request.pl +++ b/reserve/request.pl @@ -53,6 +53,7 @@ use Koha::Items; use Koha::ItemTypes; use Koha::Libraries; use Koha::Patrons; +use Koha::Clubs; my $dbh = C4::Context->dbh; my $input = new CGI; @@ -75,8 +76,12 @@ my $itemtypes = { map { $_->{itemtype} => $_ } @{ Koha::ItemTypes->search_with_l my $findborrower = $input->param('findborrower'); $findborrower = '' unless defined $findborrower; $findborrower =~ s|,| |g; +my $findclub = $input->param('findclub'); +$findclub = '' unless defined $findclub && !$findborrower; my $borrowernumber_hold = $input->param('borrowernumber') || ''; +my $club_hold = $input->param('club')||''; my $messageborrower; +my $messageclub; my $warnings; my $messages; my $exceeded_maxreserves; @@ -135,6 +140,25 @@ if ($findborrower) { } } +if($findclub) { + my $club = Koha::Clubs->find( { name => $findclub } ); + if( $club ) { + $club_hold = $club->id; + } else { + my @clubs = Koha::Clubs->search( [ + { name => { like => '%'.$findclub.'%' } }, + { description => { like => '%'.$findclub.'%' } } + ] ); + if( scalar @clubs == 1 ) { + $club_hold = $clubs[0]->id; + } elsif ( @clubs ) { + $template->param( clubs => \@clubs ); + } else { + $messageclub = "'$findclub'"; + } + } +} + my @biblionumbers = (); my $biblionumber = $input->param('biblionumber'); my $biblionumbers = $input->param('biblionumbers'); @@ -203,7 +227,54 @@ if ($borrowernumber_hold && !$action) { ); } -$template->param( messageborrower => $messageborrower ); +if ($club_hold && !$borrowernumber_hold && !$action) { + my $club = Koha::Clubs->find($club_hold); + + my $enrollments = $club->club_enrollments; + + my $maxreserves = C4::Context->preference('maxreserves'); + my $new_reserves_count = scalar( @biblionumbers ); + + my @members; + + while(my $enrollment = $enrollments->next) { + next if $enrollment->is_canceled; + my $member = { patron => $enrollment->patron->unblessed }; + my $reserves_count = $enrollment->patron->holds->count; + if ( $maxreserves + && ( $reserves_count + $new_reserves_count > $maxreserves ) ) + { + $member->{new_reserves_allowed} = $maxreserves - $reserves_count > 0 + ? $maxreserves - $reserves_count + : 0; + $member->{exceeded_maxreserves} = 1; + } + my $expiry_date = $enrollment->patron->dateexpiry; + $member->{expiry} = 0; # flag set if patron account has expired + if ($expiry_date and $expiry_date ne '0000-00-00' and + Date_to_Days(split /-/,$date) > Date_to_Days(split /-/,$expiry_date)) { + $member->{expiry} = 1; + } + $member->{amount_outstanding} = $enrollment->patron->account->balance; + if ( $enrollment->patron->branchcode ne C4::Context->userenv->{'branch'} ) { + $member->{diffbranch} = 1; + } + + push @members, $member; + } + + $template->param( + club => $club, + members => \@members, + maxreserves => $maxreserves, + new_reserves_count => $new_reserves_count + ); +} + +$template->param( + messageborrower => $messageborrower, + messageclub => $messageclub +); # FIXME launch another time GetMember perhaps until (Joubu: Why?) my $patron = Koha::Patrons->find( $borrowernumber_hold ); -- 2.17.1