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 $item1 = $builder->build_sample_item(
297 barcode => 'a_t_barcode',
298 library => $library->{branchcode},
300 itemcallnumber => 'itemcallnumber1',
303 my $biblio1 = $item1->biblio->unblessed;
304 $item1 = $item1->unblessed;
305 my $item2 = $builder->build_sample_item(
307 barcode => 'another_t_barcode',
308 library => $library->{branchcode},
310 itemcallnumber => 'itemcallnumber2',
313 my $biblio2 = $item2->biblio->unblessed;
314 $item2 = $item2->unblessed;
315 my $item3 = $builder->build_sample_item(
317 barcode => 'another_t_barcode_3',
318 library => $library->{branchcode},
320 itemcallnumber => 'itemcallnumber3',
323 my $biblio3 = $item3->biblio->unblessed;
324 $item3 = $item3->unblessed;
326 t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
328 subtest 'ACQ_NOTIF_ON_RECEIV ' => sub {
330 my $code = 'ACQ_NOTIF_ON_RECEIV';
331 my $branchcode = $library->{branchcode};
332 my $order = $builder->build({ source => 'Aqorder' });
335 Dear <<borrowers.firstname>> <<borrowers.surname>>,
336 The order <<aqorders.ordernumber>> (<<biblio.title>>) has been received.
339 my $params = { code => $code, branchcode => $branchcode, tables => { branches => $library, borrowers => $patron, biblio => $biblio1, aqorders => $order } };
340 my $letter = process_letter( { template => $template, %$params });
342 Dear [% borrower.firstname %] [% borrower.surname %],
343 The order [% order.ordernumber %] ([% biblio.title %]) has been received.
346 my $tt_letter = process_letter( { template => $tt_template, %$params });
348 is( $tt_letter->{content}, $letter->{content}, 'Verified letter content' );
351 subtest 'AR_*' => sub {
353 my $code = 'AR_CANCELED';
354 my $branchcode = $library->{branchcode};
357 <<borrowers.firstname>> <<borrowers.surname>> (<<borrowers.cardnumber>>)
359 Your request for an article from <<biblio.title>> (<<items.barcode>>) has been canceled for the following reason:
361 <<article_requests.notes>>
364 Title: <<article_requests.title>>
365 Author: <<article_requests.author>>
366 Volume: <<article_requests.volume>>
367 Issue: <<article_requests.issue>>
368 Date: <<article_requests.date>>
369 Pages: <<article_requests.pages>>
370 Chapters: <<article_requests.chapters>>
371 Notes: <<article_requests.patron_notes>>
373 reset_template( { template => $template, code => $code, module => 'circulation' } );
374 my $article_request = $builder->build({ source => 'ArticleRequest' });
375 Koha::ArticleRequests->find( $article_request->{id} )->cancel;
376 my $letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
379 [% borrower.firstname %] [% borrower.surname %] ([% borrower.cardnumber %])
381 Your request for an article from [% biblio.title %] ([% item.barcode %]) has been canceled for the following reason:
383 [% article_request.notes %]
386 Title: [% article_request.title %]
387 Author: [% article_request.author %]
388 Volume: [% article_request.volume %]
389 Issue: [% article_request.issue %]
390 Date: [% article_request.date %]
391 Pages: [% article_request.pages %]
392 Chapters: [% article_request.chapters %]
393 Notes: [% article_request.patron_notes %]
395 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
396 Koha::ArticleRequests->find( $article_request->{id} )->cancel;
397 my $tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
398 is( $tt_letter->content, $letter->content, 'Compare AR_* notices' );
399 isnt( $tt_letter->message_id, $letter->message_id, 'Comparing AR_* notices should compare 2 different messages' );
402 subtest 'CHECKOUT+CHECKIN' => sub {
405 my $checkout_code = 'CHECKOUT';
406 my $checkin_code = 'CHECKIN';
408 my $dbh = C4::Context->dbh;
409 # Enable notification for CHECKOUT - Things are hardcoded here but should work with default data
410 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 6 );
411 my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
412 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
413 # Enable notification for CHECKIN - Things are hardcoded here but should work with default data
414 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 5 );
415 $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
416 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
419 my $checkout_template = q|
420 The following items have been checked out:
424 Thank you for visiting <<branches.branchname>>.
426 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
427 my $checkin_template = q[
428 The following items have been checked out:
430 <<biblio.title>> was due on <<old_issues.date_due | dateonly>>
432 Thank you for visiting <<branches.branchname>>.
434 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
436 C4::Circulation::AddIssue( $patron, $item1->{barcode} );
437 my $first_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
438 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
439 my $second_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
441 AddReturn( $item1->{barcode} );
442 my $first_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
443 AddReturn( $item2->{barcode} );
444 my $second_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
446 Koha::Notice::Messages->delete;
449 $checkout_template = q|
450 The following items have been checked out:
454 Thank you for visiting [% branch.branchname %].
456 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
457 $checkin_template = q[
458 The following items have been checked out:
460 [% biblio.title %] was due on [% old_checkout.date_due | $KohaDates %]
462 Thank you for visiting [% branch.branchname %].
464 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
466 C4::Circulation::AddIssue( $patron, $item1->{barcode} );
467 my $first_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
468 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
469 my $second_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
471 AddReturn( $item1->{barcode} );
472 my $first_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
473 AddReturn( $item2->{barcode} );
474 my $second_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
476 is( $first_checkout_tt_letter->content, $first_checkout_letter->content, 'Verify first checkout letter' );
477 is( $second_checkout_tt_letter->content, $second_checkout_letter->content, 'Verify second checkout letter' );
478 is( $first_checkin_tt_letter->content, $first_checkin_letter->content, 'Verify first checkin letter' );
479 is( $second_checkin_tt_letter->content, $second_checkin_letter->content, 'Verify second checkin letter' );
483 subtest 'DUEDGST|count' => sub {
486 my $code = 'DUEDGST';
488 my $dbh = C4::Context->dbh;
489 # Enable notification for DUEDGST - Things are hardcoded here but should work with default data
490 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 1 );
491 my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
492 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
496 substitute => { count => 42 },
500 You have <<count>> items due
502 my $letter = process_letter( { template => $template, %$params });
505 You have [% count %] items due
507 my $tt_letter = process_letter( { template => $tt_template, %$params });
508 is( $tt_letter->{content}, $letter->{content}, );
511 subtest 'HOLD_SLIP|dates|today' => sub {
514 my $code = 'HOLD_SLIP';
516 my $reserve_id1 = C4::Reserves::AddReserve(
518 branchcode => $library->{branchcode},
519 borrowernumber => $patron->{borrowernumber},
520 biblionumber => $biblio1->{biblionumber},
522 itemnumber => $item1->{itemnumber},
525 my $reserve_id2 = C4::Reserves::AddReserve(
527 branchcode => $library->{branchcode},
528 borrowernumber => $patron->{borrowernumber},
529 biblionumber => $biblio1->{biblionumber},
531 itemnumber => $item1->{itemnumber},
534 my $reserve_id3 = C4::Reserves::AddReserve(
536 branchcode => $library->{branchcode},
537 borrowernumber => $patron->{borrowernumber},
538 biblionumber => $biblio2->{biblionumber},
539 notes => "another note",
540 itemnumber => $item2->{itemnumber},
544 my $template = <<EOF;
545 <h5>Date: <<today>></h5>
547 <h3> Transfer to/Hold in <<branches.branchname>></h3>
549 <h3><<borrowers.surname>>, <<borrowers.firstname>></h3>
552 <li><<borrowers.cardnumber>></li>
553 <li><<borrowers.phone>></li>
554 <li> <<borrowers.address>><br />
555 <<borrowers.address2>><br />
556 <<borrowers.city>> <<borrowers.zipcode>>
558 <li><<borrowers.email>></li>
561 <h3>ITEM ON HOLD</h3>
562 <h4><<biblio.title>></h4>
563 <h5><<biblio.author>></h5>
565 <li><<items.barcode>></li>
566 <li><<items.itemcallnumber>></li>
567 <li><<reserves.waitingdate>></li>
570 <pre><<reserves.reserve_id>>=<<reserves.reservenotes>></pre>
574 reset_template( { template => $template, code => $code, module => 'circulation' } );
575 my $letter_for_item1 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id1 } );
576 my $letter_for_item2 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id3 } );
578 my $tt_template = <<EOF;
579 <h5>Date: [% today | \$KohaDates with_hours => 1 %]</h5>
581 <h3> Transfer to/Hold in [% branch.branchname %]</h3>
583 <h3>[% borrower.surname %], [% borrower.firstname %]</h3>
586 <li>[% borrower.cardnumber %]</li>
587 <li>[% borrower.phone %]</li>
588 <li> [% borrower.address %]<br />
589 [% borrower.address2 %]<br />
590 [% borrower.city %] [% borrower.zipcode %]
592 <li>[% borrower.email %]</li>
595 <h3>ITEM ON HOLD</h3>
596 <h4>[% biblio.title %]</h4>
597 <h5>[% biblio.author %]</h5>
599 <li>[% item.barcode %]</li>
600 <li>[% item.itemcallnumber %]</li>
601 <li>[% hold.waitingdate | \$KohaDates %]</li>
604 <pre>[% hold.reserve_id %]=[% hold.reservenotes %]</pre>
608 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
609 my $tt_letter_for_item1 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id1 } );
610 my $tt_letter_for_item2 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id3 } );
612 is( $tt_letter_for_item1->{content}, $letter_for_item1->{content}, );
613 is( $tt_letter_for_item2->{content}, $letter_for_item2->{content}, );
616 subtest 'ISSUESLIP|checkedout|repeat' => sub {
619 my $code = 'ISSUESLIP';
620 my $now = dt_from_string;
621 my $one_minute_ago = dt_from_string->subtract( minutes => 1 );
623 my $branchcode = $library->{branchcode};
626 my $news_item = Koha::NewsItem->new({ branchcode => $branchcode, title => "A wonderful news", content => "This is the wonderful news." })->store;
629 my $template = <<EOF;
630 <h3><<branches.branchname>></h3>
631 Checked out to <<borrowers.title>> <<borrowers.firstname>> <<borrowers.initials>> <<borrowers.surname>> <br />
632 (<<borrowers.cardnumber>>) <br />
639 <<biblio.title>> <br />
640 Barcode: <<items.barcode>><br />
641 Date due: <<issues.date_due | dateonly>><br />
648 <<biblio.title>> <br />
649 Barcode: <<items.barcode>><br />
650 Date due: <<issues.date_due | dateonly>><br />
656 <h4 style="text-align: center; font-style:italic;">News</h4>
658 <div class="newsitem">
659 <h5 style="margin-bottom: 1px; margin-top: 1px"><b><<opac_news.title>></b></h5>
660 <p style="margin-bottom: 1px; margin-top: 1px"><<opac_news.content>></p>
661 <p class="newsfooter" style="font-size: 8pt; font-style:italic; margin-bottom: 1px; margin-top: 1px">Posted on <<opac_news.published_on>></p>
667 reset_template( { template => $template, code => $code, module => 'circulation' } );
669 my $checkout = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
670 $checkout->set( { timestamp => $now, issuedate => $one_minute_ago } )->store;
671 my $first_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
673 $checkout = C4::Circulation::AddIssue( $patron, $item2->{barcode} ); # Add a second checkout
674 $checkout->set( { timestamp => $now, issuedate => $now } )->store;
675 my $yesterday = dt_from_string->subtract( days => 1 );
676 C4::Circulation::AddIssue( $patron, $item3->{barcode}, $yesterday ); # Add an overdue
677 my $second_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
680 AddReturn( $item1->{barcode} );
681 AddReturn( $item2->{barcode} );
682 AddReturn( $item3->{barcode} );
685 my $tt_template = <<EOF;
686 <h3>[% branch.branchname %]</h3>
687 Checked out to [% borrower.title %] [% borrower.firstname %] [% borrower.initials %] [% borrower.surname %] <br />
688 ([% borrower.cardnumber %]) <br />
690 [% today | \$KohaDates with_hours => 1 %]<br />
693 [% FOREACH checkout IN checkouts %]
694 [%~ SET item = checkout.item %]
695 [%~ SET biblio = checkout.item.biblio %]
697 [% biblio.title %] <br />
698 Barcode: [% item.barcode %]<br />
699 Date due: [% checkout.date_due | \$KohaDates %]<br />
704 [% FOREACH overdue IN overdues %]
705 [%~ SET item = overdue.item %]
706 [%~ SET biblio = overdue.item.biblio %]
708 [% biblio.title %] <br />
709 Barcode: [% item.barcode %]<br />
710 Date due: [% overdue.date_due | \$KohaDates %]<br />
716 <h4 style="text-align: center; font-style:italic;">News</h4>
717 [% FOREACH n IN news %]
718 <div class="newsitem">
719 <h5 style="margin-bottom: 1px; margin-top: 1px"><b>[% n.title %]</b></h5>
720 <p style="margin-bottom: 1px; margin-top: 1px">[% n.content %]</p>
721 <p class="newsfooter" style="font-size: 8pt; font-style:italic; margin-bottom: 1px; margin-top: 1px">Posted on [% n.timestamp | \$KohaDates %]</p>
727 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
729 $checkout = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
730 $checkout->set( { timestamp => $now, issuedate => $one_minute_ago } )->store;
731 my $first_tt_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
733 $checkout = C4::Circulation::AddIssue( $patron, $item2->{barcode} ); # Add a second checkout
734 $checkout->set( { timestamp => $now, issuedate => $now } )->store;
735 C4::Circulation::AddIssue( $patron, $item3->{barcode}, $yesterday ); # Add an overdue
736 my $second_tt_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
738 # There is too many line breaks generated by the historic syntax
739 $second_slip->{content} =~ s|</p>\n\n\n<p>|</p>\n\n<p>|s;
741 is( $first_tt_slip->{content}, $first_slip->{content}, );
742 is( $second_tt_slip->{content}, $second_slip->{content}, );
745 AddReturn( $item1->{barcode} );
746 AddReturn( $item2->{barcode} );
747 AddReturn( $item3->{barcode} );
750 subtest 'ODUE|items.content|item' => sub {
755 my $branchcode = $library->{branchcode};
758 # FIXME items.fine does not work with TT notices
760 # <item> should contain Fine: <<items.fine>></item>
761 my $template = <<EOF;
762 Dear <<borrowers.firstname>> <<borrowers.surname>>,
764 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.
766 <<branches.branchname>>
767 <<branches.branchaddress1>>
768 <<branches.branchaddress2>> <<branches.branchaddress3>>
769 Phone: <<branches.branchphone>>
770 Fax: <<branches.branchfax>>
771 Email: <<branches.branchemail>>
773 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.
775 The following item(s) is/are currently overdue:
777 <item>"<<biblio.title>>" by <<biblio.author>>, <<items.itemcallnumber>>, Barcode: <<items.barcode>></item>
781 Thank-you for your prompt attention to this matter.
783 <<branches.branchname>> Staff
786 reset_template( { template => $template, code => $code, module => 'circulation' } );
788 my $yesterday = dt_from_string->subtract( days => 1 );
789 my $two_days_ago = dt_from_string->subtract( days => 2 );
790 my $issue1 = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
791 my $issue2 = C4::Circulation::AddIssue( $patron, $item2->{barcode}, $yesterday ); # Add an first overdue
792 my $issue3 = C4::Circulation::AddIssue( $patron, $item3->{barcode}, $two_days_ago ); # Add an second overdue
793 $issue1 = $issue1->unblessed;
794 $issue2 = $issue2->unblessed;
795 $issue3 = $issue3->unblessed;
798 my @item_fields = qw( date_due title barcode author itemnumber );
799 my $items_content = C4::Letters::get_item_content( { item => { %$item1, %$biblio1, %$issue1 }, item_content_fields => \@item_fields, dateonly => 1 } );
800 $items_content .= C4::Letters::get_item_content( { item => { %$item2, %$biblio2, %$issue2 }, item_content_fields => \@item_fields, dateonly => 1 } );
801 $items_content .= C4::Letters::get_item_content( { item => { %$item3, %$biblio3, %$issue3 }, item_content_fields => \@item_fields, dateonly => 1 } );
803 my @items = ( $item1, $item2, $item3 );
804 my $letter = C4::Overdues::parse_overdues_letter(
806 letter_code => $code,
807 borrowernumber => $patron->{borrowernumber},
808 branchcode => $library->{branchcode},
811 bib => $library->{branchname},
812 'items.content' => $items_content,
813 count => scalar( @items ),
814 message_transport_type => 'email',
820 AddReturn( $item1->{barcode} );
821 AddReturn( $item2->{barcode} );
822 AddReturn( $item3->{barcode} );
826 my $tt_template = <<EOF;
827 Dear [% borrower.firstname %] [% borrower.surname %],
829 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.
831 [% branch.branchname %]
832 [% branch.branchaddress1 %]
833 [% branch.branchaddress2 %] [% branch.branchaddress3 %]
834 Phone: [% branch.branchphone %]
835 Fax: [% branch.branchfax %]
836 Email: [% branch.branchemail %]
838 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.
840 The following item(s) is/are currently overdue:
842 [% FOREACH overdue IN overdues %]
843 [%~ SET item = overdue.item ~%]
844 "[% item.biblio.title %]" by [% item.biblio.author %], [% item.itemcallnumber %], Barcode: [% item.barcode %]
846 [% FOREACH overdue IN overdues %]
847 [%~ SET item = overdue.item ~%]
848 [% overdue.date_due | \$KohaDates %]\t[% item.biblio.title %]\t[% item.barcode %]\t[% item.biblio.author %]\t[% item.itemnumber %]
851 Thank-you for your prompt attention to this matter.
853 [% branch.branchname %] Staff
856 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
858 C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
859 C4::Circulation::AddIssue( $patron, $item2->{barcode}, $yesterday ); # Add an first overdue
860 C4::Circulation::AddIssue( $patron, $item3->{barcode}, $two_days_ago ); # Add an second overdue
862 my $tt_letter = C4::Overdues::parse_overdues_letter(
864 letter_code => $code,
865 borrowernumber => $patron->{borrowernumber},
866 branchcode => $library->{branchcode},
869 bib => $library->{branchname},
870 'items.content' => $items_content,
871 count => scalar( @items ),
872 message_transport_type => 'email',
877 is( $tt_letter->{content}, $letter->{content}, );
880 subtest 'Bug 19743 - Header and Footer should be updated on each item for checkin / checkout / renewal notices' => sub {
883 my $checkout_code = 'CHECKOUT';
884 my $checkin_code = 'CHECKIN';
886 my $dbh = C4::Context->dbh;
887 $dbh->do("DELETE FROM letter");
888 $dbh->do("DELETE FROM issues");
889 $dbh->do("DELETE FROM message_queue");
891 # Enable notification for CHECKOUT - Things are hardcoded here but should work with default data
892 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 6 );
893 my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
894 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
895 # Enable notification for CHECKIN - Things are hardcoded here but should work with default data
896 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 5 );
897 $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
898 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
900 my $checkout_template = q|
901 <<branches.branchname>>
905 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
906 my $checkin_template = q[
907 <<branches.branchname>>
911 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
913 my $issue = C4::Circulation::AddIssue( $patron, $item1->{barcode} );
914 my $first_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
916 my $library_object = Koha::Libraries->find( $issue->branchcode );
917 my $old_branchname = $library_object->branchname;
918 my $new_branchname = "Kyle M Hall Memorial Library";
920 # Change branch name for second checkout notice
921 $library_object->branchname($new_branchname);
922 $library_object->store();
924 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
925 my $second_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
927 # Restore old name for first checkin notice
928 $library_object->branchname( $old_branchname );
929 $library_object->store();
931 AddReturn( $item1->{barcode} );
932 my $first_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
934 # Change branch name for second checkin notice
935 $library_object->branchname($new_branchname);
936 $library_object->store();
938 AddReturn( $item2->{barcode} );
939 my $second_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
941 # Restore old name for first TT checkout notice
942 $library_object->branchname( $old_branchname );
943 $library_object->store();
945 Koha::Notice::Messages->delete;
948 $checkout_template = q|
949 [% branch.branchname %]
953 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
954 $checkin_template = q[
955 [% branch.branchname %]
959 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
961 C4::Circulation::AddIssue( $patron, $item1->{barcode} );
962 my $first_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
964 # Change branch name for second checkout notice
965 $library_object->branchname($new_branchname);
966 $library_object->store();
968 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
969 my $second_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
971 # Restore old name for first checkin notice
972 $library_object->branchname( $old_branchname );
973 $library_object->store();
975 AddReturn( $item1->{barcode} );
976 my $first_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
978 # Change branch name for second checkin notice
979 $library_object->branchname($new_branchname);
980 $library_object->store();
982 AddReturn( $item2->{barcode} );
983 my $second_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
985 my $first_letter = qq[
988 my $second_letter = qq[
993 is( $first_checkout_letter->content, $first_letter, 'Verify first checkout letter' );
994 is( $second_checkout_letter->content, $second_letter, 'Verify second checkout letter' );
995 is( $first_checkin_letter->content, $first_letter, 'Verify first checkin letter' );
996 is( $second_checkin_letter->content, $second_letter, 'Verify second checkin letter' );
998 is( $first_checkout_tt_letter->content, $first_letter, 'Verify TT first checkout letter' );
999 is( $second_checkout_tt_letter->content, $second_letter, 'Verify TT second checkout letter' );
1000 is( $first_checkin_tt_letter->content, $first_letter, 'Verify TT first checkin letter' );
1001 is( $second_checkin_tt_letter->content, $second_letter, 'Verify TT second checkin letter' );
1006 subtest 'loops' => sub {
1009 my $module = "TEST";
1011 subtest 'primary key is AI' => sub {
1013 my $patron_1 = $builder->build({ source => 'Borrower' });
1014 my $patron_2 = $builder->build({ source => 'Borrower' });
1016 my $template = q|[% FOREACH patron IN borrowers %][% patron.surname %][% END %]|;
1017 reset_template( { template => $template, code => $code, module => $module } );
1018 my $letter = GetPreparedLetter( module => $module, letter_code => $code, loops => { borrowers => [ $patron_1->{borrowernumber}, $patron_2->{borrowernumber} ] } );
1019 my $expected_letter = join '', ( $patron_1->{surname}, $patron_2->{surname} );
1020 is( $letter->{content}, $expected_letter, );
1023 subtest 'foreign key is used' => sub {
1025 my $patron_1 = $builder->build({ source => 'Borrower' });
1026 my $patron_2 = $builder->build({ source => 'Borrower' });
1027 my $checkout_1 = $builder->build({ source => 'Issue', value => { borrowernumber => $patron_1->{borrowernumber} } } );
1028 my $checkout_2 = $builder->build({ source => 'Issue', value => { borrowernumber => $patron_1->{borrowernumber} } } );
1030 my $template = q|[% FOREACH checkout IN checkouts %][% checkout.issue_id %][% END %]|;
1031 reset_template( { template => $template, code => $code, module => $module } );
1032 my $letter = GetPreparedLetter( module => $module, letter_code => $code, loops => { issues => [ $checkout_1->{itemnumber}, $checkout_2->{itemnumber} ] } );
1033 my $expected_letter = join '', ( $checkout_1->{issue_id}, $checkout_2->{issue_id} );
1034 is( $letter->{content}, $expected_letter, );
1038 subtest 'add_tt_filters' => sub {
1041 my $module = "TEST";
1043 my $patron = $builder->build_object(
1045 class => 'Koha::Patrons',
1046 value => { surname => "with_punctuation_" }
1049 my $biblio = $builder->build_object(
1050 { class => 'Koha::Biblios', value => { title => "with_punctuation_" } }
1052 my $biblioitem = $builder->build_object(
1054 class => 'Koha::Biblioitems',
1056 biblionumber => $biblio->biblionumber,
1057 isbn => "with_punctuation_"
1062 my $template = q|patron=[% borrower.surname %];biblio=[% biblio.title %];biblioitems=[% biblioitem.isbn %]|;
1063 reset_template( { template => $template, code => $code, module => $module } );
1064 my $letter = GetPreparedLetter(
1066 letter_code => $code,
1068 borrowers => $patron->borrowernumber,
1069 biblio => $biblio->biblionumber,
1070 biblioitems => $biblioitem->biblioitemnumber
1073 my $expected_letter = q|patron=with_punctuation_;biblio=with_punctuation;biblioitems=with_punctuation|;
1074 is( $letter->{content}, $expected_letter, "Pre-processing should call TT plugin to remove punctuation if table is biblio or biblioitems");
1077 subtest 'Dates formatting' => sub {
1079 my $code = 'TEST_DATE';
1080 t::lib::Mocks::mock_preference('dateformat', 'metric'); # MM/DD/YYYY
1081 my $biblio = $builder->build_object(
1083 class => 'Koha::Biblios',
1085 timestamp => '2018-12-13 20:21:22',
1086 datecreated => '2018-12-13'
1090 my $template = <<EOF;
1091 [%- USE KohaDates -%]
1092 [% biblio.timestamp %]
1093 [% biblio.timestamp | \$KohaDates %]
1094 [% biblio.timestamp | \$KohaDates with_hours => 1 %]
1096 [% biblio.datecreated %]
1097 [% biblio.datecreated | \$KohaDates %]
1098 [% biblio.datecreated | \$KohaDates with_hours => 1 %]
1100 [% biblio.timestamp | \$KohaDates dateformat => 'iso' %]
1101 [% KohaDates.output_preference( str => biblio.timestamp, dateformat => 'iso' ) %]
1102 [% KohaDates.output_preference( str => biblio.timestamp, dateformat => 'iso', dateonly => 1 ) %]
1104 reset_template({ template => $template, code => $code, module => 'test' });
1105 my $letter = GetPreparedLetter(
1107 letter_code => $code,
1109 biblio => $biblio->biblionumber,
1112 my $expected_content = sprintf("%s\n%s\n%s\n\n%s\n%s\n%s\n\n%s\n%s\n%s\n",
1113 '2018-12-13 20:21:22',
1125 is( $letter->{content}, $expected_content );
1128 sub reset_template {
1129 my ( $params ) = @_;
1130 my $template = $params->{template};
1131 my $code = $params->{code};
1132 my $module = $params->{module} || 'test_module';
1134 Koha::Notice::Templates->search( { code => $code } )->delete;
1135 Koha::Notice::Template->new(
1142 message_transport_type => 'email',
1143 content => $template
1148 sub process_letter {
1150 my $template = $params->{template};
1151 my $tables = $params->{tables};
1152 my $substitute = $params->{substitute};
1153 my $code = $params->{code};
1154 my $module = $params->{module} || 'test_module';
1155 my $branchcode = $params->{branchcode};
1157 reset_template( $params );
1159 my $letter = C4::Letters::GetPreparedLetter(
1161 letter_code => $code,
1164 substitute => $substitute,
1169 $schema->storage->txn_rollback;