720b9d104db5ca780171fc44d737608c1a390cb1
[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 $biblio1 = Koha::Biblio->new({title => 'Test Biblio 1', author => 'An author', })->store->unblessed;
296     my $biblioitem1 = Koha::Biblioitem->new({biblionumber => $biblio1->{biblionumber}})->store()->unblessed;
297     my $item1 = Koha::Item->new(
298         {
299             biblionumber     => $biblio1->{biblionumber},
300             biblioitemnumber => $biblioitem1->{biblioitemnumber},
301             barcode          => 'a_t_barcode',
302             homebranch       => $library->{branchcode},
303             holdingbranch    => $library->{branchcode},
304             itype            => 'BK',
305             itemcallnumber   => 'itemcallnumber1',
306         }
307     )->store->unblessed;
308     my $biblio2 = Koha::Biblio->new({title => 'Test Biblio 2'})->store->unblessed;
309     my $biblioitem2 = Koha::Biblioitem->new({biblionumber => $biblio2->{biblionumber}})->store()->unblessed;
310     my $item2 = Koha::Item->new(
311         {
312             biblionumber     => $biblio2->{biblionumber},
313             biblioitemnumber => $biblioitem2->{biblioitemnumber},
314             barcode          => 'another_t_barcode',
315             homebranch       => $library->{branchcode},
316             holdingbranch    => $library->{branchcode},
317             itype            => 'BK',
318             itemcallnumber   => 'itemcallnumber2',
319         }
320     )->store->unblessed;
321     my $biblio3 = Koha::Biblio->new({title => 'Test Biblio 3'})->store->unblessed;
322     my $biblioitem3 = Koha::Biblioitem->new({biblionumber => $biblio3->{biblionumber}})->store()->unblessed;
323     my $item3 = Koha::Item->new(
324         {
325             biblionumber     => $biblio3->{biblionumber},
326             biblioitemnumber => $biblioitem3->{biblioitemnumber},
327             barcode          => 'another_t_barcode_3',
328             homebranch       => $library->{branchcode},
329             holdingbranch    => $library->{branchcode},
330             itype            => 'BK',
331             itemcallnumber   => 'itemcallnumber3',
332         }
333     )->store->unblessed;
334
335     t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
336
337     subtest 'ACQ_NOTIF_ON_RECEIV ' => sub {
338         plan tests => 1;
339         my $code = 'ACQ_NOTIF_ON_RECEIV';
340         my $branchcode = $library->{branchcode};
341         my $order = $builder->build({ source => 'Aqorder' });
342
343         my $template = q|
344 Dear <<borrowers.firstname>> <<borrowers.surname>>,
345 The order <<aqorders.ordernumber>> (<<biblio.title>>) has been received.
346 Your library.
347         |;
348         my $params = { code => $code, branchcode => $branchcode, tables => { branches => $library, borrowers => $patron, biblio => $biblio1, aqorders => $order } };
349         my $letter = process_letter( { template => $template, %$params });
350         my $tt_template = q|
351 Dear [% borrower.firstname %] [% borrower.surname %],
352 The order [% order.ordernumber %] ([% biblio.title %]) has been received.
353 Your library.
354         |;
355         my $tt_letter = process_letter( { template => $tt_template, %$params });
356
357         is( $tt_letter->{content}, $letter->{content}, 'Verified letter content' );
358     };
359
360     subtest 'AR_*' => sub {
361         plan tests => 2;
362         my $code = 'AR_CANCELED';
363         my $branchcode = $library->{branchcode};
364
365         my $template = q|
366 <<borrowers.firstname>> <<borrowers.surname>> (<<borrowers.cardnumber>>)
367
368 Your request for an article from <<biblio.title>> (<<items.barcode>>) has been canceled for the following reason:
369
370 <<article_requests.notes>>
371
372 Article requested:
373 Title: <<article_requests.title>>
374 Author: <<article_requests.author>>
375 Volume: <<article_requests.volume>>
376 Issue: <<article_requests.issue>>
377 Date: <<article_requests.date>>
378 Pages: <<article_requests.pages>>
379 Chapters: <<article_requests.chapters>>
380 Notes: <<article_requests.patron_notes>>
381         |;
382         reset_template( { template => $template, code => $code, module => 'circulation' } );
383         my $article_request = $builder->build({ source => 'ArticleRequest' });
384         Koha::ArticleRequests->find( $article_request->{id} )->cancel;
385         my $letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
386
387         my $tt_template = q|
388 [% borrower.firstname %] [% borrower.surname %] ([% borrower.cardnumber %])
389
390 Your request for an article from [% biblio.title %] ([% item.barcode %]) has been canceled for the following reason:
391
392 [% article_request.notes %]
393
394 Article requested:
395 Title: [% article_request.title %]
396 Author: [% article_request.author %]
397 Volume: [% article_request.volume %]
398 Issue: [% article_request.issue %]
399 Date: [% article_request.date %]
400 Pages: [% article_request.pages %]
401 Chapters: [% article_request.chapters %]
402 Notes: [% article_request.patron_notes %]
403         |;
404         reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
405         Koha::ArticleRequests->find( $article_request->{id} )->cancel;
406         my $tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
407         is( $tt_letter->content, $letter->content, 'Compare AR_* notices' );
408         isnt( $tt_letter->message_id, $letter->message_id, 'Comparing AR_* notices should compare 2 different messages' );
409     };
410
411     subtest 'CHECKOUT+CHECKIN' => sub {
412         plan tests => 4;
413
414         my $checkout_code = 'CHECKOUT';
415         my $checkin_code = 'CHECKIN';
416
417         my $dbh = C4::Context->dbh;
418         # Enable notification for CHECKOUT - Things are hardcoded here but should work with default data
419         $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 6 );
420         my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
421         $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
422         # Enable notification for CHECKIN - Things are hardcoded here but should work with default data
423         $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 5 );
424         $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
425         $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
426
427         # historic syntax
428         my $checkout_template = q|
429 The following items have been checked out:
430 ----
431 <<biblio.title>>
432 ----
433 Thank you for visiting <<branches.branchname>>.
434 |;
435         reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
436         my $checkin_template = q[
437 The following items have been checked out:
438 ----
439 <<biblio.title>> was due on <<old_issues.date_due | dateonly>>
440 ----
441 Thank you for visiting <<branches.branchname>>.
442 ];
443         reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
444
445         C4::Circulation::AddIssue( $patron, $item1->{barcode} );
446         my $first_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
447         C4::Circulation::AddIssue( $patron, $item2->{barcode} );
448         my $second_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
449
450         AddReturn( $item1->{barcode} );
451         my $first_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
452         AddReturn( $item2->{barcode} );
453         my $second_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
454
455         Koha::Notice::Messages->delete;
456
457         # TT syntax
458         $checkout_template = q|
459 The following items have been checked out:
460 ----
461 [% biblio.title %]
462 ----
463 Thank you for visiting [% branch.branchname %].
464 |;
465         reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
466         $checkin_template = q[
467 The following items have been checked out:
468 ----
469 [% biblio.title %] was due on [% old_checkout.date_due | $KohaDates %]
470 ----
471 Thank you for visiting [% branch.branchname %].
472 ];
473         reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
474
475         C4::Circulation::AddIssue( $patron, $item1->{barcode} );
476         my $first_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
477         C4::Circulation::AddIssue( $patron, $item2->{barcode} );
478         my $second_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
479
480         AddReturn( $item1->{barcode} );
481         my $first_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
482         AddReturn( $item2->{barcode} );
483         my $second_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
484
485         is( $first_checkout_tt_letter->content, $first_checkout_letter->content, 'Verify first checkout letter' );
486         is( $second_checkout_tt_letter->content, $second_checkout_letter->content, 'Verify second checkout letter' );
487         is( $first_checkin_tt_letter->content, $first_checkin_letter->content, 'Verify first checkin letter'  );
488         is( $second_checkin_tt_letter->content, $second_checkin_letter->content, 'Verify second checkin letter' );
489
490     };
491
492     subtest 'DUEDGST|count' => sub {
493         plan tests => 1;
494
495         my $code = 'DUEDGST';
496
497         my $dbh = C4::Context->dbh;
498         # Enable notification for DUEDGST - Things are hardcoded here but should work with default data
499         $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 1 );
500         my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
501         $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
502
503         my $params = {
504             code => $code,
505             substitute => { count => 42 },
506         };
507
508         my $template = q|
509 You have <<count>> items due
510         |;
511         my $letter = process_letter( { template => $template, %$params });
512
513         my $tt_template = q|
514 You have [% count %] items due
515         |;
516         my $tt_letter = process_letter( { template => $tt_template, %$params });
517         is( $tt_letter->{content}, $letter->{content}, );
518     };
519
520     subtest 'HOLD_SLIP|dates|today' => sub {
521         plan tests => 2;
522
523         my $code = 'HOLD_SLIP';
524
525         my $reserve_id1 = 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_id2 = C4::Reserves::AddReserve(
535             {
536                 branchcode     => $library->{branchcode},
537                 borrowernumber => $patron->{borrowernumber},
538                 biblionumber   => $biblio1->{biblionumber},
539                 notes          => "a note",
540                 itemnumber     => $item1->{itemnumber},
541             }
542         );
543         my $reserve_id3 = C4::Reserves::AddReserve(
544             {
545                 branchcode     => $library->{branchcode},
546                 borrowernumber => $patron->{borrowernumber},
547                 biblionumber   => $biblio2->{biblionumber},
548                 notes          => "another note",
549                 itemnumber     => $item2->{itemnumber},
550             }
551         );
552
553         my $template = <<EOF;
554 <h5>Date: <<today>></h5>
555
556 <h3> Transfer to/Hold in <<branches.branchname>></h3>
557
558 <h3><<borrowers.surname>>, <<borrowers.firstname>></h3>
559
560 <ul>
561     <li><<borrowers.cardnumber>></li>
562     <li><<borrowers.phone>></li>
563     <li> <<borrowers.address>><br />
564          <<borrowers.address2>><br />
565          <<borrowers.city>>  <<borrowers.zipcode>>
566     </li>
567     <li><<borrowers.email>></li>
568 </ul>
569 <br />
570 <h3>ITEM ON HOLD</h3>
571 <h4><<biblio.title>></h4>
572 <h5><<biblio.author>></h5>
573 <ul>
574    <li><<items.barcode>></li>
575    <li><<items.itemcallnumber>></li>
576    <li><<reserves.waitingdate>></li>
577 </ul>
578 <p>Notes:
579 <pre><<reserves.reserve_id>>=<<reserves.reservenotes>></pre>
580 </p>
581 EOF
582
583         reset_template( { template => $template, code => $code, module => 'circulation' } );
584         my $letter_for_item1 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id1 } );
585         my $letter_for_item2 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id3 } );
586
587         my $tt_template = <<EOF;
588 <h5>Date: [% today | \$KohaDates with_hours => 1 %]</h5>
589
590 <h3> Transfer to/Hold in [% branch.branchname %]</h3>
591
592 <h3>[% borrower.surname %], [% borrower.firstname %]</h3>
593
594 <ul>
595     <li>[% borrower.cardnumber %]</li>
596     <li>[% borrower.phone %]</li>
597     <li> [% borrower.address %]<br />
598          [% borrower.address2 %]<br />
599          [% borrower.city %]  [% borrower.zipcode %]
600     </li>
601     <li>[% borrower.email %]</li>
602 </ul>
603 <br />
604 <h3>ITEM ON HOLD</h3>
605 <h4>[% biblio.title %]</h4>
606 <h5>[% biblio.author %]</h5>
607 <ul>
608    <li>[% item.barcode %]</li>
609    <li>[% item.itemcallnumber %]</li>
610    <li>[% hold.waitingdate | \$KohaDates %]</li>
611 </ul>
612 <p>Notes:
613 <pre>[% hold.reserve_id %]=[% hold.reservenotes %]</pre>
614 </p>
615 EOF
616
617         reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
618         my $tt_letter_for_item1 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id1 } );
619         my $tt_letter_for_item2 = C4::Reserves::ReserveSlip( { branchcode => $library->{branchcode}, reserve_id => $reserve_id3 } );
620
621         is( $tt_letter_for_item1->{content}, $letter_for_item1->{content}, );
622         is( $tt_letter_for_item2->{content}, $letter_for_item2->{content}, );
623     };
624
625     subtest 'ISSUESLIP|checkedout|repeat' => sub {
626         plan tests => 2;
627
628         my $code = 'ISSUESLIP';
629         my $now = dt_from_string;
630         my $one_minute_ago = dt_from_string->subtract( minutes => 1 );
631
632         my $branchcode = $library->{branchcode};
633
634         Koha::News->delete;
635         my $news_item = Koha::NewsItem->new({ branchcode => $branchcode, title => "A wonderful news", content => "This is the wonderful news." })->store;
636
637         # historic syntax
638         my $template = <<EOF;
639 <h3><<branches.branchname>></h3>
640 Checked out to <<borrowers.title>> <<borrowers.firstname>> <<borrowers.initials>> <<borrowers.surname>> <br />
641 (<<borrowers.cardnumber>>) <br />
642
643 <<today>><br />
644
645 <h4>Checked Out</h4>
646 <checkedout>
647 <p>
648 <<biblio.title>> <br />
649 Barcode: <<items.barcode>><br />
650 Date due: <<issues.date_due | dateonly>><br />
651 </p>
652 </checkedout>
653
654 <h4>Overdues</h4>
655 <overdue>
656 <p>
657 <<biblio.title>> <br />
658 Barcode: <<items.barcode>><br />
659 Date due: <<issues.date_due | dateonly>><br />
660 </p>
661 </overdue>
662
663 <hr>
664
665 <h4 style="text-align: center; font-style:italic;">News</h4>
666 <news>
667 <div class="newsitem">
668 <h5 style="margin-bottom: 1px; margin-top: 1px"><b><<opac_news.title>></b></h5>
669 <p style="margin-bottom: 1px; margin-top: 1px"><<opac_news.content>></p>
670 <p class="newsfooter" style="font-size: 8pt; font-style:italic; margin-bottom: 1px; margin-top: 1px">Posted on <<opac_news.timestamp>></p>
671 <hr />
672 </div>
673 </news>
674 EOF
675
676         reset_template( { template => $template, code => $code, module => 'circulation' } );
677
678         my $checkout = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
679         $checkout->set( { timestamp => $now, issuedate => $one_minute_ago } )->store;
680         my $first_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
681
682         $checkout = C4::Circulation::AddIssue( $patron, $item2->{barcode} ); # Add a second checkout
683         $checkout->set( { timestamp => $now, issuedate => $now } )->store;
684         my $yesterday = dt_from_string->subtract( days => 1 );
685         C4::Circulation::AddIssue( $patron, $item3->{barcode}, $yesterday ); # Add an overdue
686         my $second_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
687
688         # Cleanup
689         AddReturn( $item1->{barcode} );
690         AddReturn( $item2->{barcode} );
691         AddReturn( $item3->{barcode} );
692
693         # TT syntax
694         my $tt_template = <<EOF;
695 <h3>[% branch.branchname %]</h3>
696 Checked out to [% borrower.title %] [% borrower.firstname %] [% borrower.initials %] [% borrower.surname %] <br />
697 ([% borrower.cardnumber %]) <br />
698
699 [% today | \$KohaDates with_hours => 1 %]<br />
700
701 <h4>Checked Out</h4>
702 [% FOREACH checkout IN checkouts %]
703 [%~ SET item = checkout.item %]
704 [%~ SET biblio = checkout.item.biblio %]
705 <p>
706 [% biblio.title %] <br />
707 Barcode: [% item.barcode %]<br />
708 Date due: [% checkout.date_due | \$KohaDates %]<br />
709 </p>
710 [% END %]
711
712 <h4>Overdues</h4>
713 [% FOREACH overdue IN overdues %]
714 [%~ SET item = overdue.item %]
715 [%~ SET biblio = overdue.item.biblio %]
716 <p>
717 [% biblio.title %] <br />
718 Barcode: [% item.barcode %]<br />
719 Date due: [% overdue.date_due | \$KohaDates %]<br />
720 </p>
721 [% END %]
722
723 <hr>
724
725 <h4 style="text-align: center; font-style:italic;">News</h4>
726 [% FOREACH n IN news %]
727 <div class="newsitem">
728 <h5 style="margin-bottom: 1px; margin-top: 1px"><b>[% n.title %]</b></h5>
729 <p style="margin-bottom: 1px; margin-top: 1px">[% n.content %]</p>
730 <p class="newsfooter" style="font-size: 8pt; font-style:italic; margin-bottom: 1px; margin-top: 1px">Posted on [% n.timestamp | \$KohaDates %]</p>
731 <hr />
732 </div>
733 [% END %]
734 EOF
735
736         reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
737
738         $checkout = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
739         $checkout->set( { timestamp => $now, issuedate => $one_minute_ago } )->store;
740         my $first_tt_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
741
742         $checkout = C4::Circulation::AddIssue( $patron, $item2->{barcode} ); # Add a second checkout
743         $checkout->set( { timestamp => $now, issuedate => $now } )->store;
744         C4::Circulation::AddIssue( $patron, $item3->{barcode}, $yesterday ); # Add an overdue
745         my $second_tt_slip = C4::Members::IssueSlip( $branchcode, $patron->{borrowernumber} );
746
747         # There is too many line breaks generated by the historic syntax
748         $second_slip->{content} =~ s|</p>\n\n\n<p>|</p>\n\n<p>|s;
749
750         is( $first_tt_slip->{content}, $first_slip->{content}, );
751         is( $second_tt_slip->{content}, $second_slip->{content}, );
752
753         # Cleanup
754         AddReturn( $item1->{barcode} );
755         AddReturn( $item2->{barcode} );
756         AddReturn( $item3->{barcode} );
757     };
758
759     subtest 'ODUE|items.content|item' => sub {
760         plan tests => 1;
761
762         my $code = 'ODUE';
763
764         my $branchcode = $library->{branchcode};
765
766         # historic syntax
767         # FIXME items.fine does not work with TT notices
768         # See bug 17976
769         # <item> should contain Fine: <<items.fine>></item>
770         my $template = <<EOF;
771 Dear <<borrowers.firstname>> <<borrowers.surname>>,
772
773 According to our current records, you have items that are overdue.Your library does not charge late fines, but please return or renew them at the branch below as soon as possible.
774
775 <<branches.branchname>>
776 <<branches.branchaddress1>>
777 <<branches.branchaddress2>> <<branches.branchaddress3>>
778 Phone: <<branches.branchphone>>
779 Fax: <<branches.branchfax>>
780 Email: <<branches.branchemail>>
781
782 If you have registered a password with the library, and you have a renewal available, you may renew online. If an item becomes more than 30 days overdue, you will be unable to use your library card until the item is returned.
783
784 The following item(s) is/are currently overdue:
785
786 <item>"<<biblio.title>>" by <<biblio.author>>, <<items.itemcallnumber>>, Barcode: <<items.barcode>></item>
787
788 <<items.content>>
789
790 Thank-you for your prompt attention to this matter.
791
792 <<branches.branchname>> Staff
793 EOF
794
795         reset_template( { template => $template, code => $code, module => 'circulation' } );
796
797         my $yesterday = dt_from_string->subtract( days => 1 );
798         my $two_days_ago = dt_from_string->subtract( days => 2 );
799         my $issue1 = C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
800         my $issue2 = C4::Circulation::AddIssue( $patron, $item2->{barcode}, $yesterday ); # Add an first overdue
801         my $issue3 = C4::Circulation::AddIssue( $patron, $item3->{barcode}, $two_days_ago ); # Add an second overdue
802         $issue1 = $issue1->unblessed;
803         $issue2 = $issue2->unblessed;
804         $issue3 = $issue3->unblessed;
805
806         # For items.content
807         my @item_fields = qw( date_due title barcode author itemnumber );
808         my $items_content = C4::Letters::get_item_content( { item => { %$item1, %$biblio1, %$issue1 }, item_content_fields => \@item_fields, dateonly => 1 } );
809           $items_content .= C4::Letters::get_item_content( { item => { %$item2, %$biblio2, %$issue2 }, item_content_fields => \@item_fields, dateonly => 1 } );
810           $items_content .= C4::Letters::get_item_content( { item => { %$item3, %$biblio3, %$issue3 }, item_content_fields => \@item_fields, dateonly => 1 } );
811
812         my @items = ( $item1, $item2, $item3 );
813         my $letter = C4::Overdues::parse_overdues_letter(
814             {
815                 letter_code => $code,
816                 borrowernumber => $patron->{borrowernumber},
817                 branchcode  => $library->{branchcode},
818                 items       => \@items,
819                 substitute  => {
820                     bib                    => $library->{branchname},
821                     'items.content'        => $items_content,
822                     count                  => scalar( @items ),
823                     message_transport_type => 'email',
824                 }
825             }
826         );
827
828         # Cleanup
829         AddReturn( $item1->{barcode} );
830         AddReturn( $item2->{barcode} );
831         AddReturn( $item3->{barcode} );
832
833
834         # historic syntax
835         my $tt_template = <<EOF;
836 Dear [% borrower.firstname %] [% borrower.surname %],
837
838 According to our current records, you have items that are overdue.Your library does not charge late fines, but please return or renew them at the branch below as soon as possible.
839
840 [% branch.branchname %]
841 [% branch.branchaddress1 %]
842 [% branch.branchaddress2 %] [% branch.branchaddress3 %]
843 Phone: [% branch.branchphone %]
844 Fax: [% branch.branchfax %]
845 Email: [% branch.branchemail %]
846
847 If you have registered a password with the library, and you have a renewal available, you may renew online. If an item becomes more than 30 days overdue, you will be unable to use your library card until the item is returned.
848
849 The following item(s) is/are currently overdue:
850
851 [% FOREACH overdue IN overdues %]
852 [%~ SET item = overdue.item ~%]
853 "[% item.biblio.title %]" by [% item.biblio.author %], [% item.itemcallnumber %], Barcode: [% item.barcode %]
854 [% END %]
855 [% FOREACH overdue IN overdues %]
856 [%~ SET item = overdue.item ~%]
857 [% overdue.date_due | \$KohaDates %]\t[% item.biblio.title %]\t[% item.barcode %]\t[% item.biblio.author %]\t[% item.itemnumber %]
858 [% END %]
859
860 Thank-you for your prompt attention to this matter.
861
862 [% branch.branchname %] Staff
863 EOF
864
865         reset_template( { template => $tt_template, code => $code, module => 'circulation' } );
866
867         C4::Circulation::AddIssue( $patron, $item1->{barcode} ); # Add a first checkout
868         C4::Circulation::AddIssue( $patron, $item2->{barcode}, $yesterday ); # Add an first overdue
869         C4::Circulation::AddIssue( $patron, $item3->{barcode}, $two_days_ago ); # Add an second overdue
870
871         my $tt_letter = C4::Overdues::parse_overdues_letter(
872             {
873                 letter_code => $code,
874                 borrowernumber => $patron->{borrowernumber},
875                 branchcode  => $library->{branchcode},
876                 items       => \@items,
877                 substitute  => {
878                     bib                    => $library->{branchname},
879                     'items.content'        => $items_content,
880                     count                  => scalar( @items ),
881                     message_transport_type => 'email',
882                 }
883             }
884         );
885
886         is( $tt_letter->{content}, $letter->{content}, );
887     };
888
889     subtest 'Bug 19743 - Header and Footer should be updated on each item for checkin / checkout / renewal notices' => sub {
890         plan tests => 8;
891
892         my $checkout_code = 'CHECKOUT';
893         my $checkin_code = 'CHECKIN';
894
895         my $dbh = C4::Context->dbh;
896         $dbh->do("DELETE FROM letter");
897         $dbh->do("DELETE FROM issues");
898         $dbh->do("DELETE FROM message_queue");
899
900         # Enable notification for CHECKOUT - Things are hardcoded here but should work with default data
901         $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 6 );
902         my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
903         $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
904         # Enable notification for CHECKIN - Things are hardcoded here but should work with default data
905         $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ? )|, undef, $patron->{borrowernumber}, 5 );
906         $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
907         $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
908
909         my $checkout_template = q|
910 <<branches.branchname>>
911 ----
912 ----
913 |;
914         reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
915         my $checkin_template = q[
916 <<branches.branchname>>
917 ----
918 ----
919 ];
920         reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
921
922         my $issue = C4::Circulation::AddIssue( $patron, $item1->{barcode} );
923         my $first_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
924
925         my $library_object = Koha::Libraries->find( $issue->branchcode );
926         my $old_branchname = $library_object->branchname;
927         my $new_branchname = "Kyle M Hall Memorial Library";
928
929         # Change branch name for second checkout notice
930         $library_object->branchname($new_branchname);
931         $library_object->store();
932
933         C4::Circulation::AddIssue( $patron, $item2->{barcode} );
934         my $second_checkout_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
935
936         # Restore old name for first checkin notice
937         $library_object->branchname( $old_branchname );
938         $library_object->store();
939
940         AddReturn( $item1->{barcode} );
941         my $first_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
942
943         # Change branch name for second checkin notice
944         $library_object->branchname($new_branchname);
945         $library_object->store();
946
947         AddReturn( $item2->{barcode} );
948         my $second_checkin_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
949
950         # Restore old name for first TT checkout notice
951         $library_object->branchname( $old_branchname );
952         $library_object->store();
953
954         Koha::Notice::Messages->delete;
955
956         # TT syntax
957         $checkout_template = q|
958 [% branch.branchname %]
959 ----
960 ----
961 |;
962         reset_template( { template => $checkout_template, code => $checkout_code, module => 'circulation' } );
963         $checkin_template = q[
964 [% branch.branchname %]
965 ----
966 ----
967 ];
968         reset_template( { template => $checkin_template, code => $checkin_code, module => 'circulation' } );
969
970         C4::Circulation::AddIssue( $patron, $item1->{barcode} );
971         my $first_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
972
973         # Change branch name for second checkout notice
974         $library_object->branchname($new_branchname);
975         $library_object->store();
976
977         C4::Circulation::AddIssue( $patron, $item2->{barcode} );
978         my $second_checkout_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
979
980         # Restore old name for first checkin notice
981         $library_object->branchname( $old_branchname );
982         $library_object->store();
983
984         AddReturn( $item1->{barcode} );
985         my $first_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
986 #
987         # Change branch name for second checkin notice
988         $library_object->branchname($new_branchname);
989         $library_object->store();
990
991         AddReturn( $item2->{barcode} );
992         my $second_checkin_tt_letter = Koha::Notice::Messages->search( {}, { order_by => { -desc => 'message_id' } } )->next;
993
994         my $first_letter = qq[
995 $old_branchname
996 ];
997         my $second_letter = qq[
998 $new_branchname
999 ];
1000
1001
1002         is( $first_checkout_letter->content, $first_letter, 'Verify first checkout letter' );
1003         is( $second_checkout_letter->content, $second_letter, 'Verify second checkout letter' );
1004         is( $first_checkin_letter->content, $first_letter, 'Verify first checkin letter'  );
1005         is( $second_checkin_letter->content, $second_letter, 'Verify second checkin letter' );
1006
1007         is( $first_checkout_tt_letter->content, $first_letter, 'Verify TT first checkout letter' );
1008         is( $second_checkout_tt_letter->content, $second_letter, 'Verify TT second checkout letter' );
1009         is( $first_checkin_tt_letter->content, $first_letter, 'Verify TT first checkin letter'  );
1010         is( $second_checkin_tt_letter->content, $second_letter, 'Verify TT second checkin letter' );
1011     };
1012
1013 };
1014
1015 subtest 'loops' => sub {
1016     plan tests => 2;
1017     my $code = "TEST";
1018     my $module = "TEST";
1019
1020     subtest 'primary key is AI' => sub {
1021         plan tests => 1;
1022         my $patron_1 = $builder->build({ source => 'Borrower' });
1023         my $patron_2 = $builder->build({ source => 'Borrower' });
1024
1025         my $template = q|[% FOREACH patron IN borrowers %][% patron.surname %][% END %]|;
1026         reset_template( { template => $template, code => $code, module => $module } );
1027         my $letter = GetPreparedLetter( module => $module, letter_code => $code, loops => { borrowers => [ $patron_1->{borrowernumber}, $patron_2->{borrowernumber} ] } );
1028         my $expected_letter = join '', ( $patron_1->{surname}, $patron_2->{surname} );
1029         is( $letter->{content}, $expected_letter, );
1030     };
1031
1032     subtest 'foreign key is used' => sub {
1033         plan tests => 1;
1034         my $patron_1 = $builder->build({ source => 'Borrower' });
1035         my $patron_2 = $builder->build({ source => 'Borrower' });
1036         my $checkout_1 = $builder->build({ source => 'Issue', value => { borrowernumber => $patron_1->{borrowernumber} } } );
1037         my $checkout_2 = $builder->build({ source => 'Issue', value => { borrowernumber => $patron_1->{borrowernumber} } } );
1038
1039         my $template = q|[% FOREACH checkout IN checkouts %][% checkout.issue_id %][% END %]|;
1040         reset_template( { template => $template, code => $code, module => $module } );
1041         my $letter = GetPreparedLetter( module => $module, letter_code => $code, loops => { issues => [ $checkout_1->{itemnumber}, $checkout_2->{itemnumber} ] } );
1042         my $expected_letter = join '', ( $checkout_1->{issue_id}, $checkout_2->{issue_id} );
1043         is( $letter->{content}, $expected_letter, );
1044     };
1045 };
1046
1047 subtest 'add_tt_filters' => sub {
1048     plan tests => 1;
1049     my $code   = "TEST";
1050     my $module = "TEST";
1051
1052     my $patron = $builder->build_object(
1053         {
1054             class => 'Koha::Patrons',
1055             value => { surname => "with_punctuation_" }
1056         }
1057     );
1058     my $biblio = $builder->build_object(
1059         { class => 'Koha::Biblios', value => { title => "with_punctuation_" } }
1060     );
1061     my $biblioitem = $builder->build_object(
1062         {
1063             class => 'Koha::Biblioitems',
1064             value => {
1065                 biblionumber => $biblio->biblionumber,
1066                 isbn         => "with_punctuation_"
1067             }
1068         }
1069     );
1070
1071     my $template = q|patron=[% borrower.surname %];biblio=[% biblio.title %];biblioitems=[% biblioitem.isbn %]|;
1072     reset_template( { template => $template, code => $code, module => $module } );
1073     my $letter = GetPreparedLetter(
1074         module      => $module,
1075         letter_code => $code,
1076         tables      => {
1077             borrowers   => $patron->borrowernumber,
1078             biblio      => $biblio->biblionumber,
1079             biblioitems => $biblioitem->biblioitemnumber
1080         }
1081     );
1082     my $expected_letter = q|patron=with_punctuation_;biblio=with_punctuation;biblioitems=with_punctuation|;
1083     is( $letter->{content}, $expected_letter, "Pre-processing should call TT plugin to remove punctuation if table is biblio or biblioitems");
1084 };
1085
1086 subtest 'Dates formatting' => sub {
1087     plan tests => 1;
1088     my $code = 'TEST_DATE';
1089     t::lib::Mocks::mock_preference('dateformat', 'metric'); # MM/DD/YYYY
1090     my $biblio = $builder->build_object(
1091         {
1092             class => 'Koha::Biblios',
1093             value => {
1094                 timestamp   => '2018-12-13 20:21:22',
1095                 datecreated => '2018-12-13'
1096             }
1097         }
1098     );
1099     my $template = <<EOF;
1100 [%- USE KohaDates -%]
1101 [% biblio.timestamp %]
1102 [% biblio.timestamp | \$KohaDates %]
1103 [% biblio.timestamp | \$KohaDates with_hours => 1 %]
1104
1105 [% biblio.datecreated %]
1106 [% biblio.datecreated | \$KohaDates %]
1107 [% biblio.datecreated | \$KohaDates with_hours => 1 %]
1108
1109 [% biblio.timestamp | \$KohaDates dateformat => 'iso' %]
1110 [% KohaDates.output_preference( str => biblio.timestamp, dateformat => 'iso' ) %]
1111 [% KohaDates.output_preference( str => biblio.timestamp, dateformat => 'iso', dateonly => 1 ) %]
1112 EOF
1113     reset_template({ template => $template, code => $code, module => 'test' });
1114     my $letter = GetPreparedLetter(
1115         module => 'test',
1116         letter_code => $code,
1117         tables => {
1118             biblio => $biblio->biblionumber,
1119         }
1120     );
1121     my $expected_content = sprintf("%s\n%s\n%s\n\n%s\n%s\n%s\n\n%s\n%s\n%s\n",
1122         '2018-12-13 20:21:22',
1123         '13/12/2018',
1124         '13/12/2018 20:21',
1125
1126         '2018-12-13',
1127         '13/12/2018',
1128         '13/12/2018 00:00',
1129
1130         '2018-12-13',
1131         '2018-12-13 20:21',
1132         '2018-12-13',
1133     );
1134     is( $letter->{content}, $expected_content );
1135 };
1136
1137 sub reset_template {
1138     my ( $params ) = @_;
1139     my $template   = $params->{template};
1140     my $code       = $params->{code};
1141     my $module     = $params->{module} || 'test_module';
1142
1143     Koha::Notice::Templates->search( { code => $code } )->delete;
1144     Koha::Notice::Template->new(
1145         {
1146             module                 => $module,
1147             code                   => $code,
1148             branchcode             => '',
1149             name                   => $code,
1150             title                  => $code,
1151             message_transport_type => 'email',
1152             content                => $template
1153         }
1154     )->store;
1155 }
1156
1157 sub process_letter {
1158     my ($params)   = @_;
1159     my $template   = $params->{template};
1160     my $tables     = $params->{tables};
1161     my $substitute = $params->{substitute};
1162     my $code       = $params->{code};
1163     my $module     = $params->{module} || 'test_module';
1164     my $branchcode = $params->{branchcode};
1165
1166     reset_template( $params );
1167
1168     my $letter = C4::Letters::GetPreparedLetter(
1169         module      => $module,
1170         letter_code => $code,
1171         branchcode  => '',
1172         tables      => $tables,
1173         substitute  => $substitute,
1174     );
1175     return $letter;
1176 }