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