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

(-)a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss (+27 lines)
Lines 938-943 $dayTrailBackground: #fcdcb3 !default; Link Here
938
        background: #f5f5f5 !important;  /* Light background */
938
        background: #f5f5f5 !important;  /* Light background */
939
        cursor: not-allowed !important;
939
        cursor: not-allowed !important;
940
    }
940
    }
941
942
    /* BIDIRECTIONAL ENHANCEMENT: Visual indicators for existing bookings' protected periods */
943
    &.existingBookingLead {
944
        /* Existing booking's lead period (preparation time before their booking starts) */
945
        /* Use diagonal stripes to distinguish from new booking's lead period */
946
        background: repeating-linear-gradient(
947
            45deg,
948
            #e6d4f5,
949
            #e6d4f5 10px,
950
            #f0e6f8 10px,
951
            #f0e6f8 20px
952
        );
953
        opacity: 0.7;
954
    }
955
956
    &.existingBookingTrail {
957
        /* Existing booking's trail period (processing time after their booking ends) */
958
        /* Use diagonal stripes to distinguish from new booking's trail period */
959
        background: repeating-linear-gradient(
960
            45deg,
961
            #fcd4e6,
962
            #fcd4e6 10px,
963
            #ffe6f0 10px,
964
            #ffe6f0 20px
965
        );
966
        opacity: 0.7;
967
    }
941
}
968
}
942
969
943
.input-warning {
970
.input-warning {
(-)a/koha-tmpl/intranet-tmpl/prog/js/modals/place_booking.js (-2 / +209 lines)
Lines 937-942 $("#placeBookingModal").on("show.bs.modal", function (e) { Link Here
937
                                  )
937
                                  )
938
                                : null;
938
                                : null;
939
939
940
                            // Calculate new booking's lead/trail periods
940
                            const leadStart = startDate
941
                            const leadStart = startDate
941
                                ? startDate.subtract(leadDays, "day")
942
                                ? startDate.subtract(leadDays, "day")
942
                                : hoverDate.subtract(leadDays, "day");
943
                                : hoverDate.subtract(leadDays, "day");
Lines 944-951 $("#placeBookingModal").on("show.bs.modal", function (e) { Link Here
944
                            const trailStart = hoverDate;
945
                            const trailStart = hoverDate;
945
                            const trailEnd = hoverDate.add(trailDays, "day");
946
                            const trailEnd = hoverDate.add(trailDays, "day");
946
947
948
                            // BIDIRECTIONAL ENHANCEMENT: Collect closest bookings for visual feedback
949
                            // and check for mathematical conflicts in a single pass
950
                            let closestBeforeBooking = null;
951
                            let closestBeforeDistance = Infinity;
952
953
                            let closestAfterBooking = null;
954
                            let closestAfterDistance = Infinity;
955
947
                            let leadDisable = false;
956
                            let leadDisable = false;
948
                            let trailDisable = false;
957
                            let trailDisable = false;
958
959
                            bookings.forEach(booking => {
960
                                // Skip if we're editing this booking
961
                                if (
962
                                    booking_id &&
963
                                    booking_id == booking.booking_id
964
                                ) {
965
                                    return;
966
                                }
967
968
                                // Skip if not same item (for item-specific bookings)
969
                                if (
970
                                    booking.item_id &&
971
                                    booking_item_id &&
972
                                    booking.item_id != booking_item_id
973
                                ) {
974
                                    return;
975
                                }
976
977
                                const bookingStart = dayjs(
978
                                    booking.start_date
979
                                ).startOf("day");
980
                                const bookingEnd = dayjs(
981
                                    booking.end_date
982
                                ).startOf("day");
983
984
                                // BIDIRECTIONAL: Mathematical checks for conflicts (works across month boundaries)
985
                                // Calculate this booking's full protected period
986
                                const existingLeadStart = bookingStart.subtract(
987
                                    leadDays,
988
                                    "day"
989
                                );
990
                                const existingLeadEnd = bookingStart.subtract(
991
                                    1,
992
                                    "day"
993
                                );
994
                                const existingTrailStart = bookingEnd.add(
995
                                    1,
996
                                    "day"
997
                                );
998
                                const existingTrailEnd = bookingEnd.add(
999
                                    trailDays,
1000
                                    "day"
1001
                                );
1002
1003
                                // Check if new booking's LEAD period overlaps with existing booking's TRAIL period
1004
                                if (!periodPicker.selectedDates[0]) {
1005
                                    if (
1006
                                        leadStart.isSameOrBefore(
1007
                                            existingTrailEnd
1008
                                        ) &&
1009
                                        leadEnd.isSameOrAfter(
1010
                                            existingTrailStart
1011
                                        )
1012
                                    ) {
1013
                                        leadDisable = true;
1014
                                    }
1015
                                }
1016
1017
                                // Check if new booking's TRAIL period overlaps with existing booking's LEAD period
1018
                                if (periodPicker.selectedDates[0]) {
1019
                                    if (
1020
                                        trailStart.isSameOrBefore(
1021
                                            existingLeadEnd
1022
                                        ) &&
1023
                                        trailEnd.isSameOrAfter(
1024
                                            existingLeadStart
1025
                                        )
1026
                                    ) {
1027
                                        trailDisable = true;
1028
                                    }
1029
                                }
1030
1031
                                // Find closest bookings for visual feedback (when dates are in view)
1032
                                if (bookingEnd.isBefore(hoverDate)) {
1033
                                    const distance = hoverDate.diff(
1034
                                        bookingEnd,
1035
                                        "day"
1036
                                    );
1037
                                    if (distance < closestBeforeDistance) {
1038
                                        closestBeforeDistance = distance;
1039
                                        closestBeforeBooking = {
1040
                                            start: bookingStart,
1041
                                            end: bookingEnd,
1042
                                        };
1043
                                    }
1044
                                }
1045
1046
                                if (bookingStart.isAfter(hoverDate)) {
1047
                                    const distance = bookingStart.diff(
1048
                                        hoverDate,
1049
                                        "day"
1050
                                    );
1051
                                    if (distance < closestAfterDistance) {
1052
                                        closestAfterDistance = distance;
1053
                                        closestAfterBooking = {
1054
                                            start: bookingStart,
1055
                                            end: bookingEnd,
1056
                                        };
1057
                                    }
1058
                                }
1059
                            });
1060
949
                            periodPicker.calendarContainer
1061
                            periodPicker.calendarContainer
950
                                .querySelectorAll(".flatpickr-day")
1062
                                .querySelectorAll(".flatpickr-day")
951
                                .forEach(function (dayElem) {
1063
                                .forEach(function (dayElem) {
Lines 953-958 $("#placeBookingModal").on("show.bs.modal", function (e) { Link Here
953
                                        dayElem.dateObj
1065
                                        dayElem.dateObj
954
                                    ).startOf("day");
1066
                                    ).startOf("day");
955
1067
1068
                                    // Clear existing booking lead/trail classes
1069
                                    dayElem.classList.remove(
1070
                                        "existingBookingLead"
1071
                                    );
1072
                                    dayElem.classList.remove(
1073
                                        "existingBookingTrail"
1074
                                    );
1075
1076
                                    // Apply new booking's lead/trail period classes
956
                                    dayElem.classList.toggle(
1077
                                    dayElem.classList.toggle(
957
                                        "leadRangeStart",
1078
                                        "leadRangeStart",
958
                                        elemDate.isSame(leadStart)
1079
                                        elemDate.isSame(leadStart)
Lines 979-985 $("#placeBookingModal").on("show.bs.modal", function (e) { Link Here
979
                                        "trailRangeEnd",
1100
                                        "trailRangeEnd",
980
                                        elemDate.isSame(trailEnd)
1101
                                        elemDate.isSame(trailEnd)
981
                                    );
1102
                                    );
982
                                    // If we're overlapping a disabled date, disable our hoverDate
1103
1104
                                    // BIDIRECTIONAL: Show closest existing booking's trail period
1105
                                    if (closestBeforeBooking && trailDays > 0) {
1106
                                        const existingTrailStart =
1107
                                            closestBeforeBooking.end.add(
1108
                                                1,
1109
                                                "day"
1110
                                            );
1111
                                        const existingTrailEnd =
1112
                                            closestBeforeBooking.end.add(
1113
                                                trailDays,
1114
                                                "day"
1115
                                            );
1116
1117
                                        if (
1118
                                            elemDate.isSameOrAfter(
1119
                                                existingTrailStart
1120
                                            ) &&
1121
                                            elemDate.isSameOrBefore(
1122
                                                existingTrailEnd
1123
                                            )
1124
                                        ) {
1125
                                            dayElem.classList.add(
1126
                                                "existingBookingTrail"
1127
                                            );
1128
                                        }
1129
                                    }
1130
1131
                                    // BIDIRECTIONAL: Show closest existing booking's lead period
1132
                                    if (closestAfterBooking && leadDays > 0) {
1133
                                        const existingLeadStart =
1134
                                            closestAfterBooking.start.subtract(
1135
                                                leadDays,
1136
                                                "day"
1137
                                            );
1138
                                        const existingLeadEnd =
1139
                                            closestAfterBooking.start.subtract(
1140
                                                1,
1141
                                                "day"
1142
                                            );
1143
1144
                                        if (
1145
                                            elemDate.isSameOrAfter(
1146
                                                existingLeadStart
1147
                                            ) &&
1148
                                            elemDate.isSameOrBefore(
1149
                                                existingLeadEnd
1150
                                            )
1151
                                        ) {
1152
                                            dayElem.classList.add(
1153
                                                "existingBookingLead"
1154
                                            );
1155
                                        }
1156
                                    }
1157
1158
                                    // Check for conflicts with flatpickr-disabled dates (original behavior)
983
                                    if (
1159
                                    if (
984
                                        dayElem.classList.contains(
1160
                                        dayElem.classList.contains(
985
                                            "flatpickr-disabled"
1161
                                            "flatpickr-disabled"
Lines 1013-1018 $("#placeBookingModal").on("show.bs.modal", function (e) { Link Here
1013
                                            }
1189
                                            }
1014
                                        }
1190
                                        }
1015
                                    }
1191
                                    }
1192
1193
                                    // BIDIRECTIONAL: Check for conflicts with existing booking's trail period
1194
                                    if (
1195
                                        !periodPicker.selectedDates[0] &&
1196
                                        dayElem.classList.contains(
1197
                                            "existingBookingTrail"
1198
                                        )
1199
                                    ) {
1200
                                        // New booking's lead period overlaps with existing booking's trail
1201
                                        if (
1202
                                            elemDate.isSameOrAfter(leadStart) &&
1203
                                            elemDate.isBefore(leadEnd)
1204
                                        ) {
1205
                                            leadDisable = true;
1206
                                        }
1207
                                    }
1208
1209
                                    // BIDIRECTIONAL: Check for conflicts with existing booking's lead period
1210
                                    if (
1211
                                        dayElem.classList.contains(
1212
                                            "existingBookingLead"
1213
                                        )
1214
                                    ) {
1215
                                        // New booking's trail period overlaps with existing booking's lead
1216
                                        if (
1217
                                            elemDate.isAfter(trailStart) &&
1218
                                            elemDate.isSameOrBefore(trailEnd)
1219
                                        ) {
1220
                                            trailDisable = true;
1221
                                        }
1222
                                    }
1223
1016
                                    dayElem.classList.remove("leadDisable");
1224
                                    dayElem.classList.remove("leadDisable");
1017
                                    dayElem.classList.remove("trailDisable");
1225
                                    dayElem.classList.remove("trailDisable");
1018
                                    dayElem.removeEventListener(
1226
                                    dayElem.removeEventListener(
1019
- 

Return to bug 37707