Bug 28931: Use EXPORT_OK from Koha::DateUtils
[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 => 28;
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 qw( AddIssue AddReturn );
32 use C4::Letters qw( GetPreparedLetter );
33 use C4::Members qw( IssueSlip );
34 use C4::Biblio;
35 use Koha::Database;
36 use Koha::DateUtils qw( dt_from_string output_pref );
37 use Koha::ArticleRequests;
38 use Koha::Biblio;
39 use Koha::Biblioitem;
40 use Koha::Item;
41 use Koha::Hold;
42 use Koha::AdditionalContents;
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::AdditionalContents',
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',?,?)});
104
105 $sth->execute( "TEST_PATRON", "[% borrower.firstname %]", "[% 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 for content' );
116 is( $prepared_letter->{title}, $patron->{firstname}, 'Patron object used correctly with scalar for title' );
117
118 $prepared_letter = GetPreparedLetter(
119     (
120         module      => 'test',
121         letter_code => 'TEST_PATRON',
122         tables      => {
123             borrowers => $patron,
124         },
125     )
126 );
127 is( $prepared_letter->{content}, $patron->{borrowernumber}, 'Patron object used correctly with hashref for content' );
128 is( $prepared_letter->{title}, $patron->{firstname}, 'Patron object used correctly with hashref for title' );
129
130 $prepared_letter = GetPreparedLetter(
131     (
132         module      => 'test',
133         letter_code => 'TEST_PATRON',
134         tables      => {
135             borrowers => [ $patron->{borrowernumber} ],
136         },
137     )
138 );
139 is( $prepared_letter->{content}, $patron->{borrowernumber}, 'Patron object used correctly with arrayref for content' );
140 is( $prepared_letter->{title}, $patron->{firstname}, 'Patron object used correctly with arrayref for title' );
141
142 $sth->execute( "TEST_BIBLIO", "[% biblio.title %]", "[% biblio.id %]" );
143 $prepared_letter = GetPreparedLetter(
144     (
145         module      => 'test',
146         letter_code => 'TEST_BIBLIO',
147         tables      => {
148             biblio => $item->biblionumber,
149         },
150     )
151 );
152 is( $prepared_letter->{content}, $item->biblionumber, 'Biblio object used correctly for content' );
153 is( $prepared_letter->{title}, $item->biblio->title, 'Biblio object used correctly for title' );
154
155 $sth->execute( "TEST_LIBRARY", "[% branch.branchcode %]", "[% branch.id %]" );
156 $prepared_letter = GetPreparedLetter(
157     (
158         module      => 'test',
159         letter_code => 'TEST_LIBRARY',
160         tables      => {
161             branches => $library->{branchcode}
162         },
163     )
164 );
165 is( $prepared_letter->{content}, $library->{branchcode}, 'Library object used correctly for content' );
166 is( $prepared_letter->{title}, $library->{branchcode}, 'Library object used correctly for title' );
167
168 $sth->execute( "TEST_ITEM", "[% item.barcode %]", "[% item.id %]" );
169 $prepared_letter = GetPreparedLetter(
170     (
171         module      => 'test',
172         letter_code => 'TEST_ITEM',
173         tables      => {
174             items => $item->id()
175         },
176     )
177 );
178 is( $prepared_letter->{content}, $item->id(), 'Item object used correctly for content' );
179 is( $prepared_letter->{title}, $item->barcode, 'Item object used correctly for title' );
180
181 $sth->execute( "TEST_NEWS", "[% additional_content.id %]", "[% additional_content.id %]" );
182 $prepared_letter = GetPreparedLetter(
183     (
184         module      => 'test',
185         letter_code => 'TEST_NEWS',
186         tables      => {
187             additional_contents => $news->id()
188         },
189     )
190 );
191 is( $prepared_letter->{content}, $news->id(), 'News object used correctly for content' );
192 is( $prepared_letter->{title}, $news->id(), 'News object used correctly for title' );
193
194 $sth->execute( "TEST_HOLD", "[% hold.borrowernumber %]", "[% hold.id %]" );
195 $prepared_letter = GetPreparedLetter(
196     (
197         module      => 'test',
198         letter_code => 'TEST_HOLD',
199         tables      => {
200             reserves => { borrowernumber => $patron->{borrowernumber}, biblionumber => $item->biblionumber },
201         },
202     )
203 );
204 is( $prepared_letter->{content}, $hold->id(), 'Hold object used correctly for content' );
205 is( $prepared_letter->{title}, $hold->borrowernumber, 'Hold object used correctly for title' );
206
207 eval {
208     $prepared_letter = GetPreparedLetter(
209         (
210             module      => 'test',
211             letter_code => 'TEST_HOLD',
212             tables      => {
213                 reserves => [ $patron->{borrowernumber}, $item->biblionumber ],
214             },
215         )
216     )
217 };
218 my $croak = $@;
219 like( $croak, qr{^Multiple foreign keys \(table reserves\) should be passed using an hashref.*}, "GetPreparedLetter should not be called with arrayref for multiple FK" );
220
221 # Bug 16942
222 $prepared_letter = GetPreparedLetter(
223     (
224         module      => 'test',
225         letter_code => 'TEST_HOLD',
226         tables      => {
227             'branches'    => $library,
228             'borrowers'   => $patron,
229             'biblio'      => $item->biblionumber,
230             'biblioitems' => $item->biblioitemnumber,
231             'reserves'    => $hold->unblessed,
232             'items'       => $hold->itemnumber,
233         }
234     )
235 );
236 is( $prepared_letter->{content}, $hold->id(), 'Hold object used correctly' );
237
238 $sth->execute( "TEST_SERIAL", "[% serial.id %]", "[% serial.id %]" );
239 $prepared_letter = GetPreparedLetter(
240     (
241         module      => 'test',
242         letter_code => 'TEST_SERIAL',
243         tables      => {
244             serial => $serial->id()
245         },
246     )
247 );
248 is( $prepared_letter->{content}, $serial->id(), 'Serial object used correctly' );
249
250 $sth->execute( "TEST_SUBSCRIPTION", "[% subscription.id %]", "[% subscription.id %]" );
251 $prepared_letter = GetPreparedLetter(
252     (
253         module      => 'test',
254         letter_code => 'TEST_SUBSCRIPTION',
255         tables      => {
256             subscription => $subscription->id()
257         },
258     )
259 );
260 is( $prepared_letter->{content}, $subscription->id(), 'Subscription object used correctly' );
261
262 $sth->execute( "TEST_SUGGESTION", "[% suggestion.id %]", "[% suggestion.id %]" );
263 $prepared_letter = GetPreparedLetter(
264     (
265         module      => 'test',
266         letter_code => 'TEST_SUGGESTION',
267         tables      => {
268             suggestions => $suggestion->id()
269         },
270     )
271 );
272 is( $prepared_letter->{content}, $suggestion->id(), 'Suggestion object used correctly' );
273
274 $sth->execute( "TEST_ISSUE", "[% checkout.id %]", "[% checkout.id %]" );
275 $prepared_letter = GetPreparedLetter(
276     (
277         module      => 'test',
278         letter_code => 'TEST_ISSUE',
279         tables      => {
280             issues => $item->id()
281         },
282     )
283 );
284 is( $prepared_letter->{content}, $checkout->id(), 'Checkout object used correctly' );
285
286 $sth->execute( "TEST_MODIFICATION", "[% patron_modification.id %]", "[% patron_modification.id %]" );
287 $prepared_letter = GetPreparedLetter(
288     (
289         module      => 'test',
290         letter_code => 'TEST_MODIFICATION',
291         tables      => {
292             borrower_modifications => $modification->verification_token,
293         },
294     )
295 );
296 is( $prepared_letter->{content}, $modification->id(), 'Patron modification object used correctly' );
297
298 subtest 'regression tests' => sub {
299     plan tests => 8;
300
301     my $library = $builder->build( { source => 'Branch' } );
302     my $patron  = $builder->build( { source => 'Borrower' } );
303     my $item1 = $builder->build_sample_item(
304         {
305             barcode        => 'a_t_barcode',
306             library        => $library->{branchcode},
307             itype          => 'BK',
308             itemcallnumber => 'itemcallnumber1',
309         }
310     );
311     my $biblio1 = $item1->biblio->unblessed;
312     $item1 = $item1->unblessed;
313     my $item2   = $builder->build_sample_item(
314         {
315             barcode        => 'another_t_barcode',
316             library        => $library->{branchcode},
317             itype          => 'BK',
318             itemcallnumber => 'itemcallnumber2',
319         }
320     );
321     my $biblio2 = $item2->biblio->unblessed;
322     $item2 = $item2->unblessed;
323     my $item3   = $builder->build_sample_item(
324         {
325             barcode        => 'another_t_barcode_3',
326             library        => $library->{branchcode},
327             itype          => 'BK',
328             itemcallnumber => 'itemcallnumber3',
329         }
330     );
331     my $biblio3 = $item3->biblio->unblessed;
332     $item3 = $item3->unblessed;
333
334     t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
335
336     subtest 'ACQ_NOTIF_ON_RECEIV ' => sub {
337         plan tests => 1;
338         my $code = 'ACQ_NOTIF_ON_RECEIV';
339         my $branchcode = $library->{branchcode};
340         my $order = $builder->build({ source => 'Aqorder' });
341
342         my $template = q|
343 Dear <<borrowers.firstname>> <<borrowers.surname>>,
344 The order <<aqorders.ordernumber>> (<<biblio.title>>) has been received.
345 Your library.
346         |;
347         my $params = { code => $code, branchcode => $branchcode, tables => { branches => $library, borrowers => $patron, biblio => $biblio1, aqorders => $order } };
348         my $letter = process_letter( { template => $template, %$params });
349         my $tt_template = q|
350 Dear [% borrower.firstname %] [% borrower.surname %],
351 The order [% order.ordernumber %] ([% biblio.title %]) has been received.
352 Your library.
353         |;
354         my $tt_letter = process_letter( { template => $tt_template, %$params });
355
356         is( $tt_letter->{content}, $letter->{content}, 'Verified letter content' );
357     };
358
359     subtest 'AR_*' => sub {
360         plan tests => 2;
361         my $code = 'AR_CANCELED';
362         my $branchcode = $library->{branchcode};
363
364         my $template = q|
365 <<borrowers.firstname>> <<borrowers.surname>> (<<borrowers.cardnumber>>)
366
367 Your request for an article from <<biblio.title>> (<<items.barcode>>) has been canceled for the following reason:
368
369 <<article_requests.notes>>
370
371 Article requested:
372 Title: <<article_requests.title>>
373 Author: <<article_requests.author>>
374 Volume: <<article_requests.volume>>
375 Issue: <<article_requests.issue>>
376 Date: <<article_requests.date>>
377 Pages: <<article_requests.pages>>
378 Chapters: <<article_requests.chapters>>
379 Notes: <<article_requests.patron_notes>>
380         |;
381         reset_template( { template => $template, code => $code, module => 'circulation' } );
382         my $article_request = $builder->build({ source => 'ArticleRequest' });
383         Koha::ArticleRequests->find( $article_request->{id} )->cancel;
384         my $letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
385
386         my $tt_template = q|
387 [% borrower.firstname %] [% borrower.surname %] ([% borrower.cardnumber %])
388
389 Your request for an article from [% biblio.title %] ([% item.barcode %]) has been canceled for the following reason:
390
391 [% article_request.notes %]
392
393 Article requested:
394 Title: [% article_request.title %]
395 Author: [% article_request.author %]
396 Volume: [% article_request.volume %]
397 Issue: [% article_request.issue %]
398 Date: [% article_request.date %]
399 Pages: [% article_request.pages %]
400 Chapters: [% article_request.chapters %]
401 Notes: [% article_request.patron_notes %]
402         |;
403         reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
404         Koha::ArticleRequests->find( $article_request->{id} )->cancel;
405         my $tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
406         is( $tt_letter->content, $letter->content, 'Compare AR_* notices' );
407         isnt( $tt_letter->message_id, $letter->message_id, 'Comparing AR_* notices should compare 2 different messages' );
408     };
409
410     subtest 'CHECKOUT+CHECKIN' => sub {
411         plan tests => 4;
412
413         my $checkout_code = 'CHECKOUT';
414         my $checkin_code = 'CHECKIN';
415
416         my $dbh = C4::Context->dbh;
417         # Enable notification for CHECKOUT - Things are hardcoded here but should work with default data
418         $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 6 );
419         my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
420         $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
421         # Enable notification for CHECKIN - Things are hardcoded here but should work with default data
422         $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 5 );
423         $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
424         $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
425
426         # historic syntax
427         my $checkout_template = q|
428 The following items have been checked out:
429 ----
430 <<biblio.title>>
431 ----
432 Thank you for visiting <<branches.branchname>>.
433 |;
434         reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
435         my $checkin_template = q[
436 The following items have been checked out:
437 ----
438 <<biblio.title>> was due on <<old_issues.date_due | dateonly>>
439 ----
440 Thank you for visiting <<branches.branchname>>.
441 ];
442         reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
443
444         C4::Circulation::AddIssue( $patron, $item1->{barcode} );
445         my $first_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
446         C4::Circulation::AddIssue( $patron, $item2->{barcode} );
447         my $second_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
448
449         AddReturn( $item1->{barcode} );
450         my $first_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
451         AddReturn( $item2->{barcode} );
452         my $second_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
453
454         Koha::Notice::Messages->delete;
455
456         # TT syntax
457         $checkout_template = q|
458 The following items have been checked out:
459 ----
460 [% biblio.title %]
461 ----
462 Thank you for visiting [% branch.branchname %].
463 |;
464         reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
465         $checkin_template = q[
466 The following items have been checked out:
467 ----
468 [% biblio.title %] was due on [% old_checkout.date_due | $KohaDates %]
469 ----
470 Thank you for visiting [% branch.branchname %].
471 ];
472         reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
473
474         C4::Circulation::AddIssue( $patron, $item1->{barcode} );
475         my $first_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
476         C4::Circulation::AddIssue( $patron, $item2->{barcode} );
477         my $second_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
478
479         AddReturn( $item1->{barcode} );
480         my $first_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
481         AddReturn( $item2->{barcode} );
482         my $second_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
483
484         is( $first_checkout_tt_letter->content, $first_checkout_letter->content, 'Verify first checkout letter' );
485         is( $second_checkout_tt_letter->content, $second_checkout_letter->content, 'Verify second checkout letter' );
486         is( $first_checkin_tt_letter->content, $first_checkin_letter->content, 'Verify first checkin letter'  );
487         is( $second_checkin_tt_letter->content, $second_checkin_letter->content, 'Verify second checkin letter' );
488
489     };
490
491     subtest 'DUEDGST|count' => sub {
492         plan tests => 1;
493
494         my $code = 'DUEDGST';
495
496         my $dbh = C4::Context->dbh;
497         # Enable notification for DUEDGST - Things are hardcoded here but should work with default data
498         $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 1 );
499         my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
500         $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
501
502         my $params = {
503             code => $code,
504             substitute => { count => 42 },
505         };
506
507         my $template = q|
508 You have <<count>> items due
509         |;
510         my $letter = process_letter( { template => $template, %$params });
511
512         my $tt_template = q|
513 You have [% count %] items due
514         |;
515         my $tt_letter = process_letter( { template => $tt_template, %$params });
516         is( $tt_letter->{content}, $letter->{content}, );
517     };
518
519     subtest 'HOLD_SLIP|dates|today' => sub {
520         plan tests => 2;
521
522         my $code = 'HOLD_SLIP';
523
524         my $reserve_id1 = C4::Reserves::AddReserve(
525             {
526                 branchcode     => $library->{branchcode},
527                 borrowernumber => $patron->{borrowernumber},
528                 biblionumber   => $biblio1->{biblionumber},
529                 notes          => "a note",
530                 itemnumber     => $item1->{itemnumber},
531             }
532         );
533         my $reserve_id2 = C4::Reserves::AddReserve(
534             {
535                 branchcode     => $library->{branchcode},
536                 borrowernumber => $patron->{borrowernumber},
537                 biblionumber   => $biblio1->{biblionumber},
538                 notes          => "a note",
539                 itemnumber     => $item1->{itemnumber},
540             }
541         );
542         my $reserve_id3 = C4::Reserves::AddReserve(
543             {
544                 branchcode     => $library->{branchcode},
545                 borrowernumber => $patron->{borrowernumber},
546                 biblionumber   => $biblio2->{biblionumber},
547                 notes          => "another note",
548                 itemnumber     => $item2->{itemnumber},
549             }
550         );
551
552         my $template = <<EOF;
553 <h5>Date: <<today>></h5>
554
555 <h3> Transfer to/Hold in <<branches.branchname>></h3>
556
557 <h3><<borrowers.surname>>, <<borrowers.firstname>></h3>
558
559 <ul>
560     <li><<borrowers.cardnumber>></li>
561     <li><<borrowers.phone>></li>
562     <li> <<borrowers.address>><br />
563          <<borrowers.address2>><br />
564          <<borrowers.city>>  <<borrowers.zipcode>>
565     </li>
566     <li><<borrowers.email>></li>
567 </ul>
568 <br />
569 <h3>ITEM ON HOLD</h3>
570 <h4><<biblio.title>></h4>
571 <h5><<biblio.author>></h5>
572 <ul>
573    <li><<items.barcode>></li>
574    <li><<items.itemcallnumber>></li>
575    <li><<reserves.waitingdate>></li>
576 </ul>
577 <p>Notes:
578 <pre><<reserves.reserve_id>>=<<reserves.reservenotes>></pre>
579 </p>
580 EOF
581
582         reset_template( { template => $template, code => $code, module => 'circulation' } );
583         my $letter_for_item1 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id1 } );
584         my $letter_for_item2 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id3 } );
585
586         my $tt_template = <<EOF;
587 <h5>Date: [% today | \$KohaDates with_hours => 1 %]</h5>
588
589 <h3> Transfer to/Hold in [% branch.branchname %]</h3>
590
591 <h3>[% borrower.surname %], [% borrower.firstname %]</h3>
592
593 <ul>
594     <li>[% borrower.cardnumber %]</li>
595     <li>[% borrower.phone %]</li>
596     <li> [% borrower.address %]<br />
597          [% borrower.address2 %]<br />
598          [% borrower.city %]  [% borrower.zipcode %]
599     </li>
600     <li>[% borrower.email %]</li>
601 </ul>
602 <br />
603 <h3>ITEM ON HOLD</h3>
604 <h4>[% biblio.title %]</h4>
605 <h5>[% biblio.author %]</h5>
606 <ul>
607    <li>[% item.barcode %]</li>
608    <li>[% item.itemcallnumber %]</li>
609    <li>[% hold.waitingdate | \$KohaDates %]</li>
610 </ul>
611 <p>Notes:
612 <pre>[% hold.reserve_id %]=[% hold.reservenotes %]</pre>
613 </p>
614 EOF
615
616         reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
617         my $tt_letter_for_item1 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id1 } );
618         my $tt_letter_for_item2 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id3 } );
619
620         is( $tt_letter_for_item1->{content}, $letter_for_item1->{content}, );
621         is( $tt_letter_for_item2->{content}, $letter_for_item2->{content}, );
622     };
623
624     subtest 'ISSUESLIP|checkedout|repeat|news' => sub {
625         plan tests => 3;
626
627         my $code = 'ISSUESLIP';
628         my $now = dt_from_string;
629         my $one_minute_ago = dt_from_string->subtract( minutes => 1 );
630
631         my $branchcode = $library->{branchcode};
632
633         Koha::AdditionalContents->delete;
634         my $news_item = $builder->build_object(
635             {
636                 class => 'Koha::AdditionalContents',
637                 value => {
638                     category        => 'news',
639                     location        => "slip",
640                     branchcode      => $branchcode,
641                     lang            => 'default',
642                     title           => "A wonderful news",
643                     content         => "This is the wonderful news.",
644                     expiration_date => undef,
645                     published_on    => $one_minute_ago
646                 }
647             }
648         );
649
650         # historic syntax
651         my $template = <<EOF;
652 <h3><<branches.branchname>></h3>
653 Checked out to <<borrowers.title>> <<borrowers.firstname>> <<borrowers.initials>> <<borrowers.surname>> <br />
654 (<<borrowers.cardnumber>>) <br />
655
656 <<today>><br />
657
658 <h4>Checked out</h4>
659 <checkedout>
660 <p>
661 <<biblio.title>> <br />
662 Barcode: <<items.barcode>><br />
663 Date due: <<issues.date_due | dateonly>><br />
664 </p>
665 </checkedout>
666
667 <h4>Overdues</h4>
668 <overdue>
669 <p>
670 <<biblio.title>> <br />
671 Barcode: <<items.barcode>><br />
672 Date due: <<issues.date_due | dateonly>><br />
673 </p>
674 </overdue>
675
676 <hr>
677
678 <h4 style="text-align: center; font-style:italic;">News</h4>
679 <news>
680 <div class="newsitem">
681 <h5 style="margin-bottom: 1px; margin-top: 1px"><b><<additional_contents.title>></b></h5>
682 <p style="margin-bottom: 1px; margin-top: 1px"><<additional_contents.content>></p>
683 <p class="newsfooter" style="font-size: 8pt; font-style:italic; margin-bottom: 1px; margin-top: 1px">Posted on <<additional_contents.published_on>></p>
684 <hr />
685 </div>
686 </news>
687 EOF
688
689         reset_template( { template => $template, code => $code, module => 'circulation' } );
690
691         my $checkout = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
692         $checkout->set( { timestamp => $now, issuedate => $one_minute_ago } )->store;
693         my $first_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
694
695         $checkout = C4::Circulation::AddIssue( $patron, $item2->{barcode} ); # Add a second checkout
696         $checkout->set( { timestamp => $now, issuedate => $now } )->store;
697         my $yesterday = dt_from_string->subtract( days => 1 );
698         C4::Circulation::AddIssue( $patron, $item3->{barcode}, $yesterday ); # Add an overdue
699         my $second_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
700
701         # Cleanup
702         AddReturn( $item1->{barcode} );
703         AddReturn( $item2->{barcode} );
704         AddReturn( $item3->{barcode} );
705
706         # TT syntax
707         my $tt_template = <<EOF;
708 <h3>[% branch.branchname %]</h3>
709 Checked out to [% borrower.title %] [% borrower.firstname %] [% borrower.initials %] [% borrower.surname %] <br />
710 ([% borrower.cardnumber %]) <br />
711
712 [% today | \$KohaDates with_hours => 1 %]<br />
713
714 <h4>Checked out</h4>
715 [% FOREACH checkout IN checkouts %]
716 [%~ SET item = checkout.item %]
717 [%~ SET biblio = checkout.item.biblio %]
718 <p>
719 [% biblio.title %] <br />
720 Barcode: [% item.barcode %]<br />
721 Date due: [% checkout.date_due | \$KohaDates %]<br />
722 </p>
723 [% END %]
724
725 <h4>Overdues</h4>
726 [% FOREACH overdue IN overdues %]
727 [%~ SET item = overdue.item %]
728 [%~ SET biblio = overdue.item.biblio %]
729 <p>
730 [% biblio.title %] <br />
731 Barcode: [% item.barcode %]<br />
732 Date due: [% overdue.date_due | \$KohaDates %]<br />
733 </p>
734 [% END %]
735
736 <hr>
737
738 <h4 style="text-align: center; font-style:italic;">News</h4>
739 [% FOREACH n IN additional_contents %]
740 <div class="newsitem">
741 <h5 style="margin-bottom: 1px; margin-top: 1px"><b>[% n.title %]</b></h5>
742 <p style="margin-bottom: 1px; margin-top: 1px">[% n.content %]</p>
743 <p class="newsfooter" style="font-size: 8pt; font-style:italic; margin-bottom: 1px; margin-top: 1px">Posted on [% n.published_on | \$KohaDates %]</p>
744 <hr />
745 </div>
746 [% END %]
747 EOF
748
749         reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
750
751         $checkout = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
752         $checkout->set( { timestamp => $now, issuedate => $one_minute_ago } )->store;
753         my $first_tt_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
754
755         $checkout = C4::Circulation::AddIssue( $patron, $item2->{barcode} ); # Add a second checkout
756         $checkout->set( { timestamp => $now, issuedate => $now } )->store;
757         C4::Circulation::AddIssue( $patron, $item3->{barcode}, $yesterday ); # Add an overdue
758         my $second_tt_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
759
760         # There is too many line breaks generated by the historic syntax
761         $second_slip->{content} =~ s|</p>\n\n\n<p>|</p>\n\n<p>|s;
762
763         my $news_item_title = $news_item->title;
764         like( $first_slip->{content}, qr{$news_item_title} );
765         is( $first_tt_slip->{content}, $first_slip->{content}, );
766         is( $second_tt_slip->{content}, $second_slip->{content}, );
767
768         # Cleanup
769         AddReturn( $item1->{barcode} );
770         AddReturn( $item2->{barcode} );
771         AddReturn( $item3->{barcode} );
772     };
773
774     subtest 'ODUE|items.content|item' => sub {
775         plan tests => 1;
776
777         my $code = 'ODUE';
778
779         my $branchcode = $library->{branchcode};
780
781         # historic syntax
782         # FIXME items.fine does not work with TT notices
783         # See bug 17976
784         # <item> should contain Fine: <<items.fine>></item>
785         my $template = <<EOF;
786 Dear <<borrowers.firstname>> <<borrowers.surname>>,
787
788 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.
789
790 <<branches.branchname>>
791 <<branches.branchaddress1>>
792 <<branches.branchaddress2>> <<branches.branchaddress3>>
793 Phone: <<branches.branchphone>>
794 Fax: <<branches.branchfax>>
795 Email: <<branches.branchemail>>
796
797 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.
798
799 The following item(s) is/are currently overdue:
800
801 <item>"<<biblio.title>>" by <<biblio.author>>, <<items.itemcallnumber>>, Barcode: <<items.barcode>></item>
802
803 <<items.content>>
804
805 Thank-you for your prompt attention to this matter.
806
807 <<branches.branchname>> Staff
808 EOF
809
810         reset_template( { template => $template, code => $code, module => 'circulation' } );
811
812         my $yesterday = dt_from_string->subtract( days => 1 );
813         my $two_days_ago = dt_from_string->subtract( days => 2 );
814         my $issue1 = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
815         my $issue2 = C4::Circulation::AddIssue( $patron, $item2->{barcode}, $yesterday ); # Add an first overdue
816         my $issue3 = C4::Circulation::AddIssue( $patron, $item3->{barcode}, $two_days_ago ); # Add an second overdue
817         $issue1 = $issue1->unblessed;
818         $issue2 = $issue2->unblessed;
819         $issue3 = $issue3->unblessed;
820
821         # For items.content
822         my @item_fields = qw( date_due title barcode author itemnumber );
823         my $items_content = C4::Letters::get_item_content( { item => { %$item1, %$biblio1, %$issue1 }, item_content_fields => \@item_fields, dateonly => 1 } );
824           $items_content .= C4::Letters::get_item_content( { item => { %$item2, %$biblio2, %$issue2 }, item_content_fields => \@item_fields, dateonly => 1 } );
825           $items_content .= C4::Letters::get_item_content( { item => { %$item3, %$biblio3, %$issue3 }, item_content_fields => \@item_fields, dateonly => 1 } );
826
827         my @items = ( $item1, $item2, $item3 );
828         my $letter = C4::Overdues::parse_overdues_letter(
829             {
830                 letter_code => $code,
831                 borrowernumber => $patron->{borrowernumber},
832                 branchcode  => $library->{branchcode},
833                 items       => \@items,
834                 substitute  => {
835                     bib                    => $library->{branchname},
836                     'items.content'        => $items_content,
837                     count                  => scalar( @items ),
838                     message_transport_type => 'email',
839                 }
840             }
841         );
842
843         # Cleanup
844         AddReturn( $item1->{barcode} );
845         AddReturn( $item2->{barcode} );
846         AddReturn( $item3->{barcode} );
847
848
849         # historic syntax
850         my $tt_template = <<EOF;
851 Dear [% borrower.firstname %] [% borrower.surname %],
852
853 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.
854
855 [% branch.branchname %]
856 [% branch.branchaddress1 %]
857 [% branch.branchaddress2 %] [% branch.branchaddress3 %]
858 Phone: [% branch.branchphone %]
859 Fax: [% branch.branchfax %]
860 Email: [% branch.branchemail %]
861
862 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.
863
864 The following item(s) is/are currently overdue:
865
866 [% FOREACH overdue IN overdues %]
867 [%~ SET item = overdue.item ~%]
868 "[% item.biblio.title %]" by [% item.biblio.author %], [% item.itemcallnumber %], Barcode: [% item.barcode %]
869 [% END %]
870 [% FOREACH overdue IN overdues %]
871 [%~ SET item = overdue.item ~%]
872 [% overdue.date_due | \$KohaDates %]\t[% item.biblio.title %]\t[% item.barcode %]\t[% item.biblio.author %]\t[% item.itemnumber %]
873 [% END %]
874
875 Thank-you for your prompt attention to this matter.
876
877 [% branch.branchname %] Staff
878 EOF
879
880         reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
881
882         C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
883         C4::Circulation::AddIssue( $patron, $item2->{barcode}, $yesterday ); # Add an first overdue
884         C4::Circulation::AddIssue( $patron, $item3->{barcode}, $two_days_ago ); # Add an second overdue
885
886         my $tt_letter = C4::Overdues::parse_overdues_letter(
887             {
888                 letter_code => $code,
889                 borrowernumber => $patron->{borrowernumber},
890                 branchcode  => $library->{branchcode},
891                 items       => \@items,
892                 substitute  => {
893                     bib                    => $library->{branchname},
894                     'items.content'        => $items_content,
895                     count                  => scalar( @items ),
896                     message_transport_type => 'email',
897                 }
898             }
899         );
900
901         is( $tt_letter->{content}, $letter->{content}, );
902     };
903
904     subtest 'Bug 19743 - Header and Footer should be updated on each item for checkin / checkout / renewal notices' => sub {
905         plan tests => 8;
906
907         my $checkout_code = 'CHECKOUT';
908         my $checkin_code = 'CHECKIN';
909
910         my $dbh = C4::Context->dbh;
911         $dbh->do("DELETE FROM letter");
912         $dbh->do("DELETE FROM issues");
913         $dbh->do("DELETE FROM message_queue");
914
915         # Enable notification for CHECKOUT - Things are hardcoded here but should work with default data
916         $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 6 );
917         my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
918         $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
919         # Enable notification for CHECKIN - Things are hardcoded here but should work with default data
920         $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 5 );
921         $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
922         $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
923
924         my $checkout_template = q|
925 <<branches.branchname>>
926 ----
927 ----
928 |;
929         reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
930         my $checkin_template = q[
931 <<branches.branchname>>
932 ----
933 ----
934 ];
935         reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
936
937         my $issue = C4::Circulation::AddIssue( $patron, $item1->{barcode} );
938         my $first_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
939
940         my $library_object = Koha::Libraries->find( $issue->branchcode );
941         my $old_branchname = $library_object->branchname;
942         my $new_branchname = "Kyle M Hall Memorial Library";
943
944         # Change branch name for second checkout notice
945         $library_object->branchname($new_branchname);
946         $library_object->store();
947
948         C4::Circulation::AddIssue( $patron, $item2->{barcode} );
949         my $second_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
950
951         # Restore old name for first checkin notice
952         $library_object->branchname( $old_branchname );
953         $library_object->store();
954
955         AddReturn( $item1->{barcode} );
956         my $first_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
957
958         # Change branch name for second checkin notice
959         $library_object->branchname($new_branchname);
960         $library_object->store();
961
962         AddReturn( $item2->{barcode} );
963         my $second_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
964
965         # Restore old name for first TT checkout notice
966         $library_object->branchname( $old_branchname );
967         $library_object->store();
968
969         Koha::Notice::Messages->delete;
970
971         # TT syntax
972         $checkout_template = q|
973 [% branch.branchname %]
974 ----
975 ----
976 |;
977         reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
978         $checkin_template = q[
979 [% branch.branchname %]
980 ----
981 ----
982 ];
983         reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
984
985         C4::Circulation::AddIssue( $patron, $item1->{barcode} );
986         my $first_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
987
988         # Change branch name for second checkout notice
989         $library_object->branchname($new_branchname);
990         $library_object->store();
991
992         C4::Circulation::AddIssue( $patron, $item2->{barcode} );
993         my $second_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
994
995         # Restore old name for first checkin notice
996         $library_object->branchname( $old_branchname );
997         $library_object->store();
998
999         AddReturn( $item1->{barcode} );
1000         my $first_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
1001 #
1002         # Change branch name for second checkin notice
1003         $library_object->branchname($new_branchname);
1004         $library_object->store();
1005
1006         AddReturn( $item2->{barcode} );
1007         my $second_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
1008
1009         my $first_letter = qq[
1010 $old_branchname
1011 ];
1012         my $second_letter = qq[
1013 $new_branchname
1014 ];
1015
1016
1017         is( $first_checkout_letter->content, $first_letter, 'Verify first checkout letter' );
1018         is( $second_checkout_letter->content, $second_letter, 'Verify second checkout letter' );
1019         is( $first_checkin_letter->content, $first_letter, 'Verify first checkin letter'  );
1020         is( $second_checkin_letter->content, $second_letter, 'Verify second checkin letter' );
1021
1022         is( $first_checkout_tt_letter->content, $first_letter, 'Verify TT first checkout letter' );
1023         is( $second_checkout_tt_letter->content, $second_letter, 'Verify TT second checkout letter' );
1024         is( $first_checkin_tt_letter->content, $first_letter, 'Verify TT first checkin letter'  );
1025         is( $second_checkin_tt_letter->content, $second_letter, 'Verify TT second checkin letter' );
1026     };
1027
1028 };
1029
1030 subtest 'loops' => sub {
1031     plan tests => 2;
1032     my $code = "TEST";
1033     my $module = "TEST";
1034
1035     subtest 'primary key is AI' => sub {
1036         plan tests => 1;
1037         my $patron_1 = $builder->build({ source => 'Borrower' });
1038         my $patron_2 = $builder->build({ source => 'Borrower' });
1039
1040         my $template = q|[% FOREACH patron IN borrowers %][% patron.surname %][% END %]|;
1041         reset_template( { template => $template, code => $code, module => $module } );
1042         my $letter = GetPreparedLetter( module => $module, letter_code => $code, loops => { borrowers => [ $patron_1->{borrowernumber}, $patron_2->{borrowernumber} ] } );
1043         my $expected_letter = join '', ( $patron_1->{surname}, $patron_2->{surname} );
1044         is( $letter->{content}, $expected_letter, );
1045     };
1046
1047     subtest 'foreign key is used' => sub {
1048         plan tests => 1;
1049         my $patron_1 = $builder->build({ source => 'Borrower' });
1050         my $patron_2 = $builder->build({ source => 'Borrower' });
1051         my $checkout_1 = $builder->build({ source => 'Issue', value => { borrowernumber => $patron_1->{borrowernumber} } } );
1052         my $checkout_2 = $builder->build({ source => 'Issue', value => { borrowernumber => $patron_1->{borrowernumber} } } );
1053
1054         my $template = q|[% FOREACH checkout IN checkouts %][% checkout.issue_id %][% END %]|;
1055         reset_template( { template => $template, code => $code, module => $module } );
1056         my $letter = GetPreparedLetter( module => $module, letter_code => $code, loops => { issues => [ $checkout_1->{itemnumber}, $checkout_2->{itemnumber} ] } );
1057         my $expected_letter = join '', ( $checkout_1->{issue_id}, $checkout_2->{issue_id} );
1058         is( $letter->{content}, $expected_letter, );
1059     };
1060 };
1061
1062 subtest 'add_tt_filters' => sub {
1063     plan tests => 1;
1064     my $code   = "TEST";
1065     my $module = "TEST";
1066
1067     my $patron = $builder->build_object(
1068         {
1069             class => 'Koha::Patrons',
1070             value => { surname => "with_punctuation_" }
1071         }
1072     );
1073     my $biblio = $builder->build_object(
1074         { class => 'Koha::Biblios', value => { title => "with_punctuation_" } }
1075     );
1076     my $biblioitem = $builder->build_object(
1077         {
1078             class => 'Koha::Biblioitems',
1079             value => {
1080                 biblionumber => $biblio->biblionumber,
1081                 isbn         => "with_punctuation_"
1082             }
1083         }
1084     );
1085
1086     my $template = q|patron=[% borrower.surname %];biblio=[% biblio.title %];biblioitems=[% biblioitem.isbn %]|;
1087     reset_template( { template => $template, code => $code, module => $module } );
1088     my $letter = GetPreparedLetter(
1089         module      => $module,
1090         letter_code => $code,
1091         tables      => {
1092             borrowers   => $patron->borrowernumber,
1093             biblio      => $biblio->biblionumber,
1094             biblioitems => $biblioitem->biblioitemnumber
1095         }
1096     );
1097     my $expected_letter = q|patron=with_punctuation_;biblio=with_punctuation;biblioitems=with_punctuation|;
1098     is( $letter->{content}, $expected_letter, "Pre-processing should call TT plugin to remove punctuation if table is biblio or biblioitems");
1099 };
1100
1101 subtest 'Handle includes' => sub {
1102     plan tests => 1;
1103     my $cgi = CGI->new();
1104     my $code = 'TEST_INCLUDE';
1105     my $account =
1106       $builder->build_object( { class => 'Koha::Account::Lines', value => { credit_type_code => 'PAYMENT', status => 'CANCELLED' } } );
1107     my $template = <<EOF;
1108 [%- USE Price -%]
1109 [%- PROCESS 'accounts.inc' -%]
1110 [%- PROCESS account_type_description account=credit -%]
1111 EOF
1112     reset_template({ template => $template, code => $code, module => 'test' });
1113     my $letter = GetPreparedLetter(
1114         module      => 'test',
1115         letter_code => $code,
1116         tables      => {
1117             credits => $account->accountlines_id
1118         }
1119     );
1120     is($letter->{content},'    <span>Payment<span> (Cancelled)</span>    </span>', "Include used in notice");
1121 };
1122
1123 subtest 'Dates formatting' => sub {
1124     plan tests => 1;
1125     my $code = 'TEST_DATE';
1126     t::lib::Mocks::mock_preference('dateformat', 'metric'); # MM/DD/YYYY
1127     my $biblio = $builder->build_object(
1128         {
1129             class => 'Koha::Biblios',
1130             value => {
1131                 timestamp   => '2018-12-13 20:21:22',
1132                 datecreated => '2018-12-13'
1133             }
1134         }
1135     );
1136     my $template = <<EOF;
1137 [%- USE KohaDates -%]
1138 [% biblio.timestamp %]
1139 [% biblio.timestamp | \$KohaDates %]
1140 [% biblio.timestamp | \$KohaDates with_hours => 1 %]
1141
1142 [% biblio.datecreated %]
1143 [% biblio.datecreated | \$KohaDates %]
1144 [% biblio.datecreated | \$KohaDates with_hours => 1 %]
1145
1146 [% biblio.timestamp | \$KohaDates dateformat => 'iso' %]
1147 [% KohaDates.output_preference( str => biblio.timestamp, dateformat => 'iso' ) %]
1148 [% KohaDates.output_preference( str => biblio.timestamp, dateformat => 'iso', dateonly => 1 ) %]
1149 EOF
1150     reset_template({ template => $template, code => $code, module => 'test' });
1151     my $letter = GetPreparedLetter(
1152         module => 'test',
1153         letter_code => $code,
1154         tables => {
1155             biblio => $biblio->biblionumber,
1156         }
1157     );
1158     my $expected_content = sprintf("%s\n%s\n%s\n\n%s\n%s\n%s\n\n%s\n%s\n%s\n",
1159         '2018-12-13 20:21:22',
1160         '13/12/2018',
1161         '13/12/2018 20:21',
1162
1163         '2018-12-13',
1164         '13/12/2018',
1165         '13/12/2018 00:00',
1166
1167         '2018-12-13',
1168         '2018-12-13 20:21',
1169         '2018-12-13',
1170     );
1171     is( $letter->{content}, $expected_content );
1172 };
1173
1174 sub reset_template {
1175     my ( $params ) = @_;
1176     my $template   = $params->{template};
1177     my $code       = $params->{code};
1178     my $module     = $params->{module} || 'test_module';
1179
1180     Koha::Notice::Templates->search( { code => $code } )->delete;
1181     Koha::Notice::Template->new(
1182         {
1183             module                 => $module,
1184             code                   => $code,
1185             branchcode             => '',
1186             name                   => $code,
1187             title                  => $code,
1188             message_transport_type => 'email',
1189             content                => $template
1190         }
1191     )->store;
1192 }
1193
1194 sub process_letter {
1195     my ($params)   = @_;
1196     my $template   = $params->{template};
1197     my $tables     = $params->{tables};
1198     my $substitute = $params->{substitute};
1199     my $code       = $params->{code};
1200     my $module     = $params->{module} || 'test_module';
1201     my $branchcode = $params->{branchcode};
1202
1203     reset_template( $params );
1204
1205     my $letter = C4::Letters::GetPreparedLetter(
1206         module      => $module,
1207         letter_code => $code,
1208         branchcode  => '',
1209         tables      => $tables,
1210         substitute  => $substitute,
1211     );
1212     return $letter;
1213 }
1214
1215 $schema->storage->txn_rollback;