5 use Test::More tests => 23;
10 use t::lib::TestBuilder;
11 use C4::Calendar qw( new insert_single_holiday delete_holiday insert_week_day_holiday );
12 use Koha::DateUtils qw( dt_from_string );
13 use Koha::Library::Hours;
14 use Koha::CirculationRules;
16 use_ok('C4::Circulation', qw( CalcDateDue ));
18 my $schema = Koha::Database->new->schema;
19 $schema->storage->txn_begin;
20 my $builder = t::lib::TestBuilder->new;
22 t::lib::Mocks::mock_preference( 'ConsiderLibraryHoursInCirculation', 'ignore' );
23 my $library = $builder->build_object({ class => 'Koha::Libraries' })->store;
24 my $dateexpiry = '2013-01-01';
25 my $patron_category = $builder->build_object({ class => 'Koha::Patron::Categories', value => { category_type => 'B' } })->store;
26 my $borrower = $builder->build_object(
28 class => 'Koha::Patrons',
30 categorycode => $patron_category->categorycode,
31 dateexpiry => $dateexpiry,
36 my $itemtype = $builder->build_object({ class => 'Koha::ItemTypes' })->store->itemtype;
38 my $renewalperiod = 5;
39 my $lengthunit = 'days';
41 Koha::CirculationRules->search()->delete();
42 Koha::CirculationRules->set_rules(
44 categorycode => $patron_category->categorycode,
45 itemtype => $itemtype,
46 branchcode => $library->branchcode,
48 issuelength => $issuelength,
49 renewalperiod => $renewalperiod,
50 lengthunit => $lengthunit,
55 #Set syspref ReturnBeforeExpiry = 1 and useDaysMode = 'Days'
56 t::lib::Mocks::mock_preference('ReturnBeforeExpiry', 1);
57 t::lib::Mocks::mock_preference('useDaysMode', 'Days');
59 my $branchcode = $library->branchcode;
61 my $cache = Koha::Caches->get_instance();
62 my $key = $branchcode . "_holidays";
63 $cache->clear_from_cache($key);
65 my $start_date = DateTime->new({year => 2013, month => 2, day => 9});
66 my $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $borrower );
67 is($date, $dateexpiry . 'T23:59:00', 'date expiry');
68 $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $borrower, 1 );
71 #Set syspref ReturnBeforeExpiry = 1 and useDaysMode != 'Days'
72 t::lib::Mocks::mock_preference('ReturnBeforeExpiry', 1);
73 t::lib::Mocks::mock_preference('useDaysMode', 'noDays');
75 $start_date = DateTime->new({year => 2013, month => 2, day => 9});
76 $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $borrower );
77 is($date, $dateexpiry . 'T23:59:00', 'date expiry with useDaysMode to noDays');
79 # Let's add a special holiday on 2013-01-01. With ReturnBeforeExpiry and
80 # useDaysMode different from 'Days', return should forward the dateexpiry.
81 my $calendar = C4::Calendar->new(branchcode => $branchcode);
82 $calendar->insert_single_holiday(
86 title =>'holidayTest',
87 description => 'holidayDesc'
89 $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $borrower );
90 is($date, '2012-12-31T23:59:00', 'date expiry should be 2013-01-01 -1 day');
91 $calendar->insert_single_holiday(
95 title =>'holidayTest',
96 description => 'holidayDesc'
98 $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $borrower );
99 is($date, '2012-12-30T23:59:00', 'date expiry should be 2013-01-01 -2 day');
102 $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $borrower, 1 );
105 #Set syspref ReturnBeforeExpiry = 0 and useDaysMode = 'Days'
106 t::lib::Mocks::mock_preference('ReturnBeforeExpiry', 0);
107 t::lib::Mocks::mock_preference('useDaysMode', 'Days');
109 $start_date = DateTime->new({year => 2013, month => 2, day => 9});
110 $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $borrower );
111 is($date, '2013-02-' . (9 + $issuelength) . 'T23:59:00', "date expiry ( 9 + $issuelength )");
113 $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $borrower, 1 );
114 is($date, '2013-02-' . (9 + $renewalperiod) . 'T23:59:00', "date expiry ( 9 + $renewalperiod )");
117 # Now we want to test the Dayweek useDaysMode option
118 # For this we need a loan period that is a mutiple of 7 days
119 # But, since we currently don't have that, let's test it does the
120 # right thing in that case, it should act as though useDaysMode is set to
122 #Set syspref ReturnBeforeExpiry = 0 and useDaysMode = 'Dayweek'
123 t::lib::Mocks::mock_preference('ReturnBeforeExpiry', 0);
124 t::lib::Mocks::mock_preference('useDaysMode', 'Dayweek');
126 # No closed day interfering, so we should get the regular due date
127 $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $borrower );
128 is($date, '2013-02-' . (9 + $issuelength) . 'T23:59:00', "useDaysMode = Dayweek, no closed days, issue date expiry ( start + $issuelength )");
130 $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $borrower, 1 );
131 is($date, '2013-02-' . (9 + $renewalperiod) . 'T23:59:00', "useDaysMode = Dayweek, no closed days, renewal date expiry ( start + $renewalperiod )");
133 # Now let's add a closed day on the expected renewal date, it should
134 # roll forward as per Datedue (i.e. one day at a time)
136 $calendar->insert_single_holiday(
137 day => 9 + $issuelength,
140 title =>'DayweekTest1',
141 description => 'DayweekTest1'
143 $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $borrower );
144 is($date, '2013-02-' . (9 + $issuelength + 1) . 'T23:59:00', "useDaysMode = Dayweek, closed on due date, 10 day loan (should not trigger 7 day roll forward), issue date expiry ( start + $issuelength + 1 )");
145 # Remove the holiday we just created
146 $calendar->delete_holiday(
147 day => 9 + $issuelength,
152 # ...and for renewals...
153 $calendar->insert_single_holiday(
154 day => 9 + $renewalperiod,
157 title =>'DayweekTest2',
158 description => 'DayweekTest2'
160 $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $borrower, 1 );
161 is($date, '2013-02-' . (9 + $renewalperiod + 1) . 'T23:59:00', "useDaysMode = Dayweek, closed on due date, 5 day renewal (should not trigger 7 day roll forward), renewal date expiry ( start + $renewalperiod + 1 )");
162 # Remove the holiday we just created
163 $calendar->delete_holiday(
164 day => 9 + $renewalperiod,
169 # Now we test it does the right thing if the loan and renewal periods
170 # are a multiple of 7 days
171 my $dayweek_issuelength = 14;
172 my $dayweek_renewalperiod = 7;
173 my $dayweek_lengthunit = 'days';
175 $patron_category = $builder->build_object({ class => 'Koha::Patron::Categories', value => { category_type => 'K' } })->store;
177 Koha::CirculationRules->set_rules(
179 categorycode => $patron_category->categorycode,
180 itemtype => $itemtype,
181 branchcode => $branchcode,
183 issuelength => $dayweek_issuelength,
184 renewalperiod => $dayweek_renewalperiod,
185 lengthunit => $dayweek_lengthunit,
190 my $dayweek_borrower = $builder->build_object(
192 class => 'Koha::Patrons',
194 categorycode => $patron_category->categorycode,
195 dateexpiry => $dateexpiry,
201 $start_date = DateTime->new({year => 2013, month => 2, day => 9});
202 $calendar->insert_single_holiday(
203 day => 9 + $dayweek_issuelength,
206 title =>'DayweekTest3',
207 description => 'DayweekTest3'
209 $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $dayweek_borrower );
210 my $issue_should_add = $dayweek_issuelength + 7;
211 my $dayweek_issue_expected = $start_date->add( days => $issue_should_add );
212 is($date, $dayweek_issue_expected->strftime('%F') . 'T23:59:00', "useDaysMode = Dayweek, closed on due date, 14 day loan (should trigger 7 day roll forward), issue date expiry ( start + $issue_should_add )");
213 # Remove the holiday we just created
214 $calendar->delete_holiday(
215 day => 9 + $dayweek_issuelength,
220 # ...and for renewals...
221 $start_date = DateTime->new({year => 2013, month => 2, day => 9});
222 $calendar->insert_single_holiday(
223 day => 9 + $dayweek_renewalperiod,
226 title => 'DayweekTest4',
227 description => 'DayweekTest4'
229 $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $dayweek_borrower, 1 );
230 my $renewal_should_add = $dayweek_renewalperiod + 7;
231 my $dayweek_renewal_expected = $start_date->add( days => $renewal_should_add );
232 is($date, $dayweek_renewal_expected->strftime('%F') . 'T23:59:00', "useDaysMode = Dayweek, closed on due date, 7 day renewal (should trigger 7 day roll forward), renewal date expiry ( start + $renewal_should_add )");
233 # Remove the holiday we just created
234 $calendar->delete_holiday(
235 day => 9 + $dayweek_renewalperiod,
240 # Now test it continues to roll forward by 7 days until it finds
241 # an open day, so we create a 3 week period of closed Saturdays
242 $start_date = DateTime->new({year => 2013, month => 2, day => 9});
243 my $expected_rolled_date = DateTime->new({year => 2013, month => 3, day => 9});
244 my $holiday = $start_date->clone();
245 $holiday->add(days => 7);
246 $calendar->insert_single_holiday(
247 day => $holiday->day,
248 month => $holiday->month,
250 title =>'DayweekTest5',
251 description => 'DayweekTest5'
253 $holiday->add(days => 7);
254 $calendar->insert_single_holiday(
255 day => $holiday->day,
256 month => $holiday->month,
258 title =>'DayweekTest6',
259 description => 'DayweekTest6'
261 $holiday->add(days => 7);
262 $calendar->insert_single_holiday(
263 day => $holiday->day,
264 month => $holiday->month,
266 title =>'DayweekTest7',
267 description => 'DayweekTest7'
270 $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $dayweek_borrower );
271 $dayweek_issue_expected = $start_date->add( days => $issue_should_add );
272 is($date, $expected_rolled_date->strftime('%F') . 'T23:59:00', "useDaysMode = Dayweek, closed on due date and two subequent due dates, 14 day loan (should trigger 2 x 7 day roll forward), issue date expiry ( start + 28 )");
273 # ...and for renewals...
274 $start_date = DateTime->new({year => 2013, month => 2, day => 9});
275 $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $dayweek_borrower, 1 );
276 $dayweek_issue_expected = $start_date->add( days => $renewal_should_add );
277 is($date, $expected_rolled_date->strftime('%F') . 'T23:59:00', "useDaysMode = Dayweek, closed on due date and three subsequent due dates, 7 day renewal (should trigger 3 x 7 day roll forward), issue date expiry ( start + 28 )");
278 # Remove the holidays we just created
279 $start_date = DateTime->new({year => 2013, month => 2, day => 9});
280 my $del_holiday = $start_date->clone();
281 $del_holiday->add(days => 7);
282 $calendar->delete_holiday(
283 day => $del_holiday->day,
284 month => $del_holiday->month,
287 $del_holiday->add(days => 7);
288 $calendar->delete_holiday(
289 day => $del_holiday->day,
290 month => $del_holiday->month,
293 $del_holiday->add(days => 7);
294 $calendar->delete_holiday(
295 day => $del_holiday->day,
296 month => $del_holiday->month,
300 # Now test that useDaysMode "Dayweek" doesn't try to roll forward onto
301 # a permanently closed day and instead rolls forward just one day
302 $start_date = DateTime->new({year => 2013, month => 2, day => 9});
303 # Our tests are concerned with Saturdays, so let's close on Saturdays
304 $calendar->insert_week_day_holiday(
306 title => "Saturday closure",
307 description => "Closed on Saturdays"
310 $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $dayweek_borrower );
311 $dayweek_issue_expected = $start_date->add( days => $dayweek_issuelength + 1 );
312 is($date, $dayweek_issue_expected->strftime('%F') . 'T23:59:00', "useDaysMode = Dayweek, due on Saturday, closed on Saturdays, 14 day loan (should trigger 1 day roll forward), issue date expiry ( start + 15 )");
313 # ...and for renewals...
314 $start_date = DateTime->new({year => 2013, month => 2, day => 9});
315 $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $dayweek_borrower, 1 );
316 $dayweek_renewal_expected = $start_date->add( days => $dayweek_renewalperiod + 1 );
317 is($date, $dayweek_renewal_expected->strftime('%F') . 'T23:59:00', "useDaysMode = Dayweek, due on Saturday, closed on Saturdays, 7 day renewal (should trigger 1 day roll forward), issue date expiry ( start + 8 )");
318 # Remove the holiday we just created
319 $calendar->delete_holiday(
323 # Renewal period of 0 is valid
324 Koha::CirculationRules->search()->delete();
325 Koha::CirculationRules->set_rules(
327 categorycode => undef,
333 lengthunit => 'days',
338 $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $borrower, 1 );
339 is( $date->ymd, $start_date->ymd, "Dates should match for renewalperiod of 0" );
341 # Renewal period of "" should trigger fallover to issuelength for renewal
342 Koha::CirculationRules->search()->delete();
343 Koha::CirculationRules->set_rules(
345 categorycode => undef,
350 renewalperiod => q{},
351 lengthunit => 'days',
356 my $renewed_date = $start_date->clone->add( days => 7 );
357 $date = C4::Circulation::CalcDateDue( $start_date, $itemtype, $branchcode, $borrower, 1 );
358 is( $date->ymd, $renewed_date->ymd, 'Renewal period of "" should trigger fallover to issuelength for renewal' );
360 # Testing hourly loans consider library open hours
362 my $library1 = $builder->build( { source => 'Branch' } );
363 Koha::CirculationRules->set_rules(
365 categorycode => $categorycode,
366 itemtype => $itemtype,
367 branchcode => $library1->{branchcode},
369 issuelength => 3, # loan period is 3 hours
370 lengthunit => 'hours',
375 my $open = DateTime->now->subtract( hours => 4 )->hms;
376 my $close = DateTime->now->add( hours => 2 )->hms;
377 my $now = DateTime->now;
380 # library opened 4 hours ago and closes in 2 hours.
381 Koha::Library::Hour->new({ day => $_, library_id => $library1->{branchcode}, open_time => $open, close_time => $close })->store;
385 t::lib::Mocks::mock_preference('useDaysMode', 'Days');
386 t::lib::Mocks::mock_preference('ConsiderLibraryHoursInCirculation', 'close');
387 # shorten loan period
389 $date = C4::Circulation::CalcDateDue( $now, $itemtype, $library1->{branchcode}, $borrower );
390 my $expected_duetime = DateTime->now->add( hours => 2 );
391 is( $date, $expected_duetime, "Loan period was shortened because ConsiderLibraryHoursInCirculation is set to close time" );
393 t::lib::Mocks::mock_preference('ConsiderLibraryHoursWhenIssuing', 'open');
396 $date = C4::Circulation::CalcDateDue( $now, $itemtype, $library1->{branchcode}, $borrower );
397 $expected_duetime = DateTime->now->add( days => 1 )->subtract( hours => 4 );
398 is( $date, $expected_duetime, "Loan period was extended because ConsiderLibraryHoursInCirculation is set to open time" );
400 my $holiday_tomorrow = DateTime->now->add( days => 1 );
403 my $library1_calendar = C4::Calendar->new( branchcode => $library1->{branchcode} );
404 $library1_calendar->insert_single_holiday(
405 day => $holiday_tomorrow->day,
406 month => $holiday_tomorrow->month,
407 year => $holiday_tomorrow->year,
408 title => 'testholiday',
409 description => 'testholiday'
411 Koha::CirculationRules->set_rules(
413 categorycode => $categorycode,
414 itemtype => $itemtype,
415 branchcode => $library1->{branchcode},
417 issuelength => 13, # loan period must cross over into tomorrow
418 lengthunit => 'hours',
423 t::lib::Mocks::mock_preference('useDaysMode', 'Calendar');
424 t::lib::Mocks::mock_preference('ConsiderLibraryHoursInCirculation', 'close');
425 # shorten loan period
427 $date = C4::Circulation::CalcDateDue( $now, $itemtype, $library1->{branchcode}, $borrower );
428 $expected_duetime = DateTime->now->add( days => 2, hours => 2 );
429 is( $date, $expected_duetime, "Loan period was shortened (but considers the holiday) because ConsiderLibraryHoursInCirculation is set to close time" );
431 t::lib::Mocks::mock_preference( 'ConsiderLibraryHoursInCirculation', 'open' );
434 $date = C4::Circulation::CalcDateDue( $now, $itemtype, $library1->{branchcode}, $borrower );
435 $expected_duetime = DateTime->now->add( days => 2 )->subtract( hours => 4 );
436 is( $date, $expected_duetime, "Loan period was extended (but considers the holiday) because ConsiderLibraryHoursInCirculation is set to open time" );
438 $cache->clear_from_cache($key);
439 $schema->storage->txn_rollback;