3 # This file is part of Koha.
5 # Copyright (C) 2016 ByWater Solutions
6 # Copyright (C) 2017 Koha Development Team
8 # Koha is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
22 use Test::More tests => 28;
28 use t::lib::TestBuilder;
31 use C4::Circulation qw( AddIssue AddReturn );
32 use C4::Letters qw( GetPreparedLetter );
33 use C4::Members qw( IssueSlip );
36 use Koha::DateUtils qw( dt_from_string output_pref );
37 use Koha::ArticleRequests;
42 use Koha::AdditionalContents;
44 use Koha::Subscription;
47 use Koha::Notice::Messages;
48 use Koha::Notice::Templates;
49 use Koha::Patron::Modification;
51 my $schema = Koha::Database->schema;
52 $schema->storage->txn_begin();
54 my $builder = t::lib::TestBuilder->new();
56 my $dbh = C4::Context->dbh;
58 $dbh->do(q|DELETE FROM letter|);
60 my $now_value = dt_from_string();
61 my $mocked_datetime = Test::MockModule->new('DateTime');
62 $mocked_datetime->mock( 'now', sub { return $now_value->clone; } );
64 my $library = $builder->build( { source => 'Branch' } );
65 my $patron = $builder->build( { source => 'Borrower' } );
66 my $patron2 = $builder->build( { source => 'Borrower' } );
68 my $item = $builder->build_sample_item();
69 my $hold = $builder->build_object(
71 class => 'Koha::Holds',
73 borrowernumber => $patron->{borrowernumber},
74 biblionumber => $item->biblionumber
79 my $news = $builder->build_object(
81 class => 'Koha::AdditionalContents',
82 value => { title => 'a news title', content => 'a news content' }
85 my $serial = $builder->build_object( { class => 'Koha::Serials' } );
86 my $subscription = $builder->build_object( { class => 'Koha::Subscriptions' } );
87 my $suggestion = $builder->build_object( { class => 'Koha::Suggestions' } );
88 my $checkout = $builder->build_object(
89 { class => 'Koha::Checkouts', value => { itemnumber => $item->id } } );
90 my $modification = $builder->build_object(
92 class => 'Koha::Patron::Modifications',
94 verification_token => "TEST",
95 changed_fields => 'firstname,surname'
103 $dbh->prepare(q{INSERT INTO letter (module, code, name, title, content) VALUES ('test',?,'Test',?,?)});
105 $sth->execute( "TEST_PATRON", "[% borrower.firstname %]", "[% borrower.id %]" );
106 $prepared_letter = GetPreparedLetter(
109 letter_code => 'TEST_PATRON',
111 borrowers => $patron->{borrowernumber},
115 is( $prepared_letter->{content}, $patron->{borrowernumber}, 'Patron object used correctly with scalar for content' );
116 is( $prepared_letter->{title}, $patron->{firstname}, 'Patron object used correctly with scalar for title' );
118 $prepared_letter = GetPreparedLetter(
121 letter_code => 'TEST_PATRON',
123 borrowers => $patron,
127 is( $prepared_letter->{content}, $patron->{borrowernumber}, 'Patron object used correctly with hashref for content' );
128 is( $prepared_letter->{title}, $patron->{firstname}, 'Patron object used correctly with hashref for title' );
130 $prepared_letter = GetPreparedLetter(
133 letter_code => 'TEST_PATRON',
135 borrowers => [ $patron->{borrowernumber} ],
139 is( $prepared_letter->{content}, $patron->{borrowernumber}, 'Patron object used correctly with arrayref for content' );
140 is( $prepared_letter->{title}, $patron->{firstname}, 'Patron object used correctly with arrayref for title' );
142 $sth->execute( "TEST_BIBLIO", "[% biblio.title %]", "[% biblio.id %]" );
143 $prepared_letter = GetPreparedLetter(
146 letter_code => 'TEST_BIBLIO',
148 biblio => $item->biblionumber,
152 is( $prepared_letter->{content}, $item->biblionumber, 'Biblio object used correctly for content' );
153 is( $prepared_letter->{title}, $item->biblio->title, 'Biblio object used correctly for title' );
155 $sth->execute( "TEST_LIBRARY", "[% branch.branchcode %]", "[% branch.id %]" );
156 $prepared_letter = GetPreparedLetter(
159 letter_code => 'TEST_LIBRARY',
161 branches => $library->{branchcode}
165 is( $prepared_letter->{content}, $library->{branchcode}, 'Library object used correctly for content' );
166 is( $prepared_letter->{title}, $library->{branchcode}, 'Library object used correctly for title' );
168 $sth->execute( "TEST_ITEM", "[% item.barcode %]", "[% item.id %]" );
169 $prepared_letter = GetPreparedLetter(
172 letter_code => 'TEST_ITEM',
178 is( $prepared_letter->{content}, $item->id(), 'Item object used correctly for content' );
179 is( $prepared_letter->{title}, $item->barcode, 'Item object used correctly for title' );
181 $sth->execute( "TEST_NEWS", "[% additional_content.id %]", "[% additional_content.id %]" );
182 $prepared_letter = GetPreparedLetter(
185 letter_code => 'TEST_NEWS',
187 additional_contents => $news->id()
191 is( $prepared_letter->{content}, $news->id(), 'News object used correctly for content' );
192 is( $prepared_letter->{title}, $news->id(), 'News object used correctly for title' );
194 $sth->execute( "TEST_HOLD", "[% hold.borrowernumber %]", "[% hold.id %]" );
195 $prepared_letter = GetPreparedLetter(
198 letter_code => 'TEST_HOLD',
200 reserves => { borrowernumber => $patron->{borrowernumber}, biblionumber => $item->biblionumber },
204 is( $prepared_letter->{content}, $hold->id(), 'Hold object used correctly for content' );
205 is( $prepared_letter->{title}, $hold->borrowernumber, 'Hold object used correctly for title' );
208 $prepared_letter = GetPreparedLetter(
211 letter_code => 'TEST_HOLD',
213 reserves => [ $patron->{borrowernumber}, $item->biblionumber ],
219 like( $croak, qr{^Multiple foreign keys \(table reserves\) should be passed using an hashref.*}, "GetPreparedLetter should not be called with arrayref for multiple FK" );
222 $prepared_letter = GetPreparedLetter(
225 letter_code => 'TEST_HOLD',
227 'branches' => $library,
228 'borrowers' => $patron,
229 'biblio' => $item->biblionumber,
230 'biblioitems' => $item->biblioitemnumber,
231 'reserves' => $hold->unblessed,
232 'items' => $hold->itemnumber,
236 is( $prepared_letter->{content}, $hold->id(), 'Hold object used correctly' );
238 $sth->execute( "TEST_SERIAL", "[% serial.id %]", "[% serial.id %]" );
239 $prepared_letter = GetPreparedLetter(
242 letter_code => 'TEST_SERIAL',
244 serial => $serial->id()
248 is( $prepared_letter->{content}, $serial->id(), 'Serial object used correctly' );
250 $sth->execute( "TEST_SUBSCRIPTION", "[% subscription.id %]", "[% subscription.id %]" );
251 $prepared_letter = GetPreparedLetter(
254 letter_code => 'TEST_SUBSCRIPTION',
256 subscription => $subscription->id()
260 is( $prepared_letter->{content}, $subscription->id(), 'Subscription object used correctly' );
262 $sth->execute( "TEST_SUGGESTION", "[% suggestion.id %]", "[% suggestion.id %]" );
263 $prepared_letter = GetPreparedLetter(
266 letter_code => 'TEST_SUGGESTION',
268 suggestions => $suggestion->id()
272 is( $prepared_letter->{content}, $suggestion->id(), 'Suggestion object used correctly' );
274 $sth->execute( "TEST_ISSUE", "[% checkout.id %]", "[% checkout.id %]" );
275 $prepared_letter = GetPreparedLetter(
278 letter_code => 'TEST_ISSUE',
280 issues => $item->id()
284 is( $prepared_letter->{content}, $checkout->id(), 'Checkout object used correctly' );
286 $sth->execute( "TEST_MODIFICATION", "[% patron_modification.id %]", "[% patron_modification.id %]" );
287 $prepared_letter = GetPreparedLetter(
290 letter_code => 'TEST_MODIFICATION',
292 borrower_modifications => $modification->verification_token,
296 is( $prepared_letter->{content}, $modification->id(), 'Patron modification object used correctly' );
298 subtest 'regression tests' => sub {
301 my $library = $builder->build( { source => 'Branch' } );
302 my $patron = $builder->build( { source => 'Borrower' } );
303 my $item1 = $builder->build_sample_item(
305 barcode => 'a_t_barcode',
306 library => $library->{branchcode},
308 itemcallnumber => 'itemcallnumber1',
311 my $biblio1 = $item1->biblio->unblessed;
312 $item1 = $item1->unblessed;
313 my $item2 = $builder->build_sample_item(
315 barcode => 'another_t_barcode',
316 library => $library->{branchcode},
318 itemcallnumber => 'itemcallnumber2',
321 my $biblio2 = $item2->biblio->unblessed;
322 $item2 = $item2->unblessed;
323 my $item3 = $builder->build_sample_item(
325 barcode => 'another_t_barcode_3',
326 library => $library->{branchcode},
328 itemcallnumber => 'itemcallnumber3',
331 my $biblio3 = $item3->biblio->unblessed;
332 $item3 = $item3->unblessed;
334 t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
336 subtest 'ACQ_NOTIF_ON_RECEIV ' => sub {
338 my $code = 'ACQ_NOTIF_ON_RECEIV';
339 my $branchcode = $library->{branchcode};
340 my $order = $builder->build({ source => 'Aqorder' });
343 Dear <<borrowers.firstname>> <<borrowers.surname>>,
344 The order <<aqorders.ordernumber>> (<<biblio.title>>) has been received.
347 my $params = { code => $code, branchcode => $branchcode, tables => { branches => $library, borrowers => $patron, biblio => $biblio1, aqorders => $order } };
348 my $letter = process_letter( { template => $template, %$params });
350 Dear [% borrower.firstname %] [% borrower.surname %],
351 The order [% order.ordernumber %] ([% biblio.title %]) has been received.
354 my $tt_letter = process_letter( { template => $tt_template, %$params });
356 is( $tt_letter->{content}, $letter->{content}, 'Verified letter content' );
359 subtest 'AR_*' => sub {
361 my $code = 'AR_CANCELED';
362 my $branchcode = $library->{branchcode};
365 <<borrowers.firstname>> <<borrowers.surname>> (<<borrowers.cardnumber>>)
367 Your request for an article from <<biblio.title>> (<<items.barcode>>) has been canceled for the following reason:
369 <<article_requests.notes>>
372 Title: <<article_requests.title>>
373 Author: <<article_requests.author>>
374 Volume: <<article_requests.volume>>
375 Issue: <<article_requests.issue>>
376 Date: <<article_requests.date>>
377 Pages: <<article_requests.pages>>
378 Chapters: <<article_requests.chapters>>
379 Notes: <<article_requests.patron_notes>>
381 reset_template( { template => $template, code => $code, module => 'circulation' } );
382 my $article_request = $builder->build({ source => 'ArticleRequest' });
383 Koha::ArticleRequests->find( $article_request->{id} )->cancel;
384 my $letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
387 [% borrower.firstname %] [% borrower.surname %] ([% borrower.cardnumber %])
389 Your request for an article from [% biblio.title %] ([% item.barcode %]) has been canceled for the following reason:
391 [% article_request.notes %]
394 Title: [% article_request.title %]
395 Author: [% article_request.author %]
396 Volume: [% article_request.volume %]
397 Issue: [% article_request.issue %]
398 Date: [% article_request.date %]
399 Pages: [% article_request.pages %]
400 Chapters: [% article_request.chapters %]
401 Notes: [% article_request.patron_notes %]
403 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
404 Koha::ArticleRequests->find( $article_request->{id} )->cancel;
405 my $tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
406 is( $tt_letter->content, $letter->content, 'Compare AR_* notices' );
407 isnt( $tt_letter->message_id, $letter->message_id, 'Comparing AR_* notices should compare 2 different messages' );
410 subtest 'CHECKOUT+CHECKIN' => sub {
413 my $checkout_code = 'CHECKOUT';
414 my $checkin_code = 'CHECKIN';
416 my $dbh = C4::Context->dbh;
417 # Enable notification for CHECKOUT - Things are hardcoded here but should work with default data
418 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 6 );
419 my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
420 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
421 # Enable notification for CHECKIN - Things are hardcoded here but should work with default data
422 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 5 );
423 $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
424 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
427 my $checkout_template = q|
428 The following items have been checked out:
432 Thank you for visiting <<branches.branchname>>.
434 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
435 my $checkin_template = q[
436 The following items have been checked out:
438 <<biblio.title>> was due on <<old_issues.date_due | dateonly>>
440 Thank you for visiting <<branches.branchname>>.
442 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
444 C4::Circulation::AddIssue( $patron, $item1->{barcode} );
445 my $first_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
446 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
447 my $second_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
449 AddReturn( $item1->{barcode} );
450 my $first_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
451 AddReturn( $item2->{barcode} );
452 my $second_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
454 Koha::Notice::Messages->delete;
457 $checkout_template = q|
458 The following items have been checked out:
462 Thank you for visiting [% branch.branchname %].
464 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
465 $checkin_template = q[
466 The following items have been checked out:
468 [% biblio.title %] was due on [% old_checkout.date_due | $KohaDates %]
470 Thank you for visiting [% branch.branchname %].
472 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
474 C4::Circulation::AddIssue( $patron, $item1->{barcode} );
475 my $first_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
476 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
477 my $second_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
479 AddReturn( $item1->{barcode} );
480 my $first_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
481 AddReturn( $item2->{barcode} );
482 my $second_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
484 is( $first_checkout_tt_letter->content, $first_checkout_letter->content, 'Verify first checkout letter' );
485 is( $second_checkout_tt_letter->content, $second_checkout_letter->content, 'Verify second checkout letter' );
486 is( $first_checkin_tt_letter->content, $first_checkin_letter->content, 'Verify first checkin letter' );
487 is( $second_checkin_tt_letter->content, $second_checkin_letter->content, 'Verify second checkin letter' );
491 subtest 'DUEDGST|count' => sub {
494 my $code = 'DUEDGST';
496 my $dbh = C4::Context->dbh;
497 # Enable notification for DUEDGST - Things are hardcoded here but should work with default data
498 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 1 );
499 my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
500 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
504 substitute => { count => 42 },
508 You have <<count>> items due
510 my $letter = process_letter( { template => $template, %$params });
513 You have [% count %] items due
515 my $tt_letter = process_letter( { template => $tt_template, %$params });
516 is( $tt_letter->{content}, $letter->{content}, );
519 subtest 'HOLD_SLIP|dates|today' => sub {
522 my $code = 'HOLD_SLIP';
524 my $reserve_id1 = C4::Reserves::AddReserve(
526 branchcode => $library->{branchcode},
527 borrowernumber => $patron->{borrowernumber},
528 biblionumber => $biblio1->{biblionumber},
530 itemnumber => $item1->{itemnumber},
533 my $reserve_id2 = C4::Reserves::AddReserve(
535 branchcode => $library->{branchcode},
536 borrowernumber => $patron->{borrowernumber},
537 biblionumber => $biblio1->{biblionumber},
539 itemnumber => $item1->{itemnumber},
542 my $reserve_id3 = C4::Reserves::AddReserve(
544 branchcode => $library->{branchcode},
545 borrowernumber => $patron->{borrowernumber},
546 biblionumber => $biblio2->{biblionumber},
547 notes => "another note",
548 itemnumber => $item2->{itemnumber},
552 my $template = <<EOF;
553 <h5>Date: <<today>></h5>
555 <h3> Transfer to/Hold in <<branches.branchname>></h3>
557 <h3><<borrowers.surname>>, <<borrowers.firstname>></h3>
560 <li><<borrowers.cardnumber>></li>
561 <li><<borrowers.phone>></li>
562 <li> <<borrowers.address>><br />
563 <<borrowers.address2>><br />
564 <<borrowers.city>> <<borrowers.zipcode>>
566 <li><<borrowers.email>></li>
569 <h3>ITEM ON HOLD</h3>
570 <h4><<biblio.title>></h4>
571 <h5><<biblio.author>></h5>
573 <li><<items.barcode>></li>
574 <li><<items.itemcallnumber>></li>
575 <li><<reserves.waitingdate>></li>
578 <pre><<reserves.reserve_id>>=<<reserves.reservenotes>></pre>
582 reset_template( { template => $template, code => $code, module => 'circulation' } );
583 my $letter_for_item1 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id1 } );
584 my $letter_for_item2 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id3 } );
586 my $tt_template = <<EOF;
587 <h5>Date: [% today | \$KohaDates with_hours => 1 %]</h5>
589 <h3> Transfer to/Hold in [% branch.branchname %]</h3>
591 <h3>[% borrower.surname %], [% borrower.firstname %]</h3>
594 <li>[% borrower.cardnumber %]</li>
595 <li>[% borrower.phone %]</li>
596 <li> [% borrower.address %]<br />
597 [% borrower.address2 %]<br />
598 [% borrower.city %] [% borrower.zipcode %]
600 <li>[% borrower.email %]</li>
603 <h3>ITEM ON HOLD</h3>
604 <h4>[% biblio.title %]</h4>
605 <h5>[% biblio.author %]</h5>
607 <li>[% item.barcode %]</li>
608 <li>[% item.itemcallnumber %]</li>
609 <li>[% hold.waitingdate | \$KohaDates %]</li>
612 <pre>[% hold.reserve_id %]=[% hold.reservenotes %]</pre>
616 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
617 my $tt_letter_for_item1 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id1 } );
618 my $tt_letter_for_item2 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id3 } );
620 is( $tt_letter_for_item1->{content}, $letter_for_item1->{content}, );
621 is( $tt_letter_for_item2->{content}, $letter_for_item2->{content}, );
624 subtest 'ISSUESLIP|checkedout|repeat|news' => sub {
627 my $code = 'ISSUESLIP';
628 my $now = dt_from_string;
629 my $one_minute_ago = dt_from_string->subtract( minutes => 1 );
631 my $branchcode = $library->{branchcode};
633 Koha::AdditionalContents->delete;
634 my $news_item = $builder->build_object(
636 class => 'Koha::AdditionalContents',
640 branchcode => $branchcode,
642 title => "A wonderful news",
643 content => "This is the wonderful news.",
644 expiration_date => undef,
645 published_on => $one_minute_ago
651 my $template = <<EOF;
652 <h3><<branches.branchname>></h3>
653 Checked out to <<borrowers.title>> <<borrowers.firstname>> <<borrowers.initials>> <<borrowers.surname>> <br />
654 (<<borrowers.cardnumber>>) <br />
661 <<biblio.title>> <br />
662 Barcode: <<items.barcode>><br />
663 Date due: <<issues.date_due | dateonly>><br />
670 <<biblio.title>> <br />
671 Barcode: <<items.barcode>><br />
672 Date due: <<issues.date_due | dateonly>><br />
678 <h4 style="text-align: center; font-style:italic;">News</h4>
680 <div class="newsitem">
681 <h5 style="margin-bottom: 1px; margin-top: 1px"><b><<additional_contents.title>></b></h5>
682 <p style="margin-bottom: 1px; margin-top: 1px"><<additional_contents.content>></p>
683 <p class="newsfooter" style="font-size: 8pt; font-style:italic; margin-bottom: 1px; margin-top: 1px">Posted on <<additional_contents.published_on>></p>
689 reset_template( { template => $template, code => $code, module => 'circulation' } );
691 my $checkout = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
692 $checkout->set( { timestamp => $now, issuedate => $one_minute_ago } )->store;
693 my $first_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
695 $checkout = C4::Circulation::AddIssue( $patron, $item2->{barcode} ); # Add a second checkout
696 $checkout->set( { timestamp => $now, issuedate => $now } )->store;
697 my $yesterday = dt_from_string->subtract( days => 1 );
698 C4::Circulation::AddIssue( $patron, $item3->{barcode}, $yesterday ); # Add an overdue
699 my $second_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
702 AddReturn( $item1->{barcode} );
703 AddReturn( $item2->{barcode} );
704 AddReturn( $item3->{barcode} );
707 my $tt_template = <<EOF;
708 <h3>[% branch.branchname %]</h3>
709 Checked out to [% borrower.title %] [% borrower.firstname %] [% borrower.initials %] [% borrower.surname %] <br />
710 ([% borrower.cardnumber %]) <br />
712 [% today | \$KohaDates with_hours => 1 %]<br />
715 [% FOREACH checkout IN checkouts %]
716 [%~ SET item = checkout.item %]
717 [%~ SET biblio = checkout.item.biblio %]
719 [% biblio.title %] <br />
720 Barcode: [% item.barcode %]<br />
721 Date due: [% checkout.date_due | \$KohaDates %]<br />
726 [% FOREACH overdue IN overdues %]
727 [%~ SET item = overdue.item %]
728 [%~ SET biblio = overdue.item.biblio %]
730 [% biblio.title %] <br />
731 Barcode: [% item.barcode %]<br />
732 Date due: [% overdue.date_due | \$KohaDates %]<br />
738 <h4 style="text-align: center; font-style:italic;">News</h4>
739 [% FOREACH n IN additional_contents %]
740 <div class="newsitem">
741 <h5 style="margin-bottom: 1px; margin-top: 1px"><b>[% n.title %]</b></h5>
742 <p style="margin-bottom: 1px; margin-top: 1px">[% n.content %]</p>
743 <p class="newsfooter" style="font-size: 8pt; font-style:italic; margin-bottom: 1px; margin-top: 1px">Posted on [% n.published_on | \$KohaDates %]</p>
749 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
751 $checkout = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
752 $checkout->set( { timestamp => $now, issuedate => $one_minute_ago } )->store;
753 my $first_tt_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
755 $checkout = C4::Circulation::AddIssue( $patron, $item2->{barcode} ); # Add a second checkout
756 $checkout->set( { timestamp => $now, issuedate => $now } )->store;
757 C4::Circulation::AddIssue( $patron, $item3->{barcode}, $yesterday ); # Add an overdue
758 my $second_tt_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
760 # There is too many line breaks generated by the historic syntax
761 $second_slip->{content} =~ s|</p>\n\n\n<p>|</p>\n\n<p>|s;
763 my $news_item_title = $news_item->title;
764 like( $first_slip->{content}, qr{$news_item_title} );
765 is( $first_tt_slip->{content}, $first_slip->{content}, );
766 is( $second_tt_slip->{content}, $second_slip->{content}, );
769 AddReturn( $item1->{barcode} );
770 AddReturn( $item2->{barcode} );
771 AddReturn( $item3->{barcode} );
774 subtest 'ODUE|items.content|item' => sub {
779 my $branchcode = $library->{branchcode};
782 # FIXME items.fine does not work with TT notices
784 # <item> should contain Fine: <<items.fine>></item>
785 my $template = <<EOF;
786 Dear <<borrowers.firstname>> <<borrowers.surname>>,
788 According to our current records, you have items that are overdue.Your library does not charge late fines, but please return or renew them at the branch below as soon as possible.
790 <<branches.branchname>>
791 <<branches.branchaddress1>>
792 <<branches.branchaddress2>> <<branches.branchaddress3>>
793 Phone: <<branches.branchphone>>
794 Fax: <<branches.branchfax>>
795 Email: <<branches.branchemail>>
797 If you have registered a password with the library, and you have a renewal available, you may renew online. If an item becomes more than 30 days overdue, you will be unable to use your library card until the item is returned.
799 The following item(s) is/are currently overdue:
801 <item>"<<biblio.title>>" by <<biblio.author>>, <<items.itemcallnumber>>, Barcode: <<items.barcode>></item>
805 Thank-you for your prompt attention to this matter.
807 <<branches.branchname>> Staff
810 reset_template( { template => $template, code => $code, module => 'circulation' } );
812 my $yesterday = dt_from_string->subtract( days => 1 );
813 my $two_days_ago = dt_from_string->subtract( days => 2 );
814 my $issue1 = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
815 my $issue2 = C4::Circulation::AddIssue( $patron, $item2->{barcode}, $yesterday ); # Add an first overdue
816 my $issue3 = C4::Circulation::AddIssue( $patron, $item3->{barcode}, $two_days_ago ); # Add an second overdue
817 $issue1 = $issue1->unblessed;
818 $issue2 = $issue2->unblessed;
819 $issue3 = $issue3->unblessed;
822 my @item_fields = qw( date_due title barcode author itemnumber );
823 my $items_content = C4::Letters::get_item_content( { item => { %$item1, %$biblio1, %$issue1 }, item_content_fields => \@item_fields, dateonly => 1 } );
824 $items_content .= C4::Letters::get_item_content( { item => { %$item2, %$biblio2, %$issue2 }, item_content_fields => \@item_fields, dateonly => 1 } );
825 $items_content .= C4::Letters::get_item_content( { item => { %$item3, %$biblio3, %$issue3 }, item_content_fields => \@item_fields, dateonly => 1 } );
827 my @items = ( $item1, $item2, $item3 );
828 my $letter = C4::Overdues::parse_overdues_letter(
830 letter_code => $code,
831 borrowernumber => $patron->{borrowernumber},
832 branchcode => $library->{branchcode},
835 bib => $library->{branchname},
836 'items.content' => $items_content,
837 count => scalar( @items ),
838 message_transport_type => 'email',
844 AddReturn( $item1->{barcode} );
845 AddReturn( $item2->{barcode} );
846 AddReturn( $item3->{barcode} );
850 my $tt_template = <<EOF;
851 Dear [% borrower.firstname %] [% borrower.surname %],
853 According to our current records, you have items that are overdue.Your library does not charge late fines, but please return or renew them at the branch below as soon as possible.
855 [% branch.branchname %]
856 [% branch.branchaddress1 %]
857 [% branch.branchaddress2 %] [% branch.branchaddress3 %]
858 Phone: [% branch.branchphone %]
859 Fax: [% branch.branchfax %]
860 Email: [% branch.branchemail %]
862 If you have registered a password with the library, and you have a renewal available, you may renew online. If an item becomes more than 30 days overdue, you will be unable to use your library card until the item is returned.
864 The following item(s) is/are currently overdue:
866 [% FOREACH overdue IN overdues %]
867 [%~ SET item = overdue.item ~%]
868 "[% item.biblio.title %]" by [% item.biblio.author %], [% item.itemcallnumber %], Barcode: [% item.barcode %]
870 [% FOREACH overdue IN overdues %]
871 [%~ SET item = overdue.item ~%]
872 [% overdue.date_due | \$KohaDates %]\t[% item.biblio.title %]\t[% item.barcode %]\t[% item.biblio.author %]\t[% item.itemnumber %]
875 Thank-you for your prompt attention to this matter.
877 [% branch.branchname %] Staff
880 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
882 C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
883 C4::Circulation::AddIssue( $patron, $item2->{barcode}, $yesterday ); # Add an first overdue
884 C4::Circulation::AddIssue( $patron, $item3->{barcode}, $two_days_ago ); # Add an second overdue
886 my $tt_letter = C4::Overdues::parse_overdues_letter(
888 letter_code => $code,
889 borrowernumber => $patron->{borrowernumber},
890 branchcode => $library->{branchcode},
893 bib => $library->{branchname},
894 'items.content' => $items_content,
895 count => scalar( @items ),
896 message_transport_type => 'email',
901 is( $tt_letter->{content}, $letter->{content}, );
904 subtest 'Bug 19743 - Header and Footer should be updated on each item for checkin / checkout / renewal notices' => sub {
907 my $checkout_code = 'CHECKOUT';
908 my $checkin_code = 'CHECKIN';
910 my $dbh = C4::Context->dbh;
911 $dbh->do("DELETE FROM letter");
912 $dbh->do("DELETE FROM issues");
913 $dbh->do("DELETE FROM message_queue");
915 # Enable notification for CHECKOUT - Things are hardcoded here but should work with default data
916 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 6 );
917 my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
918 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
919 # Enable notification for CHECKIN - Things are hardcoded here but should work with default data
920 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 5 );
921 $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
922 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
924 my $checkout_template = q|
925 <<branches.branchname>>
929 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
930 my $checkin_template = q[
931 <<branches.branchname>>
935 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
937 my $issue = C4::Circulation::AddIssue( $patron, $item1->{barcode} );
938 my $first_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
940 my $library_object = Koha::Libraries->find( $issue->branchcode );
941 my $old_branchname = $library_object->branchname;
942 my $new_branchname = "Kyle M Hall Memorial Library";
944 # Change branch name for second checkout notice
945 $library_object->branchname($new_branchname);
946 $library_object->store();
948 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
949 my $second_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
951 # Restore old name for first checkin notice
952 $library_object->branchname( $old_branchname );
953 $library_object->store();
955 AddReturn( $item1->{barcode} );
956 my $first_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
958 # Change branch name for second checkin notice
959 $library_object->branchname($new_branchname);
960 $library_object->store();
962 AddReturn( $item2->{barcode} );
963 my $second_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
965 # Restore old name for first TT checkout notice
966 $library_object->branchname( $old_branchname );
967 $library_object->store();
969 Koha::Notice::Messages->delete;
972 $checkout_template = q|
973 [% branch.branchname %]
977 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
978 $checkin_template = q[
979 [% branch.branchname %]
983 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
985 C4::Circulation::AddIssue( $patron, $item1->{barcode} );
986 my $first_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
988 # Change branch name for second checkout notice
989 $library_object->branchname($new_branchname);
990 $library_object->store();
992 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
993 my $second_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
995 # Restore old name for first checkin notice
996 $library_object->branchname( $old_branchname );
997 $library_object->store();
999 AddReturn( $item1->{barcode} );
1000 my $first_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
1002 # Change branch name for second checkin notice
1003 $library_object->branchname($new_branchname);
1004 $library_object->store();
1006 AddReturn( $item2->{barcode} );
1007 my $second_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
1009 my $first_letter = qq[
1012 my $second_letter = qq[
1017 is( $first_checkout_letter->content, $first_letter, 'Verify first checkout letter' );
1018 is( $second_checkout_letter->content, $second_letter, 'Verify second checkout letter' );
1019 is( $first_checkin_letter->content, $first_letter, 'Verify first checkin letter' );
1020 is( $second_checkin_letter->content, $second_letter, 'Verify second checkin letter' );
1022 is( $first_checkout_tt_letter->content, $first_letter, 'Verify TT first checkout letter' );
1023 is( $second_checkout_tt_letter->content, $second_letter, 'Verify TT second checkout letter' );
1024 is( $first_checkin_tt_letter->content, $first_letter, 'Verify TT first checkin letter' );
1025 is( $second_checkin_tt_letter->content, $second_letter, 'Verify TT second checkin letter' );
1030 subtest 'loops' => sub {
1033 my $module = "TEST";
1035 subtest 'primary key is AI' => sub {
1037 my $patron_1 = $builder->build({ source => 'Borrower' });
1038 my $patron_2 = $builder->build({ source => 'Borrower' });
1040 my $template = q|[% FOREACH patron IN borrowers %][% patron.surname %][% END %]|;
1041 reset_template( { template => $template, code => $code, module => $module } );
1042 my $letter = GetPreparedLetter( module => $module, letter_code => $code, loops => { borrowers => [ $patron_1->{borrowernumber}, $patron_2->{borrowernumber} ] } );
1043 my $expected_letter = join '', ( $patron_1->{surname}, $patron_2->{surname} );
1044 is( $letter->{content}, $expected_letter, );
1047 subtest 'foreign key is used' => sub {
1049 my $patron_1 = $builder->build({ source => 'Borrower' });
1050 my $patron_2 = $builder->build({ source => 'Borrower' });
1051 my $checkout_1 = $builder->build({ source => 'Issue', value => { borrowernumber => $patron_1->{borrowernumber} } } );
1052 my $checkout_2 = $builder->build({ source => 'Issue', value => { borrowernumber => $patron_1->{borrowernumber} } } );
1054 my $template = q|[% FOREACH checkout IN checkouts %][% checkout.issue_id %][% END %]|;
1055 reset_template( { template => $template, code => $code, module => $module } );
1056 my $letter = GetPreparedLetter( module => $module, letter_code => $code, loops => { issues => [ $checkout_1->{itemnumber}, $checkout_2->{itemnumber} ] } );
1057 my $expected_letter = join '', ( $checkout_1->{issue_id}, $checkout_2->{issue_id} );
1058 is( $letter->{content}, $expected_letter, );
1062 subtest 'add_tt_filters' => sub {
1065 my $module = "TEST";
1067 my $patron = $builder->build_object(
1069 class => 'Koha::Patrons',
1070 value => { surname => "with_punctuation_" }
1073 my $biblio = $builder->build_object(
1074 { class => 'Koha::Biblios', value => { title => "with_punctuation_" } }
1076 my $biblioitem = $builder->build_object(
1078 class => 'Koha::Biblioitems',
1080 biblionumber => $biblio->biblionumber,
1081 isbn => "with_punctuation_"
1086 my $template = q|patron=[% borrower.surname %];biblio=[% biblio.title %];biblioitems=[% biblioitem.isbn %]|;
1087 reset_template( { template => $template, code => $code, module => $module } );
1088 my $letter = GetPreparedLetter(
1090 letter_code => $code,
1092 borrowers => $patron->borrowernumber,
1093 biblio => $biblio->biblionumber,
1094 biblioitems => $biblioitem->biblioitemnumber
1097 my $expected_letter = q|patron=with_punctuation_;biblio=with_punctuation;biblioitems=with_punctuation|;
1098 is( $letter->{content}, $expected_letter, "Pre-processing should call TT plugin to remove punctuation if table is biblio or biblioitems");
1101 subtest 'Handle includes' => sub {
1103 my $cgi = CGI->new();
1104 my $code = 'TEST_INCLUDE';
1106 $builder->build_object( { class => 'Koha::Account::Lines', value => { credit_type_code => 'PAYMENT', status => 'CANCELLED' } } );
1107 my $template = <<EOF;
1109 [%- PROCESS 'accounts.inc' -%]
1110 [%- PROCESS account_type_description account=credit -%]
1112 reset_template({ template => $template, code => $code, module => 'test' });
1113 my $letter = GetPreparedLetter(
1115 letter_code => $code,
1117 credits => $account->accountlines_id
1120 is($letter->{content},' <span>Payment<span> (Cancelled)</span> </span>', "Include used in notice");
1123 subtest 'Dates formatting' => sub {
1125 my $code = 'TEST_DATE';
1126 t::lib::Mocks::mock_preference('dateformat', 'metric'); # MM/DD/YYYY
1127 my $biblio = $builder->build_object(
1129 class => 'Koha::Biblios',
1131 timestamp => '2018-12-13 20:21:22',
1132 datecreated => '2018-12-13'
1136 my $template = <<EOF;
1137 [%- USE KohaDates -%]
1138 [% biblio.timestamp %]
1139 [% biblio.timestamp | \$KohaDates %]
1140 [% biblio.timestamp | \$KohaDates with_hours => 1 %]
1142 [% biblio.datecreated %]
1143 [% biblio.datecreated | \$KohaDates %]
1144 [% biblio.datecreated | \$KohaDates with_hours => 1 %]
1146 [% biblio.timestamp | \$KohaDates dateformat => 'iso' %]
1147 [% KohaDates.output_preference( str => biblio.timestamp, dateformat => 'iso' ) %]
1148 [% KohaDates.output_preference( str => biblio.timestamp, dateformat => 'iso', dateonly => 1 ) %]
1150 reset_template({ template => $template, code => $code, module => 'test' });
1151 my $letter = GetPreparedLetter(
1153 letter_code => $code,
1155 biblio => $biblio->biblionumber,
1158 my $expected_content = sprintf("%s\n%s\n%s\n\n%s\n%s\n%s\n\n%s\n%s\n%s\n",
1159 '2018-12-13 20:21:22',
1171 is( $letter->{content}, $expected_content );
1174 sub reset_template {
1175 my ( $params ) = @_;
1176 my $template = $params->{template};
1177 my $code = $params->{code};
1178 my $module = $params->{module} || 'test_module';
1180 Koha::Notice::Templates->search( { code => $code } )->delete;
1181 Koha::Notice::Template->new(
1188 message_transport_type => 'email',
1189 content => $template
1194 sub process_letter {
1196 my $template = $params->{template};
1197 my $tables = $params->{tables};
1198 my $substitute = $params->{substitute};
1199 my $code = $params->{code};
1200 my $module = $params->{module} || 'test_module';
1201 my $branchcode = $params->{branchcode};
1203 reset_template( $params );
1205 my $letter = C4::Letters::GetPreparedLetter(
1207 letter_code => $code,
1210 substitute => $substitute,
1215 $schema->storage->txn_rollback;