From dcae041e9ca86096a65fb02092e4b3c031a4c542 Mon Sep 17 00:00:00 2001 From: Martin Stenberg Date: Sun, 23 Aug 2015 18:22:50 +0200 Subject: [PATCH] Bug 14224: Allow patron notes about item shown at check in This patch adds a "Note" input field to checked out items in the "your summery" section. The field allows patrons to write notes about the item checked out, such as "this DVD is scratched", "the binding was torn", etc. The note will be emailed to the library and displayed on item check in. Patch adds two fields to the "issues" table - "note" and "notedate". Test Plan: 1) Apply this patch 2) Run updatedatabase 3) Make sure the branch has an email address specified 4) Check out an item to a patron 5) Log in with the patron you checked out the item for 6) In "your summery", write a message in the new "Note" field for the item checked out. Hit ENTER to save/send. 7) Success message should be seen and an email sent to the email address specified for the branch which the item was checked out from. 8) Log in with staff account and check in the item 9) The note should be shown The note can be changed by the patron at any time while the item is checked out. Each change will send a new email to the branch. Only the latest note version will be shown on check in. --- C4/Circulation.pm | 43 +++++++- circ/returns.pl | 3 + .../bug_14224-add_new_issue_columns.sql | 2 + installer/data/mysql/kohastructure.sql | 2 + .../intranet-tmpl/prog/en/modules/circ/returns.tt | 10 ++ .../bootstrap/en/modules/opac-sendissuenote.tt | 21 ++++ .../opac-tmpl/bootstrap/en/modules/opac-user.tt | 45 ++++++++ opac/opac-user.pl | 120 ++++++++++++++++++++- 8 files changed, 242 insertions(+), 4 deletions(-) create mode 100644 installer/data/mysql/atomicupdate/bug_14224-add_new_issue_columns.sql create mode 100644 koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-sendissuenote.tt diff --git a/C4/Circulation.pm b/C4/Circulation.pm index 7813e33..04a71d6 100644 --- a/C4/Circulation.pm +++ b/C4/Circulation.pm @@ -92,6 +92,7 @@ BEGIN { &GetBranchItemRule &GetBiblioIssues &GetOpenIssue + &SetIssueNote &AnonymiseIssueHistory &CheckIfIssuedToPatron &IsItemIssued @@ -2450,7 +2451,7 @@ sub GetOpenIssue { =head2 GetIssues $issues = GetIssues({}); # return all issues! - $issues = GetIssues({ borrowernumber => $borrowernumber, biblionumber => $biblionumber }); + $issues = GetIssues({ borrowernumber => $borrowernumber, biblionumber => $biblionumber, issue_id => $issue_id }); Returns all pending issues that match given criteria. Returns a arrayref or undef if an error occurs. @@ -2465,6 +2466,8 @@ Allowed criteria are: =item * itemnumber +=item * issue_id + =back =cut @@ -2474,7 +2477,7 @@ sub GetIssues { # Build filters my @filters; - my @allowed = qw(borrowernumber biblionumber itemnumber); + my @allowed = qw(borrowernumber biblionumber itemnumber issue_id); foreach (@allowed) { if (defined $criteria->{$_}) { push @filters, { @@ -3968,6 +3971,42 @@ sub GetPendingOnSiteCheckouts { |, { Slice => {} } ); } +=head2 SetIssueNote + + &SetIssueNote($issue_id, $note); + +Sets a note to the issuenotes table for the given issue. + +=over 4 + +=item C<$issue_id> is the id of the issue for which to set the note + +=item C<$note> is the note to set + +=back + +Returns: + True on success + False on failure + +=cut + +sub SetIssueNote { + my ( $issue_id, $note) = @_; + + my $dbh = C4::Context->dbh; + + if ( ! ( $issue_id && $note ) or not $issue_id =~ /\d+/) { + return; + } + + my $query = "UPDATE issues SET notedate=NOW(),note=? WHERE issue_id=?"; + my $sth = $dbh->prepare($query); + $sth->execute( $note, $issue_id ); + return 1; +} + + __END__ =head1 AUTHOR diff --git a/circ/returns.pl b/circ/returns.pl index 8084727..ac95f92 100755 --- a/circ/returns.pl +++ b/circ/returns.pl @@ -269,6 +269,8 @@ if ($barcode) { my $hbr = GetBranchItemRule($biblio->{'homebranch'}, $itemtype)->{'returnbranch'} || "homebranch"; my $returnbranch = $biblio->{$hbr} ; + my $issue = GetItemIssue($itemnumber); + $template->param( title => $biblio->{'title'}, homebranch => $biblio->{'homebranch'}, @@ -281,6 +283,7 @@ if ($barcode) { biblionumber => $biblio->{'biblionumber'}, borrower => $borrower, additional_materials => $biblio->{'materials'}, + issue => $issue, ); my %input = ( diff --git a/installer/data/mysql/atomicupdate/bug_14224-add_new_issue_columns.sql b/installer/data/mysql/atomicupdate/bug_14224-add_new_issue_columns.sql new file mode 100644 index 0000000..9f43c77 --- /dev/null +++ b/installer/data/mysql/atomicupdate/bug_14224-add_new_issue_columns.sql @@ -0,0 +1,2 @@ +ALTER TABLE issues ADD COLUMN `note` mediumtext default NULL; -- issue note text +ALTER TABLE issues ADD COLUMN `notedate` datetime default NULL; -- datetime of issue note (yyyy-mm-dd hh:mm::ss) diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql index 13b6a6a..b2487b8 100644 --- a/installer/data/mysql/kohastructure.sql +++ b/installer/data/mysql/kohastructure.sql @@ -1156,6 +1156,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`), KEY `issuesborridx` (`borrowernumber`), KEY `itemnumber_idx` (`itemnumber`), diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt index f710cdb..b5d0dbc 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt @@ -123,6 +123,16 @@ $(document).ready(function () {
This item is part of a rotating collection and needs to be transferred to [% collectionBranch %]
[% END %] + +[% IF ( issue.note) %] +
+

Patron note

+

[% issue.notedate %]

+

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

+

[% issue.note %]

+
+[% END %] + [% IF ( fines ) %]
diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-sendissuenote.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-sendissuenote.tt new file mode 100644 index 0000000..e4a655b --- /dev/null +++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-sendissuenote.tt @@ -0,0 +1,21 @@ + +Issue note for [% title %] [% author %] + + +[% USE HtmlToText %] + +
+[% FILTER html2text %] +

Hi,

+

[% MEMBER.firstname %] [% MEMBER.surname %] sent you the following note related to the check out of +

[% title %] [% author %].

+[% END %] + + + + +[% FILTER html2text %] + [% note %] +[% END %] + + diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt index b5bc8d2..a45fe44 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt +++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt @@ -98,6 +98,8 @@
[% END # / IF patron_flagged %] + + [% SET OPACMySummaryNote = Koha.Preference('OPACMySummaryNote') %] [% IF OPACMySummaryNote %][% OPACMySummaryNote %][% END %] @@ -142,6 +144,7 @@ [% IF ( OPACMySummaryHTML ) %] Links [% END %] + Note @@ -254,6 +257,9 @@ [% IF ( OPACMySummaryHTML ) %] [% ISSUE.MySummaryHTML %] [% END %] + + + [% END # /FOREACH ISSUES %] @@ -732,6 +738,45 @@ [% END %] [% END %] + $("input[name='note']").keyup(function(e){ + var self = $(this); + if(e.which == 13) { + e.preventDefault(); + if(!self.val()) return; + + var ajaxData = { + 'issue_id': $(this).data('issue_id'), + 'note': self.val() + }; + + $.ajax({ + url: '/cgi-bin/koha/opac-user.pl', + type: 'POST', + dataType: 'json', + data: ajaxData, + }) + .done(function(data) { + if(data.status == 'success') { + $("#notesaved").removeClass("alert-error"); + $("#notesaved").addClass("alert-info"); + $("#notesaved").html("Note saved and sent to library."); + } else { + $("#notesaved").removeClass("alert-info"); + $("#notesaved").addClass("alert-error"); + $("#notesaved").html(data.error); + } + }) + .fail(function(data) { + $("#notesaved").removeClass("alert-info"); + $("#notesaved").addClass("alert-error"); + $("#notesaved").html("Ajax request failed. Could not save note."); + }) + .always(function() { + $("#notesaved").show(); + }); + } + }); + $( ".suspend-until" ).datepicker({ minDate: 1 }); // Require that "until date" be in the future }); //]]> diff --git a/opac/opac-user.pl b/opac/opac-user.pl index d4fbbe3..457af92 100755 --- a/opac/opac-user.pl +++ b/opac/opac-user.pl @@ -22,18 +22,24 @@ use strict; use CGI qw ( -utf8 ); -use C4::Auth; +use Mail::Sendmail; +use MIME::QuotedPrint; +use Carp; +use C4::Auth qw(:DEFAULT check_cookie_auth); use C4::Koha; use C4::Circulation; use C4::Reserves; use C4::Members; use C4::Members::AttributeTypes; use C4::Members::Attributes qw/GetBorrowerAttributeValue/; -use C4::Output; +use CGI::Cookie; # need to check cookies before having CGI parse the POST request +use C4::Output qw(:DEFAULT :ajax); +use C4::Scrubber; use C4::Biblio; use C4::Items; use C4::Letters; use C4::Branch; # GetBranches +use Koha::Email; use Koha::DateUtils; use Koha::Borrower::Debarments qw(IsDebarred); @@ -54,6 +60,116 @@ BEGIN { } } +sub ajax_auth_cgi { # returns CGI object + my $needed_flags = shift; + my %cookies = fetch CGI::Cookie; + my $input = CGI->new; + my $sessid = $cookies{'CGISESSID'}->value; + my ($auth_status, $auth_sessid) = check_cookie_auth($sessid, $needed_flags); + if ($auth_status ne "ok") { + output_with_http_headers $input, undef, + "window.alert('Your CGI session cookie ($sessid) is not current. " . + "Please refresh the page and try again.');\n", 'js'; + exit 0; + } + return $input; +} + +my $is_ajax = is_ajax(); +my $query = ($is_ajax) ? &ajax_auth_cgi({}) : CGI->new(); +if ($is_ajax) { + 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 = "success"; + my $error = ""; + my ($error, $member, $issue); + + my ( $template, $borrowernumber, $cookie ) = get_template_and_user( + { + template_name => "opac-sendissuenote.tt", + query => $query, + type => "opac", + authnotrequired => 1, + } + ); + + # verify issue_id + if ( $issue_id =~ /\d+/ ) { + $member = GetMember(borrowernumber => $borrowernumber); + ($issue) = @{C4::Circulation::GetIssues({issue_id => $issue_id})}; + + if ( $issue->{'borrowernumber'} != $borrowernumber ) { + $status = "fail"; + $error = "Invalid issue id!"; + } + } else { + $status = "fail"; + $error = "Invalid issue id!"; + } + + if ( (not $error) && SetIssueNote($issue_id, $clean_note) ) { + my $branch = GetBranchDetail($issue->{'branchcode'}); + + if ( $branch->{'branchemail'} ) { + my $biblio = GetBiblioFromItemNumber($issue->{'itemnumber'}); + my $message = Koha::Email->new(); + my %mail = $message->create_message_headers(); + + $template->param( + author => $biblio->{'author'}, + title => $biblio->{'title'}, + MEMBER => $member, + note => $clean_note, + ); + + # Getting template result + my $template_res = $template->output(); + my $body; + + # Analysing information and getting mail properties + if ( $template_res =~ /(.*)/s ) { + $mail{subject} = $1; + $mail{subject} =~ s|\n?(.*)\n?|$1|; + } + else { $mail{'subject'} = "no subject"; } + $mail{subject} = Encode::encode("UTF-8", $mail{subject}); + + my $email_header = ""; + if ( $template_res =~ /
(.*)/s ) { + $email_header = $1; + $email_header =~ s|\n?(.*)\n?|$1|; + $email_header = encode_qp(Encode::encode("UTF-8", $email_header)); + } + + if ( $template_res =~ /(.*)/s ) { + $body = $1; + $body =~ s|\n?(.*)\n?|$1|; + $body = encode_qp(Encode::encode("UTF-8", $body)); + } + + $mail{to} = $branch->{'branchemail'}; + $mail{from} = $member->{'email'} || ($branch->{'branchemail'}=~s/^.+@/noreply@/ && $branch->{'branchemail'}); + $mail{body} = $email_header . $body; + + unless ( sendmail(%mail) ) { + $status = "fail"; + $error = "Could not send message to library. Message will still show at check in."; + carp "Error sending mail: $Mail::Sendmail::error \n"; + } + } + } else { + $status = "fail"; + $error = "Could not save note. Invalid issue id or empty note."; + } + + my $response = "{\"status\": \"$status\", \"note\": \"$clean_note\", \"issue_id\": \"$issue_id\", \"error\": \"$error\"}"; + output_with_http_headers($query, undef, $response, 'js'); + exit; +} + + my ( $template, $borrowernumber, $cookie ) = get_template_and_user( { template_name => "opac-user.tt", -- 2.5.0