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 |
- |
|
|