Bug 26384: Fix executable flags
[koha.git] / t / db_dependent / Letters / TemplateToolkit.t
1 #!/usr/bin/perl
2
3 # This file is part of Koha.
4 #
5 # Copyright (C) 2016 ByWater Solutions
6 # Copyright (C) 2017 Koha Development Team
7 #
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.
12 #
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.
17 #
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>.
20
21 use Modern::Perl;
22 use Test::More tests => 19;
23 use Test::MockModule;
24 use Test::Warn;
25
26 use MARC::Record;
27
28 use t::lib::TestBuilder;
29 use t::lib::Mocks;
30
31 use C4::Circulation;
32 use C4::Letters;
33 use C4::Members;
34 use C4::Biblio;
35 use Koha::Database;
36 use Koha::DateUtils;
37 use Koha::ArticleRequests;
38 use Koha::Biblio;
39 use Koha::Biblioitem;
40 use Koha::Item;
41 use Koha::Hold;
42 use Koha::NewsItem;
43 use Koha::Serial;
44 use Koha::Subscription;
45 use Koha::Suggestion;
46 use Koha::Checkout;
47 use Koha::Notice::Messages;
48 use Koha::Notice::Templates;
49 use Koha::Patron::Modification;
50
51 my $schema = Koha::Database->schema;
52 $schema->storage->txn_begin();
53
54 my $builder = t::lib::TestBuilder->new();
55
56 my $dbh = C4::Context->dbh;
57
58 $dbh->do(q|DELETE FROM letter|);
59
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; } );
63
64 my $library = $builder->build( { source => 'Branch' } );
65 my $patron  = $builder->build( { source => 'Borrower' } );
66 my $patron2 = $builder->build( { source => 'Borrower' } );
67
68 my $item = $builder->build_sample_item();
69 my $hold = $builder->build_object(
70     {
71         class => 'Koha::Holds',
72         value => {
73             borrowernumber => $patron->{borrowernumber},
74             biblionumber   => $item->biblionumber
75         }
76     }
77 );
78
79 my $news = $builder->build_object(
80     {
81         class => 'Koha::News',
82         value => { title => 'a news title', content => 'a news content' }
83     }
84 );
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(
91     {
92         class => 'Koha::Patron::Modifications',
93         value => {
94             verification_token => "TEST",
95             changed_fields     => 'firstname,surname'
96         }
97     }
98 );
99
100 my $prepared_letter;
101
102 my $sth =
103   $dbh->prepare(q{INSERT INTO letter (module, code, name, title, content) VALUES ('test',?,'Test','Test',?)});
104
105 $sth->execute( "TEST_PATRON", "[% borrower.id %]" );
106 $prepared_letter = GetPreparedLetter(
107     (
108         module      => 'test',
109         letter_code => 'TEST_PATRON',
110         tables      => {
111             borrowers => $patron->{borrowernumber},
112         },
113     )
114 );
115 is( $prepared_letter->{content}, $patron->{borrowernumber}, 'Patron object used correctly with scalar' );
116
117 $prepared_letter = GetPreparedLetter(
118     (
119         module      => 'test',
120         letter_code => 'TEST_PATRON',
121         tables      => {
122             borrowers => $patron,
123         },
124     )
125 );
126 is( $prepared_letter->{content}, $patron->{borrowernumber}, 'Patron object used correctly with hashref' );
127
128 $prepared_letter = GetPreparedLetter(
129     (
130         module      => 'test',
131         letter_code => 'TEST_PATRON',
132         tables      => {
133             borrowers => [ $patron->{borrowernumber} ],
134         },
135     )
136 );
137 is( $prepared_letter->{content}, $patron->{borrowernumber}, 'Patron object used correctly with arrayref' );
138
139 $sth->execute( "TEST_BIBLIO", "[% biblio.id %]" );
140 $prepared_letter = GetPreparedLetter(
141     (
142         module      => 'test',
143         letter_code => 'TEST_BIBLIO',
144         tables      => {
145             biblio => $item->biblionumber,
146         },
147     )
148 );
149 is( $prepared_letter->{content}, $item->biblionumber, 'Biblio object used correctly' );
150
151 $sth->execute( "TEST_LIBRARY", "[% branch.id %]" );
152 $prepared_letter = GetPreparedLetter(
153     (
154         module      => 'test',
155         letter_code => 'TEST_LIBRARY',
156         tables      => {
157             branches => $library->{branchcode}
158         },
159     )
160 );
161 is( $prepared_letter->{content}, $library->{branchcode}, 'Library object used correctly' );
162
163 $sth->execute( "TEST_ITEM", "[% item.id %]" );
164 $prepared_letter = GetPreparedLetter(
165     (
166         module      => 'test',
167         letter_code => 'TEST_ITEM',
168         tables      => {
169             items => $item->id()
170         },
171     )
172 );
173 is( $prepared_letter->{content}, $item->id(), 'Item object used correctly' );
174
175 $sth->execute( "TEST_NEWS", "[% news.id %]" );
176 $prepared_letter = GetPreparedLetter(
177     (
178         module      => 'test',
179         letter_code => 'TEST_NEWS',
180         tables      => {
181             opac_news => $news->id()
182         },
183     )
184 );
185 is( $prepared_letter->{content}, $news->id(), 'News object used correctly' );
186
187 $sth->execute( "TEST_HOLD", "[% hold.id %]" );
188 $prepared_letter = GetPreparedLetter(
189     (
190         module      => 'test',
191         letter_code => 'TEST_HOLD',
192         tables      => {
193             reserves => { borrowernumber => $patron->{borrowernumber}, biblionumber => $item->biblionumber },
194         },
195     )
196 );
197 is( $prepared_letter->{content}, $hold->id(), 'Hold object used correctly' );
198
199 eval {
200     $prepared_letter = GetPreparedLetter(
201         (
202             module      => 'test',
203             letter_code => 'TEST_HOLD',
204             tables      => {
205                 reserves => [ $patron->{borrowernumber}, $item->biblionumber ],
206             },
207         )
208     )
209 };
210 my $croak = $@;
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" );
212
213 # Bug 16942
214 $prepared_letter = GetPreparedLetter(
215     (
216         module      => 'test',
217         letter_code => 'TEST_HOLD',
218         tables      => {
219             'branches'    => $library,
220             'borrowers'   => $patron,
221             'biblio'      => $item->biblionumber,
222             'biblioitems' => $item->biblioitemnumber,
223             'reserves'    => $hold->unblessed,
224             'items'       => $hold->itemnumber,
225         }
226     )
227 );
228 is( $prepared_letter->{content}, $hold->id(), 'Hold object used correctly' );
229
230 $sth->execute( "TEST_SERIAL", "[% serial.id %]" );
231 $prepared_letter = GetPreparedLetter(
232     (
233         module      => 'test',
234         letter_code => 'TEST_SERIAL',
235         tables      => {
236             serial => $serial->id()
237         },
238     )
239 );
240 is( $prepared_letter->{content}, $serial->id(), 'Serial object used correctly' );
241
242 $sth->execute( "TEST_SUBSCRIPTION", "[% subscription.id %]" );
243 $prepared_letter = GetPreparedLetter(
244     (
245         module      => 'test',
246         letter_code => 'TEST_SUBSCRIPTION',
247         tables      => {
248             subscription => $subscription->id()
249         },
250     )
251 );
252 is( $prepared_letter->{content}, $subscription->id(), 'Subscription object used correctly' );
253
254 $sth->execute( "TEST_SUGGESTION", "[% suggestion.id %]" );
255 $prepared_letter = GetPreparedLetter(
256     (
257         module      => 'test',
258         letter_code => 'TEST_SUGGESTION',
259         tables      => {
260             suggestions => $suggestion->id()
261         },
262     )
263 );
264 is( $prepared_letter->{content}, $suggestion->id(), 'Suggestion object used correctly' );
265
266 $sth->execute( "TEST_ISSUE", "[% checkout.id %]" );
267 $prepared_letter = GetPreparedLetter(
268     (
269         module      => 'test',
270         letter_code => 'TEST_ISSUE',
271         tables      => {
272             issues => $item->id()
273         },
274     )
275 );
276 is( $prepared_letter->{content}, $checkout->id(), 'Checkout object used correctly' );
277
278 $sth->execute( "TEST_MODIFICATION", "[% patron_modification.id %]" );
279 $prepared_letter = GetPreparedLetter(
280     (
281         module      => 'test',
282         letter_code => 'TEST_MODIFICATION',
283         tables      => {
284             borrower_modifications => $modification->verification_token,
285         },
286     )
287 );
288 is( $prepared_letter->{content}, $modification->id(), 'Patron modification object used correctly' );
289
290 subtest 'regression tests' => sub {
291     plan tests => 8;
292
293     my $library = $builder->build( { source => 'Branch' } );
294     my $patron  = $builder->build( { source => 'Borrower' } );
295     my $item1 = $builder->build_sample_item(
296         {
297             barcode        => 'a_t_barcode',
298             library        => $library->{branchcode},
299             itype          => 'BK',
300             itemcallnumber => 'itemcallnumber1',
301         }
302     );
303     my $biblio1 = $item1->biblio->unblessed;
304     $item1 = $item1->unblessed;
305     my $item2   = $builder->build_sample_item(
306         {
307             barcode        => 'another_t_barcode',
308             library        => $library->{branchcode},
309             itype          => 'BK',
310             itemcallnumber => 'itemcallnumber2',
311         }
312     );
313     my $biblio2 = $item2->biblio->unblessed;
314     $item2 = $item2->unblessed;
315     my $item3   = $builder->build_sample_item(
316         {
317             barcode        => 'another_t_barcode_3',
318             library        => $library->{branchcode},
319             itype          => 'BK',
320             itemcallnumber => 'itemcallnumber3',
321         }
322     );
323     my $biblio3 = $item3->biblio->unblessed;
324     $item3 = $item3->unblessed;
325
326     t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
327
328     subtest 'ACQ_NOTIF_ON_RECEIV ' => sub {
329         plan tests => 1;
330         my $code = 'ACQ_NOTIF_ON_RECEIV';
331         my $branchcode = $library->{branchcode};
332         my $order = $builder->build({ source => 'Aqorder' });
333
334         my $template = q|
335 Dear <<borrowers.firstname>> <<borrowers.surname>>,
336 The order <<aqorders.ordernumber>> (<<biblio.title>>) has been received.
337 Your library.
338         |;
339         my $params = { code => $code, branchcode => $branchcode, tables => { branches => $library, borrowers => $patron, biblio => $biblio1, aqorders => $order } };
340         my $letter = process_letter( { template => $template, %$params });
341         my $tt_template = q|
342 Dear [% borrower.firstname %] [% borrower.surname %],
343 The order [% order.ordernumber %] ([% biblio.title %]) has been received.
344 Your library.
345         |;
346         my $tt_letter = process_letter( { template => $tt_template, %$params });
347
348         is( $tt_letter->{content}, $letter->{content}, 'Verified letter content' );
349     };
350
351     subtest 'AR_*' => sub {
352         plan tests => 2;
353         my $code = 'AR_CANCELED';
354         my $branchcode = $library->{branchcode};
355
356         my $template = q|
357 <<borrowers.firstname>> <<borrowers.surname>> (<<borrowers.cardnumber>>)
358
359 Your request for an article from <<biblio.title>> (<<items.barcode>>) has been canceled for the following reason:
360
361 <<article_requests.notes>>
362
363 Article requested:
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>>
372         |;
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;
377
378         my $tt_template = q|
379 [% borrower.firstname %] [% borrower.surname %] ([% borrower.cardnumber %])
380
381 Your request for an article from [% biblio.title %] ([% item.barcode %]) has been canceled for the following reason:
382
383 [% article_request.notes %]
384
385 Article requested:
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 %]
394         |;
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' );
400     };
401
402     subtest 'CHECKOUT+CHECKIN' => sub {
403         plan tests => 4;
404
405         my $checkout_code = 'CHECKOUT';
406         my $checkin_code = 'CHECKIN';
407
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' );
417
418         # historic syntax
419         my $checkout_template = q|
420 The following items have been checked out:
421 ----
422 <<biblio.title>>
423 ----
424 Thank you for visiting <<branches.branchname>>.
425 |;
426         reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
427         my $checkin_template = q[
428 The following items have been checked out:
429 ----
430 <<biblio.title>> was due on <<old_issues.date_due | dateonly>>
431 ----
432 Thank you for visiting <<branches.branchname>>.
433 ];
434         reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
435
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;
440
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;
445
446         Koha::Notice::Messages->delete;
447
448         # TT syntax
449         $checkout_template = q|
450 The following items have been checked out:
451 ----
452 [% biblio.title %]
453 ----
454 Thank you for visiting [% branch.branchname %].
455 |;
456         reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
457         $checkin_template = q[
458 The following items have been checked out:
459 ----
460 [% biblio.title %] was due on [% old_checkout.date_due | $KohaDates %]
461 ----
462 Thank you for visiting [% branch.branchname %].
463 ];
464         reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
465
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;
470
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;
475
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' );
480
481     };
482
483     subtest 'DUEDGST|count' => sub {
484         plan tests => 1;
485
486         my $code = 'DUEDGST';
487
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' );
493
494         my $params = {
495             code => $code,
496             substitute => { count => 42 },
497         };
498
499         my $template = q|
500 You have <<count>> items due
501         |;
502         my $letter = process_letter( { template => $template, %$params });
503
504         my $tt_template = q|
505 You have [% count %] items due
506         |;
507         my $tt_letter = process_letter( { template => $tt_template, %$params });
508         is( $tt_letter->{content}, $letter->{content}, );
509     };
510
511     subtest 'HOLD_SLIP|dates|today' => sub {
512         plan tests => 2;
513
514         my $code = 'HOLD_SLIP';
515
516         my $reserve_id1 = C4::Reserves::AddReserve(
517             {
518                 branchcode     => $library->{branchcode},
519                 borrowernumber => $patron->{borrowernumber},
520                 biblionumber   => $biblio1->{biblionumber},
521                 notes          => "a note",
522                 itemnumber     => $item1->{itemnumber},
523             }
524         );
525         my $reserve_id2 = C4::Reserves::AddReserve(
526             {
527                 branchcode     => $library->{branchcode},
528                 borrowernumber => $patron->{borrowernumber},
529                 biblionumber   => $biblio1->{biblionumber},
530                 notes          => "a note",
531                 itemnumber     => $item1->{itemnumber},
532             }
533         );
534         my $reserve_id3 = C4::Reserves::AddReserve(
535             {
536                 branchcode     => $library->{branchcode},
537                 borrowernumber => $patron->{borrowernumber},
538                 biblionumber   => $biblio2->{biblionumber},
539                 notes          => "another note",
540                 itemnumber     => $item2->{itemnumber},
541             }
542         );
543
544         my $template = <<EOF;
545 <h5>Date: <<today>></h5>
546
547 <h3> Transfer to/Hold in <<branches.branchname>></h3>
548
549 <h3><<borrowers.surname>>, <<borrowers.firstname>></h3>
550
551 <ul>
552     <li><<borrowers.cardnumber>></li>
553     <li><<borrowers.phone>></li>
554     <li> <<borrowers.address>><br />
555          <<borrowers.address2>><br />
556          <<borrowers.city>>  <<borrowers.zipcode>>
557     </li>
558     <li><<borrowers.email>></li>
559 </ul>
560 <br />
561 <h3>ITEM ON HOLD</h3>
562 <h4><<biblio.title>></h4>
563 <h5><<biblio.author>></h5>
564 <ul>
565    <li><<items.barcode>></li>
566    <li><<items.itemcallnumber>></li>
567    <li><<reserves.waitingdate>></li>
568 </ul>
569 <p>Notes:
570 <pre><<reserves.reserve_id>>=<<reserves.reservenotes>></pre>
571 </p>
572 EOF
573
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 } );
577
578         my $tt_template = <<EOF;
579 <h5>Date: [% today | \$KohaDates with_hours => 1 %]</h5>
580
581 <h3> Transfer to/Hold in [% branch.branchname %]</h3>
582
583 <h3>[% borrower.surname %], [% borrower.firstname %]</h3>
584
585 <ul>
586     <li>[% borrower.cardnumber %]</li>
587     <li>[% borrower.phone %]</li>
588     <li> [% borrower.address %]<br />
589          [% borrower.address2 %]<br />
590          [% borrower.city %]  [% borrower.zipcode %]
591     </li>
592     <li>[% borrower.email %]</li>
593 </ul>
594 <br />
595 <h3>ITEM ON HOLD</h3>
596 <h4>[% biblio.title %]</h4>
597 <h5>[% biblio.author %]</h5>
598 <ul>
599    <li>[% item.barcode %]</li>
600    <li>[% item.itemcallnumber %]</li>
601    <li>[% hold.waitingdate | \$KohaDates %]</li>
602 </ul>
603 <p>Notes:
604 <pre>[% hold.reserve_id %]=[% hold.reservenotes %]</pre>
605 </p>
606 EOF
607
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 } );
611
612         is( $tt_letter_for_item1->{content}, $letter_for_item1->{content}, );
613         is( $tt_letter_for_item2->{content}, $letter_for_item2->{content}, );
614     };
615
616     subtest 'ISSUESLIP|checkedout|repeat' => sub {
617         plan tests => 2;
618
619         my $code = 'ISSUESLIP';
620         my $now = dt_from_string;
621         my $one_minute_ago = dt_from_string->subtract( minutes => 1 );
622
623         my $branchcode = $library->{branchcode};
624
625         Koha::News->delete;
626         my $news_item = Koha::NewsItem->new({ branchcode => $branchcode, title => "A wonderful news", content => "This is the wonderful news." })->store;
627
628         # historic syntax
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 />
633
634 <<today>><br />
635
636 <h4>Checked out</h4>
637 <checkedout>
638 <p>
639 <<biblio.title>> <br />
640 Barcode: <<items.barcode>><br />
641 Date due: <<issues.date_due | dateonly>><br />
642 </p>
643 </checkedout>
644
645 <h4>Overdues</h4>
646 <overdue>
647 <p>
648 <<biblio.title>> <br />
649 Barcode: <<items.barcode>><br />
650 Date due: <<issues.date_due | dateonly>><br />
651 </p>
652 </overdue>
653
654 <hr>
655
656 <h4 style="text-align: center; font-style:italic;">News</h4>
657 <news>
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>
662 <hr />
663 </div>
664 </news>
665 EOF
666
667         reset_template( { template => $template, code => $code, module => 'circulation' } );
668
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} );
672
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} );
678
679         # Cleanup
680         AddReturn( $item1->{barcode} );
681         AddReturn( $item2->{barcode} );
682         AddReturn( $item3->{barcode} );
683
684         # TT syntax
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 />
689
690 [% today | \$KohaDates with_hours => 1 %]<br />
691
692 <h4>Checked out</h4>
693 [% FOREACH checkout IN checkouts %]
694 [%~ SET item = checkout.item %]
695 [%~ SET biblio = checkout.item.biblio %]
696 <p>
697 [% biblio.title %] <br />
698 Barcode: [% item.barcode %]<br />
699 Date due: [% checkout.date_due | \$KohaDates %]<br />
700 </p>
701 [% END %]
702
703 <h4>Overdues</h4>
704 [% FOREACH overdue IN overdues %]
705 [%~ SET item = overdue.item %]
706 [%~ SET biblio = overdue.item.biblio %]
707 <p>
708 [% biblio.title %] <br />
709 Barcode: [% item.barcode %]<br />
710 Date due: [% overdue.date_due | \$KohaDates %]<br />
711 </p>
712 [% END %]
713
714 <hr>
715
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>
722 <hr />
723 </div>
724 [% END %]
725 EOF
726
727         reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
728
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} );
732
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} );
737
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;
740
741         is( $first_tt_slip->{content}, $first_slip->{content}, );
742         is( $second_tt_slip->{content}, $second_slip->{content}, );
743
744         # Cleanup
745         AddReturn( $item1->{barcode} );
746         AddReturn( $item2->{barcode} );
747         AddReturn( $item3->{barcode} );
748     };
749
750     subtest 'ODUE|items.content|item' => sub {
751         plan tests => 1;
752
753         my $code = 'ODUE';
754
755         my $branchcode = $library->{branchcode};
756
757         # historic syntax
758         # FIXME items.fine does not work with TT notices
759         # See bug 17976
760         # <item> should contain Fine: <<items.fine>></item>
761         my $template = <<EOF;
762 Dear <<borrowers.firstname>> <<borrowers.surname>>,
763
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.
765
766 <<branches.branchname>>
767 <<branches.branchaddress1>>
768 <<branches.branchaddress2>> <<branches.branchaddress3>>
769 Phone: <<branches.branchphone>>
770 Fax: <<branches.branchfax>>
771 Email: <<branches.branchemail>>
772
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.
774
775 The following item(s) is/are currently overdue:
776
777 <item>"<<biblio.title>>" by <<biblio.author>>, <<items.itemcallnumber>>, Barcode: <<items.barcode>></item>
778
779 <<items.content>>
780
781 Thank-you for your prompt attention to this matter.
782
783 <<branches.branchname>> Staff
784 EOF
785
786         reset_template( { template => $template, code => $code, module => 'circulation' } );
787
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;
796
797         # For items.content
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 } );
802
803         my @items = ( $item1, $item2, $item3 );
804         my $letter = C4::Overdues::parse_overdues_letter(
805             {
806                 letter_code => $code,
807                 borrowernumber => $patron->{borrowernumber},
808                 branchcode  => $library->{branchcode},
809                 items       => \@items,
810                 substitute  => {
811                     bib                    => $library->{branchname},
812                     'items.content'        => $items_content,
813                     count                  => scalar( @items ),
814                     message_transport_type => 'email',
815                 }
816             }
817         );
818
819         # Cleanup
820         AddReturn( $item1->{barcode} );
821         AddReturn( $item2->{barcode} );
822         AddReturn( $item3->{barcode} );
823
824
825         # historic syntax
826         my $tt_template = <<EOF;
827 Dear [% borrower.firstname %] [% borrower.surname %],
828
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.
830
831 [% branch.branchname %]
832 [% branch.branchaddress1 %]
833 [% branch.branchaddress2 %] [% branch.branchaddress3 %]
834 Phone: [% branch.branchphone %]
835 Fax: [% branch.branchfax %]
836 Email: [% branch.branchemail %]
837
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.
839
840 The following item(s) is/are currently overdue:
841
842 [% FOREACH overdue IN overdues %]
843 [%~ SET item = overdue.item ~%]
844 "[% item.biblio.title %]" by [% item.biblio.author %], [% item.itemcallnumber %], Barcode: [% item.barcode %]
845 [% END %]
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 %]
849 [% END %]
850
851 Thank-you for your prompt attention to this matter.
852
853 [% branch.branchname %] Staff
854 EOF
855
856         reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
857
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
861
862         my $tt_letter = C4::Overdues::parse_overdues_letter(
863             {
864                 letter_code => $code,
865                 borrowernumber => $patron->{borrowernumber},
866                 branchcode  => $library->{branchcode},
867                 items       => \@items,
868                 substitute  => {
869                     bib                    => $library->{branchname},
870                     'items.content'        => $items_content,
871                     count                  => scalar( @items ),
872                     message_transport_type => 'email',
873                 }
874             }
875         );
876
877         is( $tt_letter->{content}, $letter->{content}, );
878     };
879
880     subtest 'Bug 19743 - Header and Footer should be updated on each item for checkin / checkout / renewal notices' => sub {
881         plan tests => 8;
882
883         my $checkout_code = 'CHECKOUT';
884         my $checkin_code = 'CHECKIN';
885
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");
890
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' );
899
900         my $checkout_template = q|
901 <<branches.branchname>>
902 ----
903 ----
904 |;
905         reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
906         my $checkin_template = q[
907 <<branches.branchname>>
908 ----
909 ----
910 ];
911         reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
912
913         my $issue = C4::Circulation::AddIssue( $patron, $item1->{barcode} );
914         my $first_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
915
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";
919
920         # Change branch name for second checkout notice
921         $library_object->branchname($new_branchname);
922         $library_object->store();
923
924         C4::Circulation::AddIssue( $patron, $item2->{barcode} );
925         my $second_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
926
927         # Restore old name for first checkin notice
928         $library_object->branchname( $old_branchname );
929         $library_object->store();
930
931         AddReturn( $item1->{barcode} );
932         my $first_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
933
934         # Change branch name for second checkin notice
935         $library_object->branchname($new_branchname);
936         $library_object->store();
937
938         AddReturn( $item2->{barcode} );
939         my $second_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
940
941         # Restore old name for first TT checkout notice
942         $library_object->branchname( $old_branchname );
943         $library_object->store();
944
945         Koha::Notice::Messages->delete;
946
947         # TT syntax
948         $checkout_template = q|
949 [% branch.branchname %]
950 ----
951 ----
952 |;
953         reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
954         $checkin_template = q[
955 [% branch.branchname %]
956 ----
957 ----
958 ];
959         reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
960
961         C4::Circulation::AddIssue( $patron, $item1->{barcode} );
962         my $first_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
963
964         # Change branch name for second checkout notice
965         $library_object->branchname($new_branchname);
966         $library_object->store();
967
968         C4::Circulation::AddIssue( $patron, $item2->{barcode} );
969         my $second_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
970
971         # Restore old name for first checkin notice
972         $library_object->branchname( $old_branchname );
973         $library_object->store();
974
975         AddReturn( $item1->{barcode} );
976         my $first_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
977 #
978         # Change branch name for second checkin notice
979         $library_object->branchname($new_branchname);
980         $library_object->store();
981
982         AddReturn( $item2->{barcode} );
983         my $second_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
984
985         my $first_letter = qq[
986 $old_branchname
987 ];
988         my $second_letter = qq[
989 $new_branchname
990 ];
991
992
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' );
997
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' );
1002     };
1003
1004 };
1005
1006 subtest 'loops' => sub {
1007     plan tests => 2;
1008     my $code = "TEST";
1009     my $module = "TEST";
1010
1011     subtest 'primary key is AI' => sub {
1012         plan tests => 1;
1013         my $patron_1 = $builder->build({ source => 'Borrower' });
1014         my $patron_2 = $builder->build({ source => 'Borrower' });
1015
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, );
1021     };
1022
1023     subtest 'foreign key is used' => sub {
1024         plan tests => 1;
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} } } );
1029
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, );
1035     };
1036 };
1037
1038 subtest 'add_tt_filters' => sub {
1039     plan tests => 1;
1040     my $code   = "TEST";
1041     my $module = "TEST";
1042
1043     my $patron = $builder->build_object(
1044         {
1045             class => 'Koha::Patrons',
1046             value => { surname => "with_punctuation_" }
1047         }
1048     );
1049     my $biblio = $builder->build_object(
1050         { class => 'Koha::Biblios', value => { title => "with_punctuation_" } }
1051     );
1052     my $biblioitem = $builder->build_object(
1053         {
1054             class => 'Koha::Biblioitems',
1055             value => {
1056                 biblionumber => $biblio->biblionumber,
1057                 isbn         => "with_punctuation_"
1058             }
1059         }
1060     );
1061
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(
1065         module      => $module,
1066         letter_code => $code,
1067         tables      => {
1068             borrowers   => $patron->borrowernumber,
1069             biblio      => $biblio->biblionumber,
1070             biblioitems => $biblioitem->biblioitemnumber
1071         }
1072     );
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");
1075 };
1076
1077 subtest 'Dates formatting' => sub {
1078     plan tests => 1;
1079     my $code = 'TEST_DATE';
1080     t::lib::Mocks::mock_preference('dateformat', 'metric'); # MM/DD/YYYY
1081     my $biblio = $builder->build_object(
1082         {
1083             class => 'Koha::Biblios',
1084             value => {
1085                 timestamp   => '2018-12-13 20:21:22',
1086                 datecreated => '2018-12-13'
1087             }
1088         }
1089     );
1090     my $template = <<EOF;
1091 [%- USE KohaDates -%]
1092 [% biblio.timestamp %]
1093 [% biblio.timestamp | \$KohaDates %]
1094 [% biblio.timestamp | \$KohaDates with_hours => 1 %]
1095
1096 [% biblio.datecreated %]
1097 [% biblio.datecreated | \$KohaDates %]
1098 [% biblio.datecreated | \$KohaDates with_hours => 1 %]
1099
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 ) %]
1103 EOF
1104     reset_template({ template => $template, code => $code, module => 'test' });
1105     my $letter = GetPreparedLetter(
1106         module => 'test',
1107         letter_code => $code,
1108         tables => {
1109             biblio => $biblio->biblionumber,
1110         }
1111     );
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',
1114         '13/12/2018',
1115         '13/12/2018 20:21',
1116
1117         '2018-12-13',
1118         '13/12/2018',
1119         '13/12/2018 00:00',
1120
1121         '2018-12-13',
1122         '2018-12-13 20:21',
1123         '2018-12-13',
1124     );
1125     is( $letter->{content}, $expected_content );
1126 };
1127
1128 sub reset_template {
1129     my ( $params ) = @_;
1130     my $template   = $params->{template};
1131     my $code       = $params->{code};
1132     my $module     = $params->{module} || 'test_module';
1133
1134     Koha::Notice::Templates->search( { code => $code } )->delete;
1135     Koha::Notice::Template->new(
1136         {
1137             module                 => $module,
1138             code                   => $code,
1139             branchcode             => '',
1140             name                   => $code,
1141             title                  => $code,
1142             message_transport_type => 'email',
1143             content                => $template
1144         }
1145     )->store;
1146 }
1147
1148 sub process_letter {
1149     my ($params)   = @_;
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};
1156
1157     reset_template( $params );
1158
1159     my $letter = C4::Letters::GetPreparedLetter(
1160         module      => $module,
1161         letter_code => $code,
1162         branchcode  => '',
1163         tables      => $tables,
1164         substitute  => $substitute,
1165     );
1166     return $letter;
1167 }
1168
1169 $schema->storage->txn_rollback;