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;
37 use Koha::ArticleRequests;
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::News',
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", "[% news.id %]", "[% news.id %]" );
182 $prepared_letter = GetPreparedLetter(
185 letter_code => 'TEST_NEWS',
187 opac_news => $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' => 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};
634 my $news_item = Koha::NewsItem->new({ branchcode => $branchcode, title => "A wonderful news", content => "This is the wonderful news." })->store;
637 my $template = <<EOF;
638 <h3><<branches.branchname>></h3>
639 Checked out to <<borrowers.title>> <<borrowers.firstname>> <<borrowers.initials>> <<borrowers.surname>> <br />
640 (<<borrowers.cardnumber>>) <br />
647 <<biblio.title>> <br />
648 Barcode: <<items.barcode>><br />
649 Date due: <<issues.date_due | dateonly>><br />
656 <<biblio.title>> <br />
657 Barcode: <<items.barcode>><br />
658 Date due: <<issues.date_due | dateonly>><br />
664 <h4 style="text-align: center; font-style:italic;">News</h4>
666 <div class="newsitem">
667 <h5 style="margin-bottom: 1px; margin-top: 1px"><b><<opac_news.title>></b></h5>
668 <p style="margin-bottom: 1px; margin-top: 1px"><<opac_news.content>></p>
669 <p class="newsfooter" style="font-size: 8pt; font-style:italic; margin-bottom: 1px; margin-top: 1px">Posted on <<opac_news.published_on>></p>
675 reset_template( { template => $template, code => $code, module => 'circulation' } );
677 my $checkout = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
678 $checkout->set( { timestamp => $now, issuedate => $one_minute_ago } )->store;
679 my $first_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
681 $checkout = C4::Circulation::AddIssue( $patron, $item2->{barcode} ); # Add a second checkout
682 $checkout->set( { timestamp => $now, issuedate => $now } )->store;
683 my $yesterday = dt_from_string->subtract( days => 1 );
684 C4::Circulation::AddIssue( $patron, $item3->{barcode}, $yesterday ); # Add an overdue
685 my $second_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
688 AddReturn( $item1->{barcode} );
689 AddReturn( $item2->{barcode} );
690 AddReturn( $item3->{barcode} );
693 my $tt_template = <<EOF;
694 <h3>[% branch.branchname %]</h3>
695 Checked out to [% borrower.title %] [% borrower.firstname %] [% borrower.initials %] [% borrower.surname %] <br />
696 ([% borrower.cardnumber %]) <br />
698 [% today | \$KohaDates with_hours => 1 %]<br />
701 [% FOREACH checkout IN checkouts %]
702 [%~ SET item = checkout.item %]
703 [%~ SET biblio = checkout.item.biblio %]
705 [% biblio.title %] <br />
706 Barcode: [% item.barcode %]<br />
707 Date due: [% checkout.date_due | \$KohaDates %]<br />
712 [% FOREACH overdue IN overdues %]
713 [%~ SET item = overdue.item %]
714 [%~ SET biblio = overdue.item.biblio %]
716 [% biblio.title %] <br />
717 Barcode: [% item.barcode %]<br />
718 Date due: [% overdue.date_due | \$KohaDates %]<br />
724 <h4 style="text-align: center; font-style:italic;">News</h4>
725 [% FOREACH n IN news %]
726 <div class="newsitem">
727 <h5 style="margin-bottom: 1px; margin-top: 1px"><b>[% n.title %]</b></h5>
728 <p style="margin-bottom: 1px; margin-top: 1px">[% n.content %]</p>
729 <p class="newsfooter" style="font-size: 8pt; font-style:italic; margin-bottom: 1px; margin-top: 1px">Posted on [% n.timestamp | \$KohaDates %]</p>
735 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
737 $checkout = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
738 $checkout->set( { timestamp => $now, issuedate => $one_minute_ago } )->store;
739 my $first_tt_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
741 $checkout = C4::Circulation::AddIssue( $patron, $item2->{barcode} ); # Add a second checkout
742 $checkout->set( { timestamp => $now, issuedate => $now } )->store;
743 C4::Circulation::AddIssue( $patron, $item3->{barcode}, $yesterday ); # Add an overdue
744 my $second_tt_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
746 # There is too many line breaks generated by the historic syntax
747 $second_slip->{content} =~ s|</p>\n\n\n<p>|</p>\n\n<p>|s;
749 is( $first_tt_slip->{content}, $first_slip->{content}, );
750 is( $second_tt_slip->{content}, $second_slip->{content}, );
753 AddReturn( $item1->{barcode} );
754 AddReturn( $item2->{barcode} );
755 AddReturn( $item3->{barcode} );
758 subtest 'ODUE|items.content|item' => sub {
763 my $branchcode = $library->{branchcode};
766 # FIXME items.fine does not work with TT notices
768 # <item> should contain Fine: <<items.fine>></item>
769 my $template = <<EOF;
770 Dear <<borrowers.firstname>> <<borrowers.surname>>,
772 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.
774 <<branches.branchname>>
775 <<branches.branchaddress1>>
776 <<branches.branchaddress2>> <<branches.branchaddress3>>
777 Phone: <<branches.branchphone>>
778 Fax: <<branches.branchfax>>
779 Email: <<branches.branchemail>>
781 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.
783 The following item(s) is/are currently overdue:
785 <item>"<<biblio.title>>" by <<biblio.author>>, <<items.itemcallnumber>>, Barcode: <<items.barcode>></item>
789 Thank-you for your prompt attention to this matter.
791 <<branches.branchname>> Staff
794 reset_template( { template => $template, code => $code, module => 'circulation' } );
796 my $yesterday = dt_from_string->subtract( days => 1 );
797 my $two_days_ago = dt_from_string->subtract( days => 2 );
798 my $issue1 = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
799 my $issue2 = C4::Circulation::AddIssue( $patron, $item2->{barcode}, $yesterday ); # Add an first overdue
800 my $issue3 = C4::Circulation::AddIssue( $patron, $item3->{barcode}, $two_days_ago ); # Add an second overdue
801 $issue1 = $issue1->unblessed;
802 $issue2 = $issue2->unblessed;
803 $issue3 = $issue3->unblessed;
806 my @item_fields = qw( date_due title barcode author itemnumber );
807 my $items_content = C4::Letters::get_item_content( { item => { %$item1, %$biblio1, %$issue1 }, item_content_fields => \@item_fields, dateonly => 1 } );
808 $items_content .= C4::Letters::get_item_content( { item => { %$item2, %$biblio2, %$issue2 }, item_content_fields => \@item_fields, dateonly => 1 } );
809 $items_content .= C4::Letters::get_item_content( { item => { %$item3, %$biblio3, %$issue3 }, item_content_fields => \@item_fields, dateonly => 1 } );
811 my @items = ( $item1, $item2, $item3 );
812 my $letter = C4::Overdues::parse_overdues_letter(
814 letter_code => $code,
815 borrowernumber => $patron->{borrowernumber},
816 branchcode => $library->{branchcode},
819 bib => $library->{branchname},
820 'items.content' => $items_content,
821 count => scalar( @items ),
822 message_transport_type => 'email',
828 AddReturn( $item1->{barcode} );
829 AddReturn( $item2->{barcode} );
830 AddReturn( $item3->{barcode} );
834 my $tt_template = <<EOF;
835 Dear [% borrower.firstname %] [% borrower.surname %],
837 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.
839 [% branch.branchname %]
840 [% branch.branchaddress1 %]
841 [% branch.branchaddress2 %] [% branch.branchaddress3 %]
842 Phone: [% branch.branchphone %]
843 Fax: [% branch.branchfax %]
844 Email: [% branch.branchemail %]
846 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.
848 The following item(s) is/are currently overdue:
850 [% FOREACH overdue IN overdues %]
851 [%~ SET item = overdue.item ~%]
852 "[% item.biblio.title %]" by [% item.biblio.author %], [% item.itemcallnumber %], Barcode: [% item.barcode %]
854 [% FOREACH overdue IN overdues %]
855 [%~ SET item = overdue.item ~%]
856 [% overdue.date_due | \$KohaDates %]\t[% item.biblio.title %]\t[% item.barcode %]\t[% item.biblio.author %]\t[% item.itemnumber %]
859 Thank-you for your prompt attention to this matter.
861 [% branch.branchname %] Staff
864 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
866 C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
867 C4::Circulation::AddIssue( $patron, $item2->{barcode}, $yesterday ); # Add an first overdue
868 C4::Circulation::AddIssue( $patron, $item3->{barcode}, $two_days_ago ); # Add an second overdue
870 my $tt_letter = C4::Overdues::parse_overdues_letter(
872 letter_code => $code,
873 borrowernumber => $patron->{borrowernumber},
874 branchcode => $library->{branchcode},
877 bib => $library->{branchname},
878 'items.content' => $items_content,
879 count => scalar( @items ),
880 message_transport_type => 'email',
885 is( $tt_letter->{content}, $letter->{content}, );
888 subtest 'Bug 19743 - Header and Footer should be updated on each item for checkin / checkout / renewal notices' => sub {
891 my $checkout_code = 'CHECKOUT';
892 my $checkin_code = 'CHECKIN';
894 my $dbh = C4::Context->dbh;
895 $dbh->do("DELETE FROM letter");
896 $dbh->do("DELETE FROM issues");
897 $dbh->do("DELETE FROM message_queue");
899 # Enable notification for CHECKOUT - Things are hardcoded here but should work with default data
900 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 6 );
901 my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
902 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
903 # Enable notification for CHECKIN - Things are hardcoded here but should work with default data
904 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 5 );
905 $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
906 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
908 my $checkout_template = q|
909 <<branches.branchname>>
913 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
914 my $checkin_template = q[
915 <<branches.branchname>>
919 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
921 my $issue = C4::Circulation::AddIssue( $patron, $item1->{barcode} );
922 my $first_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
924 my $library_object = Koha::Libraries->find( $issue->branchcode );
925 my $old_branchname = $library_object->branchname;
926 my $new_branchname = "Kyle M Hall Memorial Library";
928 # Change branch name for second checkout notice
929 $library_object->branchname($new_branchname);
930 $library_object->store();
932 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
933 my $second_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
935 # Restore old name for first checkin notice
936 $library_object->branchname( $old_branchname );
937 $library_object->store();
939 AddReturn( $item1->{barcode} );
940 my $first_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
942 # Change branch name for second checkin notice
943 $library_object->branchname($new_branchname);
944 $library_object->store();
946 AddReturn( $item2->{barcode} );
947 my $second_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
949 # Restore old name for first TT checkout notice
950 $library_object->branchname( $old_branchname );
951 $library_object->store();
953 Koha::Notice::Messages->delete;
956 $checkout_template = q|
957 [% branch.branchname %]
961 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
962 $checkin_template = q[
963 [% branch.branchname %]
967 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
969 C4::Circulation::AddIssue( $patron, $item1->{barcode} );
970 my $first_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
972 # Change branch name for second checkout notice
973 $library_object->branchname($new_branchname);
974 $library_object->store();
976 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
977 my $second_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
979 # Restore old name for first checkin notice
980 $library_object->branchname( $old_branchname );
981 $library_object->store();
983 AddReturn( $item1->{barcode} );
984 my $first_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
986 # Change branch name for second checkin notice
987 $library_object->branchname($new_branchname);
988 $library_object->store();
990 AddReturn( $item2->{barcode} );
991 my $second_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
993 my $first_letter = qq[
996 my $second_letter = qq[
1001 is( $first_checkout_letter->content, $first_letter, 'Verify first checkout letter' );
1002 is( $second_checkout_letter->content, $second_letter, 'Verify second checkout letter' );
1003 is( $first_checkin_letter->content, $first_letter, 'Verify first checkin letter' );
1004 is( $second_checkin_letter->content, $second_letter, 'Verify second checkin letter' );
1006 is( $first_checkout_tt_letter->content, $first_letter, 'Verify TT first checkout letter' );
1007 is( $second_checkout_tt_letter->content, $second_letter, 'Verify TT second checkout letter' );
1008 is( $first_checkin_tt_letter->content, $first_letter, 'Verify TT first checkin letter' );
1009 is( $second_checkin_tt_letter->content, $second_letter, 'Verify TT second checkin letter' );
1014 subtest 'loops' => sub {
1017 my $module = "TEST";
1019 subtest 'primary key is AI' => sub {
1021 my $patron_1 = $builder->build({ source => 'Borrower' });
1022 my $patron_2 = $builder->build({ source => 'Borrower' });
1024 my $template = q|[% FOREACH patron IN borrowers %][% patron.surname %][% END %]|;
1025 reset_template( { template => $template, code => $code, module => $module } );
1026 my $letter = GetPreparedLetter( module => $module, letter_code => $code, loops => { borrowers => [ $patron_1->{borrowernumber}, $patron_2->{borrowernumber} ] } );
1027 my $expected_letter = join '', ( $patron_1->{surname}, $patron_2->{surname} );
1028 is( $letter->{content}, $expected_letter, );
1031 subtest 'foreign key is used' => sub {
1033 my $patron_1 = $builder->build({ source => 'Borrower' });
1034 my $patron_2 = $builder->build({ source => 'Borrower' });
1035 my $checkout_1 = $builder->build({ source => 'Issue', value => { borrowernumber => $patron_1->{borrowernumber} } } );
1036 my $checkout_2 = $builder->build({ source => 'Issue', value => { borrowernumber => $patron_1->{borrowernumber} } } );
1038 my $template = q|[% FOREACH checkout IN checkouts %][% checkout.issue_id %][% END %]|;
1039 reset_template( { template => $template, code => $code, module => $module } );
1040 my $letter = GetPreparedLetter( module => $module, letter_code => $code, loops => { issues => [ $checkout_1->{itemnumber}, $checkout_2->{itemnumber} ] } );
1041 my $expected_letter = join '', ( $checkout_1->{issue_id}, $checkout_2->{issue_id} );
1042 is( $letter->{content}, $expected_letter, );
1046 subtest 'add_tt_filters' => sub {
1049 my $module = "TEST";
1051 my $patron = $builder->build_object(
1053 class => 'Koha::Patrons',
1054 value => { surname => "with_punctuation_" }
1057 my $biblio = $builder->build_object(
1058 { class => 'Koha::Biblios', value => { title => "with_punctuation_" } }
1060 my $biblioitem = $builder->build_object(
1062 class => 'Koha::Biblioitems',
1064 biblionumber => $biblio->biblionumber,
1065 isbn => "with_punctuation_"
1070 my $template = q|patron=[% borrower.surname %];biblio=[% biblio.title %];biblioitems=[% biblioitem.isbn %]|;
1071 reset_template( { template => $template, code => $code, module => $module } );
1072 my $letter = GetPreparedLetter(
1074 letter_code => $code,
1076 borrowers => $patron->borrowernumber,
1077 biblio => $biblio->biblionumber,
1078 biblioitems => $biblioitem->biblioitemnumber
1081 my $expected_letter = q|patron=with_punctuation_;biblio=with_punctuation;biblioitems=with_punctuation|;
1082 is( $letter->{content}, $expected_letter, "Pre-processing should call TT plugin to remove punctuation if table is biblio or biblioitems");
1085 subtest 'Handle includes' => sub {
1087 my $cgi = CGI->new();
1088 my $code = 'TEST_INCLUDE';
1090 $builder->build_object( { class => 'Koha::Account::Lines', value => { credit_type_code => 'PAYMENT', status => 'CANCELLED' } } );
1091 my $template = <<EOF;
1093 [%- PROCESS 'accounts.inc' -%]
1094 [%- PROCESS account_type_description account=credit -%]
1096 reset_template({ template => $template, code => $code, module => 'test' });
1097 my $letter = GetPreparedLetter(
1099 letter_code => $code,
1101 credits => $account->accountlines_id
1104 is($letter->{content},' <span>Payment<span> (Cancelled)</span> </span>', "Include used in notice");
1107 subtest 'Dates formatting' => sub {
1109 my $code = 'TEST_DATE';
1110 t::lib::Mocks::mock_preference('dateformat', 'metric'); # MM/DD/YYYY
1111 my $biblio = $builder->build_object(
1113 class => 'Koha::Biblios',
1115 timestamp => '2018-12-13 20:21:22',
1116 datecreated => '2018-12-13'
1120 my $template = <<EOF;
1121 [%- USE KohaDates -%]
1122 [% biblio.timestamp %]
1123 [% biblio.timestamp | \$KohaDates %]
1124 [% biblio.timestamp | \$KohaDates with_hours => 1 %]
1126 [% biblio.datecreated %]
1127 [% biblio.datecreated | \$KohaDates %]
1128 [% biblio.datecreated | \$KohaDates with_hours => 1 %]
1130 [% biblio.timestamp | \$KohaDates dateformat => 'iso' %]
1131 [% KohaDates.output_preference( str => biblio.timestamp, dateformat => 'iso' ) %]
1132 [% KohaDates.output_preference( str => biblio.timestamp, dateformat => 'iso', dateonly => 1 ) %]
1134 reset_template({ template => $template, code => $code, module => 'test' });
1135 my $letter = GetPreparedLetter(
1137 letter_code => $code,
1139 biblio => $biblio->biblionumber,
1142 my $expected_content = sprintf("%s\n%s\n%s\n\n%s\n%s\n%s\n\n%s\n%s\n%s\n",
1143 '2018-12-13 20:21:22',
1155 is( $letter->{content}, $expected_content );
1158 sub reset_template {
1159 my ( $params ) = @_;
1160 my $template = $params->{template};
1161 my $code = $params->{code};
1162 my $module = $params->{module} || 'test_module';
1164 Koha::Notice::Templates->search( { code => $code } )->delete;
1165 Koha::Notice::Template->new(
1172 message_transport_type => 'email',
1173 content => $template
1178 sub process_letter {
1180 my $template = $params->{template};
1181 my $tables = $params->{tables};
1182 my $substitute = $params->{substitute};
1183 my $code = $params->{code};
1184 my $module = $params->{module} || 'test_module';
1185 my $branchcode = $params->{branchcode};
1187 reset_template( $params );
1189 my $letter = C4::Letters::GetPreparedLetter(
1191 letter_code => $code,
1194 substitute => $substitute,
1199 $schema->storage->txn_rollback;