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

(-)a/koha-tmpl/intranet-tmpl/prog/js/modals/place_booking.js (-42 / +42 lines)
Lines 93-121 $("#placeBookingModal").on("show.bs.modal", function (e) { Link Here
93
            let $patron = $("<span></span>")
93
            let $patron = $("<span></span>")
94
                .append(
94
                .append(
95
                    "" +
95
                    "" +
96
                        (patron.surname
96
                    (patron.surname
97
                            ? escape_str(patron.surname) + ", "
97
                        ? escape_str(patron.surname) + ", "
98
                            : "") +
98
                        : "") +
99
                        (patron.firstname
99
                    (patron.firstname
100
                            ? escape_str(patron.firstname) + " "
100
                        ? escape_str(patron.firstname) + " "
101
                            : "") +
101
                        : "") +
102
                        (patron.cardnumber
102
                    (patron.cardnumber
103
                            ? " (" + escape_str(patron.cardnumber) + ")"
103
                        ? " (" + escape_str(patron.cardnumber) + ")"
104
                            : "") +
104
                        : "") +
105
                        "<small>" +
105
                    "<small>" +
106
                        (patron.date_of_birth
106
                    (patron.date_of_birth
107
                            ? ' <span class="age_years">' +
107
                        ? ' <span class="age_years">' +
108
                              $get_age(patron.date_of_birth) +
108
                        $get_age(patron.date_of_birth) +
109
                              " " +
109
                        " " +
110
                              __("years") +
110
                        __("years") +
111
                              "</span>"
111
                        "</span>"
112
                            : "") +
112
                        : "") +
113
                        (patron.library
113
                    (patron.library
114
                            ? ' <span class="ac-library">' +
114
                        ? ' <span class="ac-library">' +
115
                              escape_str(patron.library.name) +
115
                        escape_str(patron.library.name) +
116
                              "</span>"
116
                        "</span>"
117
                            : "") +
117
                        : "") +
118
                        "</small>"
118
                    "</small>"
119
                )
119
                )
120
                .addClass(loggedInClass);
120
                .addClass(loggedInClass);
121
            return $patron;
121
            return $patron;
Lines 211-220 $("#placeBookingModal").on("show.bs.modal", function (e) { Link Here
211
            ) {
211
            ) {
212
                let option = $(
212
                let option = $(
213
                    '<option value="' +
213
                    '<option value="' +
214
                        pickup_location.library_id +
214
                    pickup_location.library_id +
215
                        '">' +
215
                    '">' +
216
                        pickup_location.name +
216
                    pickup_location.name +
217
                        "</option>"
217
                    "</option>"
218
                );
218
                );
219
219
220
                option.attr(
220
                option.attr(
Lines 413-420 $("#placeBookingModal").on("show.bs.modal", function (e) { Link Here
413
                    if (
413
                    if (
414
                        !$("#booking_itemtype").find(
414
                        !$("#booking_itemtype").find(
415
                            "option[value='" +
415
                            "option[value='" +
416
                                item.item_type.item_type_id +
416
                            item.item_type.item_type_id +
417
                                "']"
417
                            "']"
418
                        ).length
418
                        ).length
419
                    ) {
419
                    ) {
420
                        // Create a DOM Option and de-select by default
420
                        // Create a DOM Option and de-select by default
Lines 752-758 $("#placeBookingModal").on("show.bs.modal", function (e) { Link Here
752
                                for (let i = 0; i < renewalsAllowed; i++) {
752
                                for (let i = 0; i < renewalsAllowed; i++) {
753
                                    nextDate.setDate(
753
                                    nextDate.setDate(
754
                                        nextDate.getDate() +
754
                                        nextDate.getDate() +
755
                                            parseInt(renewalLength)
755
                                        parseInt(renewalLength)
756
                                    );
756
                                    );
757
                                    boldDates.push(new Date(nextDate));
757
                                    boldDates.push(new Date(nextDate));
758
                                }
758
                                }
Lines 932-939 $("#placeBookingModal").on("show.bs.modal", function (e) { Link Here
932
                            );
932
                            );
933
                            const startDate = periodPicker.selectedDates[0]
933
                            const startDate = periodPicker.selectedDates[0]
934
                                ? dayjs(periodPicker.selectedDates[0]).startOf(
934
                                ? dayjs(periodPicker.selectedDates[0]).startOf(
935
                                      "day"
935
                                    "day"
936
                                  )
936
                                )
937
                                : null;
937
                                : null;
938
938
939
                            const leadStart = startDate
939
                            const leadStart = startDate
Lines 959-965 $("#placeBookingModal").on("show.bs.modal", function (e) { Link Here
959
                                    dayElem.classList.toggle(
959
                                    dayElem.classList.toggle(
960
                                        "leadRange",
960
                                        "leadRange",
961
                                        elemDate.isSameOrAfter(leadStart) &&
961
                                        elemDate.isSameOrAfter(leadStart) &&
962
                                            elemDate.isBefore(leadEnd)
962
                                        elemDate.isBefore(leadEnd)
963
                                    );
963
                                    );
964
                                    dayElem.classList.toggle(
964
                                    dayElem.classList.toggle(
965
                                        "leadRangeEnd",
965
                                        "leadRangeEnd",
Lines 972-978 $("#placeBookingModal").on("show.bs.modal", function (e) { Link Here
972
                                    dayElem.classList.toggle(
972
                                    dayElem.classList.toggle(
973
                                        "trailRange",
973
                                        "trailRange",
974
                                        elemDate.isAfter(trailStart) &&
974
                                        elemDate.isAfter(trailStart) &&
975
                                            elemDate.isSameOrBefore(trailEnd)
975
                                        elemDate.isSameOrBefore(trailEnd)
976
                                    );
976
                                    );
977
                                    dayElem.classList.toggle(
977
                                    dayElem.classList.toggle(
978
                                        "trailRangeEnd",
978
                                        "trailRangeEnd",
Lines 1195-1202 $("#placeBookingForm").on("submit", function (e) { Link Here
1195
            // Set feedback
1195
            // Set feedback
1196
            $("#transient_result").replaceWith(
1196
            $("#transient_result").replaceWith(
1197
                '<div id="transient_result" class="alert alert-info">' +
1197
                '<div id="transient_result" class="alert alert-info">' +
1198
                    __("Booking successfully placed") +
1198
                __("Booking successfully placed") +
1199
                    "</div>"
1199
                "</div>"
1200
            );
1200
            );
1201
1201
1202
            // Close modal
1202
            // Close modal
Lines 1206-1213 $("#placeBookingForm").on("submit", function (e) { Link Here
1206
        posting.fail(function (data) {
1206
        posting.fail(function (data) {
1207
            $("#booking_result").replaceWith(
1207
            $("#booking_result").replaceWith(
1208
                '<div id="booking_result" class="alert alert-danger">' +
1208
                '<div id="booking_result" class="alert alert-danger">' +
1209
                    __("Failure") +
1209
                __("Failure") +
1210
                    "</div>"
1210
                "</div>"
1211
            );
1211
            );
1212
        });
1212
        });
1213
    } else {
1213
    } else {
Lines 1264-1271 $("#placeBookingForm").on("submit", function (e) { Link Here
1264
            // Set feedback
1264
            // Set feedback
1265
            $("#transient_result").replaceWith(
1265
            $("#transient_result").replaceWith(
1266
                '<div id="transient_result" class="alert alert-info">' +
1266
                '<div id="transient_result" class="alert alert-info">' +
1267
                    __("Booking successfully updated") +
1267
                __("Booking successfully updated") +
1268
                    "</div>"
1268
                "</div>"
1269
            );
1269
            );
1270
1270
1271
            // Close modal
1271
            // Close modal
Lines 1275-1282 $("#placeBookingForm").on("submit", function (e) { Link Here
1275
        putting.fail(function (data) {
1275
        putting.fail(function (data) {
1276
            $("#booking_result").replaceWith(
1276
            $("#booking_result").replaceWith(
1277
                '<div id="booking_result" class="alert alert-danger">' +
1277
                '<div id="booking_result" class="alert alert-danger">' +
1278
                    __("Failure") +
1278
                __("Failure") +
1279
                    "</div>"
1279
                "</div>"
1280
            );
1280
            );
1281
        });
1281
        });
1282
    }
1282
    }
(-)a/t/cypress/fixtures/bookings/bookable_items.json (+29 lines)
Line 0 Link Here
1
[
2
  {
3
    "item_id": "789",
4
    "external_id": "BARCODE789",
5
    "effective_item_type_id": "BK",
6
    "item_type": {
7
      "item_type_id": "BK",
8
      "description": "Book"
9
    }
10
  },
11
  {
12
    "item_id": "790",
13
    "external_id": "BARCODE790",
14
    "effective_item_type_id": "BK",
15
    "item_type": {
16
      "item_type_id": "BK",
17
      "description": "Book"
18
    }
19
  },
20
  {
21
    "item_id": "791",
22
    "external_id": "BARCODE791",
23
    "effective_item_type_id": "DVD",
24
    "item_type": {
25
      "item_type_id": "DVD",
26
      "description": "DVD"
27
    }
28
  }
29
]
(-)a/t/cypress/fixtures/bookings/bookings.json (+32 lines)
Line 0 Link Here
1
[
2
  {
3
    "booking_id": "1001",
4
    "biblio_id": "123",
5
    "patron_id": "456",
6
    "item_id": "789",
7
    "pickup_library_id": "1",
8
    "start_date": "2025-05-01T00:00:00.000Z",
9
    "end_date": "2025-05-05T23:59:59.999Z",
10
    "status": "pending"
11
  },
12
  {
13
    "booking_id": "1002",
14
    "biblio_id": "123",
15
    "patron_id": "457",
16
    "item_id": "790",
17
    "pickup_library_id": "1",
18
    "start_date": "2025-05-06T00:00:00.000Z",
19
    "end_date": "2025-05-10T23:59:59.999Z",
20
    "status": "pending"
21
  },
22
  {
23
    "booking_id": "1003",
24
    "biblio_id": "123",
25
    "patron_id": "458",
26
    "item_id": null,
27
    "pickup_library_id": "1",
28
    "start_date": "2025-05-20T00:00:00.000Z",
29
    "end_date": "2025-05-25T23:59:59.999Z",
30
    "status": "pending"
31
  }
32
]
(-)a/t/cypress/fixtures/bookings/checkouts.json (+9 lines)
Line 0 Link Here
1
[
2
  {
3
    "checkout_id": "5001",
4
    "biblio_id": "123",
5
    "item_id": "791",
6
    "patron_id": "459",
7
    "due_date": "2025-05-30T23:59:59.999Z"
8
  }
9
]
(-)a/t/cypress/fixtures/bookings/circulation_rules.json (+9 lines)
Line 0 Link Here
1
[
2
  {
3
    "bookings_lead_period": 3,
4
    "bookings_trail_period": 2,
5
    "issuelength": 14,
6
    "renewalsallowed": 2,
7
    "renewalperiod": 7
8
  }
9
]
(-)a/t/cypress/fixtures/bookings/patron.json (+12 lines)
Line 0 Link Here
1
{
2
  "patron_id": "456",
3
  "surname": "Doe",
4
  "firstname": "John",
5
  "cardnumber": "12345",
6
  "library_id": "1",
7
  "library": {
8
    "name": "Main Library"
9
  },
10
  "category_id": "1",
11
  "date_of_birth": "1990-01-01"
12
}
(-)a/t/cypress/fixtures/bookings/pickup_locations.json (+20 lines)
Line 0 Link Here
1
[
2
  {
3
    "library_id": "1",
4
    "name": "Main Library",
5
    "needs_override": false,
6
    "pickup_items": [
7
      789,
8
      790
9
    ]
10
  },
11
  {
12
    "library_id": "2",
13
    "name": "Branch Library",
14
    "needs_override": false,
15
    "pickup_items": [
16
      789,
17
      791
18
    ]
19
  }
20
]
(-)a/t/cypress/integration/Biblio/bookingsModal_spec.ts (-1 / +469 lines)
Line 0 Link Here
0
- 
1
describe("Booking Modal Tests", () => {
2
    // Test data setup
3
    const testData = {
4
        biblionumber: "134",
5
        patronId: "19",
6
        pickupLibraryId: "CPL",
7
        itemNumber: "287",
8
        itemTypeId: "BK",
9
        startDate: new Date(new Date().setDate(new Date().getDate() + 1)), // 1 day from now
10
        endDate: new Date(new Date().setDate(new Date().getDate() + 5)), // 5 days from now
11
    };
12
13
    beforeEach(() => {
14
        cy.login();
15
        cy.title().should("eq", "Koha staff interface");
16
17
        // Visit the page with the booking modal
18
        cy.visit(
19
            "/cgi-bin/koha/catalogue/detail.pl?biblionumber=" +
20
                testData.biblionumber
21
        );
22
23
        // Intercept API calls and provide mock responses
24
        cy.intercept("GET", "/api/v1/biblios/*/items?bookable=1&_per_page=-1", {
25
            fixture: "bookings/bookable_items.json",
26
        }).as("getBookableItems");
27
28
        cy.fixture("bookings/bookings.json").then(bookings => {
29
            // Get today's date
30
            const today = new Date();
31
32
            // Update the dates in the fixture data relative to today
33
            bookings[0].start_date = new Date(
34
                today.getTime() + 8 * 24 * 60 * 60 * 1000
35
            ).toISOString(); // Today + 8 day
36
            bookings[0].end_date = new Date(
37
                today.getTime() + 13 * 24 * 60 * 60 * 1000
38
            ).toISOString(); // Today + 13 days
39
40
            bookings[1].start_date = new Date(
41
                today.getTime() + 14 * 24 * 60 * 60 * 1000
42
            ).toISOString(); // Today + 14 days
43
            bookings[1].end_date = new Date(
44
                today.getTime() + 18 * 24 * 60 * 60 * 1000
45
            ).toISOString(); // Today + 18 days
46
47
            bookings[2].start_date = new Date(
48
                today.getTime() + 28 * 24 * 60 * 60 * 1000
49
            ).toISOString(); // Today + 28 days
50
            bookings[2].end_date = new Date(
51
                today.getTime() + 33 * 24 * 60 * 60 * 1000
52
            ).toISOString(); // Today + 33 days
53
54
            // Use the modified fixture data in your intercept
55
            cy.intercept("GET", "/api/v1/bookings?biblio_id=*&_per_page=-1*", {
56
                body: bookings,
57
            }).as("getBookings");
58
        });
59
60
        cy.intercept("GET", "/api/v1/biblios/*/checkouts?_per_page=-1", {
61
            fixture: "bookings/checkouts.json",
62
        }).as("getCheckouts");
63
64
        cy.intercept("GET", "/api/v1/patrons/*", {
65
            fixture: "bookings/patron.json",
66
        }).as("getPatron");
67
68
        cy.intercept("GET", "/api/v1/biblios/*/pickup_locations*", {
69
            fixture: "bookings/pickup_locations.json",
70
        }).as("getPickupLocations");
71
72
        cy.intercept("GET", "/api/v1/circulation_rules*", {
73
            fixture: "bookings/circulation_rules.json",
74
        }).as("getCirculationRules");
75
76
        cy.intercept("POST", "/api/v1/bookings", {
77
            statusCode: 201,
78
            body: {
79
                booking_id: "1001",
80
                start_date: testData.startDate.toISOString(),
81
                end_date: testData.endDate.toISOString(),
82
                pickup_library_id: testData.pickupLibraryId,
83
                biblio_id: testData.biblionumber,
84
                item_id: testData.itemNumber,
85
                patron_id: testData.patronId,
86
            },
87
        }).as("createBooking");
88
89
        cy.intercept("PUT", "/api/v1/bookings/*", {
90
            statusCode: 200,
91
            body: {
92
                booking_id: "1001",
93
                start_date: testData.startDate.toISOString(),
94
                end_date: testData.endDate.toISOString(),
95
                pickup_library_id: testData.pickupLibraryId,
96
                biblio_id: testData.biblionumber,
97
                item_id: testData.itemNumber,
98
                patron_id: testData.patronId,
99
            },
100
        }).as("updateBooking");
101
102
        // Populate the select2 search results for patron search
103
        cy.intercept("GET", "/api/v1/patrons*", {
104
            body: [
105
                {
106
                    patron_id: testData.patronId,
107
                    surname: "Doe",
108
                    firstname: "John",
109
                    cardnumber: "12345",
110
                    library_id: "1",
111
                    library: {
112
                        name: "Main Library",
113
                    },
114
                    category_id: "1",
115
                    date_of_birth: "1990-01-01",
116
                },
117
            ],
118
            pagination: { more: false },
119
        }).as("searchPatrons");
120
121
        // Add clickable button
122
        cy.document().then(doc => {
123
            const button = doc.createElement("button");
124
            button.setAttribute("data-bs-toggle", "modal");
125
            button.setAttribute("data-bs-target", "#placeBookingModal");
126
            button.setAttribute("data-biblionumber", testData.biblionumber);
127
            button.setAttribute("id", "placebooking");
128
            doc.body.appendChild(button);
129
        });
130
131
        // Open the booking modal
132
        cy.get("#placebooking").click();
133
    });
134
135
    it("should load the booking modal correctly", () => {
136
        // Check modal title
137
        cy.get("#placeBookingLabel").should("contain", "Place booking");
138
139
        // Check form elements are present
140
        cy.get("#booking_patron_id").should("exist");
141
        cy.get("#pickup_library_id").should("exist");
142
        cy.get("#booking_itemtype").should("exist");
143
        cy.get("#booking_item_id").should("exist");
144
        cy.get("#period").should("exist");
145
146
        // Check hidden fields
147
        cy.get("#booking_biblio_id").should(
148
            "have.value",
149
            testData.biblionumber
150
        );
151
        cy.get("#booking_start_date").should("have.value", "");
152
        cy.get("#booking_end_date").should("have.value", "");
153
    });
154
155
    it("should enable fields in proper sequence", () => {
156
        // Initially only patron field should be enabled
157
        cy.get("#booking_patron_id").should("not.be.disabled");
158
        cy.get("#pickup_library_id").should("be.disabled");
159
        cy.get("#booking_itemtype").should("be.disabled");
160
        cy.get("#booking_item_id").should("be.disabled");
161
        cy.get("#period").should("be.disabled");
162
163
        // Select patron
164
        cy.selectFromSelect2ByIndex("#booking_patron_id", 0, "John");
165
        //cy.getSelect2("#booking_patron_id")
166
        //    .select2({ search: "John" })
167
        //    .select2({ selectIndex: 0 });
168
        cy.wait("@getPickupLocations");
169
170
        // After patron selection, pickup location, item type and item should be enabled
171
        cy.get("#pickup_library_id").should("not.be.disabled");
172
        cy.get("#booking_itemtype").should("not.be.disabled");
173
        cy.get("#booking_item_id").should("not.be.disabled");
174
        cy.get("#period").should("be.disabled");
175
176
        // Select pickup location
177
        cy.selectFromSelect2ByIndex("#pickup_library_id", 0);
178
179
        // Select item type, trigger circulation rules
180
        cy.selectFromSelect2ByIndex("#booking_itemtype", 0);
181
        cy.wait("@getCirculationRules");
182
183
        // After patron, pickup location and itemtype/item selection, date picker should be enabled
184
        cy.get("#period").should("not.be.disabled");
185
186
        // Clear item type and confirm period is disabled
187
        cy.clearSelect2("#booking_itemtype");
188
        cy.get("#period").should("be.disabled");
189
190
        // Select item, re-enable period
191
        cy.selectFromSelect2ByIndex("#booking_item_id", 1);
192
        cy.get("#period").should("not.be.disabled");
193
    });
194
195
    it("should handle item type and item dependencies correctly", () => {
196
        // Select patron and pickup location first
197
        cy.selectFromSelect2ByIndex("#booking_patron_id", 0, "John");
198
        cy.wait("@getPickupLocations");
199
        cy.selectFromSelect2ByIndex("#pickup_library_id", 0);
200
201
        // Select an item first
202
        cy.selectFromSelect2ByIndex("#booking_item_id", 1);
203
        cy.wait("@getCirculationRules");
204
205
        // Verify that item type gets selected automatically
206
        cy.get("#booking_itemtype").should("have.value", testData.itemTypeId);
207
208
        // Verify that item type gets disabled
209
        cy.get("#booking_itemtype").should("be.disabled");
210
211
        // Reset the modal
212
        cy.get('#placeBookingModal button[data-bs-dismiss="modal"]')
213
            .first()
214
            .click();
215
        cy.get("#placebooking").click();
216
217
        // Now select patron, pickup and item type first
218
        cy.selectFromSelect2ByIndex("#booking_patron_id", 0, "John");
219
        cy.wait("@getPickupLocations");
220
        cy.selectFromSelect2ByIndex("#pickup_library_id", 0);
221
        cy.selectFromSelect2ByIndex("#booking_itemtype", 0);
222
        cy.wait("@getCirculationRules");
223
        cy.wait(300);
224
225
        // Verify that only 'Any item' option and items of selected type are enabled
226
        cy.get("#booking_item_id > option").then($options => {
227
            const enabledOptions = $options.filter(":not(:disabled)");
228
            enabledOptions.each(function () {
229
                const $option = cy.wrap(this);
230
231
                // Get both the value and the data-itemtype attribute to make decisions
232
                $option.invoke("val").then(value => {
233
                    if (value === "0") {
234
                        // We need to re-wrap the element since invoke('val') changed the subject
235
                        cy.wrap(this).should("contain.text", "Any item");
236
                    } else {
237
                        // Re-wrap the element again for this assertion
238
                        cy.wrap(this).should(
239
                            "have.attr",
240
                            "data-itemtype",
241
                            testData.itemTypeId
242
                        );
243
                    }
244
                });
245
            });
246
        });
247
    });
248
249
    it("should handle date selection and availability", () => {
250
        // Select patron, pickup location and item
251
        cy.selectFromSelect2ByIndex("#booking_patron_id", 0, "John");
252
        cy.wait("@getPickupLocations");
253
        cy.selectFromSelect2ByIndex("#pickup_library_id", 0);
254
        cy.selectFromSelect2ByIndex("#booking_item_id", 1);
255
        cy.wait("@getCirculationRules");
256
257
        // Open flatpickr
258
        cy.openFlatpickr("#period");
259
260
        // Get today's date
261
        const today = new Date();
262
        const todayDay = today.getDate();
263
264
        // Get target date (5 days from now)
265
        const targetDate = new Date(today);
266
        targetDate.setDate(today.getDate() + 5);
267
        const targetDay = targetDate.getDate();
268
269
        // Check if the dates are in the same month
270
        if (
271
            today.getMonth() === targetDate.getMonth() &&
272
            today.getFullYear() === targetDate.getFullYear()
273
        ) {
274
            // Same month - select start date
275
            cy.selectDay(todayDay);
276
            cy.wait(300);
277
278
            // Select end date
279
            cy.selectDay(targetDay, "#period");
280
        } else {
281
            // Different months - select start date
282
            cy.selectDay(todayDay);
283
            cy.wait(300);
284
285
            // Navigate to the target month
286
            cy.navigateToMonth(targetDate);
287
            cy.wait(300);
288
289
            // Select end date
290
            cy.selectDay(targetDay, "#period");
291
            cy.wait(300);
292
        }
293
294
        // QUESTION: Why does my flatpickr appear to be re-triggered here
295
        // so it's open and all the below assertions fail?
296
297
        /*
298
        // Use should with retry capability instead of a simple assertion
299
        cy.get("#period").should("have.value", /\w+/); // First check that there's any value
300
301
        // Verify the flatpickr visible input also has value
302
        cy.getFlatpickr("#period").should("have.value", /\w+/);
303
304
        // Now check the hidden fields
305
        cy.get("#booking_start_date").should("have.value", /\w+/);
306
        cy.get("#booking_end_date").should("have.value", /\w+/);
307
308
        // Verify the range was selected properly
309
        cy.get("#period").should("not.have.value", "");
310
311
        // Now check the hidden fields
312
        cy.get("#booking_start_date").should("not.have.value", "");
313
        cy.get("#booking_end_date").should("not.have.value", "");
314
315
        // Check for disabled dates due to other bookings
316
        cy.get(".flatpickr-day.flatpickr-disabled").should("exist");
317
318
        // Check for event dots indicating existing bookings
319
        cy.get(".flatpickr-day .event-dots").should("exist");
320
        */
321
    });
322
323
    it("should handle date selection and availability - programatically", () => {
324
        // Select patron, pickup location and item
325
        cy.selectFromSelect2ByIndex("#booking_patron_id", 0, "John");
326
        cy.wait("@getPickupLocations");
327
        cy.selectFromSelect2ByIndex("#pickup_library_id", 0);
328
        cy.selectFromSelect2ByIndex("#booking_item_id", 1);
329
        cy.wait("@getCirculationRules");
330
331
        const picker = win.document.getElementById("period")._flatpickr;
332
333
        // Available dates
334
        let startDate = new Date(testData.startDate);
335
        let endDate = new Date(testData.endDate);
336
        picker.setDate([startDate, endDate], true);
337
338
        // Verify the range was selected properly
339
        cy.get("#period").should("not.have.value", "");
340
341
        // Now check the hidden fields
342
        cy.get("#booking_start_date").should("not.have.value", "");
343
        cy.get("#booking_end_date").should("not.have.value", "");
344
345
        // Unavailable dates - Should throw error
346
        startDate = new Date(testData.startDate);
347
        endDate = new Date(testData.endDate);
348
        picker.setDate([startDate, endDate], true);
349
    });
350
351
    it("should submit a new booking successfully", () => {
352
        // Select patron, pickup location and item
353
        cy.selectFromSelect2ByIndex("#booking_patron_id", 0, "John");
354
        cy.wait("@getPickupLocations");
355
        cy.selectFromSelect2ByIndex("#pickup_library_id", 0);
356
        cy.selectFromSelect2ByIndex("#booking_item_id", 1);
357
        cy.wait("@getCirculationRules");
358
359
        // Set dates with flatpickr
360
        cy.window().then(win => {
361
            const picker = win.document.getElementById("period")._flatpickr;
362
            const startDate = new Date(testData.startDate);
363
            const endDate = new Date(testData.endDate);
364
            picker.setDate([startDate, endDate], true);
365
        });
366
367
        // Submit the form
368
        cy.get("#placeBookingForm").submit();
369
        cy.wait("@createBooking");
370
371
        // Check success message
372
        cy.get("#transient_result").should(
373
            "contain",
374
            "Booking successfully placed"
375
        );
376
377
        // Check modal closes
378
        cy.get("#placeBookingModal").should("not.be.visible");
379
    });
380
381
    it("should edit an existing booking successfully", () => {
382
        // Open edit booking modal
383
        cy.get("#placebooking").invoke("attr", "data-booking", "1001").click();
384
385
        // Check modal title for edit
386
        cy.get("#placeBookingLabel").should("contain", "Edit booking");
387
388
        // Verify booking ID is set
389
        cy.get("#booking_id").should("have.value", "1001");
390
391
        // Change pickup location
392
        cy.get("#pickup_library_id").select("2");
393
394
        // Submit the form
395
        cy.get("#placeBookingForm").submit();
396
        cy.wait("@updateBooking");
397
398
        // Check success message
399
        cy.get("#transient_result").should(
400
            "contain",
401
            "Booking successfully updated"
402
        );
403
404
        // Check modal closes
405
        cy.get("#placeBookingModal").should("not.be.visible");
406
    });
407
408
    it("should handle booking failure gracefully", () => {
409
        // Override the create booking intercept to return an error
410
        cy.intercept("POST", "/api/v1/bookings", {
411
            statusCode: 400,
412
            body: {
413
                error: "Booking failed",
414
            },
415
        }).as("failedBooking");
416
417
        // Fill out the booking form
418
        cy.selectFromSelect2ByIndex("#booking_patron_id", 0, "John");
419
        cy.wait("@getPickupLocations");
420
        cy.selectFromSelect2ByIndex("#pickup_library_id", 0);
421
        cy.selectFromSelect2ByIndex("#booking_item_id", 1);
422
        cy.wait("@getCirculationRules");
423
424
        // Set dates with flatpickr
425
        cy.window().then(win => {
426
            const picker = win.document.getElementById("period")._flatpickr;
427
            const startDate = new Date(testData.startDate);
428
            const endDate = new Date(testData.endDate);
429
            picker.setDate([startDate, endDate], true);
430
        });
431
432
        // Submit the form
433
        cy.get("#placeBookingForm").submit();
434
        cy.wait("@failedBooking");
435
436
        // Check error message
437
        cy.get("#booking_result").should("contain", "Failure");
438
439
        // Modal should remain open
440
        cy.get("#placeBookingModal").should("be.visible");
441
    });
442
443
    it("should reset form when modal is closed", () => {
444
        // Fill out some form fields
445
        cy.selectFromSelect2ByIndex("#booking_patron_id", 0, "John");
446
        cy.wait("@getPickupLocations");
447
        cy.selectFromSelect2ByIndex("#pickup_library_id", 0);
448
        cy.selectFromSelect2ByIndex("#booking_item_id", 1);
449
        cy.wait("@getCirculationRules");
450
451
        // Close the modal
452
        cy.get('#placeBookingModal button[data-bs-dismiss="modal"]')
453
            .first()
454
            .click();
455
456
        // Re-open the modal
457
        cy.get("#placebooking").click();
458
459
        // Check fields are reset
460
        cy.get("#booking_patron_id").should("have.value", null);
461
        cy.get("#pickup_library_id").should("be.disabled");
462
        cy.get("#booking_itemtype").should("be.disabled");
463
        cy.get("#booking_item_id").should("be.disabled");
464
        cy.get("#period").should("be.disabled");
465
        cy.get("#booking_start_date").should("have.value", "");
466
        cy.get("#booking_end_date").should("have.value", "");
467
        cy.get("#booking_id").should("have.value", "");
468
    });
469
});

Return to bug 39916