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 $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','Test',?)});
105 $sth->execute( "TEST_PATRON", "[% 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' );
117 $prepared_letter = GetPreparedLetter(
120 letter_code => 'TEST_PATRON',
122 borrowers => $patron,
126 is( $prepared_letter->{content}, $patron->{borrowernumber}, 'Patron object used correctly with hashref' );
128 $prepared_letter = GetPreparedLetter(
131 letter_code => 'TEST_PATRON',
133 borrowers => [ $patron->{borrowernumber} ],
137 is( $prepared_letter->{content}, $patron->{borrowernumber}, 'Patron object used correctly with arrayref' );
139 $sth->execute( "TEST_BIBLIO", "[% biblio.id %]" );
140 $prepared_letter = GetPreparedLetter(
143 letter_code => 'TEST_BIBLIO',
145 biblio => $item->biblionumber,
149 is( $prepared_letter->{content}, $item->biblionumber, 'Biblio object used correctly' );
151 $sth->execute( "TEST_LIBRARY", "[% branch.id %]" );
152 $prepared_letter = GetPreparedLetter(
155 letter_code => 'TEST_LIBRARY',
157 branches => $library->{branchcode}
161 is( $prepared_letter->{content}, $library->{branchcode}, 'Library object used correctly' );
163 $sth->execute( "TEST_ITEM", "[% item.id %]" );
164 $prepared_letter = GetPreparedLetter(
167 letter_code => 'TEST_ITEM',
173 is( $prepared_letter->{content}, $item->id(), 'Item object used correctly' );
175 $sth->execute( "TEST_NEWS", "[% news.id %]" );
176 $prepared_letter = GetPreparedLetter(
179 letter_code => 'TEST_NEWS',
181 opac_news => $news->id()
185 is( $prepared_letter->{content}, $news->id(), 'News object used correctly' );
187 $sth->execute( "TEST_HOLD", "[% hold.id %]" );
188 $prepared_letter = GetPreparedLetter(
191 letter_code => 'TEST_HOLD',
193 reserves => { borrowernumber => $patron->{borrowernumber}, biblionumber => $item->biblionumber },
197 is( $prepared_letter->{content}, $hold->id(), 'Hold object used correctly' );
200 $prepared_letter = GetPreparedLetter(
203 letter_code => 'TEST_HOLD',
205 reserves => [ $patron->{borrowernumber}, $item->biblionumber ],
211 like( $croak, qr{^Multiple foreign keys \(table reserves\) should be passed using an hashref.*}, "GetPreparedLetter should not be called with arrayref for multiple FK" );
214 $prepared_letter = GetPreparedLetter(
217 letter_code => 'TEST_HOLD',
219 'branches' => $library,
220 'borrowers' => $patron,
221 'biblio' => $item->biblionumber,
222 'biblioitems' => $item->biblioitemnumber,
223 'reserves' => $hold->unblessed,
224 'items' => $hold->itemnumber,
228 is( $prepared_letter->{content}, $hold->id(), 'Hold object used correctly' );
230 $sth->execute( "TEST_SERIAL", "[% serial.id %]" );
231 $prepared_letter = GetPreparedLetter(
234 letter_code => 'TEST_SERIAL',
236 serial => $serial->id()
240 is( $prepared_letter->{content}, $serial->id(), 'Serial object used correctly' );
242 $sth->execute( "TEST_SUBSCRIPTION", "[% subscription.id %]" );
243 $prepared_letter = GetPreparedLetter(
246 letter_code => 'TEST_SUBSCRIPTION',
248 subscription => $subscription->id()
252 is( $prepared_letter->{content}, $subscription->id(), 'Subscription object used correctly' );
254 $sth->execute( "TEST_SUGGESTION", "[% suggestion.id %]" );
255 $prepared_letter = GetPreparedLetter(
258 letter_code => 'TEST_SUGGESTION',
260 suggestions => $suggestion->id()
264 is( $prepared_letter->{content}, $suggestion->id(), 'Suggestion object used correctly' );
266 $sth->execute( "TEST_ISSUE", "[% checkout.id %]" );
267 $prepared_letter = GetPreparedLetter(
270 letter_code => 'TEST_ISSUE',
272 issues => $item->id()
276 is( $prepared_letter->{content}, $checkout->id(), 'Checkout object used correctly' );
278 $sth->execute( "TEST_MODIFICATION", "[% patron_modification.id %]" );
279 $prepared_letter = GetPreparedLetter(
282 letter_code => 'TEST_MODIFICATION',
284 borrower_modifications => $modification->verification_token,
288 is( $prepared_letter->{content}, $modification->id(), 'Patron modification object used correctly' );
290 subtest 'regression tests' => sub {
293 my $library = $builder->build( { source => 'Branch' } );
294 my $patron = $builder->build( { source => 'Borrower' } );
295 my $biblio1 = Koha::Biblio->new({title => 'Test Biblio 1', author => 'An author', })->store->unblessed;
296 my $biblioitem1 = Koha::Biblioitem->new({biblionumber => $biblio1->{biblionumber}})->store()->unblessed;
297 my $item1 = Koha::Item->new(
299 biblionumber => $biblio1->{biblionumber},
300 biblioitemnumber => $biblioitem1->{biblioitemnumber},
301 barcode => 'a_t_barcode',
302 homebranch => $library->{branchcode},
303 holdingbranch => $library->{branchcode},
305 itemcallnumber => 'itemcallnumber1',
308 my $biblio2 = Koha::Biblio->new({title => 'Test Biblio 2'})->store->unblessed;
309 my $biblioitem2 = Koha::Biblioitem->new({biblionumber => $biblio2->{biblionumber}})->store()->unblessed;
310 my $item2 = Koha::Item->new(
312 biblionumber => $biblio2->{biblionumber},
313 biblioitemnumber => $biblioitem2->{biblioitemnumber},
314 barcode => 'another_t_barcode',
315 homebranch => $library->{branchcode},
316 holdingbranch => $library->{branchcode},
318 itemcallnumber => 'itemcallnumber2',
321 my $biblio3 = Koha::Biblio->new({title => 'Test Biblio 3'})->store->unblessed;
322 my $biblioitem3 = Koha::Biblioitem->new({biblionumber => $biblio3->{biblionumber}})->store()->unblessed;
323 my $item3 = Koha::Item->new(
325 biblionumber => $biblio3->{biblionumber},
326 biblioitemnumber => $biblioitem3->{biblioitemnumber},
327 barcode => 'another_t_barcode_3',
328 homebranch => $library->{branchcode},
329 holdingbranch => $library->{branchcode},
331 itemcallnumber => 'itemcallnumber3',
335 t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
337 subtest 'ACQ_NOTIF_ON_RECEIV ' => sub {
339 my $code = 'ACQ_NOTIF_ON_RECEIV';
340 my $branchcode = $library->{branchcode};
341 my $order = $builder->build({ source => 'Aqorder' });
344 Dear <<borrowers.firstname>> <<borrowers.surname>>,
345 The order <<aqorders.ordernumber>> (<<biblio.title>>) has been received.
348 my $params = { code => $code, branchcode => $branchcode, tables => { branches => $library, borrowers => $patron, biblio => $biblio1, aqorders => $order } };
349 my $letter = process_letter( { template => $template, %$params });
351 Dear [% borrower.firstname %] [% borrower.surname %],
352 The order [% order.ordernumber %] ([% biblio.title %]) has been received.
355 my $tt_letter = process_letter( { template => $tt_template, %$params });
357 is( $tt_letter->{content}, $letter->{content}, 'Verified letter content' );
360 subtest 'AR_*' => sub {
362 my $code = 'AR_CANCELED';
363 my $branchcode = $library->{branchcode};
366 <<borrowers.firstname>> <<borrowers.surname>> (<<borrowers.cardnumber>>)
368 Your request for an article from <<biblio.title>> (<<items.barcode>>) has been canceled for the following reason:
370 <<article_requests.notes>>
373 Title: <<article_requests.title>>
374 Author: <<article_requests.author>>
375 Volume: <<article_requests.volume>>
376 Issue: <<article_requests.issue>>
377 Date: <<article_requests.date>>
378 Pages: <<article_requests.pages>>
379 Chapters: <<article_requests.chapters>>
380 Notes: <<article_requests.patron_notes>>
382 reset_template( { template => $template, code => $code, module => 'circulation' } );
383 my $article_request = $builder->build({ source => 'ArticleRequest' });
384 Koha::ArticleRequests->find( $article_request->{id} )->cancel;
385 my $letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
388 [% borrower.firstname %] [% borrower.surname %] ([% borrower.cardnumber %])
390 Your request for an article from [% biblio.title %] ([% item.barcode %]) has been canceled for the following reason:
392 [% article_request.notes %]
395 Title: [% article_request.title %]
396 Author: [% article_request.author %]
397 Volume: [% article_request.volume %]
398 Issue: [% article_request.issue %]
399 Date: [% article_request.date %]
400 Pages: [% article_request.pages %]
401 Chapters: [% article_request.chapters %]
402 Notes: [% article_request.patron_notes %]
404 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
405 Koha::ArticleRequests->find( $article_request->{id} )->cancel;
406 my $tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
407 is( $tt_letter->content, $letter->content, 'Compare AR_* notices' );
408 isnt( $tt_letter->message_id, $letter->message_id, 'Comparing AR_* notices should compare 2 different messages' );
411 subtest 'CHECKOUT+CHECKIN' => sub {
414 my $checkout_code = 'CHECKOUT';
415 my $checkin_code = 'CHECKIN';
417 my $dbh = C4::Context->dbh;
418 # Enable notification for CHECKOUT - Things are hardcoded here but should work with default data
419 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 6 );
420 my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
421 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
422 # Enable notification for CHECKIN - Things are hardcoded here but should work with default data
423 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 5 );
424 $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
425 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
428 my $checkout_template = q|
429 The following items have been checked out:
433 Thank you for visiting <<branches.branchname>>.
435 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
436 my $checkin_template = q[
437 The following items have been checked out:
439 <<biblio.title>> was due on <<old_issues.date_due | dateonly>>
441 Thank you for visiting <<branches.branchname>>.
443 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
445 C4::Circulation::AddIssue( $patron, $item1->{barcode} );
446 my $first_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
447 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
448 my $second_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
450 AddReturn( $item1->{barcode} );
451 my $first_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
452 AddReturn( $item2->{barcode} );
453 my $second_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
455 Koha::Notice::Messages->delete;
458 $checkout_template = q|
459 The following items have been checked out:
463 Thank you for visiting [% branch.branchname %].
465 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
466 $checkin_template = q[
467 The following items have been checked out:
469 [% biblio.title %] was due on [% old_checkout.date_due | $KohaDates %]
471 Thank you for visiting [% branch.branchname %].
473 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
475 C4::Circulation::AddIssue( $patron, $item1->{barcode} );
476 my $first_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
477 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
478 my $second_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
480 AddReturn( $item1->{barcode} );
481 my $first_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
482 AddReturn( $item2->{barcode} );
483 my $second_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
485 is( $first_checkout_tt_letter->content, $first_checkout_letter->content, 'Verify first checkout letter' );
486 is( $second_checkout_tt_letter->content, $second_checkout_letter->content, 'Verify second checkout letter' );
487 is( $first_checkin_tt_letter->content, $first_checkin_letter->content, 'Verify first checkin letter' );
488 is( $second_checkin_tt_letter->content, $second_checkin_letter->content, 'Verify second checkin letter' );
492 subtest 'DUEDGST|count' => sub {
495 my $code = 'DUEDGST';
497 my $dbh = C4::Context->dbh;
498 # Enable notification for DUEDGST - Things are hardcoded here but should work with default data
499 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 1 );
500 my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
501 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
505 substitute => { count => 42 },
509 You have <<count>> items due
511 my $letter = process_letter( { template => $template, %$params });
514 You have [% count %] items due
516 my $tt_letter = process_letter( { template => $tt_template, %$params });
517 is( $tt_letter->{content}, $letter->{content}, );
520 subtest 'HOLD_SLIP|dates|today' => sub {
523 my $code = 'HOLD_SLIP';
525 my $reserve_id1 = C4::Reserves::AddReserve(
527 branchcode => $library->{branchcode},
528 borrowernumber => $patron->{borrowernumber},
529 biblionumber => $biblio1->{biblionumber},
531 itemnumber => $item1->{itemnumber},
534 my $reserve_id2 = C4::Reserves::AddReserve(
536 branchcode => $library->{branchcode},
537 borrowernumber => $patron->{borrowernumber},
538 biblionumber => $biblio1->{biblionumber},
540 itemnumber => $item1->{itemnumber},
543 my $reserve_id3 = C4::Reserves::AddReserve(
545 branchcode => $library->{branchcode},
546 borrowernumber => $patron->{borrowernumber},
547 biblionumber => $biblio2->{biblionumber},
548 notes => "another note",
549 itemnumber => $item2->{itemnumber},
553 my $template = <<EOF;
554 <h5>Date: <<today>></h5>
556 <h3> Transfer to/Hold in <<branches.branchname>></h3>
558 <h3><<borrowers.surname>>, <<borrowers.firstname>></h3>
561 <li><<borrowers.cardnumber>></li>
562 <li><<borrowers.phone>></li>
563 <li> <<borrowers.address>><br />
564 <<borrowers.address2>><br />
565 <<borrowers.city>> <<borrowers.zipcode>>
567 <li><<borrowers.email>></li>
570 <h3>ITEM ON HOLD</h3>
571 <h4><<biblio.title>></h4>
572 <h5><<biblio.author>></h5>
574 <li><<items.barcode>></li>
575 <li><<items.itemcallnumber>></li>
576 <li><<reserves.waitingdate>></li>
579 <pre><<reserves.reserve_id>>=<<reserves.reservenotes>></pre>
583 reset_template( { template => $template, code => $code, module => 'circulation' } );
584 my $letter_for_item1 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id1 } );
585 my $letter_for_item2 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id3 } );
587 my $tt_template = <<EOF;
588 <h5>Date: [% today | \$KohaDates with_hours => 1 %]</h5>
590 <h3> Transfer to/Hold in [% branch.branchname %]</h3>
592 <h3>[% borrower.surname %], [% borrower.firstname %]</h3>
595 <li>[% borrower.cardnumber %]</li>
596 <li>[% borrower.phone %]</li>
597 <li> [% borrower.address %]<br />
598 [% borrower.address2 %]<br />
599 [% borrower.city %] [% borrower.zipcode %]
601 <li>[% borrower.email %]</li>
604 <h3>ITEM ON HOLD</h3>
605 <h4>[% biblio.title %]</h4>
606 <h5>[% biblio.author %]</h5>
608 <li>[% item.barcode %]</li>
609 <li>[% item.itemcallnumber %]</li>
610 <li>[% hold.waitingdate | \$KohaDates %]</li>
613 <pre>[% hold.reserve_id %]=[% hold.reservenotes %]</pre>
617 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
618 my $tt_letter_for_item1 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id1 } );
619 my $tt_letter_for_item2 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id3 } );
621 is( $tt_letter_for_item1->{content}, $letter_for_item1->{content}, );
622 is( $tt_letter_for_item2->{content}, $letter_for_item2->{content}, );
625 subtest 'ISSUESLIP|checkedout|repeat' => sub {
628 my $code = 'ISSUESLIP';
629 my $now = dt_from_string;
630 my $one_minute_ago = dt_from_string->subtract( minutes => 1 );
632 my $branchcode = $library->{branchcode};
635 my $news_item = Koha::NewsItem->new({ branchcode => $branchcode, title => "A wonderful news", content => "This is the wonderful news." })->store;
638 my $template = <<EOF;
639 <h3><<branches.branchname>></h3>
640 Checked out to <<borrowers.title>> <<borrowers.firstname>> <<borrowers.initials>> <<borrowers.surname>> <br />
641 (<<borrowers.cardnumber>>) <br />
648 <<biblio.title>> <br />
649 Barcode: <<items.barcode>><br />
650 Date due: <<issues.date_due | dateonly>><br />
657 <<biblio.title>> <br />
658 Barcode: <<items.barcode>><br />
659 Date due: <<issues.date_due | dateonly>><br />
665 <h4 style="text-align: center; font-style:italic;">News</h4>
667 <div class="newsitem">
668 <h5 style="margin-bottom: 1px; margin-top: 1px"><b><<opac_news.title>></b></h5>
669 <p style="margin-bottom: 1px; margin-top: 1px"><<opac_news.content>></p>
670 <p class="newsfooter" style="font-size: 8pt; font-style:italic; margin-bottom: 1px; margin-top: 1px">Posted on <<opac_news.timestamp>></p>
676 reset_template( { template => $template, code => $code, module => 'circulation' } );
678 my $checkout = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
679 $checkout->set( { timestamp => $now, issuedate => $one_minute_ago } )->store;
680 my $first_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
682 $checkout = C4::Circulation::AddIssue( $patron, $item2->{barcode} ); # Add a second checkout
683 $checkout->set( { timestamp => $now, issuedate => $now } )->store;
684 my $yesterday = dt_from_string->subtract( days => 1 );
685 C4::Circulation::AddIssue( $patron, $item3->{barcode}, $yesterday ); # Add an overdue
686 my $second_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
689 AddReturn( $item1->{barcode} );
690 AddReturn( $item2->{barcode} );
691 AddReturn( $item3->{barcode} );
694 my $tt_template = <<EOF;
695 <h3>[% branch.branchname %]</h3>
696 Checked out to [% borrower.title %] [% borrower.firstname %] [% borrower.initials %] [% borrower.surname %] <br />
697 ([% borrower.cardnumber %]) <br />
699 [% today | \$KohaDates with_hours => 1 %]<br />
702 [% FOREACH checkout IN checkouts %]
703 [%~ SET item = checkout.item %]
704 [%~ SET biblio = checkout.item.biblio %]
706 [% biblio.title %] <br />
707 Barcode: [% item.barcode %]<br />
708 Date due: [% checkout.date_due | \$KohaDates %]<br />
713 [% FOREACH overdue IN overdues %]
714 [%~ SET item = overdue.item %]
715 [%~ SET biblio = overdue.item.biblio %]
717 [% biblio.title %] <br />
718 Barcode: [% item.barcode %]<br />
719 Date due: [% overdue.date_due | \$KohaDates %]<br />
725 <h4 style="text-align: center; font-style:italic;">News</h4>
726 [% FOREACH n IN news %]
727 <div class="newsitem">
728 <h5 style="margin-bottom: 1px; margin-top: 1px"><b>[% n.title %]</b></h5>
729 <p style="margin-bottom: 1px; margin-top: 1px">[% n.content %]</p>
730 <p class="newsfooter" style="font-size: 8pt; font-style:italic; margin-bottom: 1px; margin-top: 1px">Posted on [% n.timestamp | \$KohaDates %]</p>
736 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
738 $checkout = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
739 $checkout->set( { timestamp => $now, issuedate => $one_minute_ago } )->store;
740 my $first_tt_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
742 $checkout = C4::Circulation::AddIssue( $patron, $item2->{barcode} ); # Add a second checkout
743 $checkout->set( { timestamp => $now, issuedate => $now } )->store;
744 C4::Circulation::AddIssue( $patron, $item3->{barcode}, $yesterday ); # Add an overdue
745 my $second_tt_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
747 # There is too many line breaks generated by the historic syntax
748 $second_slip->{content} =~ s|</p>\n\n\n<p>|</p>\n\n<p>|s;
750 is( $first_tt_slip->{content}, $first_slip->{content}, );
751 is( $second_tt_slip->{content}, $second_slip->{content}, );
754 AddReturn( $item1->{barcode} );
755 AddReturn( $item2->{barcode} );
756 AddReturn( $item3->{barcode} );
759 subtest 'ODUE|items.content|item' => sub {
764 my $branchcode = $library->{branchcode};
767 # FIXME items.fine does not work with TT notices
769 # <item> should contain Fine: <<items.fine>></item>
770 my $template = <<EOF;
771 Dear <<borrowers.firstname>> <<borrowers.surname>>,
773 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.
775 <<branches.branchname>>
776 <<branches.branchaddress1>>
777 <<branches.branchaddress2>> <<branches.branchaddress3>>
778 Phone: <<branches.branchphone>>
779 Fax: <<branches.branchfax>>
780 Email: <<branches.branchemail>>
782 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.
784 The following item(s) is/are currently overdue:
786 <item>"<<biblio.title>>" by <<biblio.author>>, <<items.itemcallnumber>>, Barcode: <<items.barcode>></item>
790 Thank-you for your prompt attention to this matter.
792 <<branches.branchname>> Staff
795 reset_template( { template => $template, code => $code, module => 'circulation' } );
797 my $yesterday = dt_from_string->subtract( days => 1 );
798 my $two_days_ago = dt_from_string->subtract( days => 2 );
799 my $issue1 = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
800 my $issue2 = C4::Circulation::AddIssue( $patron, $item2->{barcode}, $yesterday ); # Add an first overdue
801 my $issue3 = C4::Circulation::AddIssue( $patron, $item3->{barcode}, $two_days_ago ); # Add an second overdue
802 $issue1 = $issue1->unblessed;
803 $issue2 = $issue2->unblessed;
804 $issue3 = $issue3->unblessed;
807 my @item_fields = qw( date_due title barcode author itemnumber );
808 my $items_content = C4::Letters::get_item_content( { item => { %$item1, %$biblio1, %$issue1 }, item_content_fields => \@item_fields, dateonly => 1 } );
809 $items_content .= C4::Letters::get_item_content( { item => { %$item2, %$biblio2, %$issue2 }, item_content_fields => \@item_fields, dateonly => 1 } );
810 $items_content .= C4::Letters::get_item_content( { item => { %$item3, %$biblio3, %$issue3 }, item_content_fields => \@item_fields, dateonly => 1 } );
812 my @items = ( $item1, $item2, $item3 );
813 my $letter = C4::Overdues::parse_overdues_letter(
815 letter_code => $code,
816 borrowernumber => $patron->{borrowernumber},
817 branchcode => $library->{branchcode},
820 bib => $library->{branchname},
821 'items.content' => $items_content,
822 count => scalar( @items ),
823 message_transport_type => 'email',
829 AddReturn( $item1->{barcode} );
830 AddReturn( $item2->{barcode} );
831 AddReturn( $item3->{barcode} );
835 my $tt_template = <<EOF;
836 Dear [% borrower.firstname %] [% borrower.surname %],
838 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.
840 [% branch.branchname %]
841 [% branch.branchaddress1 %]
842 [% branch.branchaddress2 %] [% branch.branchaddress3 %]
843 Phone: [% branch.branchphone %]
844 Fax: [% branch.branchfax %]
845 Email: [% branch.branchemail %]
847 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.
849 The following item(s) is/are currently overdue:
851 [% FOREACH overdue IN overdues %]
852 [%~ SET item = overdue.item ~%]
853 "[% item.biblio.title %]" by [% item.biblio.author %], [% item.itemcallnumber %], Barcode: [% item.barcode %]
855 [% FOREACH overdue IN overdues %]
856 [%~ SET item = overdue.item ~%]
857 [% overdue.date_due | \$KohaDates %]\t[% item.biblio.title %]\t[% item.barcode %]\t[% item.biblio.author %]\t[% item.itemnumber %]
860 Thank-you for your prompt attention to this matter.
862 [% branch.branchname %] Staff
865 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
867 C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
868 C4::Circulation::AddIssue( $patron, $item2->{barcode}, $yesterday ); # Add an first overdue
869 C4::Circulation::AddIssue( $patron, $item3->{barcode}, $two_days_ago ); # Add an second overdue
871 my $tt_letter = C4::Overdues::parse_overdues_letter(
873 letter_code => $code,
874 borrowernumber => $patron->{borrowernumber},
875 branchcode => $library->{branchcode},
878 bib => $library->{branchname},
879 'items.content' => $items_content,
880 count => scalar( @items ),
881 message_transport_type => 'email',
886 is( $tt_letter->{content}, $letter->{content}, );
889 subtest 'Bug 19743 - Header and Footer should be updated on each item for checkin / checkout / renewal notices' => sub {
892 my $checkout_code = 'CHECKOUT';
893 my $checkin_code = 'CHECKIN';
895 my $dbh = C4::Context->dbh;
896 $dbh->do("DELETE FROM letter");
897 $dbh->do("DELETE FROM issues");
898 $dbh->do("DELETE FROM message_queue");
900 # Enable notification for CHECKOUT - Things are hardcoded here but should work with default data
901 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 6 );
902 my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
903 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
904 # Enable notification for CHECKIN - Things are hardcoded here but should work with default data
905 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 5 );
906 $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
907 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
909 my $checkout_template = q|
910 <<branches.branchname>>
914 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
915 my $checkin_template = q[
916 <<branches.branchname>>
920 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
922 my $issue = C4::Circulation::AddIssue( $patron, $item1->{barcode} );
923 my $first_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
925 my $library_object = Koha::Libraries->find( $issue->branchcode );
926 my $old_branchname = $library_object->branchname;
927 my $new_branchname = "Kyle M Hall Memorial Library";
929 # Change branch name for second checkout notice
930 $library_object->branchname($new_branchname);
931 $library_object->store();
933 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
934 my $second_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
936 # Restore old name for first checkin notice
937 $library_object->branchname( $old_branchname );
938 $library_object->store();
940 AddReturn( $item1->{barcode} );
941 my $first_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
943 # Change branch name for second checkin notice
944 $library_object->branchname($new_branchname);
945 $library_object->store();
947 AddReturn( $item2->{barcode} );
948 my $second_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
950 # Restore old name for first TT checkout notice
951 $library_object->branchname( $old_branchname );
952 $library_object->store();
954 Koha::Notice::Messages->delete;
957 $checkout_template = q|
958 [% branch.branchname %]
962 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
963 $checkin_template = q[
964 [% branch.branchname %]
968 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
970 C4::Circulation::AddIssue( $patron, $item1->{barcode} );
971 my $first_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
973 # Change branch name for second checkout notice
974 $library_object->branchname($new_branchname);
975 $library_object->store();
977 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
978 my $second_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
980 # Restore old name for first checkin notice
981 $library_object->branchname( $old_branchname );
982 $library_object->store();
984 AddReturn( $item1->{barcode} );
985 my $first_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
987 # Change branch name for second checkin notice
988 $library_object->branchname($new_branchname);
989 $library_object->store();
991 AddReturn( $item2->{barcode} );
992 my $second_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
994 my $first_letter = qq[
997 my $second_letter = qq[
1002 is( $first_checkout_letter->content, $first_letter, 'Verify first checkout letter' );
1003 is( $second_checkout_letter->content, $second_letter, 'Verify second checkout letter' );
1004 is( $first_checkin_letter->content, $first_letter, 'Verify first checkin letter' );
1005 is( $second_checkin_letter->content, $second_letter, 'Verify second checkin letter' );
1007 is( $first_checkout_tt_letter->content, $first_letter, 'Verify TT first checkout letter' );
1008 is( $second_checkout_tt_letter->content, $second_letter, 'Verify TT second checkout letter' );
1009 is( $first_checkin_tt_letter->content, $first_letter, 'Verify TT first checkin letter' );
1010 is( $second_checkin_tt_letter->content, $second_letter, 'Verify TT second checkin letter' );
1015 subtest 'loops' => sub {
1018 my $module = "TEST";
1020 subtest 'primary key is AI' => sub {
1022 my $patron_1 = $builder->build({ source => 'Borrower' });
1023 my $patron_2 = $builder->build({ source => 'Borrower' });
1025 my $template = q|[% FOREACH patron IN borrowers %][% patron.surname %][% END %]|;
1026 reset_template( { template => $template, code => $code, module => $module } );
1027 my $letter = GetPreparedLetter( module => $module, letter_code => $code, loops => { borrowers => [ $patron_1->{borrowernumber}, $patron_2->{borrowernumber} ] } );
1028 my $expected_letter = join '', ( $patron_1->{surname}, $patron_2->{surname} );
1029 is( $letter->{content}, $expected_letter, );
1032 subtest 'foreign key is used' => sub {
1034 my $patron_1 = $builder->build({ source => 'Borrower' });
1035 my $patron_2 = $builder->build({ source => 'Borrower' });
1036 my $checkout_1 = $builder->build({ source => 'Issue', value => { borrowernumber => $patron_1->{borrowernumber} } } );
1037 my $checkout_2 = $builder->build({ source => 'Issue', value => { borrowernumber => $patron_1->{borrowernumber} } } );
1039 my $template = q|[% FOREACH checkout IN checkouts %][% checkout.issue_id %][% END %]|;
1040 reset_template( { template => $template, code => $code, module => $module } );
1041 my $letter = GetPreparedLetter( module => $module, letter_code => $code, loops => { issues => [ $checkout_1->{itemnumber}, $checkout_2->{itemnumber} ] } );
1042 my $expected_letter = join '', ( $checkout_1->{issue_id}, $checkout_2->{issue_id} );
1043 is( $letter->{content}, $expected_letter, );
1047 subtest 'add_tt_filters' => sub {
1050 my $module = "TEST";
1052 my $patron = $builder->build_object(
1054 class => 'Koha::Patrons',
1055 value => { surname => "with_punctuation_" }
1058 my $biblio = $builder->build_object(
1059 { class => 'Koha::Biblios', value => { title => "with_punctuation_" } }
1061 my $biblioitem = $builder->build_object(
1063 class => 'Koha::Biblioitems',
1065 biblionumber => $biblio->biblionumber,
1066 isbn => "with_punctuation_"
1071 my $template = q|patron=[% borrower.surname %];biblio=[% biblio.title %];biblioitems=[% biblioitem.isbn %]|;
1072 reset_template( { template => $template, code => $code, module => $module } );
1073 my $letter = GetPreparedLetter(
1075 letter_code => $code,
1077 borrowers => $patron->borrowernumber,
1078 biblio => $biblio->biblionumber,
1079 biblioitems => $biblioitem->biblioitemnumber
1082 my $expected_letter = q|patron=with_punctuation_;biblio=with_punctuation;biblioitems=with_punctuation|;
1083 is( $letter->{content}, $expected_letter, "Pre-processing should call TT plugin to remove punctuation if table is biblio or biblioitems");
1086 subtest 'Dates formatting' => sub {
1088 my $code = 'TEST_DATE';
1089 t::lib::Mocks::mock_preference('dateformat', 'metric'); # MM/DD/YYYY
1090 my $biblio = $builder->build_object(
1092 class => 'Koha::Biblios',
1094 timestamp => '2018-12-13 20:21:22',
1095 datecreated => '2018-12-13'
1099 my $template = <<EOF;
1100 [%- USE KohaDates -%]
1101 [% biblio.timestamp %]
1102 [% biblio.timestamp | \$KohaDates %]
1103 [% biblio.timestamp | \$KohaDates with_hours => 1 %]
1105 [% biblio.datecreated %]
1106 [% biblio.datecreated | \$KohaDates %]
1107 [% biblio.datecreated | \$KohaDates with_hours => 1 %]
1109 [% biblio.timestamp | \$KohaDates dateformat => 'iso' %]
1110 [% KohaDates.output_preference( str => biblio.timestamp, dateformat => 'iso' ) %]
1111 [% KohaDates.output_preference( str => biblio.timestamp, dateformat => 'iso', dateonly => 1 ) %]
1113 reset_template({ template => $template, code => $code, module => 'test' });
1114 my $letter = GetPreparedLetter(
1116 letter_code => $code,
1118 biblio => $biblio->biblionumber,
1121 my $expected_content = sprintf("%s\n%s\n%s\n\n%s\n%s\n%s\n\n%s\n%s\n%s\n",
1122 '2018-12-13 20:21:22',
1134 is( $letter->{content}, $expected_content );
1137 sub reset_template {
1138 my ( $params ) = @_;
1139 my $template = $params->{template};
1140 my $code = $params->{code};
1141 my $module = $params->{module} || 'test_module';
1143 Koha::Notice::Templates->search( { code => $code } )->delete;
1144 Koha::Notice::Template->new(
1151 message_transport_type => 'email',
1152 content => $template
1157 sub process_letter {
1159 my $template = $params->{template};
1160 my $tables = $params->{tables};
1161 my $substitute = $params->{substitute};
1162 my $code = $params->{code};
1163 my $module = $params->{module} || 'test_module';
1164 my $branchcode = $params->{branchcode};
1166 reset_template( $params );
1168 my $letter = C4::Letters::GetPreparedLetter(
1170 letter_code => $code,
1173 substitute => $substitute,