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 => 17;
27 use t::lib::TestBuilder;
35 use Koha::ArticleRequests;
42 use Koha::Subscription;
45 use Koha::Notice::Messages;
46 use Koha::Notice::Templates;
47 use Koha::Patron::Modification;
49 my $schema = Koha::Database->schema;
50 $schema->storage->txn_begin();
52 my $builder = t::lib::TestBuilder->new();
54 my $dbh = C4::Context->dbh;
55 $dbh->{RaiseError} = 1;
57 $dbh->do(q|DELETE FROM letter|);
59 my $date = dt_from_string;
61 my $library = $builder->build( { source => 'Branch' } );
62 my $patron = $builder->build( { source => 'Borrower' } );
63 my $patron2 = $builder->build( { source => 'Borrower' } );
65 my $biblio = Koha::Biblio->new(
67 title => 'Test Biblio'
71 my $biblioitem = Koha::Biblioitem->new(
73 biblionumber => $biblio->id()
77 my $item = Koha::Item->new(
79 biblionumber => $biblio->id(),
80 biblioitemnumber => $biblioitem->id()
84 my $hold = Koha::Hold->new(
86 borrowernumber => $patron->{borrowernumber},
87 biblionumber => $biblio->id()
91 my $news = Koha::NewsItem->new()->store();
92 my $serial = Koha::Serial->new()->store();
93 my $subscription = Koha::Subscription->new()->store();
94 my $suggestion = Koha::Suggestion->new()->store();
95 my $checkout = Koha::Checkout->new( { itemnumber => $item->id() } )->store();
96 my $modification = Koha::Patron::Modification->new( { verification_token => "TEST" } )->store();
101 $dbh->prepare(q{INSERT INTO letter (module, code, name, title, content) VALUES ('test',?,'Test','Test',?)});
103 $sth->execute( "TEST_PATRON", "[% borrower.id %]" );
104 $prepared_letter = GetPreparedLetter(
107 letter_code => 'TEST_PATRON',
109 borrowers => $patron->{borrowernumber},
113 is( $prepared_letter->{content}, $patron->{borrowernumber}, 'Patron object used correctly with scalar' );
115 $prepared_letter = GetPreparedLetter(
118 letter_code => 'TEST_PATRON',
120 borrowers => $patron,
124 is( $prepared_letter->{content}, $patron->{borrowernumber}, 'Patron object used correctly with hashref' );
126 $prepared_letter = GetPreparedLetter(
129 letter_code => 'TEST_PATRON',
131 borrowers => [ $patron->{borrowernumber} ],
135 is( $prepared_letter->{content}, $patron->{borrowernumber}, 'Patron object used correctly with arrayref' );
137 $sth->execute( "TEST_BIBLIO", "[% biblio.id %]" );
138 $prepared_letter = GetPreparedLetter(
141 letter_code => 'TEST_BIBLIO',
143 biblio => $biblio->id(),
147 is( $prepared_letter->{content}, $biblio->id, 'Biblio object used correctly' );
149 $sth->execute( "TEST_LIBRARY", "[% branch.id %]" );
150 $prepared_letter = GetPreparedLetter(
153 letter_code => 'TEST_LIBRARY',
155 branches => $library->{branchcode}
159 is( $prepared_letter->{content}, $library->{branchcode}, 'Library object used correctly' );
161 $sth->execute( "TEST_ITEM", "[% item.id %]" );
162 $prepared_letter = GetPreparedLetter(
165 letter_code => 'TEST_ITEM',
171 is( $prepared_letter->{content}, $item->id(), 'Item object used correctly' );
173 $sth->execute( "TEST_NEWS", "[% news.id %]" );
174 $prepared_letter = GetPreparedLetter(
177 letter_code => 'TEST_NEWS',
179 opac_news => $news->id()
183 is( $prepared_letter->{content}, $news->id(), 'News object used correctly' );
185 $sth->execute( "TEST_HOLD", "[% hold.id %]" );
186 $prepared_letter = GetPreparedLetter(
189 letter_code => 'TEST_HOLD',
191 reserves => { borrowernumber => $patron->{borrowernumber}, biblionumber => $biblio->id() },
195 is( $prepared_letter->{content}, $hold->id(), 'Hold object used correctly' );
198 $prepared_letter = GetPreparedLetter(
201 letter_code => 'TEST_HOLD',
203 reserves => [ $patron->{borrowernumber}, $biblio->id() ],
209 like( $croak, qr{^Multiple foreign keys \(table reserves\) should be passed using an hashref.*}, "GetPreparedLetter should not be called with arrayref for multiple FK" );
212 $prepared_letter = GetPreparedLetter(
215 letter_code => 'TEST_HOLD',
217 'branches' => $library,
218 'borrowers' => $patron,
219 'biblio' => $biblio->id,
220 'biblioitems' => $biblioitem->id,
221 'reserves' => $hold->unblessed,
222 'items' => $hold->itemnumber,
226 is( $prepared_letter->{content}, $hold->id(), 'Hold object used correctly' );
228 $sth->execute( "TEST_SERIAL", "[% serial.id %]" );
229 $prepared_letter = GetPreparedLetter(
232 letter_code => 'TEST_SERIAL',
234 serial => $serial->id()
238 is( $prepared_letter->{content}, $serial->id(), 'Serial object used correctly' );
240 $sth->execute( "TEST_SUBSCRIPTION", "[% subscription.id %]" );
241 $prepared_letter = GetPreparedLetter(
244 letter_code => 'TEST_SUBSCRIPTION',
246 subscription => $subscription->id()
250 is( $prepared_letter->{content}, $subscription->id(), 'Subscription object used correctly' );
252 $sth->execute( "TEST_SUGGESTION", "[% suggestion.id %]" );
253 $prepared_letter = GetPreparedLetter(
256 letter_code => 'TEST_SUGGESTION',
258 suggestions => $suggestion->id()
262 is( $prepared_letter->{content}, $suggestion->id(), 'Suggestion object used correctly' );
264 $sth->execute( "TEST_ISSUE", "[% checkout.id %]" );
265 $prepared_letter = GetPreparedLetter(
268 letter_code => 'TEST_ISSUE',
270 issues => $item->id()
274 is( $prepared_letter->{content}, $checkout->id(), 'Checkout object used correctly' );
276 $sth->execute( "TEST_MODIFICATION", "[% patron_modification.id %]" );
277 $prepared_letter = GetPreparedLetter(
280 letter_code => 'TEST_MODIFICATION',
282 borrower_modifications => $modification->verification_token,
286 is( $prepared_letter->{content}, $modification->id(), 'Patron modification object used correctly' );
288 subtest 'regression tests' => sub {
291 my $library = $builder->build( { source => 'Branch' } );
292 my $patron = $builder->build( { source => 'Borrower' } );
293 my $biblio1 = Koha::Biblio->new({title => 'Test Biblio 1', author => 'An author', })->store->unblessed;
294 my $biblioitem1 = Koha::Biblioitem->new({biblionumber => $biblio1->{biblionumber}})->store()->unblessed;
295 my $item1 = Koha::Item->new(
297 biblionumber => $biblio1->{biblionumber},
298 biblioitemnumber => $biblioitem1->{biblioitemnumber},
299 barcode => 'a_t_barcode',
300 homebranch => $library->{branchcode},
301 holdingbranch => $library->{branchcode},
303 itemcallnumber => 'itemcallnumber1',
306 my $biblio2 = Koha::Biblio->new({title => 'Test Biblio 2'})->store->unblessed;
307 my $biblioitem2 = Koha::Biblioitem->new({biblionumber => $biblio2->{biblionumber}})->store()->unblessed;
308 my $item2 = Koha::Item->new(
310 biblionumber => $biblio2->{biblionumber},
311 biblioitemnumber => $biblioitem2->{biblioitemnumber},
312 barcode => 'another_t_barcode',
313 homebranch => $library->{branchcode},
314 holdingbranch => $library->{branchcode},
316 itemcallnumber => 'itemcallnumber2',
320 C4::Context->_new_userenv('xxx');
321 C4::Context->set_userenv(0,0,0,'firstname','surname', $library->{branchcode}, 'Midway Public Library', '', '', '');
323 subtest 'ACQ_NOTIF_ON_RECEIV ' => sub {
325 my $code = 'ACQ_NOTIF_ON_RECEIV';
326 my $branchcode = $library->{branchcode};
327 my $order = $builder->build({ source => 'Aqorder' });
330 Dear <<borrowers.firstname>> <<borrowers.surname>>,
331 The order <<aqorders.ordernumber>> (<<biblio.title>>) has been received.
334 my $params = { code => $code, branchcode => $branchcode, tables => { branches => $library, borrowers => $patron, biblio => $biblio1, aqorders => $order } };
335 my $letter = process_letter( { template => $template, %$params });
337 Dear [% borrower.firstname %] [% borrower.surname %],
338 The order [% order.ordernumber %] ([% biblio.title %]) has been received.
341 my $tt_letter = process_letter( { template => $tt_template, %$params });
343 is( $tt_letter->{content}, $letter->{content}, 'Verified letter content' );
346 subtest 'AR_*' => sub {
348 my $code = 'AR_CANCELED';
349 my $branchcode = $library->{branchcode};
352 <<borrowers.firstname>> <<borrowers.surname>> (<<borrowers.cardnumber>>)
354 Your request for an article from <<biblio.title>> (<<items.barcode>>) has been canceled for the following reason:
356 <<article_requests.notes>>
359 Title: <<article_requests.title>>
360 Author: <<article_requests.author>>
361 Volume: <<article_requests.volume>>
362 Issue: <<article_requests.issue>>
363 Date: <<article_requests.date>>
364 Pages: <<article_requests.pages>>
365 Chapters: <<article_requests.chapters>>
366 Notes: <<article_requests.patron_notes>>
368 reset_template( { template => $template, code => $code, module => 'circulation' } );
369 my $article_request = $builder->build({ source => 'ArticleRequest' });
370 Koha::ArticleRequests->find( $article_request->{id} )->cancel;
371 my $letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
374 [% borrower.firstname %] [% borrower.surname %] ([% borrower.cardnumber %])
376 Your request for an article from [% biblio.title %] ([% item.barcode %]) has been canceled for the following reason:
378 [% article_request.notes %]
381 Title: [% article_request.title %]
382 Author: [% article_request.author %]
383 Volume: [% article_request.volume %]
384 Issue: [% article_request.issue %]
385 Date: [% article_request.date %]
386 Pages: [% article_request.pages %]
387 Chapters: [% article_request.chapters %]
388 Notes: [% article_request.patron_notes %]
390 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
391 Koha::ArticleRequests->find( $article_request->{id} )->cancel;
392 my $tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
393 is( $tt_letter->content, $letter->content, 'Compare AR_* notices' );
394 isnt( $tt_letter->message_id, $letter->message_id, 'Comparing AR_* notices should compare 2 different messages' );
397 subtest 'CHECKOUT+CHECKIN' => sub {
400 my $checkout_code = 'CHECKOUT';
401 my $checkin_code = 'CHECKIN';
403 my $dbh = C4::Context->dbh;
404 # Enable notification for CHECKOUT - Things are hardcoded here but should work with default data
405 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 6 );
406 my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
407 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
408 # Enable notification for CHECKIN - Things are hardcoded here but should work with default data
409 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 5 );
410 $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
411 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
414 my $checkout_template = q|
415 The following items have been checked out:
419 Thank you for visiting <<branches.branchname>>.
421 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
422 my $checkin_template = q[
423 The following items have been checked out:
425 <<biblio.title>> was due on <<old_issues.date_due | dateonly>>
427 Thank you for visiting <<branches.branchname>>.
429 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
431 C4::Circulation::AddIssue( $patron, $item1->{barcode} );
432 my $first_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
433 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
434 my $second_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
436 AddReturn( $item1->{barcode} );
437 my $first_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
438 AddReturn( $item2->{barcode} );
439 my $second_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
441 Koha::Notice::Messages->delete;
444 $checkout_template = q|
445 The following items have been checked out:
449 Thank you for visiting [% branch.branchname %].
451 reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
452 $checkin_template = q[
453 The following items have been checked out:
455 [% biblio.title %] was due on [% old_checkout.date_due | $KohaDates %]
457 Thank you for visiting [% branch.branchname %].
459 reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
461 C4::Circulation::AddIssue( $patron, $item1->{barcode} );
462 my $first_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
463 C4::Circulation::AddIssue( $patron, $item2->{barcode} );
464 my $second_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
466 AddReturn( $item1->{barcode} );
467 my $first_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
468 AddReturn( $item2->{barcode} );
469 my $second_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
471 is( $first_checkout_tt_letter->content, $first_checkout_letter->content, 'Verify first checkout letter' );
472 is( $second_checkout_tt_letter->content, $second_checkout_letter->content, 'Verify second checkout letter' );
473 is( $first_checkin_tt_letter->content, $first_checkin_letter->content, 'Verify first checkin letter' );
474 is( $second_checkin_tt_letter->content, $second_checkin_letter->content, 'Verify second checkin letter' );
478 subtest 'DUEDGST|count' => sub {
481 my $code = 'DUEDGST';
483 my $dbh = C4::Context->dbh;
484 # Enable notification for DUEDGST - Things are hardcoded here but should work with default data
485 $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 1 );
486 my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
487 $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
491 substitute => { count => 42 },
495 You have <<count>> items due
497 my $letter = process_letter( { template => $template, %$params });
500 You have [% count %] items due
502 my $tt_letter = process_letter( { template => $tt_template, %$params });
503 is( $tt_letter->{content}, $letter->{content}, );
506 subtest 'HOLD_SLIP|dates|today' => sub {
509 my $code = 'HOLD_SLIP';
511 C4::Reserves::AddReserve( $library->{branchcode}, $patron->{borrowernumber}, $biblio1->{biblionumber}, undef, undef, undef, undef, "a note", undef, $item1->{itemnumber}, 'W' );
512 C4::Reserves::AddReserve( $library->{branchcode}, $patron->{borrowernumber}, $biblio2->{biblionumber}, undef, undef, undef, undef, "another note", undef, $item2->{itemnumber} );
514 my $template = <<EOF;
515 <h5>Date: <<today>></h5>
517 <h3> Transfer to/Hold in <<branches.branchname>></h3>
519 <h3><<borrowers.surname>>, <<borrowers.firstname>></h3>
522 <li><<borrowers.cardnumber>></li>
523 <li><<borrowers.phone>></li>
524 <li> <<borrowers.address>><br />
525 <<borrowers.address2>><br />
526 <<borrowers.city>> <<borrowers.zipcode>>
528 <li><<borrowers.email>></li>
531 <h3>ITEM ON HOLD</h3>
532 <h4><<biblio.title>></h4>
533 <h5><<biblio.author>></h5>
535 <li><<items.barcode>></li>
536 <li><<items.itemcallnumber>></li>
537 <li><<reserves.waitingdate>></li>
540 <pre><<reserves.reservenotes>></pre>
544 reset_template( { template => $template, code => $code, module => 'circulation' } );
545 my $letter_for_item1 = C4::Reserves::ReserveSlip( $library->{branchcode}, $patron->{borrowernumber}, $biblio1->{biblionumber} );
546 my $letter_for_item2 = C4::Reserves::ReserveSlip( $library->{branchcode}, $patron->{borrowernumber}, $biblio2->{biblionumber} );
548 my $tt_template = <<EOF;
549 <h5>Date: [% today | \$KohaDates with_hours => 1 %]</h5>
551 <h3> Transfer to/Hold in [% branch.branchname %]</h3>
553 <h3>[% borrower.surname %], [% borrower.firstname %]</h3>
556 <li>[% borrower.cardnumber %]</li>
557 <li>[% borrower.phone %]</li>
558 <li> [% borrower.address %]<br />
559 [% borrower.address2 %]<br />
560 [% borrower.city %] [% borrower.zipcode %]
562 <li>[% borrower.email %]</li>
565 <h3>ITEM ON HOLD</h3>
566 <h4>[% biblio.title %]</h4>
567 <h5>[% biblio.author %]</h5>
569 <li>[% item.barcode %]</li>
570 <li>[% item.itemcallnumber %]</li>
571 <li>[% hold.waitingdate | \$KohaDates %]</li>
574 <pre>[% hold.reservenotes %]</pre>
578 reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
579 my $tt_letter_for_item1 = C4::Reserves::ReserveSlip( $library->{branchcode}, $patron->{borrowernumber}, $biblio1->{biblionumber} );
580 my $tt_letter_for_item2 = C4::Reserves::ReserveSlip( $library->{branchcode}, $patron->{borrowernumber}, $biblio2->{biblionumber} );
582 is( $tt_letter_for_item1->{content}, $letter_for_item1->{content}, );
583 is( $tt_letter_for_item2->{content}, $letter_for_item2->{content}, );
587 subtest 'loops' => sub {
592 subtest 'primary key is AI' => sub {
594 my $patron_1 = $builder->build({ source => 'Borrower' });
595 my $patron_2 = $builder->build({ source => 'Borrower' });
597 my $template = q|[% FOREACH patron IN borrowers %][% patron.surname %][% END %]|;
598 reset_template( { template => $template, code => $code, module => $module } );
599 my $letter = GetPreparedLetter( module => $module, letter_code => $code, loops => { borrowers => [ $patron_1->{borrowernumber}, $patron_2->{borrowernumber} ] } );
600 my $expected_letter = join '', ( $patron_1->{surname}, $patron_2->{surname} );
601 is( $letter->{content}, $expected_letter, );
604 subtest 'foreign key is used' => sub {
606 my $patron_1 = $builder->build({ source => 'Borrower' });
607 my $patron_2 = $builder->build({ source => 'Borrower' });
608 my $checkout_1 = $builder->build({ source => 'Issue', value => { borrowernumber => $patron_1->{borrowernumber} } } );
609 my $checkout_2 = $builder->build({ source => 'Issue', value => { borrowernumber => $patron_1->{borrowernumber} } } );
611 my $template = q|[% FOREACH checkout IN checkouts %][% checkout.issue_id %][% END %]|;
612 reset_template( { template => $template, code => $code, module => $module } );
613 my $letter = GetPreparedLetter( module => $module, letter_code => $code, loops => { issues => [ $checkout_1->{itemnumber}, $checkout_2->{itemnumber} ] } );
614 my $expected_letter = join '', ( $checkout_1->{issue_id}, $checkout_2->{issue_id} );
615 is( $letter->{content}, $expected_letter, );
621 my $template = $params->{template};
622 my $code = $params->{code};
623 my $module = $params->{module} || 'test_module';
625 Koha::Notice::Templates->search( { code => $code } )->delete;
626 Koha::Notice::Template->new(
633 message_transport_type => 'email',
641 my $template = $params->{template};
642 my $tables = $params->{tables};
643 my $substitute = $params->{substitute};
644 my $code = $params->{code};
645 my $module = $params->{module} || 'test_module';
646 my $branchcode = $params->{branchcode};
648 reset_template( $params );
650 my $letter = C4::Letters::GetPreparedLetter(
652 letter_code => $code,
655 substitute => $substitute,