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

(-)a/Koha/Booking.pm (+97 lines)
Lines 24-35 use Koha::DateUtils qw( dt_from_string ); Link Here
24
use Koha::Items;
24
use Koha::Items;
25
use Koha::Patrons;
25
use Koha::Patrons;
26
use Koha::Libraries;
26
use Koha::Libraries;
27
use Koha::Bookings;
28
use Koha::CirculationRules;
29
use Koha::Cache::Memory::Lite;
27
30
28
use C4::Letters;
31
use C4::Letters;
32
use C4::Circulation;
33
use C4::Biblio;
29
34
30
use List::Util qw(any);
35
use List::Util qw(any);
31
36
32
use base qw(Koha::Object);
37
use base qw(Koha::Object);
38
use List::Util qw(min);
33
39
34
=head1 NAME
40
=head1 NAME
35
41
Lines 39-44 Koha::Booking - Koha Booking object class Link Here
39
45
40
=head2 Class methods
46
=head2 Class methods
41
47
48
=head2 can_be_booked_in_advance
49
50
  $canBeBooked = &can_be_booked_in_advance($patron, $item, $branchcode)
51
  if ($canBeBooked->{status} eq 'OK') { #We can booked this Item in advance! }
52
53
@RETURNS { status => OK },              if the Item can be booked.
54
         { status => tooManyBookings, limit => $limit, rule => $rule }, if the borrower has exceeded their maximum booking amount.
55
=cut
56
57
sub can_be_booked_in_advance {
58
    my ( $self, $params ) = @_;
59
    my $patron = $self->patron;
60
    my $item = $self->item;
61
62
    my $dbh = C4::Context->dbh;
63
64
    my $borrower = $patron->unblessed;
65
66
    if ( C4::Biblio->GetMarcFromKohaField('biblioitems.agerestriction') ) {
67
        my $biblio = $item->biblio;
68
69
        # Check for the age restriction
70
        my ( $ageRestriction, $daysToAgeRestriction ) =
71
            C4::Circulation::GetAgeRestriction( $biblio->biblioitem->agerestriction, $borrower );
72
        return { status => 'ageRestricted' } if $daysToAgeRestriction && $daysToAgeRestriction > 0;
73
    }
74
75
    # By default for now, control branch is the item homebranch
76
    my $bookings_control_branch = $item->homebranch;
77
78
    # we retrieve rights
79
    my $rights = Koha::CirculationRules->get_effective_rules(
80
        {
81
            categorycode => $borrower->{'categorycode'},
82
            itemtype     => $item->effective_itemtype,
83
            branchcode   => $bookings_control_branch,
84
            rules        =>
85
                [ 'bookings_allowed_total', 'bookings_per_item', 'bookings_period_length' ]
86
        }
87
    );
88
89
    my $bookings_allowed_total = $rights->{bookings_allowed_total};
90
    my $bookings_per_item      = $rights->{bookings_per_item};
91
    my $bookings_period_length = $rights->{bookings_period_length} || 0;
92
93
    return { status => 'noBookingsAllowed' } if defined($bookings_allowed_total) && $bookings_allowed_total == 0;
94
95
    my $bookings_per_item_count = Koha::Bookings->search( { patron_id => $patron->borrowernumber, item_id => $item->itemnumber } )->count();
96
    return { status => 'tooManyBookings', limit => $bookings_per_item, rule => 'bookings_per_item' } if defined($bookings_per_item) && $bookings_per_item <= $bookings_per_item_count;
97
98
99
    my $querycount;
100
    if (C4::Context->preference('item-level_itypes')) {
101
        $querycount = q{
102
            SELECT count(*) AS count
103
                FROM bookings AS b
104
                LEFT JOIN items AS i ON (b.item_id=i.itemnumber)
105
                WHERE b.patron_id = ?
106
                AND i.itype = ?
107
                };
108
    } else {
109
        $querycount = q{
110
            SELECT count(*) AS count
111
                FROM bookings AS b
112
                LEFT JOIN biblioitems AS bi ON (b.biblio_id=bi.biblionumber)
113
                WHERE b.patron_id = ?
114
                AND bi.itemtype = ?
115
                };
116
    }
117
118
    my $sthcount = $dbh->prepare($querycount);
119
    $sthcount->execute( $patron->borrowernumber, $item->effective_itemtype );
120
    my $total_bookings_count = $sthcount->fetchrow_hashref()->{count};
121
122
    return { status => 'tooManyBookings', limit => $bookings_allowed_total, rule => 'bookings_allowed_total' } if defined($bookings_allowed_total) && $bookings_allowed_total <= $total_bookings_count;
123
124
    my $start_date = dt_from_string( $self->start_date );
125
    my $end_date   = dt_from_string( $self->end_date );
126
    my $duration = $end_date->delta_days($start_date);
127
128
    my $delta_days = $duration->in_units('days');
129
130
    return { status => 'bookingPeriodNotValid'} if $bookings_period_length == 0;
131
    return { status => 'tooLongBookingPeriod', limit => $bookings_period_length } if $delta_days > $bookings_period_length;
132
133
    return { status => 'OK' };
134
}
135
42
=head3 biblio
136
=head3 biblio
43
137
44
Returns the related Koha::Biblio object for this booking
138
Returns the related Koha::Biblio object for this booking
Lines 151-156 sub store { Link Here
151
245
152
            # FIXME: We should be able to combine the above two functions into one
246
            # FIXME: We should be able to combine the above two functions into one
153
247
248
            my $canBeBooked = can_be_booked_in_advance( $self );
249
            Koha::Exceptions::Booking::Rule->throw( $canBeBooked ) if $canBeBooked->{'status'} ne "OK";
250
154
            # Assign item at booking time
251
            # Assign item at booking time
155
            if ( !$self->item_id ) {
252
            if ( !$self->item_id ) {
156
                $self->_assign_item_for_booking;
253
                $self->_assign_item_for_booking;
(-)a/Koha/CirculationRules.pm (-1 / +12 lines)
Lines 227-233 our $RULE_KINDS = { Link Here
227
    bookings_trail_period => {
227
    bookings_trail_period => {
228
        scope => [ 'branchcode', 'itemtype' ],
228
        scope => [ 'branchcode', 'itemtype' ],
229
    },
229
    },
230
230
    bookings_allowed_total => {
231
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
232
        can_be_blank => 0,
233
    },
234
    bookings_per_item => {
235
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
236
        can_be_blank => 0,
237
    },
238
    bookings_period_length => {
239
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
240
        can_be_blank => 0,
241
    },
231
    # Not included (deprecated?):
242
    # Not included (deprecated?):
232
    #   * accountsent
243
    #   * accountsent
233
    #   * reservecharge
244
    #   * reservecharge
(-)a/Koha/Exceptions/Booking.pm (+4 lines)
Lines 8-13 use Exception::Class ( Link Here
8
        isa         => 'Koha::Exceptions::Booking',
8
        isa         => 'Koha::Exceptions::Booking',
9
        description => "Adding or updating the booking would result in a clash"
9
        description => "Adding or updating the booking would result in a clash"
10
    },
10
    },
11
    'Koha::Exceptions::Booking::Rule' => {
12
        isa         => 'Koha::Exceptions::Booking',
13
        description => "Booking rejected by circulation rules"
14
    }
11
);
15
);
12
16
13
1;
17
1;
(-)a/Koha/REST/V1/Bookings.pm (+8 lines)
Lines 101-106 sub add { Link Here
101
                    error => "Duplicate booking_id",
101
                    error => "Duplicate booking_id",
102
                }
102
                }
103
            );
103
            );
104
        } elsif ( blessed $_ and $_->isa('Koha::Exceptions::Booking::Rule') ) {
105
            return $c->render(
106
                status  => 403,
107
                openapi => {
108
                    error => $_->{'message'}->{'status'},
109
                    limit => $_->{'message'}->{'limit'}
110
                }
111
            );
104
        }
112
        }
105
113
106
        return $c->unhandled_exception($_);
114
        return $c->unhandled_exception($_);
(-)a/admin/smart-rules.pl (+9 lines)
Lines 121-126 if ( $op eq 'cud-delete' ) { Link Here
121
                recall_shelf_time                => undef,
121
                recall_shelf_time                => undef,
122
                decreaseloanholds                => undef,
122
                decreaseloanholds                => undef,
123
                holds_pickup_period              => undef,
123
                holds_pickup_period              => undef,
124
                bookings_allowed_total           => undef,
125
                bookings_per_item                => undef,
126
                bookings_period_length           => undef
124
            }
127
            }
125
        }
128
        }
126
    );
129
    );
Lines 318-323 elsif ( $op eq 'cud-add' ) { Link Here
318
    my $recall_overdue_fine           = $input->param('recall_overdue_fine');
321
    my $recall_overdue_fine           = $input->param('recall_overdue_fine');
319
    my $recall_shelf_time             = $input->param('recall_shelf_time');
322
    my $recall_shelf_time             = $input->param('recall_shelf_time');
320
    my $holds_pickup_period           = strip_non_numeric( scalar $input->param('holds_pickup_period') );
323
    my $holds_pickup_period           = strip_non_numeric( scalar $input->param('holds_pickup_period') );
324
    my $bookings_allowed_total        = strip_non_numeric( scalar $input->param('bookings_allowed_total') );
325
    my $bookings_per_item             = strip_non_numeric( scalar $input->param('bookings_per_item') );
326
    my $bookings_period_length        = $input->param('bookings_period_length') || 0;
321
327
322
    my $rules = {
328
    my $rules = {
323
        maxissueqty                      => $maxissueqty,
329
        maxissueqty                      => $maxissueqty,
Lines 361-366 elsif ( $op eq 'cud-add' ) { Link Here
361
        recall_overdue_fine              => $recall_overdue_fine,
367
        recall_overdue_fine              => $recall_overdue_fine,
362
        recall_shelf_time                => $recall_shelf_time,
368
        recall_shelf_time                => $recall_shelf_time,
363
        holds_pickup_period              => $holds_pickup_period,
369
        holds_pickup_period              => $holds_pickup_period,
370
        bookings_allowed_total           => $bookings_allowed_total,
371
        bookings_per_item                => $bookings_per_item,
372
        bookings_period_length           => $bookings_period_length,
364
    };
373
    };
365
374
366
    Koha::CirculationRules->set_rules(
375
    Koha::CirculationRules->set_rules(
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt (-1 / +34 lines)
Lines 156-161 Link Here
156
                        <th>On shelf holds allowed</th>
156
                        <th>On shelf holds allowed</th>
157
                        <th>OPAC item level holds</th>
157
                        <th>OPAC item level holds</th>
158
                        <th>Holds pickup period (day)</th>
158
                        <th>Holds pickup period (day)</th>
159
                        <th>Bookings allowed (total)</th>
160
                        <th>Bookings per item (total)</th>
161
                        <th>Bookings period length (day)</th>
159
                        [% IF Koha.Preference('ArticleRequests') %]
162
                        [% IF Koha.Preference('ArticleRequests') %]
160
                            <th>Article requests</th>
163
                            <th>Article requests</th>
161
                        [% END %]
164
                        [% END %]
Lines 218-225 Link Here
218
                            [% SET recall_overdue_fine = all_rules.$c.$i.recall_overdue_fine %]
221
                            [% SET recall_overdue_fine = all_rules.$c.$i.recall_overdue_fine %]
219
                            [% SET recall_shelf_time = all_rules.$c.$i.recall_shelf_time %]
222
                            [% SET recall_shelf_time = all_rules.$c.$i.recall_shelf_time %]
220
                            [% SET holds_pickup_period = all_rules.$c.$i.holds_pickup_period %]
223
                            [% SET holds_pickup_period = all_rules.$c.$i.holds_pickup_period %]
224
                            [% SET bookings_allowed_total = all_rules.$c.$i.bookings_allowed_total %]
225
                            [% SET bookings_per_item = all_rules.$c.$i.bookings_per_item %]
226
                            [% SET bookings_period_length = all_rules.$c.$i.bookings_period_length %]
221
227
222
                            [% SET show_rule = note || maxissueqty || maxonsiteissueqty || issuelength || daysmode || lengthunit || hardduedate || hardduedatecompare || fine || chargeperiod || chargeperiod_charge_at || firstremind || overduefinescap || cap_fine_to_replacement_price || expire_reserves_charge || finedays || maxsuspensiondays || suspension_chargeperiod || renewalsallowed || unseenrenewalsallowed || renewalperiod || norenewalbefore || noautorenewalbefore || auto_renew || no_auto_renewal_after || no_auto_renewal_after_hard_limit || reservesallowed || holds_per_day || holds_per_record || onshelfholds || opacitemholds || article_requests || rentaldiscount || decreaseloanholds || recalls_allowed || recalls_per_record || on_shelf_recalls || recall_due_date_interval || recall_overdue_fine || recall_shelf_time || holds_pickup_period %]
228
                            [% SET show_rule = note || maxissueqty || maxonsiteissueqty || issuelength || daysmode || lengthunit || hardduedate || hardduedatecompare || fine || chargeperiod || chargeperiod_charge_at || firstremind || overduefinescap || cap_fine_to_replacement_price || expire_reserves_charge || finedays || maxsuspensiondays || suspension_chargeperiod || renewalsallowed || unseenrenewalsallowed || renewalperiod || norenewalbefore || noautorenewalbefore || auto_renew || no_auto_renewal_after || no_auto_renewal_after_hard_limit || reservesallowed || holds_per_day || holds_per_record || onshelfholds || opacitemholds || article_requests || rentaldiscount || decreaseloanholds || recalls_allowed || recalls_per_record || on_shelf_recalls || recall_due_date_interval || recall_overdue_fine || recall_shelf_time || holds_pickup_period || bookings_allowed_total || bookings_per_item || bookings_period_length %]
223
                            [% IF show_rule %]
229
                            [% IF show_rule %]
224
                                [% SET row_count = row_count + 1 %]
230
                                [% SET row_count = row_count + 1 %]
225
                                <tr row_countd="row_[% row_count | html %]">
231
                                <tr row_countd="row_[% row_count | html %]">
Lines 412-417 Link Here
412
                                            [% holds_pickup_period | html %]
418
                                            [% holds_pickup_period | html %]
413
                                        [% END %]
419
                                        [% END %]
414
                                    </td>
420
                                    </td>
421
                                    <td>
422
                                        [% IF bookings_allowed_total.defined && bookings_allowed_total != '' %]
423
                                            [% bookings_allowed_total | html %]
424
                                        [% ELSE %]
425
                                            <span>Unlimited</span>
426
                                        [% END %]
427
                                    </td>
428
                                    <td>
429
                                        [% IF bookings_per_item.defined && bookings_per_item != '' %]
430
                                            [% bookings_per_item | html %]
431
                                        [% ELSE %]
432
                                            <span>Unlimited</span>
433
                                        [% END %]
434
                                    </td>
435
                                    <td>
436
                                        [% IF bookings_period_length.defined && bookings_period_length != '' %]
437
                                            [% bookings_period_length | html %]
438
                                        [% ELSE %]
439
                                            <span>Not defined</span>
440
                                        [% END %]
441
                                    </td>
415
                                    [% IF Koha.Preference('ArticleRequests') %]
442
                                    [% IF Koha.Preference('ArticleRequests') %]
416
                                        <td data-code="[% article_requests | html %]">
443
                                        <td data-code="[% article_requests | html %]">
417
                                            [% IF article_requests == 'no' %]
444
                                            [% IF article_requests == 'no' %]
Lines 565-570 Link Here
565
                            </select>
592
                            </select>
566
                        </td>
593
                        </td>
567
                        <td><input type="text" name="holds_pickup_period" id="holds_pickup_period" size="2" /></td>
594
                        <td><input type="text" name="holds_pickup_period" id="holds_pickup_period" size="2" /></td>
595
                        <td><input type="text" name="bookings_allowed_total" id="bookings_allowed_total" size="2" /></td>
596
                        <td><input type="text" name="bookings_per_item" id="bookings_per_item" size="2" /></td>
597
                        <td><input type="text" name="bookings_period_length" id="bookings_period_length" size="3" /></td>
568
                        [% IF Koha.Preference('ArticleRequests') %]
598
                        [% IF Koha.Preference('ArticleRequests') %]
569
                            <td>
599
                            <td>
570
                                <select id="article_requests" name="article_requests">
600
                                <select id="article_requests" name="article_requests">
Lines 637-642 Link Here
637
                        <th>On shelf holds allowed</th>
667
                        <th>On shelf holds allowed</th>
638
                        <th>OPAC item level holds</th>
668
                        <th>OPAC item level holds</th>
639
                        <th>Holds pickup period (day)</th>
669
                        <th>Holds pickup period (day)</th>
670
                        <th>Bookings allowed (total)</th>
671
                        <th>Bookings per item (total)</th>
672
                        <th>Bookings period length (day)</th>
640
                        [% IF Koha.Preference('ArticleRequests') %]
673
                        [% IF Koha.Preference('ArticleRequests') %]
641
                            <th>Article requests</th>
674
                            <th>Article requests</th>
642
                        [% END %]
675
                        [% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/js/modals/place_booking.js (-3 / +30 lines)
Lines 1204-1213 $("#placeBookingForm").on("submit", function (e) { Link Here
1204
        });
1204
        });
1205
1205
1206
        posting.fail(function (data) {
1206
        posting.fail(function (data) {
1207
            const error = data.responseJSON.error;
1208
            const limit = data.responseJSON.limit;
1209
1210
            let displayMessage;
1211
1212
            switch (error) {
1213
                case 'noBookingsAllowed':
1214
                    displayMessage = __("Bookings are not allowed according to circulation rules");
1215
                break;
1216
                case 'tooManyBookings':
1217
                    displayMessage = __("Patron has reached the maximum of booking according to circulation rules");
1218
                    break;
1219
                case 'ageRestricted':
1220
                    displayMessage = __("Age restricted");
1221
                    break;
1222
                case 'tooLongBookingPeriod':
1223
                    displayMessage = __("Booking period exceed booking period limit according to circulation rules");
1224
                    break;
1225
                case 'bookingPeriodNotValid':
1226
                    displayMessage = __("Booking period must be valid");
1227
                    break;
1228
                default:
1229
                    displayMessage = __("Failure");
1230
            }
1231
1232
            if (limit) {
1233
                displayMessage += ` (${limit})`;
1234
            }
1235
1207
            $("#booking_result").replaceWith(
1236
            $("#booking_result").replaceWith(
1208
                '<div id="booking_result" class="alert alert-danger">' +
1237
                `<div id="booking_result" class="alert alert-danger">${__(displayMessage)}</div>`
1209
                    __("Failure") +
1210
                    "</div>"
1211
            );
1238
            );
1212
        });
1239
        });
1213
    } else {
1240
    } else {
(-)a/koha-tmpl/opac-tmpl/bootstrap/js/modals/place_booking.js (-3 / +28 lines)
Lines 234-241 $("#opac-add-form-booking").on('submit', function(e) { Link Here
234
234
235
        request.fail(function (data) {
235
        request.fail(function (data) {
236
            var error = data.responseJSON.error;
236
            var error = data.responseJSON.error;
237
            var errorMessage = error ? error : "Failure";
237
            var limit = data.responseJSON.limit;
238
            errors.push(item_id + " - " + errorMessage);
238
            let displayMessage;
239
240
            switch (error) {
241
                case 'noBookingsAllowed':
242
                    displayMessage = __("Bookings are not allowed according to circulation rules");
243
                break;
244
                case 'tooManyBookings':
245
                    displayMessage = __("Patron has reached the maximum of booking according to circulation rules");
246
                    break;
247
                case 'ageRestricted':
248
                    displayMessage = __("Age restricted");
249
                    break;
250
                case 'tooLongBookingPeriod':
251
                    displayMessage = __("Booking period exceed booking period limit according to circulation rules");
252
                    break;
253
                case 'bookingPeriodNotValid':
254
                    displayMessage = __("Booking period must be valid");
255
                    break;
256
                default:
257
                    displayMessage = __("Failure");
258
            }
259
260
            if (limit) {
261
                displayMessage += ` (${limit})`;
262
            }
263
264
            errors.push(`${displayMessage} - (${item_id})`);
239
        });
265
        });
240
    });
266
    });
241
267
242
- 

Return to bug 36271