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