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

(-)a/C4/Circulation.pm (-7 / +61 lines)
Lines 4166-4178 sub CalcDateDue { Link Here
4166
        $considerlibraryhours = 'ignore';
4166
        $considerlibraryhours = 'ignore';
4167
    }
4167
    }
4168
4168
4169
    # For 'open' mode, if tomorrow is a calendar holiday, find the next open day
4170
    my $next_open_day;
4171
    my $next_open_hours;
4172
    if ( $considerlibraryhours eq 'open' && $daysmode ne 'Days' ) {
4173
        my $calendar     = Koha::Calendar->new( branchcode => $branch, days_mode => $daysmode );
4174
        my $check_day    = $datedue->clone->add( days => 1 );
4175
        my $days_forward = 1;
4176
4177
        # Look ahead up to 7 days to find next open day with defined hours
4178
        while ( $days_forward <= 7 ) {
4179
            if ( !$calendar->is_holiday($check_day) ) {
4180
4181
                # This day is not a holiday, check if it has operating hours
4182
                my $check_dayofweek = $check_day->day_of_week;
4183
                if ( $check_dayofweek == 7 ) {
4184
                    $check_dayofweek = 0;
4185
                }
4186
                my $check_hours = Koha::Library::Hours->find( { library_id => $branch, day => $check_dayofweek } );
4187
                if ( $check_hours && $check_hours->open_time ) {
4188
                    $next_open_day   = $check_day;
4189
                    $next_open_hours = $check_hours;
4190
                    last;
4191
                }
4192
            }
4193
            $check_day->add( days => 1 );
4194
            $days_forward++;
4195
        }
4196
4197
        # If we couldn't find a suitable day, fall back to ignoring library hours
4198
        if ( !$next_open_day ) {
4199
            $considerlibraryhours = 'ignore';
4200
        }
4201
    }
4202
4169
    my @open = undef;
4203
    my @open = undef;
4170
    if ( $considerlibraryhours ne 'ignore' and $todayhours and $todayhours->close_time ) {
4204
    if ( $considerlibraryhours ne 'ignore' and $todayhours and $todayhours->close_time ) {
4171
        @close             = split( ":", $todayhours->close_time );
4205
        @close             = split( ":", $todayhours->close_time );
4172
        $library_close     = $library_close->set( hour => $close[0], minute => $close[1] );
4206
        $library_close     = $library_close->set( hour => $close[0], minute => $close[1] );
4173
        $potential_datedue = $potential_datedue->add( hours => $loanlength->{$length_key} )
4207
        $potential_datedue = $potential_datedue->add( hours => $loanlength->{$length_key} )
4174
            ;    # datedue without consideration for open hours
4208
            ;    # datedue without consideration for open hours
4175
        if ( $considerlibraryhours eq 'open' and $tomorrowhours and $tomorrowhours->open_time ) {
4209
        if ( $considerlibraryhours eq 'open' and $next_open_hours and $next_open_hours->open_time ) {
4210
            @open = split( ":", $next_open_hours->open_time );
4211
        } elsif ( $considerlibraryhours eq 'open' and $tomorrowhours and $tomorrowhours->open_time ) {
4176
            @open = split( ":", $tomorrowhours->open_time );
4212
            @open = split( ":", $tomorrowhours->open_time );
4177
        }
4213
        }
4178
    }
4214
    }
Lines 4189-4199 sub CalcDateDue { Link Here
4189
                    # datedue will be after the library closes on that day
4225
                    # datedue will be after the library closes on that day
4190
                    # shorten loan period to end when library closes
4226
                    # shorten loan period to end when library closes
4191
                    $datedue->set( hour => $close[0], minute => $close[1] );
4227
                    $datedue->set( hour => $close[0], minute => $close[1] );
4192
                } elsif ( $considerlibraryhours eq 'open' and $tomorrowhours and $tomorrowhours->open_time ) {
4228
                } elsif ( $considerlibraryhours eq 'open' and @open ) {
4193
4229
4194
                    # datedue will be after the library closes on that day
4230
                    # datedue will be after the library closes on that day
4195
                    # extend loan period to when library opens following day
4231
                    # extend loan period to when library opens on next open day
4196
                    $datedue->add( days => 1 )->set( hour => $open[0], minute => $open[1] );
4232
                    if ($next_open_day) {
4233
4234
                        # Use the calculated next open day (accounting for calendar holidays)
4235
                        my $days_to_add = $next_open_day->delta_days($datedue)->in_units('days');
4236
                        $datedue->add( days => $days_to_add )->set( hour => $open[0], minute => $open[1] );
4237
                    } else {
4238
4239
                        # Tomorrow is open and not a holiday
4240
                        $datedue->add( days => 1 )->set( hour => $open[0], minute => $open[1] );
4241
                    }
4197
                } else {
4242
                } else {
4198
4243
4199
                    # ignore library open hours or can't extend to tomorrow (no tomorrow hours)
4244
                    # ignore library open hours or can't extend to tomorrow (no tomorrow hours)
Lines 4221-4231 sub CalcDateDue { Link Here
4221
                    # datedue will be after the library closes on that day
4266
                    # datedue will be after the library closes on that day
4222
                    # shorten loan period to end when library closes by hardcoding due time
4267
                    # shorten loan period to end when library closes by hardcoding due time
4223
                    $datedue->set( hour => $close[0], minute => $close[1] );
4268
                    $datedue->set( hour => $close[0], minute => $close[1] );
4224
                } elsif ( $considerlibraryhours eq 'open' and $tomorrowhours and $tomorrowhours->open_time ) {
4269
                } elsif ( $considerlibraryhours eq 'open' and @open ) {
4225
4270
4226
                    # datedue will be after the library closes on that day
4271
                    # datedue will be after the library closes on that day
4227
                    # extend loan period to when library opens following day by hardcoding due time for next open day
4272
                    # extend loan period to when library opens on next open day by hardcoding due time for next open day
4228
                    $dur = DateTime::Duration->new( days => 1 );
4273
                    if ($next_open_day) {
4274
4275
                        # Use the calculated next open day (accounting for calendar holidays)
4276
                        my $days_to_add = $next_open_day->delta_days($datedue)->in_units('days');
4277
                        $dur = DateTime::Duration->new( days => $days_to_add );
4278
                    } else {
4279
4280
                        # Tomorrow is open and not a holiday
4281
                        $dur = DateTime::Duration->new( days => 1 );
4282
                    }
4229
                    $datedue->set( hour => $open[0], minute => $open[1] );
4283
                    $datedue->set( hour => $open[0], minute => $open[1] );
4230
                } else {
4284
                } else {
4231
4285
(-)a/t/db_dependent/Circulation/CalcDateDue.t (-2 / +174 lines)
Lines 3-9 Link Here
3
use Modern::Perl;
3
use Modern::Perl;
4
4
5
use Test::NoWarnings;
5
use Test::NoWarnings;
6
use Test::More tests => 27;
6
use Test::More tests => 32;
7
use Test::MockModule;
7
use Test::MockModule;
8
use DBI;
8
use DBI;
9
use DateTime;
9
use DateTime;
Lines 552-556 is( Link Here
552
    "Bug 38940: Loan period shortened with Calendar mode when next day has null hours (ConsiderLibraryHoursInCirculation='close')"
552
    "Bug 38940: Loan period shortened with Calendar mode when next day has null hours (ConsiderLibraryHoursInCirculation='close')"
553
);
553
);
554
554
555
# Bug 38940 follow-up - Test that ConsiderLibraryHoursInCirculation='open' extends to next open day
556
# when tomorrow is marked as a holiday in the calendar (even though it has operating hours defined)
557
558
my $library3 = $builder->build( { source => 'Branch' } );
559
Koha::CirculationRules->set_rules(
560
    {
561
        categorycode => $borrower->categorycode,
562
        itemtype     => $itemtype,
563
        branchcode   => $library3->{branchcode},
564
        rules        => {
565
            issuelength => 4,         # loan period is 4 hours
566
            lengthunit  => 'hours',
567
            daysmode    => '',
568
        }
569
    }
570
);
571
572
# Set up hours for Friday (day 5), Saturday (day 6), and Monday (day 1)
573
# Friday: 09:00-17:00, Saturday: 09:00-17:00, Monday: 09:00-17:00
574
my $fri_open  = DateTime->new( year => 2023, month => 5, day => 5, hour => 10 )->hms;
575
my $fri_close = DateTime->new( year => 2023, month => 5, day => 5, hour => 17 )->hms;
576
my $sat_open  = DateTime->new( year => 2023, month => 5, day => 6, hour => 10 )->hms;
577
my $sat_close = DateTime->new( year => 2023, month => 5, day => 6, hour => 17 )->hms;
578
my $mon_open  = DateTime->new( year => 2023, month => 5, day => 1, hour => 10 )->hms;
579
my $mon_close = DateTime->new( year => 2023, month => 5, day => 1, hour => 17 )->hms;
580
my $friday_14 = DateTime->new( year => 2023, month => 5, day => 5, hour => 14 );        # 2PM on Friday
581
582
# Set operating hours for Friday (day 5), Saturday (day 6), and Monday (day 1)
583
Koha::Library::Hour->new(
584
    { day => 5, library_id => $library3->{branchcode}, open_time => $fri_open, close_time => $fri_close } )->store;
585
Koha::Library::Hour->new(
586
    { day => 6, library_id => $library3->{branchcode}, open_time => $sat_open, close_time => $sat_close } )->store;
587
Koha::Library::Hour->new(
588
    { day => 1, library_id => $library3->{branchcode}, open_time => $mon_open, close_time => $mon_close } )->store;
589
590
# Mark Saturday and Sunday as holidays in the calendar
591
my $calendar3 = C4::Calendar->new( branchcode => $library3->{branchcode} );
592
$calendar3->insert_week_day_holiday(
593
    weekday     => 6,
594
    title       => 'Saturday',
595
    description => 'Saturday closure'
596
);
597
$calendar3->insert_week_day_holiday(
598
    weekday     => 0,
599
    title       => 'Sunday',
600
    description => 'Sunday closure'
601
);
602
603
t::lib::Mocks::mock_preference( 'useDaysMode',                       'Calendar' );
604
t::lib::Mocks::mock_preference( 'ConsiderLibraryHoursInCirculation', 'open' );
605
606
# Test with 'open' mode - should extend to Monday's opening time (skipping Saturday and Sunday holidays)
607
$date             = C4::Circulation::CalcDateDue( $friday_14, $itemtype, $library3->{branchcode}, $borrower );
608
$expected_duetime = DateTime->new( year => 2023, month => 5, day => 8, hour => 10, minute => 0 );
609
is(
610
    $date, $expected_duetime,
611
    "Bug 38940 follow-up: Loan period extended to Monday opening when Saturday/Sunday are calendar holidays (ConsiderLibraryHoursInCirculation='open')"
612
);
613
614
# Test with 'close' mode - should still shorten to Friday's closing time (calendar holidays don't affect today)
615
t::lib::Mocks::mock_preference( 'ConsiderLibraryHoursInCirculation', 'close' );
616
$date             = C4::Circulation::CalcDateDue( $friday_14, $itemtype, $library3->{branchcode}, $borrower );
617
$expected_duetime = $friday_14->clone->set( hour => 17, minute => 0 );
618
is(
619
    $date, $expected_duetime,
620
    "Bug 38940 follow-up: Loan period shortened to closing time, unaffected by weekend holidays (ConsiderLibraryHoursInCirculation='close')"
621
);
622
623
# Test the same with Days mode (ignoring calendar) - should use tomorrow's hours directly
624
t::lib::Mocks::mock_preference( 'useDaysMode',                       'Days' );
625
t::lib::Mocks::mock_preference( 'ConsiderLibraryHoursInCirculation', 'open' );
626
$date             = C4::Circulation::CalcDateDue( $friday_14, $itemtype, $library3->{branchcode}, $borrower );
627
$expected_duetime = DateTime->new( year => 2023, month => 5, day => 6, hour => 10, minute => 0 );
628
is(
629
    $date, $expected_duetime,
630
    "Bug 38940 follow-up: In Days mode, extends to Saturday (ignoring calendar holidays)"
631
);
632
633
# Test scenario where next open day has no operating hours - should fall back to ignoring library hours
634
my $library4 = $builder->build( { source => 'Branch' } );
635
Koha::CirculationRules->set_rules(
636
    {
637
        categorycode => $borrower->categorycode,
638
        itemtype     => $itemtype,
639
        branchcode   => $library4->{branchcode},
640
        rules        => {
641
            issuelength => 4,
642
            lengthunit  => 'hours',
643
            daysmode    => '',
644
        }
645
    }
646
);
647
648
# Set up hours only for Friday (day 5)
649
Koha::Library::Hour->new(
650
    { day => 5, library_id => $library4->{branchcode}, open_time => $fri_open, close_time => $fri_close } )->store;
651
652
# Mark Saturday and Sunday as holidays, and don't define hours for Monday
653
my $calendar4 = C4::Calendar->new( branchcode => $library4->{branchcode} );
654
$calendar4->insert_week_day_holiday(
655
    weekday     => 6,
656
    title       => 'Saturday',
657
    description => 'Saturday closure'
658
);
659
$calendar4->insert_week_day_holiday(
660
    weekday     => 0,
661
    title       => 'Sunday',
662
    description => 'Sunday closure'
663
);
664
665
t::lib::Mocks::mock_preference( 'useDaysMode',                       'Calendar' );
666
t::lib::Mocks::mock_preference( 'ConsiderLibraryHoursInCirculation', 'open' );
667
668
# Should fall back to standard loan since no suitable open day with hours found
669
$date             = C4::Circulation::CalcDateDue( $friday_14, $itemtype, $library4->{branchcode}, $borrower );
670
$expected_duetime = $friday_14->clone->add( hours => 4 );
671
is(
672
    $date, $expected_duetime,
673
    "Bug 38940 follow-up: Falls back to standard loan when next open day has no operating hours"
674
);
675
676
# Test with a single day holiday (not weekly repeating)
677
my $library5 = $builder->build( { source => 'Branch' } );
678
Koha::CirculationRules->set_rules(
679
    {
680
        categorycode => $borrower->categorycode,
681
        itemtype     => $itemtype,
682
        branchcode   => $library5->{branchcode},
683
        rules        => {
684
            issuelength => 4,
685
            lengthunit  => 'hours',
686
            daysmode    => '',
687
        }
688
    }
689
);
690
691
# Set hours for all days of the week (0-6)
692
for my $day ( 0 .. 6 ) {
693
    Koha::Library::Hour->new(
694
        { day => $day, library_id => $library5->{branchcode}, open_time => $fri_open, close_time => $fri_close } )
695
        ->store;
696
}
697
698
# Mark Saturday May 6, 2023 and Sunday May 7, 2023 as single day holidays
699
my $calendar5         = C4::Calendar->new( branchcode => $library5->{branchcode} );
700
my $specific_saturday = DateTime->new( year => 2023, month => 5, day => 6 );
701
$calendar5->insert_single_holiday(
702
    day         => $specific_saturday->day,
703
    month       => $specific_saturday->month,
704
    year        => $specific_saturday->year,
705
    title       => 'Public Holiday',
706
    description => 'Special bank holiday'
707
);
708
my $specific_sunday = DateTime->new( year => 2023, month => 5, day => 7 );
709
$calendar5->insert_single_holiday(
710
    day         => $specific_sunday->day,
711
    month       => $specific_sunday->month,
712
    year        => $specific_sunday->year,
713
    title       => 'Sunday',
714
    description => 'Sunday closure'
715
);
716
717
t::lib::Mocks::mock_preference( 'useDaysMode',                       'Calendar' );
718
t::lib::Mocks::mock_preference( 'ConsiderLibraryHoursInCirculation', 'open' );
719
720
# Should extend to Monday (skipping Saturday public holiday and Sunday which has no hours)
721
$date             = C4::Circulation::CalcDateDue( $friday_14, $itemtype, $library5->{branchcode}, $borrower );
722
$expected_duetime = DateTime->new( year => 2023, month => 5, day => 8, hour => 10, minute => 0 );
723
is(
724
    $date, $expected_duetime,
725
    "Bug 38940 follow-up: Extends to Monday when Saturday is single-day holiday"
726
);
727
555
$cache->clear_from_cache($key);
728
$cache->clear_from_cache($key);
556
$schema->storage->txn_rollback;
729
$schema->storage->txn_rollback;
557
- 

Return to bug 38940