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

(-)a/Koha/Booking.pm (+100 lines)
Lines 21-28 use Modern::Perl; Link Here
21
21
22
use Koha::Exceptions::Booking;
22
use Koha::Exceptions::Booking;
23
use Koha::DateUtils qw( dt_from_string );
23
use Koha::DateUtils qw( dt_from_string );
24
use Koha::Bookings;
25
use Koha::CirculationRules;
26
use Koha::Cache::Memory::Lite;
27
28
use C4::Circulation;
29
use C4::Biblio;
24
30
25
use base qw(Koha::Object);
31
use base qw(Koha::Object);
32
use List::Util qw(min);
26
33
27
=head1 NAME
34
=head1 NAME
28
35
Lines 32-37 Koha::Booking - Koha Booking object class Link Here
32
39
33
=head2 Class methods
40
=head2 Class methods
34
41
42
=head2 can_be_booked_in_advance
43
44
  $canBeBooked = &can_be_booked_in_advance($patron, $item, $branchcode)
45
  if ($canBeBooked->{status} eq 'OK') { #We can booked this Item in advance! }
46
47
@RETURNS { status => OK },              if the Item can be booked.
48
         { status => tooManyBookings, limit => $limit, rule => $rule }, if the borrower has exceeded their maximum booking amount.
49
         { status => tooLongBookingPeriod, limit => $limit }, if the borrower has exceeded their maximum booking period.
50
         { status => bookingPeriodNotValid }, if booking period is not valid (undef or equal to 0).
51
=cut
52
53
sub can_be_booked_in_advance {
54
    my ( $self, $params ) = @_;
55
    my $patron = $self->patron;
56
    my $item = $self->item;
57
58
    my $dbh = C4::Context->dbh;
59
60
    my $borrower = $patron->unblessed;
61
62
    if ( C4::Biblio->GetMarcFromKohaField('biblioitems.agerestriction') ) {
63
        my $biblio = $item->biblio;
64
65
        # Check for the age restriction
66
        my ( $ageRestriction, $daysToAgeRestriction ) =
67
            C4::Circulation::GetAgeRestriction( $biblio->biblioitem->agerestriction, $borrower );
68
        return { status => 'ageRestricted' } if $daysToAgeRestriction && $daysToAgeRestriction > 0;
69
    }
70
71
    # By default for now, control branch is the item homebranch
72
    my $bookings_control_branch = $item->homebranch;
73
74
    # we retrieve rights
75
    my $rights = Koha::CirculationRules->get_effective_rules(
76
        {
77
            categorycode => $borrower->{'categorycode'},
78
            itemtype     => $item->effective_itemtype,
79
            branchcode   => $bookings_control_branch,
80
            rules        =>
81
                [ 'bookings_allowed_total', 'bookings_per_item', 'bookings_period_length' ]
82
        }
83
    );
84
85
    my $bookings_allowed_total = $rights->{bookings_allowed_total} || undef;
86
    my $bookings_per_item      = $rights->{bookings_per_item} || undef;
87
    my $bookings_period_length = $rights->{bookings_period_length} || 0;
88
89
    return { status => 'noBookingsAllowed' } if defined($bookings_allowed_total) && $bookings_allowed_total == 0;
90
91
    my $bookings_per_item_count = Koha::Bookings->search( { patron_id => $patron->borrowernumber, item_id => $item->itemnumber } )->count();
92
    return { status => 'tooManyBookings', limit => $bookings_per_item, rule => 'bookings_per_item' } if defined($bookings_per_item) && $bookings_per_item <= $bookings_per_item_count;
93
94
95
    my $querycount;
96
    if (C4::Context->preference('item-level_itypes')) {
97
        $querycount = q{
98
            SELECT count(*) AS count
99
                FROM bookings AS b
100
                LEFT JOIN items AS i ON (b.item_id=i.itemnumber)
101
                WHERE b.patron_id = ?
102
                AND i.itype = ?
103
                };
104
    } else {
105
        $querycount = q{
106
            SELECT count(*) AS count
107
                FROM bookings AS b
108
                LEFT JOIN biblioitems AS bi ON (b.biblio_id=bi.biblionumber)
109
                WHERE b.patron_id = ?
110
                AND bi.itemtype = ?
111
                };
112
    }
113
114
    my $sthcount = $dbh->prepare($querycount);
115
    $sthcount->execute( $patron->borrowernumber, $item->effective_itemtype );
116
    my $total_bookings_count = $sthcount->fetchrow_hashref()->{count};
117
118
    return { status => 'tooManyBookings', limit => $bookings_allowed_total, rule => 'bookings_allowed_total' } if defined($bookings_allowed_total) && $bookings_allowed_total <= $total_bookings_count;
119
120
    my $start_date = dt_from_string( $self->start_date );
121
    my $end_date   = dt_from_string( $self->end_date );
122
    my $duration = $end_date->delta_days($start_date);
123
124
    my $delta_days = $duration->in_units('days');
125
126
    return { status => 'bookingPeriodNotValid'} if $bookings_period_length == 0;
127
    return { status => 'tooLongBookingPeriod', limit => $bookings_period_length } if $delta_days > $bookings_period_length;
128
129
    return { status => 'OK' };
130
}
131
35
=head3 biblio
132
=head3 biblio
36
133
37
Returns the related Koha::Biblio object for this booking
134
Returns the related Koha::Biblio object for this booking
Lines 139-144 sub store { Link Here
139
236
140
            # FIXME: We should be able to combine the above two functions into one
237
            # FIXME: We should be able to combine the above two functions into one
141
238
239
            my $canBeBooked = can_be_booked_in_advance( $self );
240
            Koha::Exceptions::Booking::Rule->throw( $canBeBooked ) if $canBeBooked->{'status'} ne "OK";
241
142
            # Assign item at booking time
242
            # Assign item at booking time
143
            if ( !$self->item_id ) {
243
            if ( !$self->item_id ) {
144
                $self->_assign_item_for_booking;
244
                $self->_assign_item_for_booking;
(-)a/Koha/CirculationRules.pm (+9 lines)
Lines 216-221 our $RULE_KINDS = { Link Here
216
    holds_pickup_period => {
216
    holds_pickup_period => {
217
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
217
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
218
    },
218
    },
219
    bookings_allowed_total => {
220
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
221
    },
222
    bookings_per_item => {
223
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
224
    },
225
    bookings_period_length => {
226
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
227
    },
219
    # Not included (deprecated?):
228
    # Not included (deprecated?):
220
    #   * accountsent
229
    #   * accountsent
221
    #   * reservecharge
230
    #   * 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/Item.pm (+2 lines)
Lines 1853-1858 sub effective_not_for_loan_status { Link Here
1853
    my $itemtype = $self->itemtype;
1853
    my $itemtype = $self->itemtype;
1854
    my $itype_notforloan = $itemtype ? $itemtype->notforloan : undef;
1854
    my $itype_notforloan = $itemtype ? $itemtype->notforloan : undef;
1855
    return ( defined($itype_notforloan) && !$self->notforloan ) ? $itype_notforloan : $self->notforloan;
1855
    return ( defined($itype_notforloan) && !$self->notforloan ) ? $itype_notforloan : $self->notforloan;
1856
}
1857
1856
=head3 effective_bookable
1858
=head3 effective_bookable
1857
1859
1858
  my $bookable = $item->effective_bookable;
1860
  my $bookable = $item->effective_bookable;
(-)a/Koha/REST/V1/Bookings.pm (+18 lines)
Lines 95-100 sub add { Link Here
95
                    error => "Duplicate booking_id",
95
                    error => "Duplicate booking_id",
96
                }
96
                }
97
            );
97
            );
98
        } elsif ( blessed $_ and $_->isa('Koha::Exceptions::Booking::Rule') ) {
99
            my $error_code = $_->{'message'}->{'status'};
100
            my $limit = $_->{'message'}->{'limit'} // '';
101
            my $rule = $_->{'message'}->{'rule'} // '';
102
            my %error_strings = (
103
                'noBookingsAllowed' => 'Bookings are not allowed according to circulation rules',
104
                'tooManyBookings' => sprintf('Patron has reached the maximum of booking according to circulation rules : %s maximum (%s)', $limit, $rule),
105
                'tooLongBookingPeriod' => sprintf('Booking period exceed booking period limit according to circulation rules : %s day(s)', $limit),
106
                'bookingPeriodNotValid' => sprintf('Booking period must be valid'),
107
                'ageRestricted' =>  "Age restricted",
108
            );
109
110
            return $c->render(
111
                status  => 403,
112
                openapi => {
113
                    error => $error_strings{$error_code},
114
                }
115
            );
98
        }
116
        }
99
117
100
        return $c->unhandled_exception($_);
118
        return $c->unhandled_exception($_);
(-)a/admin/smart-rules.pl (+9 lines)
Lines 117-122 if ($op eq 'cud-delete') { Link Here
117
                recall_shelf_time                => undef,
117
                recall_shelf_time                => undef,
118
                decreaseloanholds                => undef,
118
                decreaseloanholds                => undef,
119
                holds_pickup_period              => undef,
119
                holds_pickup_period              => undef,
120
                bookings_allowed_total           => undef,
121
                bookings_per_item                => undef,
122
                bookings_period_length           => undef,
120
            }
123
            }
121
        }
124
        }
122
    );
125
    );
Lines 302-307 elsif ( $op eq 'cud-add' ) { Link Here
302
    my $recall_overdue_fine           = $input->param('recall_overdue_fine');
305
    my $recall_overdue_fine           = $input->param('recall_overdue_fine');
303
    my $recall_shelf_time             = $input->param('recall_shelf_time');
306
    my $recall_shelf_time             = $input->param('recall_shelf_time');
304
    my $holds_pickup_period           = strip_non_numeric( scalar $input->param('holds_pickup_period') );
307
    my $holds_pickup_period           = strip_non_numeric( scalar $input->param('holds_pickup_period') );
308
    my $bookings_allowed_total        = strip_non_numeric( scalar $input->param('bookings_allowed_total') );
309
    my $bookings_per_item             = strip_non_numeric( scalar $input->param('bookings_per_item') );
310
    my $bookings_period_length        = $input->param('bookings_period_length') || 0;
305
311
306
    my $rules = {
312
    my $rules = {
307
        maxissueqty                      => $maxissueqty,
313
        maxissueqty                      => $maxissueqty,
Lines 344-349 elsif ( $op eq 'cud-add' ) { Link Here
344
        recall_overdue_fine              => $recall_overdue_fine,
350
        recall_overdue_fine              => $recall_overdue_fine,
345
        recall_shelf_time                => $recall_shelf_time,
351
        recall_shelf_time                => $recall_shelf_time,
346
        holds_pickup_period              => $holds_pickup_period,
352
        holds_pickup_period              => $holds_pickup_period,
353
        bookings_allowed_total           => $bookings_allowed_total,
354
        bookings_per_item                => $bookings_per_item,
355
        bookings_period_length           => $bookings_period_length,
347
    };
356
    };
348
357
349
    Koha::CirculationRules->set_rules(
358
    Koha::CirculationRules->set_rules(
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt (-1 / +34 lines)
Lines 143-148 Link Here
143
                            <th>On shelf holds allowed</th>
143
                            <th>On shelf holds allowed</th>
144
                            <th>OPAC item level holds</th>
144
                            <th>OPAC item level holds</th>
145
                            <th>Holds pickup period (day)</th>
145
                            <th>Holds pickup period (day)</th>
146
                            <th>Bookings allowed (total)</th>
147
                            <th>Bookings per item (total)</th>
148
                            <th>Bookings period length (day)</th>
146
                            [% IF Koha.Preference('ArticleRequests') %]
149
                            [% IF Koha.Preference('ArticleRequests') %]
147
                            <th>Article requests</th>
150
                            <th>Article requests</th>
148
                            [% END %]
151
                            [% END %]
Lines 204-211 Link Here
204
                                    [% SET recall_overdue_fine = all_rules.$c.$i.recall_overdue_fine %]
207
                                    [% SET recall_overdue_fine = all_rules.$c.$i.recall_overdue_fine %]
205
                                    [% SET recall_shelf_time = all_rules.$c.$i.recall_shelf_time %]
208
                                    [% SET recall_shelf_time = all_rules.$c.$i.recall_shelf_time %]
206
                                    [% SET holds_pickup_period = all_rules.$c.$i.holds_pickup_period %]
209
                                    [% SET holds_pickup_period = all_rules.$c.$i.holds_pickup_period %]
210
                                    [% SET bookings_allowed_total = all_rules.$c.$i.bookings_allowed_total %]
211
                                    [% SET bookings_per_item = all_rules.$c.$i.bookings_per_item %]
212
                                    [% SET bookings_period_length = all_rules.$c.$i.bookings_period_length %]
207
213
208
                                    [% SET show_rule = note || maxissueqty || maxonsiteissueqty || issuelength || daysmode || lengthunit || hardduedate || hardduedatecompare || fine || chargeperiod || chargeperiod_charge_at || firstremind || overduefinescap || cap_fine_to_replacement_price || 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 %]
214
                                    [% SET show_rule = note || maxissueqty || maxonsiteissueqty || issuelength || daysmode || lengthunit || hardduedate || hardduedatecompare || fine || chargeperiod || chargeperiod_charge_at || firstremind || overduefinescap || cap_fine_to_replacement_price || 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 %]
209
                                    [% IF show_rule %]
215
                                    [% IF show_rule %]
210
                                        [% SET row_count = row_count + 1 %]
216
                                        [% SET row_count = row_count + 1 %]
211
                                        <tr row_countd="row_[% row_count | html %]">
217
                                        <tr row_countd="row_[% row_count | html %]">
Lines 372-377 Link Here
372
                                                        [% holds_pickup_period | html %]
378
                                                        [% holds_pickup_period | html %]
373
                                                    [% END %]
379
                                                    [% END %]
374
                                                </td>
380
                                                </td>
381
                                                <td>
382
                                                    [% IF bookings_allowed_total.defined && bookings_allowed_total != '' %]
383
                                                        [% bookings_allowed_total | html %]
384
                                                    [% ELSE %]
385
                                                        <span>Unlimited</span>
386
                                                    [% END %]
387
                                                </td>
388
                                                <td>
389
                                                    [% IF bookings_per_item.defined && bookings_per_item != '' %]
390
                                                        [% bookings_per_item | html %]
391
                                                    [% ELSE %]
392
                                                        <span>Unlimited</span>
393
                                                    [% END %]
394
                                                </td>
395
                                                <td>
396
                                                    [% IF bookings_period_length.defined && bookings_period_length != '' %]
397
                                                        [% bookings_period_length | html %]
398
                                                    [% ELSE %]
399
                                                        <span>Not defined</span>
400
                                                    [% END %]
401
                                                </td>
375
                                                [% IF Koha.Preference('ArticleRequests') %]
402
                                                [% IF Koha.Preference('ArticleRequests') %]
376
                                                <td data-code="[% article_requests | html %]">
403
                                                <td data-code="[% article_requests | html %]">
377
                                                    [% IF article_requests == 'no' %]
404
                                                    [% IF article_requests == 'no' %]
Lines 522-527 Link Here
522
                                    </select>
549
                                    </select>
523
                                </td>
550
                                </td>
524
                                <td><input type="text" name="holds_pickup_period" id="holds_pickup_period" size="2" /></td>
551
                                <td><input type="text" name="holds_pickup_period" id="holds_pickup_period" size="2" /></td>
552
                                <td><input type="text" name="bookings_allowed_total" id="bookings_allowed_total" size="2" /></td>
553
                                <td><input type="text" name="bookings_per_item" id="bookings_per_item" size="2" /></td>
554
                                <td><input type="text" name="bookings_period_length" id="bookings_period_length" size="3" /></td>
525
                                [% IF Koha.Preference('ArticleRequests') %]
555
                                [% IF Koha.Preference('ArticleRequests') %]
526
                                <td>
556
                                <td>
527
                                    <select id="article_requests" name="article_requests">
557
                                    <select id="article_requests" name="article_requests">
Lines 593-598 Link Here
593
                                  <th>On shelf holds allowed</th>
623
                                  <th>On shelf holds allowed</th>
594
                                  <th>OPAC item level holds</th>
624
                                  <th>OPAC item level holds</th>
595
                                  <th>Holds pickup period (day)</th>
625
                                  <th>Holds pickup period (day)</th>
626
                                  <th>Bookings allowed (total)</th>
627
                                  <th>Bookings per item (total)</th>
628
                                  <th>Bookings period length (day)</th>
596
                                  [% IF Koha.Preference('ArticleRequests') %]
629
                                  [% IF Koha.Preference('ArticleRequests') %]
597
                                  <th>Article requests</th>
630
                                  <th>Article requests</th>
598
                                  [% END %]
631
                                  [% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/js/modals/place_booking.js (-6 / +3 lines)
Lines 856-866 $("#placeBookingForm").on("submit", function (e) { Link Here
856
        });
856
        });
857
857
858
        posting.fail(function (data) {
858
        posting.fail(function (data) {
859
            $("#booking_result").replaceWith(
859
            var error = data.responseJSON.error;
860
                '<div id="booking_result" class="alert alert-danger">' +
860
            var errorMessage = error ? error : "Failure";
861
                    __("Failure") +
861
            $('#booking_result').replaceWith('<div id="booking_result" class="alert alert-danger">'+_(errorMessage)+'</div>');
862
                    "</div>"
863
            );
864
        });
862
        });
865
    } else {
863
    } else {
866
        url += "/" + booking_id;
864
        url += "/" + booking_id;
867
- 

Return to bug 36271