Bug 18174: Add update to Koha::Object
[koha.git] / t / db_dependent / Letters.t
blobcb8e6cab8124732e575a0a47bf4ae54d96cd72b4
1 #!/usr/bin/perl
3 # This file is part of Koha.
5 # Copyright (C) 2013 Equinox Software, Inc.
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20 use Modern::Perl;
21 use Test::More tests => 80;
22 use Test::MockModule;
23 use Test::Warn;
25 use MARC::Record;
27 my %mail;
28 my $module = new Test::MockModule('Mail::Sendmail');
29 $module->mock(
30 'sendmail',
31 sub {
32 warn "Fake sendmail";
33 %mail = @_;
37 use_ok('C4::Context');
38 use_ok('C4::Members');
39 use_ok('C4::Acquisition');
40 use_ok('C4::Biblio');
41 use_ok('C4::Letters');
42 use t::lib::Mocks;
43 use t::lib::TestBuilder;
44 use Koha::Database;
45 use Koha::DateUtils qw( dt_from_string output_pref );
46 use Koha::Acquisition::Order;
47 use Koha::Acquisition::Booksellers;
48 use Koha::Acquisition::Bookseller::Contacts;
49 use Koha::Libraries;
50 use Koha::Notice::Templates;
51 my $schema = Koha::Database->schema;
52 $schema->storage->txn_begin();
54 my $builder = t::lib::TestBuilder->new;
55 my $dbh = C4::Context->dbh;
56 $dbh->{RaiseError} = 1;
58 $dbh->do(q|DELETE FROM letter|);
59 $dbh->do(q|DELETE FROM message_queue|);
60 $dbh->do(q|DELETE FROM message_transport_types|);
62 my $library = $builder->build({
63 source => 'Branch',
64 });
65 my $patron_category = $builder->build({ source => 'Category' });
66 my $date = dt_from_string;
67 my $borrowernumber = AddMember(
68 firstname => 'Jane',
69 surname => 'Smith',
70 categorycode => $patron_category,,
71 branchcode => $library->{branchcode},
72 dateofbirth => $date,
75 my $marc_record = MARC::Record->new;
76 my( $biblionumber, $biblioitemnumber ) = AddBiblio( $marc_record, '' );
78 # GetMessageTransportTypes
79 my $mtts = C4::Letters::GetMessageTransportTypes();
80 is( @$mtts, 0, 'GetMessageTransportTypes returns the correct number of message types' );
82 $dbh->do(q|
83 INSERT INTO message_transport_types( message_transport_type ) VALUES ('email'), ('phone'), ('print'), ('sms')
84 |);
85 $mtts = C4::Letters::GetMessageTransportTypes();
86 is_deeply( $mtts, ['email', 'phone', 'print', 'sms'], 'GetMessageTransportTypes returns all values' );
89 # EnqueueLetter
90 is( C4::Letters::EnqueueLetter(), undef, 'EnqueueLetter without argument returns undef' );
92 my $my_message = {
93 borrowernumber => $borrowernumber,
94 message_transport_type => 'sms',
95 to_address => 'to@example.com',
96 from_address => 'from@example.com',
98 my $message_id = C4::Letters::EnqueueLetter($my_message);
99 is( $message_id, undef, 'EnqueueLetter without the letter argument returns undef' );
101 delete $my_message->{message_transport_type};
102 $my_message->{letter} = {
103 content => 'a message',
104 title => 'message title',
105 metadata => 'metadata',
106 code => 'TEST_MESSAGE',
107 content_type => 'text/plain',
109 $message_id = C4::Letters::EnqueueLetter($my_message);
110 is( $message_id, undef, 'EnqueueLetter without the message type argument argument returns undef' );
112 $my_message->{message_transport_type} = 'sms';
113 $message_id = C4::Letters::EnqueueLetter($my_message);
114 ok(defined $message_id && $message_id > 0, 'new message successfully queued');
117 # GetQueuedMessages
118 my $messages = C4::Letters::GetQueuedMessages();
119 is( @$messages, 1, 'GetQueuedMessages without argument returns all the entries' );
121 $messages = C4::Letters::GetQueuedMessages({ borrowernumber => $borrowernumber });
122 is( @$messages, 1, 'one message stored for the borrower' );
123 is( $messages->[0]->{message_id}, $message_id, 'EnqueueLetter returns the message id correctly' );
124 is( $messages->[0]->{borrowernumber}, $borrowernumber, 'EnqueueLetter stores the borrower number correctly' );
125 is( $messages->[0]->{subject}, $my_message->{letter}->{title}, 'EnqueueLetter stores the subject correctly' );
126 is( $messages->[0]->{content}, $my_message->{letter}->{content}, 'EnqueueLetter stores the content correctly' );
127 is( $messages->[0]->{message_transport_type}, $my_message->{message_transport_type}, 'EnqueueLetter stores the message type correctly' );
128 is( $messages->[0]->{status}, 'pending', 'EnqueueLetter stores the status pending correctly' );
131 # SendQueuedMessages
132 my $messages_processed = C4::Letters::SendQueuedMessages();
133 is($messages_processed, 1, 'all queued messages processed');
135 $messages = C4::Letters::GetQueuedMessages({ borrowernumber => $borrowernumber });
137 $messages->[0]->{status},
138 'failed',
139 'message marked failed if tried to send SMS message for borrower with no smsalertnumber set (bug 11208)'
142 # ResendMessage
143 my $resent = C4::Letters::ResendMessage($messages->[0]->{message_id});
144 my $message = C4::Letters::GetMessage( $messages->[0]->{message_id});
145 is( $resent, 1, 'The message should have been resent' );
146 is($message->{status},'pending', 'ResendMessage sets status to pending correctly (bug 12426)');
147 $resent = C4::Letters::ResendMessage($messages->[0]->{message_id});
148 is( $resent, 0, 'The message should not have been resent again' );
149 $resent = C4::Letters::ResendMessage();
150 is( $resent, undef, 'ResendMessage should return undef if not message_id given' );
152 # GetLetters
153 my $letters = C4::Letters::GetLetters();
154 is( @$letters, 0, 'GetLetters returns the correct number of letters' );
156 my $title = q|<<branches.branchname>> - <<status>>|;
157 my $content = q{Dear <<borrowers.firstname>> <<borrowers.surname>>,
158 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.
160 <<branches.branchname>>
161 <<branches.branchaddress1>>
162 URL: <<OPACBaseURL>>
164 The following item(s) is/are currently <<status>>:
166 <item> <<count>>. <<items.itemcallnumber>>, Barcode: <<items.barcode>> </item>
168 Thank-you for your prompt attention to this matter.
169 Don't forget your date of birth: <<borrowers.dateofbirth>>.
170 Look at this wonderful biblio timestamp: <<biblio.timestamp>>.
173 $dbh->do( q|INSERT INTO letter(branchcode,module,code,name,is_html,title,content,message_transport_type) VALUES (?,'my module','my code','my name',1,?,?,'email')|, undef, $library->{branchcode}, $title, $content );
174 $letters = C4::Letters::GetLetters();
175 is( @$letters, 1, 'GetLetters returns the correct number of letters' );
176 is( $letters->[0]->{branchcode}, $library->{branchcode}, 'GetLetters gets the branch code correctly' );
177 is( $letters->[0]->{module}, 'my module', 'GetLetters gets the module correctly' );
178 is( $letters->[0]->{code}, 'my code', 'GetLetters gets the code correctly' );
179 is( $letters->[0]->{name}, 'my name', 'GetLetters gets the name correctly' );
182 # getletter
183 my $letter = C4::Letters::getletter('my module', 'my code', $library->{branchcode}, 'email');
184 is( $letter->{branchcode}, $library->{branchcode}, 'GetLetters gets the branch code correctly' );
185 is( $letter->{module}, 'my module', 'GetLetters gets the module correctly' );
186 is( $letter->{code}, 'my code', 'GetLetters gets the code correctly' );
187 is( $letter->{name}, 'my name', 'GetLetters gets the name correctly' );
188 is( $letter->{is_html}, 1, 'GetLetters gets the boolean is_html correctly' );
189 is( $letter->{title}, $title, 'GetLetters gets the title correctly' );
190 is( $letter->{content}, $content, 'GetLetters gets the content correctly' );
191 is( $letter->{message_transport_type}, 'email', 'GetLetters gets the message type correctly' );
193 # Regression test for Bug 14206
194 $dbh->do( q|INSERT INTO letter(branchcode,module,code,name,is_html,title,content,message_transport_type) VALUES ('FFL','my module','my code','my name',1,?,?,'print')|, undef, $title, $content );
195 my $letter14206_a = C4::Letters::getletter('my module', 'my code', 'FFL' );
196 is( $letter14206_a->{message_transport_type}, 'print', 'Bug 14206 - message_transport_type not passed, correct mtt detected' );
197 my $letter14206_b = C4::Letters::getletter('my module', 'my code', 'FFL', 'print');
198 is( $letter14206_b->{message_transport_type}, 'print', 'Bug 14206 - message_transport_type passed, correct mtt detected' );
200 # test for overdue_notices.pl
201 my $overdue_rules = {
202 letter1 => 'my code',
204 my $i = 1;
205 my $branchcode = 'FFL';
206 my $letter14206_c = C4::Letters::getletter('my module', $overdue_rules->{"letter$i"}, $branchcode);
207 is( $letter14206_c->{message_transport_type}, 'print', 'Bug 14206 - correct mtt detected for call from overdue_notices.pl' );
209 # addalert
210 my $type = 'my type';
211 my $externalid = 'my external id';
212 my $alert_id = C4::Letters::addalert($borrowernumber, $type, $externalid);
213 isnt( $alert_id, undef, 'addalert does not return undef' );
216 # getalert
217 my $alerts = C4::Letters::getalert();
218 is( @$alerts, 1, 'getalert should not fail without parameter' );
219 $alerts = C4::Letters::getalert($borrowernumber);
220 is( @$alerts, 1, 'addalert adds an alert' );
221 is( $alerts->[0]->{alertid}, $alert_id, 'addalert returns the alert id correctly' );
222 is( $alerts->[0]->{type}, $type, 'addalert stores the type correctly' );
223 is( $alerts->[0]->{externalid}, $externalid, 'addalert stores the externalid correctly' );
225 $alerts = C4::Letters::getalert($borrowernumber, $type);
226 is( @$alerts, 1, 'getalert returns the correct number of alerts' );
227 $alerts = C4::Letters::getalert($borrowernumber, $type, $externalid);
228 is( @$alerts, 1, 'getalert returns the correct number of alerts' );
229 $alerts = C4::Letters::getalert($borrowernumber, 'another type');
230 is( @$alerts, 0, 'getalert returns the correct number of alerts' );
231 $alerts = C4::Letters::getalert($borrowernumber, $type, 'another external id');
232 is( @$alerts, 0, 'getalert returns the correct number of alerts' );
235 # delalert
236 eval {
237 C4::Letters::delalert();
239 isnt( $@, undef, 'delalert without argument returns an error' );
240 $alerts = C4::Letters::getalert($borrowernumber);
241 is( @$alerts, 1, 'delalert without argument does not remove an alert' );
243 C4::Letters::delalert($alert_id);
244 $alerts = C4::Letters::getalert($borrowernumber);
245 is( @$alerts, 0, 'delalert removes an alert' );
248 # GetPreparedLetter
249 t::lib::Mocks::mock_preference('OPACBaseURL', 'http://thisisatest.com');
251 my $sms_content = 'This is a SMS for an <<status>>';
252 $dbh->do( q|INSERT INTO letter(branchcode,module,code,name,is_html,title,content,message_transport_type) VALUES (?,'my module','my code','my name',1,'my title',?,'sms')|, undef, $library->{branchcode}, $sms_content );
254 my $tables = {
255 borrowers => $borrowernumber,
256 branches => $library->{branchcode},
257 biblio => $biblionumber,
259 my $substitute = {
260 status => 'overdue',
262 my $repeat = [
264 itemcallnumber => 'my callnumber1',
265 barcode => '1234',
268 itemcallnumber => 'my callnumber2',
269 barcode => '5678',
272 my $prepared_letter = GetPreparedLetter((
273 module => 'my module',
274 branchcode => $library->{branchcode},
275 letter_code => 'my code',
276 tables => $tables,
277 substitute => $substitute,
278 repeat => $repeat,
280 my $retrieved_library = Koha::Libraries->find($library->{branchcode});
281 my $my_title_letter = $retrieved_library->branchname . qq| - $substitute->{status}|;
282 my $my_content_letter = qq|Dear Jane Smith,
283 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.
285 |.$retrieved_library->branchname.qq|
286 |.$retrieved_library->branchaddress1.qq|
287 URL: http://thisisatest.com
289 The following item(s) is/are currently $substitute->{status}:
291 <item> 1. $repeat->[0]->{itemcallnumber}, Barcode: $repeat->[0]->{barcode} </item>
292 <item> 2. $repeat->[1]->{itemcallnumber}, Barcode: $repeat->[1]->{barcode} </item>
294 Thank-you for your prompt attention to this matter.
295 Don't forget your date of birth: | . output_pref({ dt => $date, dateonly => 1 }) . q|.
296 Look at this wonderful biblio timestamp: | . output_pref({ dt => $date }) . ".\n";
298 is( $prepared_letter->{title}, $my_title_letter, 'GetPreparedLetter returns the title correctly' );
299 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
301 $prepared_letter = GetPreparedLetter((
302 module => 'my module',
303 branchcode => $library->{branchcode},
304 letter_code => 'my code',
305 tables => $tables,
306 substitute => $substitute,
307 repeat => $repeat,
308 message_transport_type => 'sms',
310 $my_content_letter = qq|This is a SMS for an $substitute->{status}|;
311 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
313 $dbh->do(q{INSERT INTO letter (module, code, name, title, content) VALUES ('test_date','TEST_DATE','Test dates','A title with a timestamp: <<biblio.timestamp>>','This one only contains the date: <<biblio.timestamp | dateonly>>.');});
314 $prepared_letter = GetPreparedLetter((
315 module => 'test_date',
316 branchcode => '',
317 letter_code => 'test_date',
318 tables => $tables,
319 substitute => $substitute,
320 repeat => $repeat,
322 is( $prepared_letter->{content}, q|This one only contains the date: | . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 1' );
324 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp | dateonly>>.' WHERE code = 'test_date';});
325 $prepared_letter = GetPreparedLetter((
326 module => 'test_date',
327 branchcode => '',
328 letter_code => 'test_date',
329 tables => $tables,
330 substitute => $substitute,
331 repeat => $repeat,
333 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 2' );
335 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp|dateonly >>.' WHERE code = 'test_date';});
336 $prepared_letter = GetPreparedLetter((
337 module => 'test_date',
338 branchcode => '',
339 letter_code => 'test_date',
340 tables => $tables,
341 substitute => $substitute,
342 repeat => $repeat,
344 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 3' );
346 t::lib::Mocks::mock_preference( 'TimeFormat', '12hr' );
347 my $yesterday_night = $date->clone->add( days => -1 )->set_hour(22);
348 $dbh->do(q|UPDATE biblio SET timestamp = ? WHERE biblionumber = ?|, undef, $yesterday_night, $biblionumber );
349 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp>>.' WHERE code = 'test_date';});
350 $prepared_letter = GetPreparedLetter((
351 module => 'test_date',
352 branchcode => '',
353 letter_code => 'test_date',
354 tables => $tables,
355 substitute => $substitute,
356 repeat => $repeat,
358 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $yesterday_night }) . q|.|, 'dateonly test 3' );
360 $dbh->do(q{INSERT INTO letter (module, code, name, title, content) VALUES ('claimacquisition','TESTACQCLAIM','Acquisition Claim','Item Not Received','<<aqbooksellers.name>>|<<aqcontacts.name>>|<order>Ordernumber <<aqorders.ordernumber>> (<<biblio.title>>) (<<aqorders.quantity>> ordered)</order>');});
361 $dbh->do(q{INSERT INTO letter (module, code, name, title, content) VALUES ('orderacquisition','TESTACQORDER','Acquisition Order','Order','<<aqbooksellers.name>>|<<aqcontacts.name>>|<order>Ordernumber <<aqorders.ordernumber>> (<<biblio.title>>) (<<aqorders.quantity>> ordered)</order>');});
363 # Test that _parseletter doesn't modify its parameters bug 15429
365 my $values = { dateexpiry => '2015-12-13', };
366 C4::Letters::_parseletter($prepared_letter, 'borrowers', $values);
367 is( $values->{dateexpiry}, '2015-12-13', "_parseletter doesn't modify its parameters" );
370 my $bookseller = Koha::Acquisition::Bookseller->new(
372 name => "my vendor",
373 address1 => "bookseller's address",
374 phone => "0123456",
375 active => 1,
376 deliverytime => 5,
378 )->store;
379 my $booksellerid = $bookseller->id;
381 Koha::Acquisition::Bookseller::Contact->new( { name => 'John Smith', phone => '0123456x1', claimacquisition => 1, orderacquisition => 1, booksellerid => $booksellerid } )->store;
382 Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues => 1, booksellerid => $booksellerid } )->store;
383 my $basketno = NewBasket($booksellerid, 1);
385 my $budgetid = C4::Budgets::AddBudget({
386 budget_code => "budget_code_test_letters",
387 budget_name => "budget_name_test_letters",
390 my $bib = MARC::Record->new();
391 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
392 $bib->append_fields(
393 MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
395 } else {
396 $bib->append_fields(
397 MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
401 ($biblionumber, $biblioitemnumber) = AddBiblio($bib, '');
402 my $order = Koha::Acquisition::Order->new(
404 basketno => $basketno,
405 quantity => 1,
406 biblionumber => $biblionumber,
407 budget_id => $budgetid,
409 )->insert;
410 my $ordernumber = $order->{ordernumber};
412 C4::Acquisition::CloseBasket( $basketno );
413 my $err;
414 warning_like {
415 $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
416 qr/^Bookseller .* without emails at/,
417 "SendAlerts prints a warning";
418 is($err->{'error'}, 'no_email', "Trying to send an alert when there's no e-mail results in an error");
420 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
421 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
423 # Ensure that the preference 'LetterLog' is set to logging
424 t::lib::Mocks::mock_preference( 'LetterLog', 'on' );
427 warning_is {
428 $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
429 "Fake sendmail",
430 "SendAlerts is using the mocked sendmail routine (orderacquisition)";
431 is($err, 1, "Successfully sent order.");
432 is($mail{'To'}, 'testemail@mydomain.com', "mailto correct in sent order");
433 is($mail{'Message'}, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered)', 'Order notice text constructed successfully');
435 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTACQORDER';});
436 warning_like {
437 $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
438 qr/No orderacquisition TESTACQORDER letter transported by email/,
439 "GetPreparedLetter warns about missing notice template";
440 is($err->{'error'}, 'no_letter', "No TESTACQORDER letter was defined.");
443 subtest 'GetPreparedLetter' => sub {
444 plan tests => 4;
446 Koha::Notice::Template->new(
448 module => 'test',
449 code => 'test',
450 branchcode => '',
451 message_transport_type => 'email'
453 )->store;
454 my $letter;
455 warning_like {
456 $letter = C4::Letters::GetPreparedLetter(
457 module => 'test',
458 letter_code => 'test',
461 qr{^ERROR: nothing to substitute},
462 'GetPreparedLetter should warn if tables, substiture and repeat are not set';
463 is( $letter, undef,
464 'No letter should be returned by GetPreparedLetter if something went wrong'
467 warning_like {
468 $letter = C4::Letters::GetPreparedLetter(
469 module => 'test',
470 letter_code => 'test',
471 substitute => {}
474 qr{^ERROR: nothing to substitute},
475 'GetPreparedLetter should warn if tables, substiture and repeat are not set, even if the key is passed';
476 is( $letter, undef,
477 'No letter should be returned by GetPreparedLetter if something went wrong'
483 warning_is {
484 $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
485 "Fake sendmail",
486 "SendAlerts is using the mocked sendmail routine";
488 is($err, 1, "Successfully sent claim");
489 is($mail{'To'}, 'testemail@mydomain.com', "mailto correct in sent claim");
490 is($mail{'Message'}, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered)', 'Claim notice text constructed successfully');
494 use C4::Serials;
496 my $notes = 'notes';
497 my $internalnotes = 'intnotes';
498 $dbh->do(q|UPDATE subscription_numberpatterns SET numberingmethod='No. {X}' WHERE id=1|);
499 my $subscriptionid = NewSubscription(
500 undef, "", undef, undef, undef, $biblionumber,
501 '2013-01-01', 1, undef, undef, undef,
502 undef, undef, undef, undef, undef, undef,
503 1, $notes,undef, '2013-01-01', undef, 1,
504 undef, undef, 0, $internalnotes, 0,
505 undef, undef, 0, undef, '2013-12-31', 0
507 $dbh->do(q{INSERT INTO letter (module, code, name, title, content) VALUES ('serial','RLIST','Serial issue notification','Serial issue notification','<<biblio.title>>,<<subscription.subscriptionid>>,<<serial.serialseq>>');});
508 my ($serials_count, @serials) = GetSerials($subscriptionid);
509 my $serial = $serials[0];
511 my $borrowernumber = AddMember(
512 firstname => 'John',
513 surname => 'Smith',
514 categorycode => $patron_category,
515 branchcode => $library->{branchcode},
516 dateofbirth => $date,
517 email => 'john.smith@test.de',
519 my $alert_id = C4::Letters::addalert($borrowernumber, 'issue', $subscriptionid);
522 my $err2;
523 warning_is {
524 $err2 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
525 "Fake sendmail",
526 "SendAlerts is using the mocked sendmail routine";
527 is($err2, 1, "Successfully sent serial notification");
528 is($mail{'To'}, 'john.smith@test.de', "mailto correct in sent serial notification");
529 is($mail{'Message'}, 'Silence in the library,'.$subscriptionid.',No. 0', 'Serial notification text constructed successfully');