View | Details | Raw Unified | Return to bug 14224
Collapse All | Expand All

(-)a/C4/Circulation.pm (-2 / +40 lines)
Lines 92-97 BEGIN { Link Here
92
        &GetBranchItemRule
92
        &GetBranchItemRule
93
		&GetBiblioIssues
93
		&GetBiblioIssues
94
		&GetOpenIssue
94
		&GetOpenIssue
95
        &SetIssueNote
95
		&AnonymiseIssueHistory
96
		&AnonymiseIssueHistory
96
        &CheckIfIssuedToPatron
97
        &CheckIfIssuedToPatron
97
        &IsItemIssued
98
        &IsItemIssued
Lines 2450-2456 sub GetOpenIssue { Link Here
2450
=head2 GetIssues
2451
=head2 GetIssues
2451
2452
2452
    $issues = GetIssues({});    # return all issues!
2453
    $issues = GetIssues({});    # return all issues!
2453
    $issues = GetIssues({ borrowernumber => $borrowernumber, biblionumber => $biblionumber });
2454
    $issues = GetIssues({ borrowernumber => $borrowernumber, biblionumber => $biblionumber, issue_id => $issue_id });
2454
2455
2455
Returns all pending issues that match given criteria.
2456
Returns all pending issues that match given criteria.
2456
Returns a arrayref or undef if an error occurs.
2457
Returns a arrayref or undef if an error occurs.
Lines 2465-2470 Allowed criteria are: Link Here
2465
2466
2466
=item * itemnumber
2467
=item * itemnumber
2467
2468
2469
=item * issue_id
2470
2468
=back
2471
=back
2469
2472
2470
=cut
2473
=cut
Lines 2474-2480 sub GetIssues { Link Here
2474
2477
2475
    # Build filters
2478
    # Build filters
2476
    my @filters;
2479
    my @filters;
2477
    my @allowed = qw(borrowernumber biblionumber itemnumber);
2480
    my @allowed = qw(borrowernumber biblionumber itemnumber issue_id);
2478
    foreach (@allowed) {
2481
    foreach (@allowed) {
2479
        if (defined $criteria->{$_}) {
2482
        if (defined $criteria->{$_}) {
2480
            push @filters, {
2483
            push @filters, {
Lines 3968-3973 sub GetPendingOnSiteCheckouts { Link Here
3968
    |, { Slice => {} } );
3971
    |, { Slice => {} } );
3969
}
3972
}
3970
3973
3974
=head2 SetIssueNote
3975
3976
  &SetIssueNote($issue_id, $note);
3977
3978
Sets a note to the issuenotes table for the given issue.
3979
3980
=over 4
3981
3982
=item C<$issue_id> is the id of the issue for which to set the note
3983
3984
=item C<$note> is the note to set
3985
3986
=back
3987
3988
Returns:
3989
  True on success
3990
  False on failure
3991
3992
=cut
3993
3994
sub SetIssueNote {
3995
    my ( $issue_id, $note) = @_;
3996
3997
    my $dbh  = C4::Context->dbh;
3998
3999
    unless ( $issue_id =~ /\d+/ ) {
4000
      return;
4001
    }
4002
4003
    my $query = "UPDATE issues SET notedate=NOW(),note=? WHERE issue_id=?";
4004
    my $sth = $dbh->prepare($query);
4005
    return $sth->execute( $note, $issue_id );
4006
}
4007
4008
3971
__END__
4009
__END__
3972
4010
3973
=head1 AUTHOR
4011
=head1 AUTHOR
(-)a/circ/returns.pl (+3 lines)
Lines 269-274 if ($barcode) { Link Here
269
    my $hbr = GetBranchItemRule($biblio->{'homebranch'}, $itemtype)->{'returnbranch'} || "homebranch";
269
    my $hbr = GetBranchItemRule($biblio->{'homebranch'}, $itemtype)->{'returnbranch'} || "homebranch";
270
    my $returnbranch = $biblio->{$hbr} ;
270
    my $returnbranch = $biblio->{$hbr} ;
271
271
272
    my $issue = GetItemIssue($itemnumber);
273
272
    $template->param(
274
    $template->param(
273
        title            => $biblio->{'title'},
275
        title            => $biblio->{'title'},
274
        homebranch       => $biblio->{'homebranch'},
276
        homebranch       => $biblio->{'homebranch'},
Lines 281-286 if ($barcode) { Link Here
281
        biblionumber     => $biblio->{'biblionumber'},
283
        biblionumber     => $biblio->{'biblionumber'},
282
        borrower         => $borrower,
284
        borrower         => $borrower,
283
        additional_materials => $biblio->{'materials'},
285
        additional_materials => $biblio->{'materials'},
286
        issue            => $issue,
284
    );
287
    );
285
288
286
    my %input = (
289
    my %input = (
(-)a/installer/data/mysql/atomicupdate/bug_14224-add_new_issue_columns.sql (+2 lines)
Line 0 Link Here
1
ALTER IGNORE TABLE issues ADD COLUMN `note` mediumtext default NULL; -- issue note text
2
ALTER IGNORE TABLE issues ADD COLUMN `notedate` datetime default NULL; -- datetime of issue note (yyyy-mm-dd hh:mm::ss)
(-)a/installer/data/mysql/atomicupdate/bug_14224-issue_notes_syspref.sql (+1 lines)
Line 0 Link Here
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 (+2 lines)
Lines 1156-1161 CREATE TABLE `issues` ( -- information related to check outs or issues Link Here
1156
  `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, -- the date and time this record was last touched
1156
  `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, -- the date and time this record was last touched
1157
  `issuedate` datetime default NULL, -- date the item was checked out or issued
1157
  `issuedate` datetime default NULL, -- date the item was checked out or issued
1158
  `onsite_checkout` int(1) NOT NULL default 0, -- in house use flag
1158
  `onsite_checkout` int(1) NOT NULL default 0, -- in house use flag
1159
  `note` mediumtext default NULL, -- issue note text
1160
  `notedate` datetime default NULL, -- datetime of issue note (yyyy-mm-dd hh:mm::ss)
1159
  PRIMARY KEY (`issue_id`),
1161
  PRIMARY KEY (`issue_id`),
1160
  KEY `issuesborridx` (`borrowernumber`),
1162
  KEY `issuesborridx` (`borrowernumber`),
1161
  KEY `itemnumber_idx` (`itemnumber`),
1163
  KEY `itemnumber_idx` (`itemnumber`),
(-)a/installer/data/mysql/sysprefs.sql (+1 lines)
Lines 25-30 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
25
('AllowMultipleIssuesOnABiblio',1,'Allow/Don\'t allow patrons to check out multiple items from one biblio','','YesNo'),
25
('AllowMultipleIssuesOnABiblio',1,'Allow/Don\'t allow patrons to check out multiple items from one biblio','','YesNo'),
26
('AllowNotForLoanOverride','0','','If ON, Koha will allow the librarian to loan a not for loan item.','YesNo'),
26
('AllowNotForLoanOverride','0','','If ON, Koha will allow the librarian to loan a not for loan item.','YesNo'),
27
('AllowOfflineCirculation','0','','If on, enables HTML5 offline circulation functionality.','YesNo'),
27
('AllowOfflineCirculation','0','','If on, enables HTML5 offline circulation functionality.','YesNo'),
28
('AllowIssueNotes', '0', NULL, 'Allow patrons to submit notes about checked out items.','YesNo'),
28
('AllowPKIAuth','None','None|Common Name|emailAddress','Use the field from a client-side SSL certificate to look a user in the Koha database','Choice'),
29
('AllowPKIAuth','None','None|Common Name|emailAddress','Use the field from a client-side SSL certificate to look a user in the Koha database','Choice'),
29
('AllowPurchaseSuggestionBranchChoice','0','1','Allow user to choose branch when making a purchase suggestion','YesNo'),
30
('AllowPurchaseSuggestionBranchChoice','0','1','Allow user to choose branch when making a purchase suggestion','YesNo'),
30
('AllowRenewalIfOtherItemsAvailable','0',NULL,'If enabled, allow a patron to renew an item with unfilled holds if other available items can fill that hold.','YesNo'),
31
('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 (+6 lines)
Lines 136-141 Circulation: Link Here
136
                  yes: Enable
136
                  yes: Enable
137
                  no: "Do not enable"
137
                  no: "Do not enable"
138
            - "offline circulation on regular circulation computers. (NOTE: This system preference does not affect the Firefox plugin or the desktop application)"
138
            - "offline circulation on regular circulation computers. (NOTE: This system preference does not affect the Firefox plugin or the desktop application)"
139
        -
140
            - pref: AllowIssueNotes
141
              choices:
142
                  yes: Allow
143
                  no: "Don't allow"
144
            - patrons to submit notes about checked out items.
139
145
140
    Checkout Policy:
146
    Checkout Policy:
141
        -
147
        -
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt (+10 lines)
Lines 123-128 $(document).ready(function () { Link Here
123
 <div id="rotating-collection" class="dialog message">This item is part of a rotating collection and needs to be transferred to [% collectionBranch %]</div>
123
 <div id="rotating-collection" class="dialog message">This item is part of a rotating collection and needs to be transferred to [% collectionBranch %]</div>
124
[% END %]
124
[% END %]
125
125
126
<!-- Patron has added an issue note -->
127
[% IF ( issue.note) %]
128
    <div class="dialog message">
129
        <h1>Patron note</h1>
130
        <p>[% issue.notedate %]</p>
131
        <p><a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% itembiblionumber %]"> [% title |html %]</a> [% author %]</p>
132
        <p>[% issue.note %]</p>
133
    </div>
134
[% END %]
135
126
<!-- Patron has fines -->
136
<!-- Patron has fines -->
127
[% IF ( fines ) %]
137
[% IF ( fines ) %]
128
    <div class="dialog alert">
138
    <div class="dialog alert">
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-sendissuenote.tt (+21 lines)
Line 0 Link Here
1
<SUBJECT>
2
Issue note for [% title %] [% author %]
3
<END_SUBJECT>
4
5
[% USE HtmlToText %]
6
7
<HEADER>
8
[% FILTER html2text %]
9
    <p>Hi,</p>
10
    <p>[% MEMBER.firstname %] [% MEMBER.surname %] sent you the following note related to the check out of
11
    <p>[% title %] [% author %].</p>
12
[% END %]
13
14
<END_HEADER>
15
16
<MESSAGE>
17
[% FILTER html2text %]
18
    [% note %]
19
[% END %]
20
21
<END_MESSAGE>
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt (+96 lines)
Lines 98-103 Link Here
98
                        </div>
98
                        </div>
99
                    [% END # / IF patron_flagged %]
99
                    [% END # / IF patron_flagged %]
100
100
101
                    <div class="alert alert-info" id="notesaved" style="display:none"></div>
102
101
                    [% SET OPACMySummaryNote = Koha.Preference('OPACMySummaryNote') %]
103
                    [% SET OPACMySummaryNote = Koha.Preference('OPACMySummaryNote') %]
102
                    [% IF OPACMySummaryNote %][% OPACMySummaryNote %][% END %]
104
                    [% IF OPACMySummaryNote %][% OPACMySummaryNote %][% END %]
103
105
Lines 142-147 Link Here
142
                                                [% IF ( OPACMySummaryHTML ) %]
144
                                                [% IF ( OPACMySummaryHTML ) %]
143
                                                    <th class="nosort">Links</th>
145
                                                    <th class="nosort">Links</th>
144
                                                [% END %]
146
                                                [% END %]
147
                                                [% IF ( AllowIssueNotes ) %]
148
                                                    <th class="nosort">Note</th>
149
                                                [% END %]
145
                                            </tr>
150
                                            </tr>
146
                                        </thead>
151
                                        </thead>
147
                                        <tbody>
152
                                        <tbody>
Lines 254-259 Link Here
254
                                                [% IF ( OPACMySummaryHTML ) %]
259
                                                [% IF ( OPACMySummaryHTML ) %]
255
                                                    <td class="links">[% ISSUE.MySummaryHTML %]</td>
260
                                                    <td class="links">[% ISSUE.MySummaryHTML %]</td>
256
                                                [% END %]
261
                                                [% END %]
262
                                                [% IF ( AllowIssueNotes ) %]
263
                                                    <td class="note">
264
                                                        <input type="text"
265
                                                            name="note"
266
                                                            data-issue_id="[% ISSUE.issue_id%]"
267
                                                            data-origvalue="[% ISSUE.note %]"
268
                                                            value="[% ISSUE.note %]">
269
                                                        </input>
270
                                                        <a class="btn"
271
                                                            name="submitnote"
272
                                                            id="save_[% ISSUE.issue_id %]"
273
                                                            style="display:none;">Submit note</a>
274
                                                    </td>
275
                                                [% END %]
257
                                            </tr>
276
                                            </tr>
258
                                        [% END # /FOREACH ISSUES %]
277
                                        [% END # /FOREACH ISSUES %]
259
                                    </tbody>
278
                                    </tbody>
Lines 732-737 Link Here
732
                [% END %]
751
                [% END %]
733
            [% END %]
752
            [% END %]
734
753
754
            [% IF ( AllowIssueNotes ) %]
755
                $("input[name='note']").keyup(function(e){
756
                    /* prevent submitting of renewselected form */
757
                    if(e.which == 13)
758
                        e.preventDefault();
759
760
                    var $btn_save = $('#save_'+$(this).data('issue_id'));
761
                    var origvalue = $(this).data('origvalue');
762
                    var value = $(this).val();
763
764
                    if(origvalue != value) {
765
                        if(origvalue != "")
766
                            $btn_save.text('Submit changes');
767
                        else
768
                            $btn_save.text('Submit note');
769
                        $btn_save.show();
770
                    } else {
771
                        $btn_save.hide();
772
                    }
773
                });
774
775
                $("a[name='submitnote']").click(function(e){
776
                    var $self = $(this);
777
                    var title = $(this).parent().siblings('.title').html();
778
                    var $noteinput = $(this).siblings('input[name="note"]').first();
779
780
                    var ajaxData = {
781
                        'action': 'issuenote',
782
                        'issue_id': $noteinput.data('issue_id'),
783
                        'note': $noteinput.val(),
784
                    };
785
786
                    $.ajax({
787
                        url: '/cgi-bin/koha/opac-user.pl',
788
                        type: 'POST',
789
                        dataType: 'json',
790
                        data: ajaxData,
791
                    })
792
                    .done(function(data) {
793
                        var message = "";
794
                        if(data.status == 'saved') {
795
                            $("#notesaved").removeClass("alert-error");
796
                            $("#notesaved").addClass("alert-info");
797
                            $noteinput.data('origvalue', data.note);
798
                            $noteinput.val(data.note);
799
                            message = "<p>Your note about " + title + " was saved and have been sent to the library.</p>";
800
                            $self.hide();
801
                        } else if(data.status == 'removed') {
802
                            $("#notesaved").removeClass("alert-error");
803
                            $("#notesaved").addClass("alert-info");
804
                            $noteinput.data('origvalue', "");
805
                            $noteinput.val("");
806
                            message = "<p>Your note about " + title + " was removed.</p>";
807
                            $self.hide();
808
                        } else {
809
                            $("#notesaved").removeClass("alert-info");
810
                            $("#notesaved").addClass("alert-error");
811
                            message = "<p>Your note about " + title + " could not be saved.</p>" +
812
                                      "<p style=\"font-weight:bold;\">" + data.error + "</p>";
813
                        }
814
815
                        message += "<p style=\"font-style:italic;\">" + data.note + "</p>";
816
                        $("#notesaved").html(message);
817
                    })
818
                    .fail(function(data) {
819
                        $("#notesaved").removeClass("alert-info");
820
                        $("#notesaved").addClass("alert-error");
821
                        var message = "<p>Your note about " + title + " could not be saved.</p>" +
822
                                      "<p style=\"font-weight:bold;\">Ajax request has failed.</p>";
823
                        $("#notesaved").html(message);
824
                    })
825
                    .always(function() {
826
                        $("#notesaved").show();
827
                    });
828
                });
829
            [% END %]
830
735
            $( ".suspend-until" ).datepicker({ minDate: 1 }); // Require that "until date" be in the future
831
            $( ".suspend-until" ).datepicker({ minDate: 1 }); // Require that "until date" be in the future
736
        });
832
        });
737
        //]]>
833
        //]]>
(-)a/opac/opac-user.pl (-3 / +129 lines)
Lines 22-39 use strict; Link Here
22
22
23
use CGI qw ( -utf8 );
23
use CGI qw ( -utf8 );
24
24
25
use C4::Auth;
25
use Mail::Sendmail;
26
use MIME::QuotedPrint;
27
use Carp;
28
use C4::Auth qw(:DEFAULT check_cookie_auth);
26
use C4::Koha;
29
use C4::Koha;
27
use C4::Circulation;
30
use C4::Circulation;
28
use C4::Reserves;
31
use C4::Reserves;
29
use C4::Members;
32
use C4::Members;
30
use C4::Members::AttributeTypes;
33
use C4::Members::AttributeTypes;
31
use C4::Members::Attributes qw/GetBorrowerAttributeValue/;
34
use C4::Members::Attributes qw/GetBorrowerAttributeValue/;
32
use C4::Output;
35
use CGI::Cookie; # need to check cookies before having CGI parse the POST request
36
use C4::Output qw(:DEFAULT :ajax);
37
use C4::Scrubber;
33
use C4::Biblio;
38
use C4::Biblio;
34
use C4::Items;
39
use C4::Items;
35
use C4::Letters;
40
use C4::Letters;
36
use C4::Branch; # GetBranches
41
use C4::Branch; # GetBranches
42
use Koha::Email;
37
use Koha::DateUtils;
43
use Koha::DateUtils;
38
use Koha::Borrower::Debarments qw(IsDebarred);
44
use Koha::Borrower::Debarments qw(IsDebarred);
39
45
Lines 54-59 BEGIN { Link Here
54
    }
60
    }
55
}
61
}
56
62
63
sub ajax_auth_cgi {     # returns CGI object
64
	my $needed_flags = shift;
65
	my %cookies = fetch CGI::Cookie;
66
	my $input = CGI->new;
67
    my $sessid = $cookies{'CGISESSID'}->value;
68
	my ($auth_status, $auth_sessid) = check_cookie_auth($sessid, $needed_flags);
69
	if ($auth_status ne "ok") {
70
		output_with_http_headers $input, undef,
71
		"window.alert('Your CGI session cookie ($sessid) is not current.  " .
72
		"Please refresh the page and try again.');\n", 'js';
73
		exit 0;
74
	}
75
	return $input;
76
}
77
78
# AJAX requests
79
my $is_ajax = is_ajax();
80
my $query = ($is_ajax) ? &ajax_auth_cgi({}) : CGI->new();
81
if ($is_ajax) {
82
    my $action = $query->param('action');
83
84
    # Issue Note
85
    if ( $action == 'issuenote' && C4::Context->preference('AllowIssueNotes') ) {
86
        my $scrubber = C4::Scrubber->new();
87
        my $note = $query->param('note');
88
        my $issue_id = $query->param('issue_id');
89
        my $clean_note = $scrubber->scrub($note);
90
        my $status = "saved";
91
        my $error = "";
92
        my ($error, $member, $issue);
93
94
        my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
95
            {
96
                template_name   => "opac-sendissuenote.tt",
97
                query           => $query,
98
                type            => "opac",
99
                authnotrequired => 1,
100
            }
101
        );
102
103
        # verify issue_id
104
        if ( $issue_id =~ /\d+/ ) {
105
            $member = GetMember(borrowernumber => $borrowernumber);
106
            ($issue) = @{C4::Circulation::GetIssues({issue_id => $issue_id})};
107
108
            if ( $issue->{'borrowernumber'} != $borrowernumber ) {
109
                $status = "fail";
110
                $error = "Invalid issue id!";
111
            }
112
        } else {
113
            $status = "fail";
114
            $error = "Invalid issue id!";
115
        }
116
117
        if ( (not $error) && SetIssueNote($issue_id, $clean_note) ) {
118
            if($clean_note) { # only send email if note not empty
119
                my $branch = GetBranchDetail($issue->{'branchcode'});
120
121
                if ( $branch->{'branchemail'} ) {
122
                    my $biblio = GetBiblioFromItemNumber($issue->{'itemnumber'});
123
                    my $message = Koha::Email->new();
124
                    my %mail = $message->create_message_headers();
125
126
                    $template->param(
127
                        author => $biblio->{'author'},
128
                        title => $biblio->{'title'},
129
                        MEMBER => $member,
130
                        note => $clean_note,
131
                    );
132
133
                    # Getting template result
134
                    my $template_res = $template->output();
135
                    my $body;
136
137
                    # Analysing information and getting mail properties
138
                    if ( $template_res =~ /<SUBJECT>(.*)<END_SUBJECT>/s ) {
139
                        $mail{subject} = $1;
140
                        $mail{subject} =~ s|\n?(.*)\n?|$1|;
141
                    }
142
                    else { $mail{'subject'} = "no subject"; }
143
                    $mail{subject} = Encode::encode("UTF-8", $mail{subject});
144
145
                    my $email_header = "";
146
                    if ( $template_res =~ /<HEADER>(.*)<END_HEADER>/s ) {
147
                        $email_header = $1;
148
                        $email_header =~ s|\n?(.*)\n?|$1|;
149
                        $email_header = encode_qp(Encode::encode("UTF-8", $email_header));
150
                    }
151
152
                    if ( $template_res =~ /<MESSAGE>(.*)<END_MESSAGE>/s ) {
153
                        $body = $1;
154
                        $body =~ s|\n?(.*)\n?|$1|;
155
                        $body = encode_qp(Encode::encode("UTF-8", $body));
156
                    }
157
158
                    $mail{to} = $branch->{'branchemail'};
159
                    $mail{from} = $member->{'email'} || ($branch->{'branchemail'}=~s/^.+@/noreply@/ && $branch->{'branchemail'});
160
                    $mail{body} = $email_header . $body;
161
162
                    unless ( sendmail(%mail) ) {
163
                        $status = "fail";
164
                        $error = "Could not send message to library. Message will still show at check in.";
165
                        carp "Error sending mail: $Mail::Sendmail::error \n";
166
                    }
167
                }
168
            } else { # note empty, i.e removed
169
                $status = "removed";
170
            }
171
        } else {
172
            $status = "fail";
173
            $error = "Perhaps the item has already been check in?";
174
        }
175
176
        my $response = "{\"status\": \"$status\", \"note\": \"$clean_note\", \"issue_id\": \"$issue_id\", \"error\": \"$error\"}";
177
        output_with_http_headers($query, undef, $response, 'js');
178
        exit;
179
    } # END Issue Note
180
}
181
182
57
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
183
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
58
    {
184
    {
59
        template_name   => "opac-user.tt",
185
        template_name   => "opac-user.tt",
Lines 244-249 if ($issues){ Link Here
244
                }
370
                }
245
    }
371
    }
246
}
372
}
373
$template->param( AllowIssueNotes => C4::Context->preference('AllowIssueNotes') );
247
$template->param( ISSUES       => \@issuedat );
374
$template->param( ISSUES       => \@issuedat );
248
$template->param( issues_count => $count );
375
$template->param( issues_count => $count );
249
$template->param( canrenew     => $canrenew );
376
$template->param( canrenew     => $canrenew );
250
- 

Return to bug 14224