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

(-)a/C4/Circulation.pm (-1 / +19 lines)
Lines 3780-3790 sub CalcDateDue { Link Here
3780
        }
3780
        }
3781
    );
3781
    );
3782
3782
3783
    my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branch });
3783
    # calculate the datedue as normal
3784
    # calculate the datedue as normal
3784
    if ( $daysmode eq 'Days' )
3785
    if ( $daysmode eq 'Days' )
3785
    {    # ignoring calendar
3786
    {    # ignoring calendar
3786
        if ( $loanlength->{lengthunit} eq 'hours' ) {
3787
        if ( $loanlength->{lengthunit} eq 'hours' ) {
3787
            $datedue->add( hours => $loanlength->{$length_key} );
3788
            $datedue->add( hours => $loanlength->{$length_key} );
3789
	    } elsif ( $loanlength->{lengthunit} eq 'minutes' ) {
3790
	        $datedue->add( minutes => $loanlength->{$length_key} );
3788
        } else {    # days
3791
        } else {    # days
3789
            $datedue->add( days => $loanlength->{$length_key} );
3792
            $datedue->add( days => $loanlength->{$length_key} );
3790
            $datedue->set_hour(23);
3793
            $datedue->set_hour(23);
Lines 3795-3804 sub CalcDateDue { Link Here
3795
        if ($loanlength->{lengthunit} eq 'hours') {
3798
        if ($loanlength->{lengthunit} eq 'hours') {
3796
            $dur = DateTime::Duration->new( hours => $loanlength->{$length_key});
3799
            $dur = DateTime::Duration->new( hours => $loanlength->{$length_key});
3797
        }
3800
        }
3801
	    elsif ($loanlength->{lengthunit} eq 'minutes') {
3802
	        $dur = DateTime::Duration->new( minutes => $loanlength->{$length_key});
3803
	    }
3798
        else { # days
3804
        else { # days
3799
            $dur = DateTime::Duration->new( days => $loanlength->{$length_key});
3805
            $dur = DateTime::Duration->new( days => $loanlength->{$length_key});
3800
        }
3806
        }
3801
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branch, days_mode => $daysmode });
3802
        $datedue = $calendar->addDuration( $datedue, $dur, $loanlength->{lengthunit} );
3807
        $datedue = $calendar->addDuration( $datedue, $dur, $loanlength->{lengthunit} );
3803
        if ($loanlength->{lengthunit} eq 'days') {
3808
        if ($loanlength->{lengthunit} eq 'days') {
3804
            $datedue->set_hour(23);
3809
            $datedue->set_hour(23);
Lines 3844-3849 sub CalcDateDue { Link Here
3844
          }
3849
          }
3845
        }
3850
        }
3846
    }
3851
    }
3852
3853
    #check if it's minutes or hourly loan and set due date to close hour, if the current due date is passed closing hours.
3854
    if ($loanlength->{lengthunit} eq 'hours' || $loanlength->{lengthunit} eq 'minutes') {
3855
	    my $dateInfo = $calendar->get_date_info($datedue);
3856
	    my $close = dt_from_string($dateInfo->{date} ." ". $dateInfo->{close_hour}, 'iso', C4::Context->tz());
3857
3858
	    my $close_datetime = $datedue->clone()->set(hour => $close->hour(), minute=> $close->minute());
3859
3860
	    if(DateTime->compare($datedue, $close_datetime) > 0) {
3861
	        $datedue = $close_datetime->clone();
3862
	    }
3863
    }
3864
3847
    return $datedue;
3865
    return $datedue;
3848
}
3866
}
3849
3867
(-)a/C4/Overdues.pm (+12 lines)
Lines 330-335 sub get_chargeable_units { Link Here
330
        }
330
        }
331
        return $charge_duration->in_units('hours');
331
        return $charge_duration->in_units('hours');
332
    }
332
    }
333
    elsif($unit eq 'minutes'){
334
	    if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
335
	        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
336
	        $charge_duration = $calendar->open_minutes_between( $date_due, $date_returned );
337
	    } else {
338
	        $charge_duration = $date_returned->delta_ms( $date_due );
339
        }
340
	    if($charge_duration == 0 && $charge_duration * 60 > 0) {
341
	        return 1;
342
	}
343
	    return $charge_duration;
344
    }
333
    else { # days
345
    else { # days
334
        if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
346
        if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
335
            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
347
            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') - $not_used_minutes->next()->get_column('not_used_minutes'));
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 266-271 Link Here
266
                                                        <span>Days</span>
266
                                                        <span>Days</span>
267
                                                    [% ELSIF ( lengthunit == 'hours') %]
267
                                                    [% ELSIF ( lengthunit == 'hours') %]
268
                                                        <span>Hours</span>
268
                                                        <span>Hours</span>
269
                                                    [% ELSIF ( lengthunit == 'minutes') %]
270
                                                        <span>Minutes</span>
269
                                                    [% ELSE %]
271
                                                    [% ELSE %]
270
                                                        <span>Undefined</span>
272
                                                        <span>Undefined</span>
271
                                                    [% END %]
273
                                                    [% END %]
Lines 450-455 Link Here
450
                                  <select name="lengthunit" id="lengthunit">
452
                                  <select name="lengthunit" id="lengthunit">
451
                                    <option value="days" selected="selected">Days</option>
453
                                    <option value="days" selected="selected">Days</option>
452
                                    <option value="hours">Hours</option>
454
                                    <option value="hours">Hours</option>
455
                                    <option value="minutes">Minutes</option>
453
                                  </select>
456
                                  </select>
454
                                </td>
457
                                </td>
455
                                <td>
458
                                <td>
(-)a/t/db_dependent/MinuteLoans.t (-1 / +278 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 = 'X';
85
my $itemtype = 'MINUTES';
86
my $borrower = {categorycode => 'X', dateexpiry => $dateexpiry};
87
$dbh->do("INSERT INTO itemtypes (itemtype, parent_type) VALUES('$itemtype', NULL)");
88
$dbh->do("INSERT INTO categories (categorycode) VALUES('$categorycode')");
89
$dbh->do("INSERT INTO circulation_rules (branchcode, categorycode, itemtype, rule_name, rule_value) VALUES('$branch', '$categorycode', '$itemtype', 'issuelength', '$issuelength')");
90
$dbh->do("INSERT INTO circulation_rules (branchcode, categorycode, itemtype, rule_name, rule_value) VALUES('$branch', '$categorycode', '$itemtype', 'lengthunit', '$lengthunit')");
91
92
93
my $date = C4::Circulation::CalcDateDue( $today, $itemtype, $branch, $borrower );
94
is($date, DateTime->today->add(minutes => $issuelength), "Due date calculated correctly");
95
96
#passed closing hour
97
$issuelength = 1300;
98
Koha::CirculationRules->set_rules(
99
    {
100
        categorycode => $categorycode,
101
        branchcode   => $branch,
102
        itemtype     => $itemtype,
103
        rules        => {
104
            issuelength => $issuelength,
105
        }
106
    }
107
);
108
109
$date = C4::Circulation::CalcDateDue( $today, $itemtype, $branch, $borrower );
110
is($date, DateTime->today->add(hours =>17), "Due date passed close hour, item due at close hour");
111
112
#############################
113
# Chargeable minutes between
114
############################
115
116
$issuelength = 25;
117
Koha::CirculationRules->set_rules(
118
    {
119
        categorycode => $categorycode,
120
        branchcode   => $branch,
121
        itemtype     => $itemtype,
122
        rules        => {
123
            issuelength => $issuelength,
124
        }
125
    }
126
);
127
128
my $date_due = DateTime->today;
129
my $date_returned = DateTime->today->add(minutes => 40);
130
131
my $chargeable_units = C4::Overdues::get_chargeable_units('minutes', $date_due, $date_returned, $branch);
132
is($chargeable_units, 40, "40 minutes of use");
133
134
######################
135
# Fines calculation
136
#####################
137
138
my $category = $builder->build(
139
    {
140
        source => 'Category',
141
    }
142
);
143
144
my $patron = $builder->build(
145
    {
146
        source => 'Borrower',
147
        value  => {
148
            categorycode => $category->{categorycode},
149
            branchcode   => $branch,
150
        },
151
    }
152
);
153
154
my $biblio = $builder->build_sample_biblio();
155
156
my $item = $builder->build(
157
    {
158
        source => 'Item',
159
        value  => {
160
            biblionumber     => $biblio->biblionumber,
161
            homebranch       => $branch,
162
            holdingbranch    => $branch,
163
            replacementprice => '5.00',
164
        },
165
    }
166
);
167
my $rule = $builder->schema->resultset('CirculationRule')->search({
168
    branchcode                    => undef,
169
    categorycode                  => undef,
170
    itemtype                      => undef,
171
});
172
$rule->delete_all if $rule;
173
# FinesIncludeGracePeriod included
174
t::lib::Mocks::mock_preference('FinesIncludeGracePeriod', 1);
175
$builder->build(
176
    {
177
        source => 'CirculationRule',
178
        value  => {
179
            branchcode                    => undef,
180
            categorycode                  => undef,
181
            itemtype                      => undef,
182
            rule_name                     => 'fine',
183
            rule_value                    => '1',
184
        },
185
    }
186
);
187
$builder->build(
188
    {
189
        source => 'CirculationRule',
190
        value  => {
191
            branchcode                    => undef,
192
            categorycode                  => undef,
193
            itemtype                      => undef,
194
            rule_name                     => 'lengthunit',
195
            rule_value                    => 'minutes',
196
        },
197
    }
198
);
199
$builder->build(
200
    {
201
        source => 'CirculationRule',
202
        value  => {
203
            branchcode                    => undef,
204
            categorycode                  => undef,
205
            itemtype                      => undef,
206
            rule_name                     => 'finedays',
207
            rule_value                    => 0,
208
        },
209
    }
210
);
211
$builder->build(
212
    {
213
        source => 'CirculationRule',
214
        value  => {
215
            branchcode                    => undef,
216
            categorycode                  => undef,
217
            itemtype                      => undef,
218
            rule_name                     => 'firstremind',
219
            rule_value                    => 5,
220
        },
221
    }
222
);
223
$builder->build(
224
    {
225
        source => 'CirculationRule',
226
        value  => {
227
            branchcode                    => undef,
228
            categorycode                  => undef,
229
            itemtype                      => undef,
230
            rule_name                     => 'chargeperiod',
231
            rule_value                    => 1,
232
        },
233
    }
234
);
235
$builder->build(
236
    {
237
        source => 'CirculationRule',
238
        value  => {
239
            branchcode                    => undef,
240
            categorycode                  => undef,
241
            itemtype                      => undef,
242
            rule_name                     => 'overduefinescap',
243
            rule_value                    => 9000,
244
        },
245
    }
246
);
247
$builder->build(
248
    {
249
        source => 'CirculationRule',
250
        value  => {
251
            branchcode                    => undef,
252
            categorycode                  => undef,
253
            itemtype                      => undef,
254
            rule_name                     => 'cap_fine_to_replacement_price',
255
            rule_value                    => 0,
256
        },
257
    }
258
);
259
260
my $start_dt = dt_from_string->set( hour => 15, minute => 0, second => 0);
261
my $end_dt = dt_from_string->set( hour => 15, minute => 4, second => 0);
262
263
my @amount = C4::Overdues::CalcFine( $item, $patron->{categorycode}, $branch, $start_dt, $end_dt );
264
is($amount[0], 0, "No fine when under the fine grace period");
265
266
$end_dt = dt_from_string->set( hour => 15, minute => 6, second => 0);
267
@amount = C4::Overdues::CalcFine( $item, $patron->{categorycode}, $branch, $start_dt, $end_dt );
268
is($amount[0], 6, "6\$ fine for 6 minutes delay, fine grace period included");
269
270
# FinesIncludeGracePeriod not included
271
t::lib::Mocks::mock_preference('FinesIncludeGracePeriod', 0);
272
273
@amount = C4::Overdues::CalcFine( $item, $patron->{categorycode}, $branch, $start_dt, $end_dt );
274
is($amount[0], 1, "1\$ fine for 6 minutes delay, fine grace period not included");
275
276
$schema->storage->txn_rollback;
277
278
1;

Return to bug 17983