@@ -, +, @@ the schema changes for you) out to yourself) --- C4/Circulation.pm | 1 - Koha/Schema/Result/Issue.pm | 23 ++++- circ/returns.pl | 3 + .../bug_14224-add_new_issue_columns.sql | 2 + .../bug_14224-add_patron_notice_to_letters.sql | 1 + .../atomicupdate/bug_14224-issue_notes_syspref.sql | 1 + installer/data/mysql/kohastructure.sql | 2 + installer/data/mysql/sysprefs.sql | 1 + .../en/modules/admin/preferences/circulation.pref | 6 ++ .../intranet-tmpl/prog/en/modules/circ/returns.tt | 10 ++ koha-tmpl/intranet-tmpl/prog/js/checkouts.js | 13 ++- .../bootstrap/en/modules/opac-issue-note.tt | 54 ++++++++++ .../opac-tmpl/bootstrap/en/modules/opac-user.tt | 94 ++++++++++++++++++ opac/opac-issue-note.pl | 89 +++++++++++++++++ opac/opac-user.pl | 3 + opac/svc/patron_notes | 109 +++++++++++++++++++++ svc/checkin | 10 +- 17 files changed, 414 insertions(+), 8 deletions(-) create mode 100644 installer/data/mysql/atomicupdate/bug_14224-add_new_issue_columns.sql create mode 100644 installer/data/mysql/atomicupdate/bug_14224-add_patron_notice_to_letters.sql create mode 100644 installer/data/mysql/atomicupdate/bug_14224-issue_notes_syspref.sql create mode 100644 koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-issue-note.tt create mode 100755 opac/opac-issue-note.pl create mode 100755 opac/svc/patron_notes --- a/C4/Circulation.pm +++ a/C4/Circulation.pm @@ -2511,7 +2511,6 @@ sub GetOpenIssue { =head2 GetIssues $issues = GetIssues({}); # return all issues! - $issues = GetIssues({ borrowernumber => $borrowernumber, biblionumber => $biblionumber }); Returns all pending issues that match given criteria. Returns a arrayref or undef if an error occurs. --- a/Koha/Schema/Result/Issue.pm +++ a/Koha/Schema/Result/Issue.pm @@ -101,6 +101,17 @@ __PACKAGE__->table("issues"); default_value: 0 is_nullable: 0 +=head2 note + + data_type: 'mediumtext' + is_nullable: 1 + +=head2 notedate + + data_type: 'datetime' + datetime_undef_if_invalid: 1 + is_nullable: 1 + =cut __PACKAGE__->add_columns( @@ -151,6 +162,14 @@ __PACKAGE__->add_columns( }, "onsite_checkout", { data_type => "integer", default_value => 0, is_nullable => 0 }, + "note", + { data_type => "mediumtext", is_nullable => 1 }, + "notedate", + { + data_type => "datetime", + datetime_undef_if_invalid => 1, + is_nullable => 1, + }, ); =head1 PRIMARY KEY @@ -222,8 +241,8 @@ __PACKAGE__->belongs_to( ); -# Created by DBIx::Class::Schema::Loader v0.07039 @ 2015-11-04 12:00:58 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:kREecsHr6wZPiokS946BHw +# Created by DBIx::Class::Schema::Loader v0.07042 @ 2016-12-01 02:20:55 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:a7FCuypwxtuE6oJjEnRJRg __PACKAGE__->belongs_to( "borrower", --- a/circ/returns.pl +++ a/circ/returns.pl @@ -282,6 +282,8 @@ if ($barcode) { my $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => '', kohafield =>'items.materials', authorised_value => $materials }); $materials = $descriptions->{lib} // ''; + my $issue = GetItemIssue($itemnumber); + $template->param( title => $biblio->{'title'}, homebranch => $biblio->{'homebranch'}, @@ -295,6 +297,7 @@ if ($barcode) { biblionumber => $biblio->{'biblionumber'}, borrower => $borrower, additional_materials => $materials, + issue => $issue, ); my %input = ( --- a/installer/data/mysql/atomicupdate/bug_14224-add_new_issue_columns.sql +++ a/installer/data/mysql/atomicupdate/bug_14224-add_new_issue_columns.sql @@ -0,0 +1,2 @@ +ALTER IGNORE TABLE issues ADD `note` mediumtext default NULL AFTER `onsite_checkout`; +ALTER IGNORE TABLE issues ADD `notedate` datetime default NULL AFTER `note`; --- a/installer/data/mysql/atomicupdate/bug_14224-add_patron_notice_to_letters.sql +++ a/installer/data/mysql/atomicupdate/bug_14224-add_patron_notice_to_letters.sql @@ -0,0 +1, @@ +INSERT IGNORE INTO letter (`module`, `code`, `branchcode`, `name`, `is_html`, `title`, `content`, `message_transport_type`) VALUES ('circulation', 'PATRON_NOTE', '', 'Patron note on item', '0', 'Patron issue note', '<> <> has added a note to the item <> - <> (<>).','email'); --- a/installer/data/mysql/atomicupdate/bug_14224-issue_notes_syspref.sql +++ a/installer/data/mysql/atomicupdate/bug_14224-issue_notes_syspref.sql @@ -0,0 +1, @@ +INSERT IGNORE INTO systempreferences (`variable`, `value`, `options`, `explanation`,`type`) VALUES ('AllowIssueNotes', '0', NULL, 'Allow patrons to submit notes about checked out items.','YesNo'); --- a/installer/data/mysql/kohastructure.sql +++ a/installer/data/mysql/kohastructure.sql @@ -1744,6 +1744,8 @@ CREATE TABLE `issues` ( -- information related to check outs or issues `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, -- the date and time this record was last touched `issuedate` datetime default NULL, -- date the item was checked out or issued `onsite_checkout` int(1) NOT NULL default 0, -- in house use flag + `note` mediumtext default NULL, -- issue note text + `notedate` datetime default NULL, -- datetime of issue note (yyyy-mm-dd hh:mm::ss) PRIMARY KEY (`issue_id`), UNIQUE KEY `itemnumber` (`itemnumber`), KEY `issuesborridx` (`borrowernumber`), --- a/installer/data/mysql/sysprefs.sql +++ a/installer/data/mysql/sysprefs.sql @@ -22,6 +22,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('AllowHoldPolicyOverride','0',NULL,'Allow staff to override hold policies when placing holds','YesNo'), ('AllowHoldsOnDamagedItems','1','','Allow hold requests to be placed on damaged items','YesNo'), ('AllowHoldsOnPatronsPossessions','1',NULL,'Allow holds on records that patron have items of it','YesNo'), +('AllowIssueNotes', '0', NULL, 'Allow patrons to submit notes about checked out items.','YesNo'), ('AllowItemsOnHoldCheckout','0','','Do not generate RESERVE_WAITING and RESERVED warning when checking out items reserved to someone else. This allows self checkouts for those items.','YesNo'), ('AllowItemsOnHoldCheckoutSCO','0','','Do not generate RESERVE_WAITING and RESERVED warning in the SCO module when checking out items reserved to someone else. This allows self checkouts for those items.','YesNo'), ('AllowMultipleCovers','0','1','Allow multiple cover images to be attached to each bibliographic record.','YesNo'), --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref @@ -136,6 +136,12 @@ Circulation: yes: Show no: "Do not show" - all items in the "Checked-in items" list, even items that were not checked out. + - + - pref: AllowIssueNotes + choices: + yes: Allow + no: "Don't allow" + - patrons to submit notes about checked out items. Checkout Policy: - --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt @@ -183,6 +183,16 @@ $(document).ready(function () { [% END %] + +[% IF ( issue.note) %] +
+

Patron note

+

[% issue.notedate %]

+

[% title |html %] [% author %]

+

[% issue.note %]

+
+[% END %] + [% IF ( fines ) %]
--- a/koha-tmpl/intranet-tmpl/prog/js/checkouts.js +++ a/koha-tmpl/intranet-tmpl/prog/js/checkouts.js @@ -67,6 +67,7 @@ $(document).ready(function() { content = CIRCULATION_RETURNED; $(id).parent().parent().addClass('ok'); $('#date_due_' + data.itemnumber).html(CIRCULATION_RETURNED); + $('#patron_note_' + data.itemnumber).html("Patron note: " + data.patronnote); } else { content = CIRCULATION_NOT_RETURNED; $(id).parent().parent().addClass('warn'); @@ -219,7 +220,7 @@ $(document).ready(function() { }, { "mDataProp": function ( oObj ) { - title = "" + oObj.title; @@ -243,7 +244,7 @@ $(document).ready(function() { if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) { span_class = "circ-hlt"; } - title += " - " + oObj.itemnotes + "" + title += " - " + oObj.itemnotes + ""; } if ( oObj.itemnotes_nonpublic ) { @@ -251,7 +252,7 @@ $(document).ready(function() { if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) { span_class = "circ-hlt"; } - title += " - " + oObj.itemnotes_nonpublic + "" + title += " - " + oObj.itemnotes_nonpublic + ""; } var onsite_checkout = ''; @@ -259,6 +260,8 @@ $(document).ready(function() { onsite_checkout += " (" + INHOUSE_USE + ")"; } + var patron_note = " "; + title += " " + "" + oObj.barcode + "" - + onsite_checkout; + + onsite_checkout + + "
" + + patron_note return title; }, --- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-issue-note.tt +++ a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-issue-note.tt @@ -0,0 +1,54 @@ +[% USE Koha %] +[% USE KohaDates %] +[% USE Branches %] +[% INCLUDE 'doc-head-open.inc' %] +[% IF ( LibraryNameTitle ) %][% LibraryNameTitle %][% ELSE %]Koha online[% END %] catalog › Your library home +[% INCLUDE 'doc-head-close.inc' %] +[% BLOCK cssinclude %][% END %] + +[% INCLUDE 'bodytag.inc' bodyid='opac-issue-note' %] +[% INCLUDE 'masthead.inc' %] + +
+ + +
+
+
+ +
+
+
+

Editing issue note for [% title %] [% author %]

+ [% IF not(Koha.Preference("AllowIssueNotes")) %] + Issue notes have not been enabled. Please contact the library. + [% ELSE %] +
+
+ + + + + + +
+
+ Cancel +
+
+ [% END %] +
+
+
+
+ +
+ +[% INCLUDE 'opac-bottom.inc' %] +[% BLOCK jsinclude %][% END %] --- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt +++ a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt @@ -120,6 +120,8 @@ Using this account is not recommended because some parts of Koha will not functi
[% END # / IF patron_flagged %] + + [% SET OPACMySummaryNote = Koha.Preference('OPACMySummaryNote') %] [% IF OPACMySummaryNote %][% OPACMySummaryNote %][% END %] @@ -165,6 +167,9 @@ Using this account is not recommended because some parts of Koha will not functi [% IF ( OPACMySummaryHTML ) %] Links [% END %] + [% IF ( AllowIssueNotes ) %] + Note + [% END %] @@ -283,6 +288,13 @@ Using this account is not recommended because some parts of Koha will not functi [% IF ( OPACMySummaryHTML ) %] [% ISSUE.MySummaryHTML %] [% END %] + [% IF ( AllowIssueNotes ) %] + + + + Edit / Create note + + [% END %] [% END # /FOREACH ISSUES %] @@ -883,6 +895,88 @@ Using this account is not recommended because some parts of Koha will not functi [% END %] [% END %] + [% IF ( AllowIssueNotes ) %] + + /* If JS enabled, show button, otherwise show link to redirect to a page where note can be submitted */ + $("a[name='nonjs_submitnote']").hide(); + + $("input[name='note']").prop('readonly', false); + $("input[name='note']").keyup(function(e){ + /* prevent submitting of renewselected form */ + if(e.which == 13) + e.preventDefault(); + + var $btn_save = $('#save_'+$(this).data('issue_id')); + var origvalue = $(this).data('origvalue'); + var value = $(this).val(); + + if(origvalue != value) { + if(origvalue != "") + $btn_save.text('Submit changes'); + else + $btn_save.text('Submit note'); + $btn_save.show(); + } else { + $btn_save.hide(); + } + }); + + $("a[name='js_submitnote']").click(function(e){ + var $self = $(this); + var title = $(this).parent().siblings('.title').html(); + var $noteinput = $(this).siblings('input[name="note"]').first(); + + var ajaxData = { + 'action': 'issuenote', + 'issue_id': $noteinput.data('issue_id'), + 'note': $noteinput.val(), + }; + + $.ajax({ + url: '/cgi-bin/koha/svc/patron_notes/', + type: 'POST', + dataType: 'json', + data: ajaxData, + }) + .done(function(data) { + var message = ""; + if(data.status == 'saved') { + $("#notesaved").removeClass("alert-error"); + $("#notesaved").addClass("alert-info"); + $noteinput.data('origvalue', data.note); + $noteinput.val(data.note); + message = "

Your note about " + title + " has been saved and sent to the library.

"; + $self.hide(); + } else if(data.status == 'removed') { + $("#notesaved").removeClass("alert-error"); + $("#notesaved").addClass("alert-info"); + $noteinput.data('origvalue', ""); + $noteinput.val(""); + message = "

Your note about " + title + " was removed.

"; + $self.hide(); + } else { + $("#notesaved").removeClass("alert-info"); + $("#notesaved").addClass("alert-error"); + message = "

Your note about " + title + " could not be saved.

" + + "

" + data.error + "

"; + } + + message += "

" + data.note + "

"; + $("#notesaved").html(message); + }) + .fail(function(data) { + $("#notesaved").removeClass("alert-info"); + $("#notesaved").addClass("alert-error"); + var message = "

Your note about " + title + " could not be saved.

" + + "

Ajax request has failed.

"; + $("#notesaved").html(message); + }) + .always(function() { + $("#notesaved").show(); + }); + }); + [% END %] + $( ".suspend-until" ).datepicker({ minDate: 1 }); // Require that "until date" be in the future }); //]]> --- a/opac/opac-issue-note.pl +++ a/opac/opac-issue-note.pl @@ -0,0 +1,89 @@ +#!/usr/bin/perl + +# Copyright 2016 Aleisha Amohia +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use CGI qw ( -utf8 ); +use C4::Koha; +use C4::Context; +use C4::Scrubber; +use C4::Members; +use C4::Output; +use C4::Auth; +use C4::Biblio; +use C4::Letters; +use Koha::Issues; +use Koha::DateUtils; + +my $query = new CGI; + +my ( $template, $borrowernumber, $cookie ) = get_template_and_user( + { + template_name => "opac-issue-note.tt", + query => $query, + type => "opac", + authnotrequired => 0, + debug => 1, + } +); + +my $member = C4::Members::GetMember( borrowernumber => $borrowernumber ); +$template->param( + firstname => $member->{'firstname'}, + surname => $member->{'surname'}, + borrowernumber => $borrowernumber, +); + +my $issue_id = $query->param('issue_id'); +my $issueobj = Koha::Issues->find( $issue_id ); +my $issue = $issueobj->unblessed; +my $itemnumber = $issue->{'itemnumber'}; +my $biblio = GetBiblioFromItemNumber($itemnumber); +$template->param( + issue_id => $issue_id, + title => $biblio->{'title'}, + author => $biblio->{'author'}, + note => $issue->{'note'}, + itemnumber => $issue->{'itemnumber'}, +); + +my $action = $query->param('action') || ""; +if ( $action eq 'issuenote' && C4::Context->preference('AllowIssueNotes') ) { + my $note = $query->param('note'); + my $scrubber = C4::Scrubber->new(); + my $clean_note = $scrubber->scrub($note); + if ( $issueobj->set({ notedate => dt_from_string(), note => $clean_note })->store ) { + if ($clean_note) { # only send email if note not empty + my $branch = Koha::Libraries->find( $issue->{branchcode} ); + my $letter = C4::Letters::GetPreparedLetter ( + module => 'circulation', + letter_code => 'PATRON_NOTE', + branchcode => $branch, + tables => { + 'biblio' => $biblio->{biblionumber}, + 'borrowers' => $member->{borrowernumber}, + }, + ); + C4::Message->enqueue($letter, $member, 'email'); + } + } + print $query->redirect("/cgi-bin/koha/opac-user.pl"); +} + +output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 }; --- a/opac/opac-user.pl +++ a/opac/opac-user.pl @@ -33,6 +33,7 @@ use C4::Output; use C4::Biblio; use C4::Items; use C4::Letters; +use Koha::Libraries; use Koha::DateUtils; use Koha::Holds; use Koha::Database; @@ -262,6 +263,8 @@ if ($issues){ } my $overduesblockrenewing = C4::Context->preference('OverduesBlockRenewing'); $canrenew = 0 if ($overduesblockrenewing ne 'allow' and $overdues_count == $count); + +$template->param( AllowIssueNotes => C4::Context->preference('AllowIssueNotes') ); $template->param( ISSUES => \@issuedat ); $template->param( issues_count => $count ); $template->param( canrenew => $canrenew ); --- a/opac/svc/patron_notes +++ a/opac/svc/patron_notes @@ -0,0 +1,109 @@ +#!/usr/bin/perl + +# This file is part of Koha. +# +# Copyright 2014 BibLibre +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use C4::Service; +use C4::Auth qw /check_cookie_auth/; +use C4::Letters; +use CGI; +use C4::Output qw(:DEFAULT :ajax); +use C4::Scrubber; +use C4::Circulation; +use C4::Members; +use C4::Biblio; +use Koha::Issues; +use Koha::DateUtils; + +=head1 NAME + +svc/patron_notes - Web service for setting patron notes on items + +=head1 DESCRIPTION + +=cut + +# AJAX requests +my $is_ajax = is_ajax(); +my $query = new CGI; +my ( $auth_status, $sessionID ) = check_cookie_auth( $query->cookie('CGISESSID'), {} ); +if ( $auth_status ne "ok" ) { + exit 0; +} +if ($is_ajax) { + my $action = $query->param('action'); + + # Issue Note + if ( $action eq 'issuenote' && C4::Context->preference('AllowIssueNotes') ) { + my $scrubber = C4::Scrubber->new(); + my $note = $query->param('note'); + my $issue_id = $query->param('issue_id'); + my $clean_note = $scrubber->scrub($note); + my $status = "saved"; + my $error = ""; + my ($member, $issueobj, $issue); + + my ( $template, $borrowernumber, $cookie ) = C4::Auth::get_template_and_user({ + template_name => "opac-user.tt", + query => $query, + type => "opac", + authnotrequired => 1, + }); + + # verify issue_id + if ( $issue_id =~ /\d+/ ) { + $member = GetMember(borrowernumber => $borrowernumber); + $issueobj = Koha::Issues->find($issue_id); + $issue = $issueobj->unblessed; + if ( $issue->{'borrowernumber'} != $borrowernumber ) { + $status = "fail"; + $error = "Invalid issue id!"; + } + } else { + $status = "fail"; + $error = "Invalid issue id!"; + } + + if ( (not $error) && $issueobj->set({ notedate => dt_from_string(), note => $clean_note })->store ) { + if($clean_note) { # only send email if note not empty + my $branch = Koha::Libraries->find( $issue->{branchcode} ); + my $biblio = GetBiblioFromItemNumber($issue->{'itemnumber'}); + my $letter = C4::Letters::GetPreparedLetter ( + module => 'circulation', + letter_code => 'PATRON_NOTE', + branchcode => $branch, + tables => { + 'biblio' => $biblio->{biblionumber}, + 'borrowers' => $member->{borrowernumber}, + }, + ); + C4::Message->enqueue($letter, $member, 'email'); + } else { # note empty, i.e removed + $status = "removed"; + } + } else { + $status = "fail"; + $error = "Perhaps the item has already been checked in?"; + } + + my $response = "{\"status\": \"$status\", \"note\": \"$clean_note\", \"issue_id\": \"$issue_id\", \"error\": \"$error\"}"; + output_with_http_headers($query, undef, $response, 'js'); + exit; + } # END Issue Note +} --- a/svc/checkin +++ a/svc/checkin @@ -1,6 +1,6 @@ #!/usr/bin/perl -# Copyright 2014 ByWater Solutions +# Copyright 2016 Aleisha Amohia # # This file is part of Koha. # @@ -72,6 +72,14 @@ if ( C4::Context->preference("ReturnToShelvingCart") ) { ModItem( $item, $item->{'biblionumber'}, $item->{'itemnumber'} ); } +my $dbh = C4::Context->dbh; +my $query = "SELECT note FROM issues WHERE itemnumber = ?"; +my $sth = $dbh->prepare($query); +$sth->execute($itemnumber); +my $issue = $sth->fetchrow_hashref; +my $patronnote = $issue->{note}; +$data->{patronnote} = $patronnote; + ( $data->{returned} ) = AddReturn( $barcode, $branchcode, $exempt_fine ); print to_json($data); --