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

(-)a/C4/Circulation.pm (+19 lines)
Lines 3968-3973 sub CalcDateDue { Link Here
3968
                # due time doesn't conflict with library open hours, don't need to check
3968
                # due time doesn't conflict with library open hours, don't need to check
3969
                $datedue->add( hours => $loanlength->{$length_key} );
3969
                $datedue->add( hours => $loanlength->{$length_key} );
3970
            }
3970
            }
3971
        } elsif ( $loanlength->{lengthunit} eq 'minutes' ) {
3972
            $datedue->add( minutes => $loanlength->{$length_key} );
3971
        } else {    # days
3973
        } else {    # days
3972
            $datedue->add( days => $loanlength->{$length_key} );
3974
            $datedue->add( days => $loanlength->{$length_key} );
3973
            $datedue->set_hour(23);
3975
            $datedue->set_hour(23);
Lines 3999-4004 sub CalcDateDue { Link Here
3999
                $dur = DateTime::Duration->new( hours => $loanlength->{$length_key} );
4001
                $dur = DateTime::Duration->new( hours => $loanlength->{$length_key} );
4000
            }
4002
            }
4001
        }
4003
        }
4004
        elsif ( $loanlength->{lengthunit} eq 'minutes' ) {
4005
            $dur = DateTime::Duration->new( minutes => $loanlength->{$length_key} );
4006
        }
4002
        else { # days
4007
        else { # days
4003
            $dur = DateTime::Duration->new( days => $loanlength->{$length_key} );
4008
            $dur = DateTime::Duration->new( days => $loanlength->{$length_key} );
4004
        }
4009
        }
Lines 4048-4053 sub CalcDateDue { Link Here
4048
          }
4053
          }
4049
        }
4054
        }
4050
    }
4055
    }
4056
4057
    #check if it's minutes or hourly loan and set due date to close hour, if the current due date is passed closing hours.
4058
    if ($loanlength->{lengthunit} eq 'hours' || $loanlength->{lengthunit} eq 'minutes') {
4059
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branch, days_mode => $daysmode });
4060
        my $dateInfo = $calendar->get_date_info($datedue);
4061
        my $close = dt_from_string($dateInfo->{date} ." ". $dateInfo->{close_hour}, 'iso', C4::Context->tz());
4062
4063
        my $close_datetime = $datedue->clone()->set(hour => $close->hour(), minute=> $close->minute());
4064
4065
        if (DateTime->compare($datedue, $close_datetime) > 0) {
4066
            $datedue = $close_datetime->clone();
4067
        }
4068
    }
4069
4051
    return $datedue;
4070
    return $datedue;
4052
}
4071
}
4053
4072
(-)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 1255-1260 sub open_hours_between { Link Here
1255
    return ($working_hours_between->next()->get_column('hours_between') - $not_used_hours->next()->get_column('not_used_hours'));
1255
    return ($working_hours_between->next()->get_column('hours_between') - $not_used_hours->next()->get_column('not_used_hours'));
1256
}
1256
}
1257
1257
1258
=head2 open_minutes_between
1259
1260
  $minutes = $calendar->open_minutes_between($start_date, $end_date)
1261
1262
Returns the number of minutes between C<$start_date> and C<$end_date>, taking into
1263
account the opening hours of the library.
1264
1265
=cut
1266
1267
sub open_minutes_between {
1268
    my ($self, $start_date, $end_date) = @_;
1269
    my $branchcode = $self->{branchcode};
1270
    my $schema = Koha::Database->new->schema;
1271
    my $dtf = DateTime::Format::Strptime->new(pattern => "%F %T");
1272
    $start_date = $dtf->format_datetime($start_date);
1273
    $end_date = $dtf->format_datetime($end_date);
1274
1275
    my $working_minutes_between = $schema->resultset('DiscreteCalendar')->search(
1276
        {
1277
            branchcode  => $branchcode,
1278
            is_opened   => 1,
1279
        },
1280
        {
1281
            select  => \['sum(time_to_sec(timediff(close_hour, open_hour)) / 60)'],
1282
            as      => [qw /minutes_between/],
1283
            where   => \['date BETWEEN DATE(?) AND DATE(?)', $start_date, $end_date]
1284
        }
1285
    );
1286
1287
    my $loan_day = $schema->resultset('DiscreteCalendar')->search(
1288
        {
1289
            branchcode  => $branchcode,
1290
        },
1291
        {
1292
            order_by => \[ 'ABS(DATEDIFF(date, ?))', $start_date ],
1293
            rows => 1,
1294
        }
1295
    );
1296
1297
    my $return_day = $schema->resultset('DiscreteCalendar')->search(
1298
        {
1299
            branchcode  => $branchcode,
1300
        },
1301
        {
1302
            order_by => \[ 'ABS(DATEDIFF(date, ?))', $end_date ],
1303
            rows => 1,
1304
        }
1305
    );
1306
1307
    #Capture the time portion of the date
1308
    $start_date =~ /\s(.*)/;
1309
    my $loan_date_time = $1;
1310
    $end_date =~ /\s(.*)/;
1311
    my $return_date_time = $1;
1312
1313
    my $not_used_minutes = $schema->resultset('DiscreteCalendar')->search(
1314
        {
1315
            branchcode  => $branchcode,
1316
            is_opened    => 1,
1317
        },
1318
        {
1319
            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()],
1320
            as      => [qw /not_used_minutes/],
1321
        }
1322
    );
1323
1324
    return ($working_minutes_between->next()->get_column('minutes_between') || 0 ) - ( $not_used_minutes->next()->get_column('not_used_minutes') || 0);
1325
}
1326
1258
=head2 addDuration
1327
=head2 addDuration
1259
1328
1260
  my $dt = $calendar->addDuration($date, $dur, $unit)
1329
  my $dt = $calendar->addDuration($date, $dur, $unit)
Lines 1281-1286 sub addDuration { Link Here
1281
        my $return_by_hour = 10;
1350
        my $return_by_hour = 10;
1282
1351
1283
        $dt = $self->addHours($startdate, $add_duration, $return_by_hour);
1352
        $dt = $self->addHours($startdate, $add_duration, $return_by_hour);
1353
    } elsif($unit eq 'minutes') {
1354
        $dt = $self->addMinutes($startdate, $add_duration);
1284
    } else {
1355
    } else {
1285
        # days
1356
        # days
1286
        $dt = $self->addDays($startdate, $add_duration);
1357
        $dt = $self->addDays($startdate, $add_duration);
Lines 1323-1328 sub addHours { Link Here
1323
    return $base_date;
1394
    return $base_date;
1324
}
1395
}
1325
1396
1397
=head2 addMinutes
1398
1399
  $end = $calendar->addMinutes($date, $dur)
1400
1401
Add C<$dur> minutes to C<$start> date.
1402
1403
=cut
1404
1405
sub addMinutes {
1406
    my ( $self, $startdate, $minutes_duration) = @_;
1407
    my $base_date = $startdate->clone();
1408
1409
    $base_date->add_duration($minutes_duration);
1410
1411
    my $dateInfo = $self->get_date_info($base_date);
1412
1413
    my $close_datetime = dt_from_string($dateInfo->{date} ." ". $dateInfo->{close_hour}, 'iso');
1414
1415
    if(DateTime->compare($base_date, $close_datetime) > 0) {
1416
        return $close_datetime;
1417
    }
1418
1419
    return $base_date;
1420
}
1421
1326
=head2 addDays
1422
=head2 addDays
1327
1423
1328
  $date = $calendar->addDays($start, $duration)
1424
  $date = $calendar->addDays($start, $duration)
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt (+3 lines)
Lines 271-276 Link Here
271
                                                        <span>Days</span>
271
                                                        <span>Days</span>
272
                                                    [% ELSIF ( lengthunit == 'hours') %]
272
                                                    [% ELSIF ( lengthunit == 'hours') %]
273
                                                        <span>Hours</span>
273
                                                        <span>Hours</span>
274
                                                    [% ELSIF ( lengthunit == 'minutes') %]
275
                                                        <span>Minutes</span>
274
                                                    [% ELSE %]
276
                                                    [% ELSE %]
275
                                                        <span>Undefined</span>
277
                                                        <span>Undefined</span>
276
                                                    [% END %]
278
                                                    [% END %]
Lines 460-465 Link Here
460
                                  <select name="lengthunit" id="lengthunit">
462
                                  <select name="lengthunit" id="lengthunit">
461
                                    <option value="days" selected="selected">Days</option>
463
                                    <option value="days" selected="selected">Days</option>
462
                                    <option value="hours">Hours</option>
464
                                    <option value="hours">Hours</option>
465
                                    <option value="minutes">Minutes</option>
463
                                  </select>
466
                                  </select>
464
                                </td>
467
                                </td>
465
                                <td>
468
                                <td>
(-)a/t/db_dependent/MinuteLoans.t (-1 / +267 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 $builder = t::lib::TestBuilder->new();
25
my $branch = $builder->build({ source => 'Branch' })->{ branchcode };
26
27
my $calendar = Koha::DiscreteCalendar->new( { branchcode => $branch } );
28
29
#######################################
30
# Add minutes and open minutes between
31
#######################################
32
33
my $start_date = dt_from_string->set( hour => 14, minute => 15, second => 0);
34
my $end_date = dt_from_string->set( hour => 14, minute => 20, second => 0);
35
36
is($calendar->open_minutes_between($start_date, $end_date), 5, "Item returned 5 minutes late");
37
38
#Adding 10 minutes
39
my $ten_mins_duration = DateTime::Duration->new( minutes => 10);
40
is($calendar->addDuration($start_date, $ten_mins_duration, 'minutes' ),
41
    dt_from_string->set( hour => 14, minute => 25, second => 0),
42
        'Added 10 minutes to loan' );
43
44
#Adding 10 minutes, passed closing hour
45
$start_date = dt_from_string->set( hour => 16, minute => 51, second => 0 );
46
is($calendar->addDuration($start_date, $ten_mins_duration, 'minutes' ),
47
    dt_from_string->set( hour => 17, minute => 0, second => 0),
48
        'Added 10 minutes, due date passed closing hours, set due date to same day at close hour' );
49
50
#Item returned next open day after a holiday.
51
my $open_minutes_today = DateTime->today->add(hours =>11, minutes => 10);
52
my $open_minutes_tomorrow = DateTime->today->add(days =>1);
53
my $open_minutes_day_after_tomorrow = DateTime->today->add(days => 2, hours =>11);
54
55
$calendar->edit_holiday({
56
    title => "Single holiday Today",
57
    holiday_type => $Koha::DiscreteCalendar::HOLIDAYS->{EXCEPTION},
58
    start_date => $open_minutes_tomorrow,
59
    end_date =>$open_minutes_tomorrow
60
});
61
62
is($calendar->open_minutes_between($open_minutes_today, $open_minutes_day_after_tomorrow),
63
    530,
64
    "Correct open minutes, with a holiday in between");
65
66
######################
67
# DueDate calculation
68
######################
69
70
#Set syspref ReturnBeforeExpiry = 1 and useDaysMode = 'Days'
71
t::lib::Mocks::mock_preference('ReturnBeforeExpiry', 1);
72
t::lib::Mocks::mock_preference('useDaysMode', 'Days');
73
74
my $issuelength = 25;
75
my $renewalperiod = 5;
76
my $lengthunit = 'minutes';
77
my $dateexpiry = DateTime->today->add(years => 1);
78
79
my $mock_loan_length = [
80
    ['issuelength', 'renewalperiod', 'lengthunit'],
81
    [$issuelength, $renewalperiod, $lengthunit]
82
];
83
84
my $categorycode = $builder->build({ source => 'Category' })->{categorycode};
85
my $patron   = $builder->build_object({
86
    class => 'Koha::Patrons',
87
    value => {
88
        categorycode => $categorycode,
89
        branchcode   => $branch,
90
        dateexpiry   => $dateexpiry,
91
    },
92
});
93
my $itemtype = $builder->build_object( { class => 'Koha::ItemTypes' } )->itemtype;
94
$dbh->do("INSERT INTO circulation_rules (branchcode, categorycode, itemtype, rule_name, rule_value) VALUES('$branch', '$categorycode', '$itemtype', 'issuelength', '$issuelength')");
95
$dbh->do("INSERT INTO circulation_rules (branchcode, categorycode, itemtype, rule_name, rule_value) VALUES('$branch', '$categorycode', '$itemtype', 'lengthunit', '$lengthunit')");
96
97
98
my $date = C4::Circulation::CalcDateDue( $today, $itemtype, $branch, $patron );
99
is($date, DateTime->today->add(minutes => $issuelength), "Due date calculated correctly");
100
101
#passed closing hour
102
$issuelength = 1300;
103
Koha::CirculationRules->set_rules(
104
    {
105
        categorycode => $categorycode,
106
        branchcode   => $branch,
107
        itemtype     => $itemtype,
108
        rules        => {
109
            issuelength => $issuelength,
110
        }
111
    }
112
);
113
114
$date = C4::Circulation::CalcDateDue( $today, $itemtype, $branch, $patron );
115
is($date, DateTime->today->add(hours =>17), "Due date passed close hour, item due at close hour");
116
117
#############################
118
# Chargeable minutes between
119
############################
120
121
$issuelength = 25;
122
Koha::CirculationRules->set_rules(
123
    {
124
        categorycode => $categorycode,
125
        branchcode   => $branch,
126
        itemtype     => $itemtype,
127
        rules        => {
128
            issuelength => $issuelength,
129
        }
130
    }
131
);
132
133
my $date_due = DateTime->today;
134
my $date_returned = DateTime->today->add(minutes => 40);
135
136
my $chargeable_units = C4::Overdues::get_chargeable_units('minutes', $date_due, $date_returned, $branch);
137
is($chargeable_units, 40, "40 minutes of use");
138
139
######################
140
# Fines calculation
141
#####################
142
143
my $biblio = $builder->build_sample_biblio();
144
145
my $item = $builder->build(
146
    {
147
        source => 'Item',
148
        value  => {
149
            biblionumber     => $biblio->biblionumber,
150
            homebranch       => $branch,
151
            holdingbranch    => $branch,
152
            replacementprice => '5.00',
153
        },
154
    }
155
);
156
my $rule = $builder->schema->resultset('CirculationRule')->search({
157
    branchcode                    => undef,
158
    categorycode                  => undef,
159
    itemtype                      => undef,
160
});
161
$rule->delete_all if $rule;
162
# FinesIncludeGracePeriod included
163
t::lib::Mocks::mock_preference('FinesIncludeGracePeriod', 1);
164
$builder->build(
165
    {
166
        source => 'CirculationRule',
167
        value  => {
168
            branchcode                    => undef,
169
            categorycode                  => undef,
170
            itemtype                      => undef,
171
            rule_name                     => 'fine',
172
            rule_value                    => '1',
173
        },
174
    }
175
);
176
$builder->build(
177
    {
178
        source => 'CirculationRule',
179
        value  => {
180
            branchcode                    => undef,
181
            categorycode                  => undef,
182
            itemtype                      => undef,
183
            rule_name                     => 'lengthunit',
184
            rule_value                    => 'minutes',
185
        },
186
    }
187
);
188
$builder->build(
189
    {
190
        source => 'CirculationRule',
191
        value  => {
192
            branchcode                    => undef,
193
            categorycode                  => undef,
194
            itemtype                      => undef,
195
            rule_name                     => 'finedays',
196
            rule_value                    => 0,
197
        },
198
    }
199
);
200
$builder->build(
201
    {
202
        source => 'CirculationRule',
203
        value  => {
204
            branchcode                    => undef,
205
            categorycode                  => undef,
206
            itemtype                      => undef,
207
            rule_name                     => 'firstremind',
208
            rule_value                    => 5,
209
        },
210
    }
211
);
212
$builder->build(
213
    {
214
        source => 'CirculationRule',
215
        value  => {
216
            branchcode                    => undef,
217
            categorycode                  => undef,
218
            itemtype                      => undef,
219
            rule_name                     => 'chargeperiod',
220
            rule_value                    => 1,
221
        },
222
    }
223
);
224
$builder->build(
225
    {
226
        source => 'CirculationRule',
227
        value  => {
228
            branchcode                    => undef,
229
            categorycode                  => undef,
230
            itemtype                      => undef,
231
            rule_name                     => 'overduefinescap',
232
            rule_value                    => 9000,
233
        },
234
    }
235
);
236
$builder->build(
237
    {
238
        source => 'CirculationRule',
239
        value  => {
240
            branchcode                    => undef,
241
            categorycode                  => undef,
242
            itemtype                      => undef,
243
            rule_name                     => 'cap_fine_to_replacement_price',
244
            rule_value                    => 0,
245
        },
246
    }
247
);
248
249
my $start_dt = dt_from_string->set( hour => 15, minute => 0, second => 0);
250
my $end_dt = dt_from_string->set( hour => 15, minute => 4, second => 0);
251
252
my @amount = C4::Overdues::CalcFine( $item, $patron->{categorycode}, $branch, $start_dt, $end_dt );
253
is($amount[0], 0, "No fine when under the fine grace period");
254
255
$end_dt = dt_from_string->set( hour => 15, minute => 6, second => 0);
256
@amount = C4::Overdues::CalcFine( $item, $patron->{categorycode}, $branch, $start_dt, $end_dt );
257
is($amount[0], 6, "6\$ fine for 6 minutes delay, fine grace period included");
258
259
# FinesIncludeGracePeriod not included
260
t::lib::Mocks::mock_preference('FinesIncludeGracePeriod', 0);
261
262
@amount = C4::Overdues::CalcFine( $item, $patron->{categorycode}, $branch, $start_dt, $end_dt );
263
is($amount[0], 1, "1\$ fine for 6 minutes delay, fine grace period not included");
264
265
$schema->storage->txn_rollback;
266
267
1;

Return to bug 17983