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

(-)a/Koha/Booking.pm (+99 lines)
Lines 24-33 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 base qw(Koha::Object);
35
use base qw(Koha::Object);
36
use List::Util qw(min);
31
37
32
=head1 NAME
38
=head1 NAME
33
39
Lines 37-42 Koha::Booking - Koha Booking object class Link Here
37
43
38
=head2 Class methods
44
=head2 Class methods
39
45
46
=head2 can_be_booked_in_advance
47
48
  $canBeBooked = &can_be_booked_in_advance($patron, $item, $branchcode)
49
  if ($canBeBooked->{status} eq 'OK') { #We can booked this Item in advance! }
50
51
@RETURNS { status => OK },              if the Item can be booked.
52
         { status => tooManyBookings, limit => $limit, rule => $rule }, if the borrower has exceeded their maximum booking amount.
53
         { status => tooLongBookingPeriod, limit => $limit }, if the borrower has exceeded their maximum booking period.
54
         { status => bookingPeriodNotValid }, if booking period is not valid (undef or equal to 0).
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} || undef;
90
    my $bookings_per_item      = $rights->{bookings_per_item} || undef;
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
40
=head3 biblio
136
=head3 biblio
41
137
42
Returns the related Koha::Biblio object for this booking
138
Returns the related Koha::Biblio object for this booking
Lines 144-149 sub store { Link Here
144
240
145
            # FIXME: We should be able to combine the above two functions into one
241
            # FIXME: We should be able to combine the above two functions into one
146
242
243
            my $canBeBooked = can_be_booked_in_advance( $self );
244
            Koha::Exceptions::Booking::Rule->throw( $canBeBooked ) if $canBeBooked->{'status'} ne "OK";
245
147
            # Assign item at booking time
246
            # Assign item at booking time
148
            if ( !$self->item_id ) {
247
            if ( !$self->item_id ) {
149
                $self->_assign_item_for_booking;
248
                $self->_assign_item_for_booking;
(-)a/Koha/CirculationRules.pm (+9 lines)
Lines 222-227 our $RULE_KINDS = { Link Here
222
    bookings_trail_period => {
222
    bookings_trail_period => {
223
        scope => [ 'branchcode', 'itemtype' ],
223
        scope => [ 'branchcode', 'itemtype' ],
224
    },
224
    },
225
    bookings_allowed_total => {
226
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
227
    },
228
    bookings_per_item => {
229
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
230
    },
231
    bookings_period_length => {
232
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
233
    },
225
    # Not included (deprecated?):
234
    # Not included (deprecated?):
226
    #   * accountsent
235
    #   * accountsent
227
    #   * reservecharge
236
    #   * 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 (+18 lines)
Lines 96-101 sub add { Link Here
96
                    error => "Duplicate booking_id",
96
                    error => "Duplicate booking_id",
97
                }
97
                }
98
            );
98
            );
99
        } elsif ( blessed $_ and $_->isa('Koha::Exceptions::Booking::Rule') ) {
100
            my $error_code = $_->{'message'}->{'status'};
101
            my $limit = $_->{'message'}->{'limit'} // '';
102
            my $rule = $_->{'message'}->{'rule'} // '';
103
            my %error_strings = (
104
                'noBookingsAllowed' => 'Bookings are not allowed according to circulation rules',
105
                'tooManyBookings' => sprintf('Patron has reached the maximum of booking according to circulation rules : %s maximum (%s)', $limit, $rule),
106
                'tooLongBookingPeriod' => sprintf('Booking period exceed booking period limit according to circulation rules : %s day(s)', $limit),
107
                'bookingPeriodNotValid' => sprintf('Booking period must be valid'),
108
                'ageRestricted' =>  "Age restricted",
109
            );
110
111
            return $c->render(
112
                status  => 403,
113
                openapi => {
114
                    error => $error_strings{$error_code},
115
                }
116
            );
99
        }
117
        }
100
118
101
        return $c->unhandled_exception($_);
119
        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 314-319 elsif ( $op eq 'cud-add' ) { Link Here
314
    my $recall_overdue_fine           = $input->param('recall_overdue_fine');
317
    my $recall_overdue_fine           = $input->param('recall_overdue_fine');
315
    my $recall_shelf_time             = $input->param('recall_shelf_time');
318
    my $recall_shelf_time             = $input->param('recall_shelf_time');
316
    my $holds_pickup_period           = strip_non_numeric( scalar $input->param('holds_pickup_period') );
319
    my $holds_pickup_period           = strip_non_numeric( scalar $input->param('holds_pickup_period') );
320
    my $bookings_allowed_total        = strip_non_numeric( scalar $input->param('bookings_allowed_total') );
321
    my $bookings_per_item             = strip_non_numeric( scalar $input->param('bookings_per_item') );
322
    my $bookings_period_length        = $input->param('bookings_period_length') || 0;
317
323
318
    my $rules = {
324
    my $rules = {
319
        maxissueqty                      => $maxissueqty,
325
        maxissueqty                      => $maxissueqty,
Lines 356-361 elsif ( $op eq 'cud-add' ) { Link Here
356
        recall_overdue_fine              => $recall_overdue_fine,
362
        recall_overdue_fine              => $recall_overdue_fine,
357
        recall_shelf_time                => $recall_shelf_time,
363
        recall_shelf_time                => $recall_shelf_time,
358
        holds_pickup_period              => $holds_pickup_period,
364
        holds_pickup_period              => $holds_pickup_period,
365
        bookings_allowed_total           => $bookings_allowed_total,
366
        bookings_per_item                => $bookings_per_item,
367
        bookings_period_length           => $bookings_period_length,
359
    };
368
    };
360
369
361
    Koha::CirculationRules->set_rules(
370
    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 378-383 Link Here
378
                                                        [% holds_pickup_period | html %]
384
                                                        [% holds_pickup_period | html %]
379
                                                    [% END %]
385
                                                    [% END %]
380
                                                </td>
386
                                                </td>
387
                                                <td>
388
                                                    [% IF bookings_allowed_total.defined && bookings_allowed_total != '' %]
389
                                                        [% bookings_allowed_total | html %]
390
                                                    [% ELSE %]
391
                                                        <span>Unlimited</span>
392
                                                    [% END %]
393
                                                </td>
394
                                                <td>
395
                                                    [% IF bookings_per_item.defined && bookings_per_item != '' %]
396
                                                        [% bookings_per_item | html %]
397
                                                    [% ELSE %]
398
                                                        <span>Unlimited</span>
399
                                                    [% END %]
400
                                                </td>
401
                                                <td>
402
                                                    [% IF bookings_period_length.defined && bookings_period_length != '' %]
403
                                                        [% bookings_period_length | html %]
404
                                                    [% ELSE %]
405
                                                        <span>Not defined</span>
406
                                                    [% END %]
407
                                                </td>
381
                                                [% IF Koha.Preference('ArticleRequests') %]
408
                                                [% IF Koha.Preference('ArticleRequests') %]
382
                                                <td data-code="[% article_requests | html %]">
409
                                                <td data-code="[% article_requests | html %]">
383
                                                    [% IF article_requests == 'no' %]
410
                                                    [% IF article_requests == 'no' %]
Lines 528-533 Link Here
528
                                    </select>
555
                                    </select>
529
                                </td>
556
                                </td>
530
                                <td><input type="text" name="holds_pickup_period" id="holds_pickup_period" size="2" /></td>
557
                                <td><input type="text" name="holds_pickup_period" id="holds_pickup_period" size="2" /></td>
558
                                <td><input type="text" name="bookings_allowed_total" id="bookings_allowed_total" size="2" /></td>
559
                                <td><input type="text" name="bookings_per_item" id="bookings_per_item" size="2" /></td>
560
                                <td><input type="text" name="bookings_period_length" id="bookings_period_length" size="3" /></td>
531
                                [% IF Koha.Preference('ArticleRequests') %]
561
                                [% IF Koha.Preference('ArticleRequests') %]
532
                                <td>
562
                                <td>
533
                                    <select id="article_requests" name="article_requests">
563
                                    <select id="article_requests" name="article_requests">
Lines 599-604 Link Here
599
                                  <th>On shelf holds allowed</th>
629
                                  <th>On shelf holds allowed</th>
600
                                  <th>OPAC item level holds</th>
630
                                  <th>OPAC item level holds</th>
601
                                  <th>Holds pickup period (day)</th>
631
                                  <th>Holds pickup period (day)</th>
632
                                  <th>Bookings allowed (total)</th>
633
                                  <th>Bookings per item (total)</th>
634
                                  <th>Bookings period length (day)</th>
602
                                  [% IF Koha.Preference('ArticleRequests') %]
635
                                  [% IF Koha.Preference('ArticleRequests') %]
603
                                  <th>Article requests</th>
636
                                  <th>Article requests</th>
604
                                  [% END %]
637
                                  [% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/js/modals/place_booking.js (-2 / +3 lines)
Lines 1084-1092 $("#placeBookingForm").on("submit", function (e) { Link Here
1084
        });
1084
        });
1085
1085
1086
        posting.fail(function (data) {
1086
        posting.fail(function (data) {
1087
            var error = data.responseJSON.error;
1088
            var errorMessage = error ? error : "Failure";
1087
            $("#booking_result").replaceWith(
1089
            $("#booking_result").replaceWith(
1088
                '<div id="booking_result" class="alert alert-danger">' +
1090
                '<div id="booking_result" class="alert alert-danger">' +
1089
                    __("Failure") +
1091
                    _(errorMessage) +
1090
                    "</div>"
1092
                    "</div>"
1091
            );
1093
            );
1092
        });
1094
        });
1093
- 

Return to bug 36271