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

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

Return to bug 40636