View | Details | Raw Unified | Return to bug 17983
Collapse All | Expand All

(-)a/C4/Circulation.pm (-1 / +19 lines)
Lines 3702-3712 sub CalcDateDue { Link Here
3702
        }
3702
        }
3703
    );
3703
    );
3704
3704
3705
    my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branch });
3705
    # calculate the datedue as normal
3706
    # calculate the datedue as normal
3706
    if ( $daysmode eq 'Days' )
3707
    if ( $daysmode eq 'Days' )
3707
    {    # ignoring calendar
3708
    {    # ignoring calendar
3708
        if ( $loanlength->{lengthunit} eq 'hours' ) {
3709
        if ( $loanlength->{lengthunit} eq 'hours' ) {
3709
            $datedue->add( hours => $loanlength->{$length_key} );
3710
            $datedue->add( hours => $loanlength->{$length_key} );
3711
	    } elsif ( $loanlength->{lengthunit} eq 'minutes' ) {
3712
	        $datedue->add( minutes => $loanlength->{$length_key} );
3710
        } else {    # days
3713
        } else {    # days
3711
            $datedue->add( days => $loanlength->{$length_key} );
3714
            $datedue->add( days => $loanlength->{$length_key} );
3712
            $datedue->set_hour(23);
3715
            $datedue->set_hour(23);
Lines 3717-3726 sub CalcDateDue { Link Here
3717
        if ($loanlength->{lengthunit} eq 'hours') {
3720
        if ($loanlength->{lengthunit} eq 'hours') {
3718
            $dur = DateTime::Duration->new( hours => $loanlength->{$length_key});
3721
            $dur = DateTime::Duration->new( hours => $loanlength->{$length_key});
3719
        }
3722
        }
3723
	    elsif ($loanlength->{lengthunit} eq 'minutes') {
3724
	        $dur = DateTime::Duration->new( minutes => $loanlength->{$length_key});
3725
	    }
3720
        else { # days
3726
        else { # days
3721
            $dur = DateTime::Duration->new( days => $loanlength->{$length_key});
3727
            $dur = DateTime::Duration->new( days => $loanlength->{$length_key});
3722
        }
3728
        }
3723
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branch, days_mode => $daysmode });
3724
        $datedue = $calendar->addDuration( $datedue, $dur, $loanlength->{lengthunit} );
3729
        $datedue = $calendar->addDuration( $datedue, $dur, $loanlength->{lengthunit} );
3725
        if ($loanlength->{lengthunit} eq 'days') {
3730
        if ($loanlength->{lengthunit} eq 'days') {
3726
            $datedue->set_hour(23);
3731
            $datedue->set_hour(23);
Lines 3766-3771 sub CalcDateDue { Link Here
3766
          }
3771
          }
3767
        }
3772
        }
3768
    }
3773
    }
3774
3775
    #check if it's minutes or hourly loan and set due date to close hour, if the current due date is passed closing hours.
3776
    if ($loanlength->{lengthunit} eq 'hours' || $loanlength->{lengthunit} eq 'minutes') {
3777
	    my $dateInfo = $calendar->get_date_info($datedue);
3778
	    my $close = dt_from_string($dateInfo->{date} ." ". $dateInfo->{close_hour}, 'iso', C4::Context->tz());
3779
3780
	    my $close_datetime = $datedue->clone()->set(hour => $close->hour(), minute=> $close->minute());
3781
3782
	    if(DateTime->compare($datedue, $close_datetime) > 0) {
3783
	        $datedue = $close_datetime->clone();
3784
	    }
3785
    }
3786
3769
    return $datedue;
3787
    return $datedue;
3770
}
3788
}
3771
3789
(-)a/C4/Overdues.pm (+12 lines)
Lines 333-338 sub get_chargeable_units { Link Here
333
        }
333
        }
334
        return $charge_duration->in_units('hours');
334
        return $charge_duration->in_units('hours');
335
    }
335
    }
336
    elsif($unit eq 'minutes'){
337
	    if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
338
	        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
339
	        $charge_duration = $calendar->open_minutes_between( $date_due, $date_returned );
340
	    } else {
341
	        $charge_duration = $date_returned->delta_ms( $date_due );
342
        }
343
	    if($charge_duration == 0 && $charge_duration * 60 > 0) {
344
	        return 1;
345
	}
346
	    return $charge_duration;
347
    }
336
    else { # days
348
    else { # days
337
        if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
349
        if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
338
            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
350
            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
(-)a/Koha/DiscreteCalendar.pm (+96 lines)
Lines 1246-1251 sub open_hours_between { Link Here
1246
    return ($working_hours_between->next()->get_column('hours_between') - $not_used_hours->next()->get_column('not_used_hours'));
1246
    return ($working_hours_between->next()->get_column('hours_between') - $not_used_hours->next()->get_column('not_used_hours'));
1247
}
1247
}
1248
1248
1249
=head2 open_minutes_between
1250
1251
  $minutes = $calendar->open_minutes_between($start_date, $end_date)
1252
1253
Returns the number of minutes between C<$start_date> and C<$end_date>, taking into
1254
account the opening hours of the library.
1255
1256
=cut
1257
1258
sub open_minutes_between {
1259
    my ($self, $start_date, $end_date) = @_;
1260
    my $branchcode = $self->{branchcode};
1261
    my $schema = Koha::Database->new->schema;
1262
    my $dtf = DateTime::Format::Strptime->new(pattern => "%F %T");
1263
    $start_date = $dtf->format_datetime($start_date);
1264
    $end_date = $dtf->format_datetime($end_date);
1265
1266
    my $working_minutes_between = $schema->resultset('DiscreteCalendar')->search(
1267
	    {
1268
	        branchcode  => $branchcode,
1269
	        is_opened   => 1,
1270
        },
1271
	    {
1272
	        select  => \['sum(time_to_sec(timediff(close_hour, open_hour)) / 60)'],
1273
	        as      => [qw /minutes_between/],
1274
	        where   => \['date BETWEEN DATE(?) AND DATE(?)', $start_date, $end_date]
1275
        }
1276
    );
1277
1278
    my $loan_day = $schema->resultset('DiscreteCalendar')->search(
1279
	    {
1280
	        branchcode  => $branchcode,
1281
	    },
1282
	    {
1283
	        order_by => \[ 'ABS(DATEDIFF(date, ?))', $start_date ],
1284
	        rows => 1,
1285
	    }
1286
    );
1287
1288
    my $return_day = $schema->resultset('DiscreteCalendar')->search(
1289
	    {
1290
	        branchcode  => $branchcode,
1291
	    },
1292
	    {
1293
	        order_by => \[ 'ABS(DATEDIFF(date, ?))', $end_date ],
1294
	        rows => 1,
1295
        }
1296
    );
1297
1298
    #Capture the time portion of the date
1299
    $start_date =~ /\s(.*)/;
1300
    my $loan_date_time = $1;
1301
    $end_date =~ /\s(.*)/;
1302
    my $return_date_time = $1;
1303
1304
    my $not_used_minutes = $schema->resultset('DiscreteCalendar')->search(
1305
	    {
1306
	        branchcode  => $branchcode,
1307
	        is_opened    => 1,
1308
	    },
1309
	    {
1310
	        select  => \[ '(time_to_sec(timediff(?, ?)) + time_to_sec(timediff(?, ?)) ) / 60', $return_day->next()->close_hour(), $return_date_time, $loan_date_time, $loan_day->next()->open_hour()],
1311
	        as      => [qw /not_used_minutes/],
1312
	    }
1313
    );
1314
1315
    return ($working_minutes_between->next()->get_column('minutes_between') - $not_used_minutes->next()->get_column('not_used_minutes'));
1316
}
1317
1249
=head2 addDuration
1318
=head2 addDuration
1250
1319
1251
  my $dt = $calendar->addDuration($date, $dur, $unit)
1320
  my $dt = $calendar->addDuration($date, $dur, $unit)
Lines 1272-1277 sub addDuration { Link Here
1272
        my $return_by_hour = 10;
1341
        my $return_by_hour = 10;
1273
1342
1274
        $dt = $self->addHours($startdate, $add_duration, $return_by_hour);
1343
        $dt = $self->addHours($startdate, $add_duration, $return_by_hour);
1344
    } elsif($unit eq 'minutes') {
1345
	    $dt = $self->addMinutes($startdate, $add_duration);
1275
    } else {
1346
    } else {
1276
        # days
1347
        # days
1277
        $dt = $self->addDays($startdate, $add_duration);
1348
        $dt = $self->addDays($startdate, $add_duration);
Lines 1314-1319 sub addHours { Link Here
1314
    return $base_date;
1385
    return $base_date;
1315
}
1386
}
1316
1387
1388
=head2 addMinutes
1389
1390
  $end = $calendar->addMinutes($date, $dur)
1391
1392
Add C<$dur> minutes to C<$start> date.
1393
1394
=cut
1395
1396
sub addMinutes {
1397
    my ( $self, $startdate, $minutes_duration) = @_;
1398
    my $base_date = $startdate->clone();
1399
1400
    $base_date->add_duration($minutes_duration);
1401
1402
    my $dateInfo = $self->get_date_info($base_date);
1403
1404
    my $close_datetime = dt_from_string($dateInfo->{date} ." ". $dateInfo->{close_hour}, 'iso');
1405
1406
    if(DateTime->compare($base_date, $close_datetime) > 0) {
1407
	    return $close_datetime;
1408
    }
1409
1410
    return $base_date;
1411
}
1412
1317
=head2 addDays
1413
=head2 addDays
1318
1414
1319
  $date = $calendar->addDays($start, $duration)
1415
  $date = $calendar->addDays($start, $duration)
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt (+3 lines)
Lines 273-278 Link Here
273
                                                        <span>Days</span>
273
                                                        <span>Days</span>
274
                                                    [% ELSIF ( lengthunit == 'hours') %]
274
                                                    [% ELSIF ( lengthunit == 'hours') %]
275
                                                        <span>Hours</span>
275
                                                        <span>Hours</span>
276
                                                    [% ELSIF ( lengthunit == 'minutes') %]
277
                                                        <span>Minutes</span>
276
                                                    [% ELSE %]
278
                                                    [% ELSE %]
277
                                                        <span>Undefined</span>
279
                                                        <span>Undefined</span>
278
                                                    [% END %]
280
                                                    [% END %]
Lines 457-462 Link Here
457
                                  <select name="lengthunit" id="lengthunit">
459
                                  <select name="lengthunit" id="lengthunit">
458
                                    <option value="days" selected="selected">Days</option>
460
                                    <option value="days" selected="selected">Days</option>
459
                                    <option value="hours">Hours</option>
461
                                    <option value="hours">Hours</option>
462
                                    <option value="minutes">Minutes</option>
460
                                  </select>
463
                                  </select>
461
                                </td>
464
                                </td>
462
                                <td>
465
                                <td>
(-)a/t/db_dependent/MinuteLoans.t (-1 / +283 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/env perl
2
3
use strict;
4
use warnings;
5
6
use DateTime;
7
use DateTime::Duration;
8
use Test::More tests => 10;
9
use Test::MockModule;
10
11
use C4::Circulation;
12
use C4::Overdues;
13
use Koha::DateUtils qw( dt_from_string );
14
use Koha::DiscreteCalendar;
15
16
use t::lib::Mocks;
17
use t::lib::TestBuilder;
18
19
my $dbh = C4::Context->dbh();
20
my $today = DateTime->today;
21
my $schema = Koha::Database->new->schema;
22
$schema->storage->txn_begin;
23
24
my $branch1 = 'Library1';
25
26
# This test assumes branchcode 'UPL' exist inside discrete_calendar table of your database
27
# Should probably find a better alternative
28
$schema->resultset('DiscreteCalendar')->search({
29
    branchcode  => 'UPL'
30
})->update_all({
31
    is_opened    => 1,
32
    holiday_type => '',
33
    note         => '',
34
    open_hour    => '08:00:00',
35
    close_hour   => '16:00:00'
36
});
37
38
Koha::DiscreteCalendar->add_new_branch('UPL', $branch1);
39
40
my $calendar = Koha::DiscreteCalendar->new( { branchcode => $branch1 } );
41
42
#######################################
43
# Add minutes and open minutes between
44
#######################################
45
46
my $start_date = dt_from_string->set( hour => 14, minute => 15, second => 0);
47
my $end_date = dt_from_string->set( hour => 14, minute => 20, second => 0);
48
49
is($calendar->open_minutes_between($start_date, $end_date), 5, "Item returned 5 minutes late");
50
51
#Adding 10 minutes
52
my $ten_mins_duration = DateTime::Duration->new( minutes => 10);
53
is($calendar->addDuration($start_date, $ten_mins_duration, 'minutes' ),
54
    dt_from_string->set( hour => 14, minute => 25, second => 0),
55
        'Added 10 minutes to loan' );
56
57
#Adding 10 minutes, passed closing hour
58
$start_date = dt_from_string->set( hour => 15, minute => 51, second => 0 );
59
is($calendar->addDuration($start_date, $ten_mins_duration, 'minutes' ),
60
    dt_from_string->set( hour => 16, minute => 0, second => 0),
61
        'Added 10 minutes, due date passed closing hours, set due date to same day at close hour' );
62
63
#Item returned next open day after a holiday.
64
my $open_minutes_today = DateTime->today->add(hours =>10, minutes => 10);
65
my $open_minutes_tomorrow = DateTime->today->add(days =>1);
66
my $open_minutes_day_after_tomorrow = DateTime->today->add(days => 2, hours =>11);
67
68
$calendar->edit_holiday({
69
    title => "Single holiday Today",
70
    holiday_type => $Koha::DiscreteCalendar::HOLIDAYS->{EXCEPTION},
71
    start_date => $open_minutes_tomorrow,
72
    end_date =>$open_minutes_tomorrow
73
});
74
75
is($calendar->open_minutes_between($open_minutes_today, $open_minutes_day_after_tomorrow),
76
    530,
77
    "Correct open minutes, with a holiday in between");
78
79
######################
80
# DueDate calculation
81
######################
82
83
#Set syspref ReturnBeforeExpiry = 1 and useDaysMode = 'Days'
84
t::lib::Mocks::mock_preference('ReturnBeforeExpiry', 1);
85
t::lib::Mocks::mock_preference('useDaysMode', 'Days');
86
87
my $issuelength = 25;
88
my $renewalperiod = 5;
89
my $lengthunit = 'minutes';
90
my $dateexpiry = DateTime->today->add(years => 1);
91
92
my $mock_loan_length = [
93
    ['issuelength', 'renewalperiod', 'lengthunit'],
94
    [$issuelength, $renewalperiod, $lengthunit]
95
];
96
97
my $categorycode = 'X';
98
my $itemtype = 'MINUTES';
99
my $borrower = {categorycode => 'X', dateexpiry => $dateexpiry};
100
$dbh->do("INSERT INTO itemtypes (itemtype, parent_type) VALUES('$itemtype', NULL)");
101
$dbh->do("INSERT INTO categories (categorycode) VALUES('$categorycode')");
102
$dbh->do("INSERT INTO branches (branchcode, branchname) VALUES('$branch1', '$branch1')");
103
$dbh->do("INSERT INTO circulation_rules (branchcode, categorycode, itemtype, rule_name, rule_value) VALUES('$branch1', '$categorycode', '$itemtype', 'issuelength', '$issuelength')");
104
$dbh->do("INSERT INTO circulation_rules (branchcode, categorycode, itemtype, rule_name, rule_value) VALUES('$branch1', '$categorycode', '$itemtype', 'lengthunit', '$lengthunit')");
105
106
107
my $date = C4::Circulation::CalcDateDue( $today, $itemtype, $branch1, $borrower );
108
is($date, DateTime->today->add(minutes => $issuelength), "Due date calculated correctly");
109
110
#passed closing hour
111
$issuelength = 1300;
112
$dbh->do("UPDATE circulation_rules SET rule_value=$issuelength where branchcode='$branch1' and itemtype='$itemtype' and rule_name='issuelength'");
113
114
$date = C4::Circulation::CalcDateDue( $today, $itemtype, $branch1, $borrower );
115
is($date, DateTime->today->add(hours =>16), "Due date passed close hour, item due at close hour");
116
117
#############################
118
# Chargeable minutes between
119
############################
120
121
$issuelength = 25;
122
$dbh->do("UPDATE circulation_rules SET rule_value=$issuelength where branchcode='$branch1' and itemtype='$itemtype' and rule_name='issuelength'");
123
124
my $date_due = DateTime->today;
125
my $date_returned = DateTime->today->add(minutes => 40);
126
127
my $chargeable_units = C4::Overdues::get_chargeable_units('minutes', $date_due, $date_returned, $branch1);
128
is($chargeable_units, 40, "40 minutes of use");
129
130
######################
131
# Fines calculation
132
#####################
133
134
my $builder = t::lib::TestBuilder->new();
135
136
my $category = $builder->build(
137
    {
138
        source => 'Category',
139
    }
140
);
141
142
my $patron = $builder->build(
143
    {
144
        source => 'Borrower',
145
        value  => {
146
            categorycode => $category->{categorycode},
147
            branchcode   => $branch1,
148
        },
149
    }
150
);
151
152
my $biblio = $builder->build(
153
    {
154
        source => 'Biblio',
155
        value  => {
156
            branchcode => $branch1,
157
        },
158
    }
159
);
160
161
my $item = $builder->build(
162
    {
163
        source => 'Item',
164
        value  => {
165
            biblionumber     => $biblio->{biblionumber},
166
            homebranch       => $branch1,
167
            holdingbranch    => $branch1,
168
            replacementprice => '5.00',
169
        },
170
    }
171
);
172
my $rule = $builder->schema->resultset('CirculationRule')->search({
173
    branchcode                    => undef,
174
    categorycode                  => undef,
175
    itemtype                      => undef,
176
});
177
$rule->delete_all if $rule;
178
# FinesIncludeGracePeriod included
179
t::lib::Mocks::mock_preference('FinesIncludeGracePeriod', 1);
180
$builder->build(
181
    {
182
        source => 'CirculationRule',
183
        value  => {
184
            branchcode                    => undef,
185
            categorycode                  => undef,
186
            itemtype                      => undef,
187
            rule_name                     => 'fine',
188
            rule_value                    => '1',
189
        },
190
    }
191
);
192
$builder->build(
193
    {
194
        source => 'CirculationRule',
195
        value  => {
196
            branchcode                    => undef,
197
            categorycode                  => undef,
198
            itemtype                      => undef,
199
            rule_name                     => 'lengthunit',
200
            rule_value                    => 'minutes',
201
        },
202
    }
203
);
204
$builder->build(
205
    {
206
        source => 'CirculationRule',
207
        value  => {
208
            branchcode                    => undef,
209
            categorycode                  => undef,
210
            itemtype                      => undef,
211
            rule_name                     => 'finedays',
212
            rule_value                    => 0,
213
        },
214
    }
215
);
216
$builder->build(
217
    {
218
        source => 'CirculationRule',
219
        value  => {
220
            branchcode                    => undef,
221
            categorycode                  => undef,
222
            itemtype                      => undef,
223
            rule_name                     => 'firstremind',
224
            rule_value                    => 5,
225
        },
226
    }
227
);
228
$builder->build(
229
    {
230
        source => 'CirculationRule',
231
        value  => {
232
            branchcode                    => undef,
233
            categorycode                  => undef,
234
            itemtype                      => undef,
235
            rule_name                     => 'chargeperiod',
236
            rule_value                    => 1,
237
        },
238
    }
239
);
240
$builder->build(
241
    {
242
        source => 'CirculationRule',
243
        value  => {
244
            branchcode                    => undef,
245
            categorycode                  => undef,
246
            itemtype                      => undef,
247
            rule_name                     => 'overduefinescap',
248
            rule_value                    => 9000,
249
        },
250
    }
251
);
252
$builder->build(
253
    {
254
        source => 'CirculationRule',
255
        value  => {
256
            branchcode                    => undef,
257
            categorycode                  => undef,
258
            itemtype                      => undef,
259
            rule_name                     => 'cap_fine_to_replacement_price',
260
            rule_value                    => 0,
261
        },
262
    }
263
);
264
265
my $start_dt = dt_from_string->set( hour => 15, minute => 0, second => 0);
266
my $end_dt = dt_from_string->set( hour => 15, minute => 4, second => 0);
267
268
my @amount = C4::Overdues::CalcFine( $item, $patron->{categorycode}, $branch1, $start_dt, $end_dt );
269
is($amount[0], 0, "No fine when under the fine grace period");
270
271
$end_dt = dt_from_string->set( hour => 15, minute => 6, second => 0);
272
@amount = C4::Overdues::CalcFine( $item, $patron->{categorycode}, $branch1, $start_dt, $end_dt );
273
is($amount[0], 6, "6\$ fine for 6 minutes delay, fine grace period included");
274
275
# FinesIncludeGracePeriod not included
276
t::lib::Mocks::mock_preference('FinesIncludeGracePeriod', 0);
277
278
@amount = C4::Overdues::CalcFine( $item, $patron->{categorycode}, $branch1, $start_dt, $end_dt );
279
is($amount[0], 1, "1\$ fine for 6 minutes delay, fine grace period not included");
280
281
$schema->storage->txn_rollback;
282
283
1;

Return to bug 17983