@@ -, +, @@ checked out. Hit ENTER to save/send. specified for the branch which the item was checked out from. - This patch is not yet ready for testing. This patch fixes merge - Still to do: - GetIssue/GetIssues - subroutine for ajax code - moving ajax part to svc/ --- C4/Circulation.pm | 41 ++++++- circ/returns.pl | 3 + .../bug_14224-add_new_issue_columns.sql | 2 + .../atomicupdate/bug_14224-issue_notes_syspref.sql | 1 + installer/data/mysql/kohastructure.sql | 30 +++++ installer/data/mysql/sysprefs.sql | 1 + .../en/modules/admin/preferences/circulation.pref | 6 + .../intranet-tmpl/prog/en/modules/circ/returns.tt | 10 ++ .../bootstrap/en/modules/opac-sendissuenote.tt | 21 ++++ .../opac-tmpl/bootstrap/en/modules/opac-user.tt | 96 +++++++++++++++ opac/opac-user.pl | 133 ++++++++++++++++++++- 11 files changed, 340 insertions(+), 4 deletions(-) create mode 100644 installer/data/mysql/atomicupdate/bug_14224-add_new_issue_columns.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-sendissuenote.tt --- a/C4/Circulation.pm +++ a/C4/Circulation.pm @@ -96,6 +96,7 @@ BEGIN { &GetBranchItemRule &GetBiblioIssues &GetOpenIssue + &SetIssueNote &AnonymiseIssueHistory &CheckIfIssuedToPatron &IsItemIssued @@ -2540,7 +2541,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. @@ -2555,6 +2556,8 @@ Allowed criteria are: =item * itemnumber +=item * issue_id + =back =cut @@ -2564,7 +2567,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, { @@ -4133,6 +4136,40 @@ sub _CalculateAndUpdateFine { } } +=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; + + unless ( $issue_id =~ /\d+/ ) { + return; + } + + my $query = "UPDATE issues SET notedate=NOW(),note=? WHERE issue_id=?"; + my $sth = $dbh->prepare($query); + return $sth->execute( $note, $issue_id ); +} + 1; __END__ --- a/circ/returns.pl +++ a/circ/returns.pl @@ -285,6 +285,8 @@ if ($barcode) { $materials = $av->count ? $av->next->lib : ''; } + my $issue = GetItemIssue($itemnumber); + $template->param( title => $biblio->{'title'}, homebranch => $biblio->{'homebranch'}, @@ -298,6 +300,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; +ALTER IGNORE TABLE issues ADD `notedate` datetime default NULL; --- 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 @@ -843,6 +843,36 @@ CREATE TABLE `import_items` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -- +-- Table structure for table `issues` +-- + +DROP TABLE IF EXISTS `issues`; +CREATE TABLE `issues` ( -- information related to check outs or issues + `issue_id` int(11) NOT NULL AUTO_INCREMENT, -- primary key for issues table + `borrowernumber` int(11), -- foreign key, linking this to the borrowers table for the patron this item was checked out to + `itemnumber` int(11), -- foreign key, linking this to the items table for the item that was checked out + `date_due` datetime default NULL, -- datetime the item is due (yyyy-mm-dd hh:mm::ss) + `branchcode` varchar(10) default NULL, -- foreign key, linking to the branches table for the location the item was checked out + `returndate` datetime default NULL, -- date the item was returned, will be NULL until moved to old_issues + `lastreneweddate` datetime default NULL, -- date the item was last renewed + `return` varchar(4) default NULL, + `renewals` tinyint(4) default NULL, -- lists the number of times the item was renewed + `auto_renew` BOOLEAN default FALSE, -- automatic renewal + `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`), + KEY `branchcode_idx` (`branchcode`), + KEY `bordate` (`borrowernumber`,`timestamp`), + CONSTRAINT `issues_ibfk_1` FOREIGN KEY (`borrowernumber`) REFERENCES `borrowers` (`borrowernumber`) ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT `issues_ibfk_2` FOREIGN KEY (`itemnumber`) REFERENCES `items` (`itemnumber`) ON DELETE RESTRICT ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +-- -- Table structure for table `issuingrules` -- --- a/installer/data/mysql/sysprefs.sql +++ a/installer/data/mysql/sysprefs.sql @@ -27,6 +27,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('AllowNotForLoanOverride','0','','If ON, Koha will allow the librarian to loan a not for loan item.','YesNo'), ('AllowOfflineCirculation','0','','If on, enables HTML5 offline circulation functionality.','YesNo'), ('AllowPatronToSetCheckoutsVisibilityForGuarantor', '0', NULL, 'If enabled, the patron can set checkouts to be visible to his or her guarantor', 'YesNo'), +('AllowIssueNotes', '0', NULL, 'Allow patrons to submit notes about checked out items.','YesNo'), ('AllowPKIAuth','None','None|Common Name|emailAddress','Use the field from a client-side SSL certificate to look a user in the Koha database','Choice'), ('AllowPurchaseSuggestionBranchChoice','0','1','Allow user to choose branch when making a purchase suggestion','YesNo'), ('AllowRenewalIfOtherItemsAvailable','0',NULL,'If enabled, allow a patron to renew an item with unfilled holds if other available items can fill that hold.','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/opac-tmpl/bootstrap/en/modules/opac-sendissuenote.tt +++ a/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 %] + + --- 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 %] @@ -164,6 +166,9 @@ Using this account is not recommended because some parts of Koha will not functi [% IF ( OPACMySummaryHTML ) %] Links [% END %] + [% IF ( AllowIssueNotes ) %] + Note + [% END %] @@ -280,6 +285,20 @@ Using this account is not recommended because some parts of Koha will not functi [% IF ( OPACMySummaryHTML ) %] [% ISSUE.MySummaryHTML %] [% END %] + [% IF ( AllowIssueNotes ) %] + + + + + + [% END %] [% END # /FOREACH ISSUES %] @@ -782,6 +801,83 @@ Using this account is not recommended because some parts of Koha will not functi [% END %] [% END %] + [% IF ( AllowIssueNotes ) %] + $("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='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/opac-user.pl', + 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 + " was saved and have been 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-user.pl +++ a/opac/opac-user.pl @@ -22,17 +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::Holds; use Koha::Database; @@ -57,6 +64,126 @@ 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; +} + +# AJAX requests +my $is_ajax = is_ajax(); +my $query = ($is_ajax) ? &ajax_auth_cgi({}) : CGI->new(); +if ($is_ajax) { + my $action = $query->param('action'); + + # Issue Note + if ( $action == '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 ($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) ) { + if($clean_note) { # only send email if note not empty + 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 { # note empty, i.e removed + $status = "removed"; + } + } else { + $status = "fail"; + $error = "Perhaps the item has already been check 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 +} + + my ( $template, $borrowernumber, $cookie ) = get_template_and_user( { template_name => "opac-user.tt", @@ -258,6 +385,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 ); --