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 => 19;
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 $biblio = Koha::Biblio->new(
70 title => 'Test Biblio'
74 my $biblioitem = Koha::Biblioitem->new(
76 biblionumber => $biblio->id()
80 my $item = Koha::Item->new(
82 biblionumber => $biblio->id(),
83 biblioitemnumber => $biblioitem->id()
87 my $hold = Koha::Hold->new(
89 borrowernumber => $patron->{borrowernumber},
90 biblionumber => $biblio->id()
94 my $news = Koha::NewsItem->new({ title => 'a news title', content => 'a news content'})->store();
95 my $serial = Koha::Serial->new()->store();
96 my $subscription = Koha::Subscription->new()->store();
97 my $suggestion = Koha::Suggestion->new()->store();
98 my $checkout = Koha::Checkout->new( { itemnumber => $item->id() } )->store();
99 my $modification = Koha::Patron::Modification->new( { verification_token => "TEST", changed_fields => 'firstname,surname' } )->store();
104 $dbh->prepare(q{INSERT INTO letter (module, code, name, title, content) VALUES ('test',?,'Test','Test',?)});
106 $sth->execute( "TEST_PATRON", "[% borrower.id %]" );
107 $prepared_letter = GetPreparedLetter(
110 letter_code => 'TEST_PATRON',
112 borrowers => $patron->{borrowernumber},
116 is( $prepared_letter->{content}, $patron->{borrowernumber}, 'Patron object used correctly with scalar' );
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' );
129 $prepared_letter = GetPreparedLetter(
132 letter_code => 'TEST_PATRON',
134 borrowers => [ $patron->{borrowernumber} ],
138 is( $prepared_letter->{content}, $patron->{borrowernumber}, 'Patron object used correctly with arrayref' );
140 $sth->execute( "TEST_BIBLIO", "[% biblio.id %]" );
141 $prepared_letter = GetPreparedLetter(
144 letter_code => 'TEST_BIBLIO',
146 biblio => $biblio->id(),
150 is( $prepared_letter->{content}, $biblio->id, 'Biblio object used correctly' );
152 $sth->execute( "TEST_LIBRARY", "[% branch.id %]" );
153 $prepared_letter = GetPreparedLetter(
156 letter_code => 'TEST_LIBRARY',
158 branches => $library->{branchcode}
162 is( $prepared_letter->{content}, $library->{branchcode}, 'Library object used correctly' );
164 $sth->execute( "TEST_ITEM", "[% item.id %]" );
165 $prepared_letter = GetPreparedLetter(
168 letter_code => 'TEST_ITEM',
174 is( $prepared_letter->{content}, $item->id(), 'Item object used correctly' );
176 $sth->execute( "TEST_NEWS", "[% news.id %]" );
177 $prepared_letter = GetPreparedLetter(
180 letter_code => 'TEST_NEWS',
182 opac_news => $news->id()
186 is( $prepared_letter->{content}, $news->id(), 'News object used correctly' );
188 $sth->execute( "TEST_HOLD", "[% hold.id %]" );
189 $prepared_letter = GetPreparedLetter(
192 letter_code => 'TEST_HOLD',
194 reserves => { borrowernumber => $patron->{borrowernumber}, biblionumber => $biblio->id() },
198 is( $prepared_letter->{content}, $hold->id(), 'Hold object used correctly' );
201 $prepared_letter = GetPreparedLetter(
204 letter_code => 'TEST_HOLD',
206 reserves => [ $patron->{borrowernumber}, $biblio->id() ],
212 like( $croak, qr{^Multiple foreign keys \(table reserves\) should be passed using an hashref.*}, "GetPreparedLetter should not be called with arrayref for multiple FK" );
215 $prepared_letter = GetPreparedLetter(
218 letter_code => 'TEST_HOLD',
220 'branches' => $library,
221 'borrowers' => $patron,
222 'biblio' => $biblio->id,
223 'biblioitems' => $biblioitem->id,
224 'reserves' => $hold->unblessed,
225 'items' => $hold->itemnumber,
229 is( $prepared_letter->{content}, $hold->id(), 'Hold object used correctly' );
231 $sth->execute( "TEST_SERIAL", "[% serial.id %]" );
232 $prepared_letter = GetPreparedLetter(
235 letter_code => 'TEST_SERIAL',
237 serial => $serial->id()
241 is( $prepared_letter->{content}, $serial->id(), 'Serial object used correctly' );
243 $sth->execute( "TEST_SUBSCRIPTION", "[% subscription.id %]" );
244 $prepared_letter = GetPreparedLetter(
247 letter_code => 'TEST_SUBSCRIPTION',
249 subscription => $subscription->id()
253 is( $prepared_letter->{content}, $subscription->id(), 'Subscription object used correctly' );
255 $sth->execute( "TEST_SUGGESTION", "[% suggestion.id %]" );
256 $prepared_letter = GetPreparedLetter(
259 letter_code => 'TEST_SUGGESTION',
261 suggestions => $suggestion->id()
265 is( $prepared_letter->{content}, $suggestion->id(), 'Suggestion object used correctly' );
267 $sth->execute( "TEST_ISSUE", "[% checkout.id %]" );
268 $prepared_letter = GetPreparedLetter(
271 letter_code => 'TEST_ISSUE',
273 issues => $item->id()
277 is( $prepared_letter->{content}, $checkout->id(), 'Checkout object used correctly' );
279 $sth->execute( "TEST_MODIFICATION", "[% patron_modification.id %]" );
280 $prepared_letter = GetPreparedLetter(
283 letter_code => 'TEST_MODIFICATION',
285 borrower_modifications => $modification->verification_token,
289 is( $prepared_letter->{content}, $modification->id(), 'Patron modification object used correctly' );
291 subtest 'regression tests' => sub {
294 my $library = $builder->build( { source => 'Branch' } );
295 my $patron = $builder->build( { source => 'Borrower' } );
296 my $biblio1 = Koha::Biblio->new({title => 'Test Biblio 1', author => 'An author', })->store->unblessed;
297 my $biblioitem1 = Koha::Biblioitem->new({biblionumber => $biblio1->{biblionumber}})->store()->unblessed;
298 my $item1 = Koha::Item->new(
300 biblionumber => $biblio1->{biblionumber},
301 biblioitemnumber => $biblioitem1->{biblioitemnumber},
302 barcode => 'a_t_barcode',
303 homebranch => $library->{branchcode},
304 holdingbranch => $library->{branchcode},
306 itemcallnumber => 'itemcallnumber1',
309 my $biblio2 = Koha::Biblio->new({title => 'Test Biblio 2'})->store->unblessed;
310 my $biblioitem2 = Koha::Biblioitem->new({biblionumber => $biblio2->{biblionumber}})->store()->unblessed;
311 my $item2 = Koha::Item->new(
313 biblionumber => $biblio2->{biblionumber},
314 biblioitemnumber => $biblioitem2->{biblioitemnumber},
315 barcode => 'another_t_barcode',
316 homebranch => $library->{branchcode},
317 holdingbranch => $library->{branchcode},
319 itemcallnumber => 'itemcallnumber2',
322 my $biblio3 = Koha::Biblio->new({title => 'Test Biblio 3'})->store->unblessed;
323 my $biblioitem3 = Koha::Biblioitem->new({biblionumber => $biblio3->{biblionumber}})->store()->unblessed;
324 my $item3 = Koha::Item->new(
326 biblionumber => $biblio3->{biblionumber},
327 biblioitemnumber => $biblioitem3->{biblioitemnumber},
328 barcode => 'another_t_barcode_3',
329 homebranch => $library->{branchcode},
330 holdingbranch => $library->{branchcode},
332 itemcallnumber => 'itemcallnumber3',
336 t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
338 subtest 'ACQ_NOTIF_ON_RECEIV ' => sub {
340 my $code = 'ACQ_NOTIF_ON_RECEIV';
341 my $branchcode = $library->{branchcode};
342 my $order = $builder->build({ source => 'Aqorder' });
345 Dear <<borrowers.firstname>> <<borrowers.surname>>,
346 The order <<aqorders.ordernumber>> (<<biblio.title>>) has been received.
349 my $params = { code => $code, branchcode => $branchcode, tables => { branches => $library, borrowers => $patron, biblio => $biblio1, aqorders => $order } };
350 my $letter = process_letter( { template => $template, %$params });
352 Dear [% borrower.firstname %] [% borrower.surname %],
353 The order [% order.ordernumber %] ([% biblio.title %]) has been received.
356 my $tt_letter = process_letter( { template => $tt_template, %$params });
358 is( $tt_letter->{content}, $letter->{content}, 'Verified letter content' );
361 subtest 'AR_*' => sub {
363 my $code = 'AR_CANCELED';
364 my $branchcode = $library->{branchcode};
367 <<borrowers.firstname>> <<borrowers.surname>> (<<borrowers.cardnumber>>)
369 Your request for an article from <<biblio.title>> (<<items.barcode>>) has been canceled for the following reason:
371 <<article_requests.notes>>
374 Title: <<article_requests.title>>
375 Author: <<article_requests.author>>
376 Volume: <<article_requests.volume>>
377 Issue: <<article_requests.issue>>
378 Date: <<article_requests.date>>
379 Pages: <<article_requests.pages>>
380 Chapters: <<article_requests.chapters>>
381 Notes: <<article_requests.patron_notes>>
383 reset_template( { template => $template, code => $code, module => 'circulation' } );
384 my $article_request = $builder->build({ source => 'ArticleRequest' });
385 Koha::ArticleRequests->find( $article_request->{id} )->cancel;
386 my $letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
389 [% borrower.firstname %] [% borrower.surname %] ([% borrower.cardnumber %])
391 Your request for an article from [% biblio.title %] ([% item.barcode %]) has been canceled for the following reason:
393 [% article_request.notes %]
396 Title: [% article_request.title %]
397 Author: [% article_request.author %]
398 Volume: [% article_request.volume %]
399 Issue: [% article_request.issue %]
400 Date: [% article_request.date %]
401 Pages: [% article_request.pages %]
402 Chapters: [% article_request.chapters %]
403 Notes: [% article_request.patron_notes %]
405 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
406 Koha::ArticleRequests->find( $article_request->{id} )->cancel;
407 my $tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
408 is( $tt_letter->content, $letter->content, 'Compare AR_* notices' );
409 isnt( $tt_letter->message_id, $letter->message_id, 'Comparing AR_* notices should compare 2 different messages' );
412 subtest 'CHECKOUT+CHECKIN' => sub {
415 my $checkout_code = 'CHECKOUT';
416 my $checkin_code = 'CHECKIN';
418 my $dbh = C4::Context->dbh;
419 # Enable notification for CHECKOUT - Things are hardcoded here but should work with default data
420 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 6 );
421 my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
422 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
423 # Enable notification for CHECKIN - Things are hardcoded here but should work with default data
424 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 5 );
425 $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
426 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
429 my $checkout_template = q|
430 The following items have been checked out:
434 Thank you for visiting <<branches.branchname>>.
436 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
437 my $checkin_template = q[
438 The following items have been checked out:
440 <<biblio.title>> was due on <<old_issues.date_due | dateonly>>
442 Thank you for visiting <<branches.branchname>>.
444 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
446 C4::Circulation::AddIssue( $patron, $item1->{barcode} );
447 my $first_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
448 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
449 my $second_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
451 AddReturn( $item1->{barcode} );
452 my $first_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
453 AddReturn( $item2->{barcode} );
454 my $second_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
456 Koha::Notice::Messages->delete;
459 $checkout_template = q|
460 The following items have been checked out:
464 Thank you for visiting [% branch.branchname %].
466 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
467 $checkin_template = q[
468 The following items have been checked out:
470 [% biblio.title %] was due on [% old_checkout.date_due | $KohaDates %]
472 Thank you for visiting [% branch.branchname %].
474 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
476 C4::Circulation::AddIssue( $patron, $item1->{barcode} );
477 my $first_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
478 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
479 my $second_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
481 AddReturn( $item1->{barcode} );
482 my $first_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
483 AddReturn( $item2->{barcode} );
484 my $second_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
486 is( $first_checkout_tt_letter->content, $first_checkout_letter->content, 'Verify first checkout letter' );
487 is( $second_checkout_tt_letter->content, $second_checkout_letter->content, 'Verify second checkout letter' );
488 is( $first_checkin_tt_letter->content, $first_checkin_letter->content, 'Verify first checkin letter' );
489 is( $second_checkin_tt_letter->content, $second_checkin_letter->content, 'Verify second checkin letter' );
493 subtest 'DUEDGST|count' => sub {
496 my $code = 'DUEDGST';
498 my $dbh = C4::Context->dbh;
499 # Enable notification for DUEDGST - Things are hardcoded here but should work with default data
500 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 1 );
501 my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
502 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
506 substitute => { count => 42 },
510 You have <<count>> items due
512 my $letter = process_letter( { template => $template, %$params });
515 You have [% count %] items due
517 my $tt_letter = process_letter( { template => $tt_template, %$params });
518 is( $tt_letter->{content}, $letter->{content}, );
521 subtest 'HOLD_SLIP|dates|today' => sub {
524 my $code = 'HOLD_SLIP';
526 C4::Reserves::AddReserve(
528 branchcode => $library->{branchcode},
529 borrowernumber => $patron->{borrowernumber},
530 biblionumber => $biblio1->{biblionumber},
532 itemnumber => $item1->{itemnumber},
536 C4::Reserves::AddReserve(
538 branchcode => $library->{branchcode},
539 borrowernumber => $patron->{borrowernumber},
540 biblionumber => $biblio2->{biblionumber},
541 notes => "another note",
542 itemnumber => $item2->{itemnumber},
546 my $template = <<EOF;
547 <h5>Date: <<today>></h5>
549 <h3> Transfer to/Hold in <<branches.branchname>></h3>
551 <h3><<borrowers.surname>>, <<borrowers.firstname>></h3>
554 <li><<borrowers.cardnumber>></li>
555 <li><<borrowers.phone>></li>
556 <li> <<borrowers.address>><br />
557 <<borrowers.address2>><br />
558 <<borrowers.city>> <<borrowers.zipcode>>
560 <li><<borrowers.email>></li>
563 <h3>ITEM ON HOLD</h3>
564 <h4><<biblio.title>></h4>
565 <h5><<biblio.author>></h5>
567 <li><<items.barcode>></li>
568 <li><<items.itemcallnumber>></li>
569 <li><<reserves.waitingdate>></li>
572 <pre><<reserves.reservenotes>></pre>
576 reset_template( { template => $template, code => $code, module => 'circulation' } );
577 my $letter_for_item1 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, borrowernumber => $patron->{borrowernumber}, biblionumber => $biblio1->{biblionumber} } );
578 my $letter_for_item2 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, borrowernumber => $patron->{borrowernumber}, biblionumber => $biblio2->{biblionumber} } );
580 my $tt_template = <<EOF;
581 <h5>Date: [% today | \$KohaDates with_hours => 1 %]</h5>
583 <h3> Transfer to/Hold in [% branch.branchname %]</h3>
585 <h3>[% borrower.surname %], [% borrower.firstname %]</h3>
588 <li>[% borrower.cardnumber %]</li>
589 <li>[% borrower.phone %]</li>
590 <li> [% borrower.address %]<br />
591 [% borrower.address2 %]<br />
592 [% borrower.city %] [% borrower.zipcode %]
594 <li>[% borrower.email %]</li>
597 <h3>ITEM ON HOLD</h3>
598 <h4>[% biblio.title %]</h4>
599 <h5>[% biblio.author %]</h5>
601 <li>[% item.barcode %]</li>
602 <li>[% item.itemcallnumber %]</li>
603 <li>[% hold.waitingdate | \$KohaDates %]</li>
606 <pre>[% hold.reservenotes %]</pre>
610 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
611 my $tt_letter_for_item1 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, borrowernumber => $patron->{borrowernumber}, biblionumber => $biblio1->{biblionumber} } );
612 my $tt_letter_for_item2 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, borrowernumber => $patron->{borrowernumber}, biblionumber => $biblio2->{biblionumber} } );
614 is( $tt_letter_for_item1->{content}, $letter_for_item1->{content}, );
615 is( $tt_letter_for_item2->{content}, $letter_for_item2->{content}, );
618 subtest 'ISSUESLIP|checkedout|repeat' => sub {
621 my $code = 'ISSUESLIP';
622 my $now = dt_from_string;
623 my $one_minute_ago = dt_from_string->subtract( minutes => 1 );
625 my $branchcode = $library->{branchcode};
628 my $news_item = Koha::NewsItem->new({ branchcode => $branchcode, title => "A wonderful news", content => "This is the wonderful news." })->store;
631 my $template = <<EOF;
632 <h3><<branches.branchname>></h3>
633 Checked out to <<borrowers.title>> <<borrowers.firstname>> <<borrowers.initials>> <<borrowers.surname>> <br />
634 (<<borrowers.cardnumber>>) <br />
641 <<biblio.title>> <br />
642 Barcode: <<items.barcode>><br />
643 Date due: <<issues.date_due | dateonly>><br />
650 <<biblio.title>> <br />
651 Barcode: <<items.barcode>><br />
652 Date due: <<issues.date_due | dateonly>><br />
658 <h4 style="text-align: center; font-style:italic;">News</h4>
660 <div class="newsitem">
661 <h5 style="margin-bottom: 1px; margin-top: 1px"><b><<opac_news.title>></b></h5>
662 <p style="margin-bottom: 1px; margin-top: 1px"><<opac_news.content>></p>
663 <p class="newsfooter" style="font-size: 8pt; font-style:italic; margin-bottom: 1px; margin-top: 1px">Posted on <<opac_news.timestamp>></p>
669 reset_template( { template => $template, code => $code, module => 'circulation' } );
671 my $checkout = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
672 $checkout->set( { timestamp => $now, issuedate => $one_minute_ago } )->store;
673 my $first_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
675 $checkout = C4::Circulation::AddIssue( $patron, $item2->{barcode} ); # Add a second checkout
676 $checkout->set( { timestamp => $now, issuedate => $now } )->store;
677 my $yesterday = dt_from_string->subtract( days => 1 );
678 C4::Circulation::AddIssue( $patron, $item3->{barcode}, $yesterday ); # Add an overdue
679 my $second_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
682 AddReturn( $item1->{barcode} );
683 AddReturn( $item2->{barcode} );
684 AddReturn( $item3->{barcode} );
687 my $tt_template = <<EOF;
688 <h3>[% branch.branchname %]</h3>
689 Checked out to [% borrower.title %] [% borrower.firstname %] [% borrower.initials %] [% borrower.surname %] <br />
690 ([% borrower.cardnumber %]) <br />
692 [% today | \$KohaDates with_hours => 1 %]<br />
695 [% FOREACH checkout IN checkouts %]
696 [%~ SET item = checkout.item %]
697 [%~ SET biblio = checkout.item.biblio %]
699 [% biblio.title %] <br />
700 Barcode: [% item.barcode %]<br />
701 Date due: [% checkout.date_due | \$KohaDates %]<br />
706 [% FOREACH overdue IN overdues %]
707 [%~ SET item = overdue.item %]
708 [%~ SET biblio = overdue.item.biblio %]
710 [% biblio.title %] <br />
711 Barcode: [% item.barcode %]<br />
712 Date due: [% overdue.date_due | \$KohaDates %]<br />
718 <h4 style="text-align: center; font-style:italic;">News</h4>
719 [% FOREACH n IN news %]
720 <div class="newsitem">
721 <h5 style="margin-bottom: 1px; margin-top: 1px"><b>[% n.title %]</b></h5>
722 <p style="margin-bottom: 1px; margin-top: 1px">[% n.content %]</p>
723 <p class="newsfooter" style="font-size: 8pt; font-style:italic; margin-bottom: 1px; margin-top: 1px">Posted on [% n.timestamp | \$KohaDates %]</p>
729 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
731 $checkout = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
732 $checkout->set( { timestamp => $now, issuedate => $one_minute_ago } )->store;
733 my $first_tt_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
735 $checkout = C4::Circulation::AddIssue( $patron, $item2->{barcode} ); # Add a second checkout
736 $checkout->set( { timestamp => $now, issuedate => $now } )->store;
737 C4::Circulation::AddIssue( $patron, $item3->{barcode}, $yesterday ); # Add an overdue
738 my $second_tt_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
740 # There is too many line breaks generated by the historic syntax
741 $second_slip->{content} =~ s|</p>\n\n\n<p>|</p>\n\n<p>|s;
743 is( $first_tt_slip->{content}, $first_slip->{content}, );
744 is( $second_tt_slip->{content}, $second_slip->{content}, );
747 AddReturn( $item1->{barcode} );
748 AddReturn( $item2->{barcode} );
749 AddReturn( $item3->{barcode} );
752 subtest 'ODUE|items.content|item' => sub {
757 my $branchcode = $library->{branchcode};
760 # FIXME items.fine does not work with TT notices
762 # <item> should contain Fine: <<items.fine>></item>
763 my $template = <<EOF;
764 Dear <<borrowers.firstname>> <<borrowers.surname>>,
766 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.
768 <<branches.branchname>>
769 <<branches.branchaddress1>>
770 <<branches.branchaddress2>> <<branches.branchaddress3>>
771 Phone: <<branches.branchphone>>
772 Fax: <<branches.branchfax>>
773 Email: <<branches.branchemail>>
775 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.
777 The following item(s) is/are currently overdue:
779 <item>"<<biblio.title>>" by <<biblio.author>>, <<items.itemcallnumber>>, Barcode: <<items.barcode>></item>
783 Thank-you for your prompt attention to this matter.
785 <<branches.branchname>> Staff
788 reset_template( { template => $template, code => $code, module => 'circulation' } );
790 my $yesterday = dt_from_string->subtract( days => 1 );
791 my $two_days_ago = dt_from_string->subtract( days => 2 );
792 my $issue1 = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
793 my $issue2 = C4::Circulation::AddIssue( $patron, $item2->{barcode}, $yesterday ); # Add an first overdue
794 my $issue3 = C4::Circulation::AddIssue( $patron, $item3->{barcode}, $two_days_ago ); # Add an second overdue
795 $issue1 = $issue1->unblessed;
796 $issue2 = $issue2->unblessed;
797 $issue3 = $issue3->unblessed;
800 my @item_fields = qw( date_due title barcode author itemnumber );
801 my $items_content = C4::Letters::get_item_content( { item => { %$item1, %$biblio1, %$issue1 }, item_content_fields => \@item_fields, dateonly => 1 } );
802 $items_content .= C4::Letters::get_item_content( { item => { %$item2, %$biblio2, %$issue2 }, item_content_fields => \@item_fields, dateonly => 1 } );
803 $items_content .= C4::Letters::get_item_content( { item => { %$item3, %$biblio3, %$issue3 }, item_content_fields => \@item_fields, dateonly => 1 } );
805 my @items = ( $item1, $item2, $item3 );
806 my $letter = C4::Overdues::parse_overdues_letter(
808 letter_code => $code,
809 borrowernumber => $patron->{borrowernumber},
810 branchcode => $library->{branchcode},
813 bib => $library->{branchname},
814 'items.content' => $items_content,
815 count => scalar( @items ),
816 message_transport_type => 'email',
822 AddReturn( $item1->{barcode} );
823 AddReturn( $item2->{barcode} );
824 AddReturn( $item3->{barcode} );
828 my $tt_template = <<EOF;
829 Dear [% borrower.firstname %] [% borrower.surname %],
831 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.
833 [% branch.branchname %]
834 [% branch.branchaddress1 %]
835 [% branch.branchaddress2 %] [% branch.branchaddress3 %]
836 Phone: [% branch.branchphone %]
837 Fax: [% branch.branchfax %]
838 Email: [% branch.branchemail %]
840 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.
842 The following item(s) is/are currently overdue:
844 [% FOREACH overdue IN overdues %]
845 [%~ SET item = overdue.item ~%]
846 "[% item.biblio.title %]" by [% item.biblio.author %], [% item.itemcallnumber %], Barcode: [% item.barcode %]
848 [% FOREACH overdue IN overdues %]
849 [%~ SET item = overdue.item ~%]
850 [% overdue.date_due | \$KohaDates %]\t[% item.biblio.title %]\t[% item.barcode %]\t[% item.biblio.author %]\t[% item.itemnumber %]
853 Thank-you for your prompt attention to this matter.
855 [% branch.branchname %] Staff
858 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
860 C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
861 C4::Circulation::AddIssue( $patron, $item2->{barcode}, $yesterday ); # Add an first overdue
862 C4::Circulation::AddIssue( $patron, $item3->{barcode}, $two_days_ago ); # Add an second overdue
864 my $tt_letter = C4::Overdues::parse_overdues_letter(
866 letter_code => $code,
867 borrowernumber => $patron->{borrowernumber},
868 branchcode => $library->{branchcode},
871 bib => $library->{branchname},
872 'items.content' => $items_content,
873 count => scalar( @items ),
874 message_transport_type => 'email',
879 is( $tt_letter->{content}, $letter->{content}, );
882 subtest 'Bug 19743 - Header and Footer should be updated on each item for checkin / checkout / renewal notices' => sub {
885 my $checkout_code = 'CHECKOUT';
886 my $checkin_code = 'CHECKIN';
888 my $dbh = C4::Context->dbh;
889 $dbh->do("DELETE FROM letter");
890 $dbh->do("DELETE FROM issues");
891 $dbh->do("DELETE FROM message_queue");
893 # Enable notification for CHECKOUT - Things are hardcoded here but should work with default data
894 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 6 );
895 my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
896 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
897 # Enable notification for CHECKIN - Things are hardcoded here but should work with default data
898 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 5 );
899 $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
900 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
902 my $checkout_template = q|
903 <<branches.branchname>>
907 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
908 my $checkin_template = q[
909 <<branches.branchname>>
913 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
915 my $issue = C4::Circulation::AddIssue( $patron, $item1->{barcode} );
916 my $first_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
918 my $library_object = Koha::Libraries->find( $issue->branchcode );
919 my $old_branchname = $library_object->branchname;
920 my $new_branchname = "Kyle M Hall Memorial Library";
922 # Change branch name for second checkout notice
923 $library_object->branchname($new_branchname);
924 $library_object->store();
926 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
927 my $second_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
929 # Restore old name for first checkin notice
930 $library_object->branchname( $old_branchname );
931 $library_object->store();
933 AddReturn( $item1->{barcode} );
934 my $first_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
936 # Change branch name for second checkin notice
937 $library_object->branchname($new_branchname);
938 $library_object->store();
940 AddReturn( $item2->{barcode} );
941 my $second_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
943 # Restore old name for first TT checkout notice
944 $library_object->branchname( $old_branchname );
945 $library_object->store();
947 Koha::Notice::Messages->delete;
950 $checkout_template = q|
951 [% branch.branchname %]
955 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
956 $checkin_template = q[
957 [% branch.branchname %]
961 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
963 C4::Circulation::AddIssue( $patron, $item1->{barcode} );
964 my $first_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
966 # Change branch name for second checkout notice
967 $library_object->branchname($new_branchname);
968 $library_object->store();
970 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
971 my $second_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
973 # Restore old name for first checkin notice
974 $library_object->branchname( $old_branchname );
975 $library_object->store();
977 AddReturn( $item1->{barcode} );
978 my $first_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
980 # Change branch name for second checkin notice
981 $library_object->branchname($new_branchname);
982 $library_object->store();
984 AddReturn( $item2->{barcode} );
985 my $second_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
987 my $first_letter = qq[
990 my $second_letter = qq[
995 is( $first_checkout_letter->content, $first_letter, 'Verify first checkout letter' );
996 is( $second_checkout_letter->content, $second_letter, 'Verify second checkout letter' );
997 is( $first_checkin_letter->content, $first_letter, 'Verify first checkin letter' );
998 is( $second_checkin_letter->content, $second_letter, 'Verify second checkin letter' );
1000 is( $first_checkout_tt_letter->content, $first_letter, 'Verify TT first checkout letter' );
1001 is( $second_checkout_tt_letter->content, $second_letter, 'Verify TT second checkout letter' );
1002 is( $first_checkin_tt_letter->content, $first_letter, 'Verify TT first checkin letter' );
1003 is( $second_checkin_tt_letter->content, $second_letter, 'Verify TT second checkin letter' );
1008 subtest 'loops' => sub {
1011 my $module = "TEST";
1013 subtest 'primary key is AI' => sub {
1015 my $patron_1 = $builder->build({ source => 'Borrower' });
1016 my $patron_2 = $builder->build({ source => 'Borrower' });
1018 my $template = q|[% FOREACH patron IN borrowers %][% patron.surname %][% END %]|;
1019 reset_template( { template => $template, code => $code, module => $module } );
1020 my $letter = GetPreparedLetter( module => $module, letter_code => $code, loops => { borrowers => [ $patron_1->{borrowernumber}, $patron_2->{borrowernumber} ] } );
1021 my $expected_letter = join '', ( $patron_1->{surname}, $patron_2->{surname} );
1022 is( $letter->{content}, $expected_letter, );
1025 subtest 'foreign key is used' => sub {
1027 my $patron_1 = $builder->build({ source => 'Borrower' });
1028 my $patron_2 = $builder->build({ source => 'Borrower' });
1029 my $checkout_1 = $builder->build({ source => 'Issue', value => { borrowernumber => $patron_1->{borrowernumber} } } );
1030 my $checkout_2 = $builder->build({ source => 'Issue', value => { borrowernumber => $patron_1->{borrowernumber} } } );
1032 my $template = q|[% FOREACH checkout IN checkouts %][% checkout.issue_id %][% END %]|;
1033 reset_template( { template => $template, code => $code, module => $module } );
1034 my $letter = GetPreparedLetter( module => $module, letter_code => $code, loops => { issues => [ $checkout_1->{itemnumber}, $checkout_2->{itemnumber} ] } );
1035 my $expected_letter = join '', ( $checkout_1->{issue_id}, $checkout_2->{issue_id} );
1036 is( $letter->{content}, $expected_letter, );
1040 subtest 'add_tt_filters' => sub {
1043 my $module = "TEST";
1045 my $patron = $builder->build_object(
1047 class => 'Koha::Patrons',
1048 value => { surname => "with_punctuation_" }
1051 my $biblio = $builder->build_object(
1052 { class => 'Koha::Biblios', value => { title => "with_punctuation_" } }
1054 my $biblioitem = $builder->build_object(
1056 class => 'Koha::Biblioitems',
1058 biblionumber => $biblio->biblionumber,
1059 isbn => "with_punctuation_"
1064 my $template = q|patron=[% borrower.surname %];biblio=[% biblio.title %];biblioitems=[% biblioitem.isbn %]|;
1065 reset_template( { template => $template, code => $code, module => $module } );
1066 my $letter = GetPreparedLetter(
1068 letter_code => $code,
1070 borrowers => $patron->borrowernumber,
1071 biblio => $biblio->biblionumber,
1072 biblioitems => $biblioitem->biblioitemnumber
1075 my $expected_letter = q|patron=with_punctuation_;biblio=with_punctuation;biblioitems=with_punctuation|;
1076 is( $letter->{content}, $expected_letter, "Pre-processing should call TT plugin to remove punctuation if table is biblio or biblioitems");
1079 subtest 'Dates formatting' => sub {
1081 my $code = 'TEST_DATE';
1082 t::lib::Mocks::mock_preference('dateformat', 'metric'); # MM/DD/YYYY
1083 my $biblio = $builder->build_object(
1085 class => 'Koha::Biblios',
1087 timestamp => '2018-12-13 20:21:22',
1088 datecreated => '2018-12-13'
1092 my $template = <<EOF;
1093 [%- USE KohaDates -%]
1094 [% biblio.timestamp %]
1095 [% biblio.timestamp | \$KohaDates %]
1096 [% biblio.timestamp | \$KohaDates with_hours => 1 %]
1098 [% biblio.datecreated %]
1099 [% biblio.datecreated | \$KohaDates %]
1100 [% biblio.datecreated | \$KohaDates with_hours => 1 %]
1102 [% biblio.timestamp | \$KohaDates dateformat => 'iso' %]
1103 [% KohaDates.output_preference( str => biblio.timestamp, dateformat => 'iso' ) %]
1104 [% KohaDates.output_preference( str => biblio.timestamp, dateformat => 'iso', dateonly => 1 ) %]
1106 reset_template({ template => $template, code => $code, module => 'test' });
1107 my $letter = GetPreparedLetter(
1109 letter_code => $code,
1111 biblio => $biblio->biblionumber,
1114 my $expected_content = sprintf("%s\n%s\n%s\n\n%s\n%s\n%s\n\n%s\n%s\n%s\n",
1115 '2018-12-13 20:21:22',
1127 is( $letter->{content}, $expected_content );
1130 sub reset_template {
1131 my ( $params ) = @_;
1132 my $template = $params->{template};
1133 my $code = $params->{code};
1134 my $module = $params->{module} || 'test_module';
1136 Koha::Notice::Templates->search( { code => $code } )->delete;
1137 Koha::Notice::Template->new(
1144 message_transport_type => 'email',
1145 content => $template
1150 sub process_letter {
1152 my $template = $params->{template};
1153 my $tables = $params->{tables};
1154 my $substitute = $params->{substitute};
1155 my $code = $params->{code};
1156 my $module = $params->{module} || 'test_module';
1157 my $branchcode = $params->{branchcode};
1159 reset_template( $params );
1161 my $letter = C4::Letters::GetPreparedLetter(
1163 letter_code => $code,
1166 substitute => $substitute,