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

(-)a/C4/Circulation.pm (-2 / +19 lines)
Lines 3460-3471 sub CalcDateDue { Link Here
3460
          ->truncate( to => 'minute' );
3460
          ->truncate( to => 'minute' );
3461
    }
3461
    }
3462
3462
3463
3463
    my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branch });
3464
    # calculate the datedue as normal
3464
    # calculate the datedue as normal
3465
    if ( C4::Context->preference('useDaysMode') eq 'Days' )
3465
    if ( C4::Context->preference('useDaysMode') eq 'Days' )
3466
    {    # ignoring calendar
3466
    {    # ignoring calendar
3467
        if ( $loanlength->{lengthunit} eq 'hours' ) {
3467
        if ( $loanlength->{lengthunit} eq 'hours' ) {
3468
            $datedue->add( hours => $loanlength->{$length_key} );
3468
            $datedue->add( hours => $loanlength->{$length_key} );
3469
        } elsif ( $loanlength->{lengthunit} eq 'minutes' ) {
3470
            $datedue->add( minutes => $loanlength->{$length_key});
3469
        } else {    # days
3471
        } else {    # days
3470
            $datedue->add( days => $loanlength->{$length_key} );
3472
            $datedue->add( days => $loanlength->{$length_key} );
3471
            $datedue->set_hour(23);
3473
            $datedue->set_hour(23);
Lines 3476-3485 sub CalcDateDue { Link Here
3476
        if ($loanlength->{lengthunit} eq 'hours') {
3478
        if ($loanlength->{lengthunit} eq 'hours') {
3477
            $dur = DateTime::Duration->new( hours => $loanlength->{$length_key});
3479
            $dur = DateTime::Duration->new( hours => $loanlength->{$length_key});
3478
        }
3480
        }
3481
        elsif ($loanlength->{lengthunit} eq 'minutes') {
3482
            $dur = DateTime::Duration->new( minutes => $loanlength->{$length_key});
3483
        }
3479
        else { # days
3484
        else { # days
3480
            $dur = DateTime::Duration->new( days => $loanlength->{$length_key});
3485
            $dur = DateTime::Duration->new( days => $loanlength->{$length_key});
3481
        }
3486
        }
3482
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branch });
3483
        $datedue = $calendar->addDate( $datedue, $dur, $loanlength->{lengthunit} );
3487
        $datedue = $calendar->addDate( $datedue, $dur, $loanlength->{lengthunit} );
3484
        if ($loanlength->{lengthunit} eq 'days') {
3488
        if ($loanlength->{lengthunit} eq 'days') {
3485
            $datedue->set_hour(23);
3489
            $datedue->set_hour(23);
Lines 3525-3530 sub CalcDateDue { Link Here
3525
          }
3529
          }
3526
        }
3530
        }
3527
    }
3531
    }
3532
3533
    #check if it's minutes or hourly loan and set due date to close hour, if the current due date is passed closing hours.
3534
    if ($loanlength->{lengthunit} eq 'hours' || $loanlength->{lengthunit} eq 'minutes'){
3535
        my $dateInfo = $calendar->get_date_info($datedue);
3536
        my $close = dt_from_string($dateInfo->{date} ." ". $dateInfo->{close_hour}, 'iso', C4::Context->tz());
3537
3538
        my $close_datetime = $datedue->clone()->set(hour => $close->hour(), minute=> $close->minute());
3539
3540
        if(DateTime->compare($datedue, $close_datetime) > 0) {
3541
            $datedue = $close_datetime->clone();
3542
        }
3543
    }
3544
3528
    return $datedue;
3545
    return $datedue;
3529
}
3546
}
3530
3547
(-)a/C4/Overdues.pm (+12 lines)
Lines 304-309 sub get_chargeable_units { Link Here
304
        }
304
        }
305
        return $charge_duration->in_units('hours');
305
        return $charge_duration->in_units('hours');
306
    }
306
    }
307
    elsif($unit eq 'minutes'){
308
        if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
309
            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
310
            $charge_duration = $calendar->open_minutes_between( $date_due, $date_returned );
311
        } else {
312
            $charge_duration = $date_returned->delta_ms( $date_due );
313
        }
314
        if($charge_duration->in_units('minutes') == 0 && $charge_duration->in_units('seconds') > 0){
315
            return 1;
316
        }
317
        return $charge_duration->in_units('minutes');
318
    }
307
    else { # days
319
    else { # days
308
        if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
320
        if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
309
            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
321
            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
(-)a/Koha/DiscreteCalendar.pm (+96 lines)
Lines 1178-1183 sub open_hours_between { Link Here
1178
    return ($working_hours_between->next()->get_column('hours_between') - $not_used_hours->next()->get_column('not_used_hours'));
1178
    return ($working_hours_between->next()->get_column('hours_between') - $not_used_hours->next()->get_column('not_used_hours'));
1179
}
1179
}
1180
1180
1181
=head2 open_minutes_between
1182
1183
  $minutes = $calendar->open_minutes_between($start_date, $end_date)
1184
1185
Returns the number of minutes between C<$start_date> and C<$end_date>, taking into
1186
account the opening hours of the library.
1187
1188
=cut
1189
1190
sub open_minutes_between {
1191
    my ($self, $start_date, $end_date) = @_;
1192
    my $branchcode = $self->{branchcode};
1193
    my $schema = Koha::Database->new->schema;
1194
    my $dtf = $schema->storage->datetime_parser;
1195
    $start_date = $dtf->format_datetime($start_date);
1196
    $end_date = $dtf->format_datetime($end_date);
1197
1198
    my $working_minutes_between = $schema->resultset('DiscreteCalendar')->search(
1199
        {
1200
            branchcode  => $branchcode,
1201
            is_opened    => 1,
1202
        },
1203
        {
1204
            select  => \['sum(time_to_sec(timediff(close_hour, open_hour)) / 60)'],
1205
            as      => [qw /minutes_between/],
1206
            where   => \['date BETWEEN DATE(?) AND DATE(?)', $start_date, $end_date]
1207
        }
1208
    );
1209
1210
    my $loan_day = $schema->resultset('DiscreteCalendar')->search(
1211
        {
1212
            branchcode  => $branchcode,
1213
        },
1214
        {
1215
            order_by => \[ 'ABS(DATEDIFF(date, ?))', $start_date ],
1216
            rows => 1,
1217
        }
1218
    );
1219
1220
    my $return_day = $schema->resultset('DiscreteCalendar')->search(
1221
        {
1222
            branchcode  => $branchcode,
1223
        },
1224
        {
1225
            order_by => \[ 'ABS(DATEDIFF(date, ?))', $end_date ],
1226
            rows => 1,
1227
        }
1228
    );
1229
1230
    #Capture the time portion of the date
1231
    $start_date =~ /\s(.*)/;
1232
    my $loan_date_time = $1;
1233
    $end_date =~ /\s(.*)/;
1234
    my $return_date_time = $1;
1235
1236
    my $not_used_minutes = $schema->resultset('DiscreteCalendar')->search(
1237
        {
1238
            branchcode  => $branchcode,
1239
            is_opened    => 1,
1240
        },
1241
        {
1242
            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()],
1243
            as      => [qw /not_used_minutes/],
1244
        }
1245
    );
1246
1247
    return DateTime::Duration->new( minutes => $working_minutes_between->next()->get_column('minutes_between') - $not_used_minutes->next()->get_column('not_used_minutes'));
1248
}
1249
1181
=head2 addDate
1250
=head2 addDate
1182
1251
1183
  my $dt = $calendar->addDate($date, $dur, $unit)
1252
  my $dt = $calendar->addDate($date, $dur, $unit)
Lines 1204-1209 sub addDate { Link Here
1204
        my $return_by_hour = 10;
1273
        my $return_by_hour = 10;
1205
1274
1206
        $dt = $self->addHours($startdate, $add_duration, $return_by_hour);
1275
        $dt = $self->addHours($startdate, $add_duration, $return_by_hour);
1276
    } elsif($unit eq 'minutes') {
1277
        $dt = $self->addMinutes($startdate, $add_duration);
1207
    } else {
1278
    } else {
1208
        # days
1279
        # days
1209
        $dt = $self->addDays($startdate, $add_duration);
1280
        $dt = $self->addDays($startdate, $add_duration);
Lines 1246-1251 sub addHours { Link Here
1246
    return $base_date;
1317
    return $base_date;
1247
}
1318
}
1248
1319
1320
=head2 addMinutes
1321
1322
  $end = $calendar->addMinutes($date, $dur)
1323
1324
Add C<$dur> minutes to C<$start> date.
1325
1326
=cut
1327
1328
sub addMinutes {
1329
    my ( $self, $startdate, $minutes_duration) = @_;
1330
    my $base_date = $startdate->clone();
1331
1332
    $base_date->add_duration($minutes_duration);
1333
1334
    my $dateInfo = $self->get_date_info($base_date);
1335
1336
    my $close_datetime = dt_from_string($dateInfo->{date} ." ". $dateInfo->{close_hour}, 'iso');
1337
1338
    if(DateTime->compare($base_date, $close_datetime) > 0) {
1339
        return $close_datetime;
1340
    }
1341
1342
    return $base_date;
1343
}
1344
1249
=head2 addDays
1345
=head2 addDays
1250
1346
1251
  $date = $calendar->addDays($start, $duration)
1347
  $date = $calendar->addDays($start, $duration)
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt (+1 lines)
Lines 248-253 Link Here
248
                      <select name="lengthunit" id="lengthunit">
248
                      <select name="lengthunit" id="lengthunit">
249
                        <option value="days" selected="selected">Days</option>
249
                        <option value="days" selected="selected">Days</option>
250
                        <option value="hours">Hours</option>
250
                        <option value="hours">Hours</option>
251
                        <option value="minutes">Minutes</option>
251
                      </select>
252
                      </select>
252
                    </td>
253
                    </td>
253
                    <td>
254
                    <td>
(-)a/t/db_dependent/MinuteLoans.t (-1 / +206 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;
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 $branch1 = "";
22
my $schema = Koha::Database->new->schema;
23
my $calendar = Koha::DiscreteCalendar->new( { branchcode => $branch1 } );
24
25
$schema->storage->txn_begin;
26
$schema->resultset('DiscreteCalendar')->search({
27
    branchcode  => ""
28
})->update_all({
29
    is_opened    => 1,
30
    holiday_type => $Koha::DiscreteCalendar::HOLIDAYS->{NONE},
31
    note         => '',
32
    open_hour    => '08:00:00',
33
    close_hour   => '16:00:00'
34
});
35
36
37
#######################################
38
# Add minutes and open minutes between
39
#######################################
40
41
my $start_date = dt_from_string->set( hour => 14, minute => 15, second => 0);
42
my $end_date = dt_from_string->set( hour => 14, minute => 20, second => 0);
43
44
is($calendar->open_minutes_between($start_date, $end_date)->in_units('minutes'), 5, "Item returned 5 minutes late");
45
46
#Adding 10 minutes
47
my $ten_mins_duration = DateTime::Duration->new( minutes => 10);
48
is($calendar->addDate($start_date, $ten_mins_duration, 'minutes' ),
49
    dt_from_string->set( hour => 14, minute => 25, second => 0),
50
        'Added 10 minutes to loan' );
51
52
#Adding 10 minutes, passed closing hour
53
$start_date = dt_from_string->set( hour => 15, minute => 51, second =>0 );
54
is($calendar->addDate($start_date, $ten_mins_duration, 'minutes' ),
55
    dt_from_string->set( hour => 16, minute => 0, second => 0),
56
        'Added 10 minutes, due date passed closing hours, set due date to same day at close hour' );
57
58
#Item returened next open day after a holiday.
59
my $open_minutes_today = DateTime->today->add(hours =>10, minutes => 10);
60
my $open_minutes_tomorrow = DateTime->today->add(days =>1);
61
my $open_minutes_day_after_tomorrow = DateTime->today->add(days => 2, hours =>11);
62
63
$calendar->edit_holiday({
64
    title => "Single holiday Today",
65
    holiday_type => $Koha::DiscreteCalendar::HOLIDAYS->{EXCEPTION},
66
    start_date => $open_minutes_tomorrow,
67
    end_date =>$open_minutes_tomorrow
68
});
69
70
is($calendar->open_minutes_between($open_minutes_today, $open_minutes_day_after_tomorrow)->in_units('minutes'),
71
    530,
72
    "Correct open minutes, with a holiday in between");
73
74
######################
75
# DueDate calculation
76
######################
77
78
#Set syspref ReturnBeforeExpiry = 1 and useDaysMode = 'Days'
79
t::lib::Mocks::mock_preference('ReturnBeforeExpiry', 1);
80
t::lib::Mocks::mock_preference('useDaysMode', 'Days');
81
82
my $issuelength = 25;
83
my $renewalperiod = 5;
84
my $lengthunit = 'minutes';
85
my $dateexpiry = DateTime->today->add(years => 1);
86
87
my $mock_loan_length = [
88
    ['issuelength', 'renewalperiod', 'lengthunit'],
89
    [$issuelength, $renewalperiod, $lengthunit]
90
];
91
92
my $categorycode = 'X';
93
my $itemtype = 'MINUTES';
94
my $borrower = {categorycode => 'X', dateexpiry => $dateexpiry};
95
$dbh->do("INSERT INTO issuingrules (categorycode, branchcode, itemtype, issuelength, lengthunit) VALUES('$categorycode', '$branch1', '$itemtype', '$issuelength', '$lengthunit')");
96
97
my $date = C4::Circulation::CalcDateDue( $today, $itemtype, $branch1, $borrower );
98
is($date, DateTime->today->add(minutes => $issuelength), "Due date calculated correctly");
99
100
#passed closing hour
101
$issuelength = 1300;
102
$dbh->do("UPDATE issuingrules SET issuelength=$issuelength where branchcode='$branch1' and itemtype='$itemtype'");
103
104
$date = C4::Circulation::CalcDateDue( $today, $itemtype, $branch1, $borrower );
105
is($date, DateTime->today->add(hours =>16), "Due date passed close hour, item due at close hour");
106
107
#############################
108
# Chargeable minutes between
109
############################
110
111
$issuelength = 25;
112
$dbh->do("UPDATE issuingrules SET issuelength=$issuelength where branchcode='$branch1' and itemtype='$itemtype'");
113
114
my $date_due = DateTime->today;
115
my $date_returned = DateTime->today->add(minutes => 40);
116
117
my $chargeable_units = C4::Overdues::get_chargeable_units('minutes', $date_due, $date_returned, $branch1);
118
is($chargeable_units, 40, "40 minutes of use");
119
120
######################
121
# Fines calculation
122
#####################
123
124
my $builder = t::lib::TestBuilder->new();
125
126
my $category = $builder->build(
127
    {
128
        source => 'Category',
129
    }
130
);
131
132
my $patron = $builder->build(
133
    {
134
        source => 'Borrower',
135
        value  => {
136
            categorycode => $category->{categorycode},
137
            branchcode   => $branch1
138
        },
139
    }
140
);
141
142
my $biblio = $builder->build(
143
    {
144
        source => 'Biblio',
145
        value  => {
146
            branchcode => $branch1,
147
        },
148
    }
149
);
150
151
my $item = $builder->build(
152
    {
153
        source => 'Item',
154
        value  => {
155
            biblionumber     => $biblio->{biblionumber},
156
            homebranch       => $branch1,
157
            holdingbranch    => $branch1,
158
            replacementprice => '5.00',
159
        },
160
    }
161
);
162
163
my $rule = $builder->schema->resultset('Issuingrule')->find({
164
    branchcode                    => '*',
165
    categorycode                  => '*',
166
    itemtype                      => '*',
167
});
168
$rule->delete if $rule;
169
# FinesIncludeGracePeriod included
170
t::lib::Mocks::mock_preference('FinesIncludeGracePeriod', 1);
171
my $issuingrule = $builder->build(
172
    {
173
        source => 'Issuingrule',
174
        value  => {
175
            branchcode                    => '*',
176
            categorycode                  => '*',
177
            itemtype                      => '*',
178
            fine                          => '1',
179
            lengthunit                    => 'minutes',
180
            finedays                      => 0,
181
            firstremind                   => 5,
182
            chargeperiod                  => 1,
183
            overduefinescap               => undef,
184
            cap_fine_to_replacement_price => 0,
185
        },
186
    }
187
);
188
my $start_dt = dt_from_string('2017-01-02 15:00:00', 'iso');
189
my $end_dt = dt_from_string('2017-01-02 15:04:00', 'iso');
190
191
my ($amount) = C4::Overdues::CalcFine( $item, $patron->{categorycode}, $branch1, $start_dt, $end_dt );
192
is($amount, 0, "No fine when under the fine grace periode");
193
194
$end_dt = dt_from_string('2017-01-02 15:06:00', 'iso');
195
($amount) = C4::Overdues::CalcFine( $item, $patron->{categorycode}, $branch1, $start_dt, $end_dt );
196
is($amount, 6, "6\$ fine for 6 minutes delay, fine grace periode included");
197
198
# FinesIncludeGracePeriod not included
199
t::lib::Mocks::mock_preference('FinesIncludeGracePeriod', 0);
200
201
($amount) = C4::Overdues::CalcFine( $item, $patron->{categorycode}, $branch1, $start_dt, $end_dt );
202
is($amount, 1, "1\$ fine for 6 minutes delay, fine grace periode not included");
203
204
$schema->storage->txn_rollback;
205
206
1;

Return to bug 17983