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

(-)a/C4/Reserves.pm (-3 / +106 lines)
Lines 985-994 sub CheckReserves { Link Here
985
985
986
=head2 CancelExpiredReserves
986
=head2 CancelExpiredReserves
987
987
988
  CancelExpiredReserves();
988
    CancelExpiredReserves();
989
    CancelExpiredReserves($cancellation_reason);
989
990
990
Cancels all reserves with an expiration date from before today.
991
Cancels all reserves with an expiration date from before today.
991
992
993
When the ExpireReservesOnHolidays system preference is disabled (0), implements
994
sophisticated holiday logic to handle various edge cases:
995
996
=head3 Use Cases and Logic
997
998
=over 4
999
1000
=item B<Case 1: Hold expires today on a holiday>
1001
1002
If a hold expires today and today is a holiday, the hold is NOT cancelled.
1003
This respects the library's policy of not processing cancellations on holidays.
1004
1005
Example: Hold expires on Christmas Day, script runs on Christmas Day => Hold preserved
1006
1007
=item B<Case 2: Hold expired in the past, script runs on business day>
1008
1009
If a hold expired in the past (regardless of whether the expiration date was a holiday),
1010
and today is a business day, the hold IS cancelled.
1011
1012
Example: Hold expired on Christmas 2023, script runs in August 2024 => Hold cancelled
1013
1014
=item B<Case 3: Hold expired in the past, script runs on holiday, no business days missed>
1015
1016
If a hold expired recently and today is a holiday, but no business days have passed
1017
since expiration, the hold is NOT cancelled.
1018
1019
Example: Hold expires Friday, script runs Saturday (holiday) => Hold preserved
1020
1021
=item B<Case 4: Hold expired in the past, script runs on holiday, business days were missed>
1022
1023
If a hold expired in the past and today is a holiday, but business days have passed
1024
since expiration (due to server downtime, cron failures, etc.), the hold IS cancelled
1025
retrospectively to prevent holds from remaining active indefinitely.
1026
1027
Example: Hold expires Monday, server down Tue-Wed (business days), script runs Thursday (holiday) => Hold cancelled
1028
1029
=item B<Case 5: ExpireReservesOnHolidays enabled>
1030
1031
When ExpireReservesOnHolidays is enabled (1), all expired holds are cancelled
1032
regardless of holidays, maintaining the original simple behavior.
1033
1034
=back
1035
1036
=head3 Parameters
1037
1038
=over 4
1039
1040
=item $cancellation_reason (optional)
1041
1042
Optional cancellation reason to be recorded in the hold's cancellation_reason field.
1043
1044
=back
1045
1046
This logic ensures that:
1047
- Library holiday policies are respected
1048
- Holds don't remain active indefinitely due to technical issues
1049
- The system is robust against server downtime and cron failures
1050
- Behavior is predictable and well-defined for all scenarios
1051
992
=cut
1052
=cut
993
1053
994
sub CancelExpiredReserves {
1054
sub CancelExpiredReserves {
Lines 1013-1019 sub CancelExpiredReserves { Link Here
1013
    while ( my $hold = $holds->next ) {
1073
    while ( my $hold = $holds->next ) {
1014
        my $calendar = Koha::Calendar->new( branchcode => $hold->branchcode );
1074
        my $calendar = Koha::Calendar->new( branchcode => $hold->branchcode );
1015
1075
1016
        next if !$cancel_on_holidays && $calendar->is_holiday($today);
1076
        # Get the actual expiration date for this hold
1077
        my $expiration_date = $hold->expirationdate || $hold->patron_expiration_date;
1078
1079
        # When ExpireReservesOnHolidays is disabled, implement proper holiday logic:
1080
        if ( !$cancel_on_holidays && $expiration_date ) {
1081
            my $expiration_dt = dt_from_string($expiration_date);
1082
1083
            # Check if hold expired today
1084
            my $expired_today = $expiration_dt->ymd eq $today->ymd;
1085
1086
            if ($expired_today) {
1087
1088
                # Rule 1: Don't cancel holds that expired TODAY if today is a holiday
1089
                next if $calendar->is_holiday($today);
1090
            } else {
1091
1092
                # Rule 2: For holds that expired in the past, check for missed business days
1093
                if ( $calendar->is_holiday($today) ) {
1094
1095
                    # If today is a holiday, check if business days were missed since expiration
1096
                    my $days_since_expiration = $today->delta_days($expiration_dt)->in_units('days');
1097
1098
                    if ( $days_since_expiration > 0 ) {
1099
1100
                        # Use Calendar's has_business_days_between for cleaner logic
1101
                        my $business_days_missed = $calendar->has_business_days_between( $expiration_dt, $today );
1102
1103
                        # Rule 3: If business days were missed, cancel retrospectively even on holidays
1104
                        if ( !$business_days_missed ) {
1105
1106
                            # No business days were missed, skip cancellation
1107
                            next;
1108
                        }
1109
1110
                        # If business days were missed, continue with cancellation
1111
                    } else {
1112
1113
                        # This shouldn't happen (expired_today would be true), but skip to be safe
1114
                        next;
1115
                    }
1116
                }
1117
1118
                # If today is a business day, cancel expired holds from the past
1119
            }
1120
        }
1017
1121
1018
        my $cancel_params = {};
1122
        my $cancel_params = {};
1019
        $cancel_params->{cancellation_reason} = $cancellation_reason if defined($cancellation_reason);
1123
        $cancel_params->{cancellation_reason} = $cancellation_reason if defined($cancellation_reason);
1020
- 

Return to bug 40636