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

(-)a/t/cypress/integration/Circulation/bookingsModalBasic_spec.ts (-3 / +611 lines)
Lines 418-425 describe("Booking Modal Basic Tests", () => { Link Here
418
        cy.get("#period").should("not.be.disabled");
418
        cy.get("#period").should("not.be.disabled");
419
419
420
        // Use the flatpickr helper to select date range
420
        // Use the flatpickr helper to select date range
421
        const startDate = dayjs().add(1, "day");
421
        // Note: Add enough days to account for lead period (3 days) to avoid past-date constraint
422
        const endDate = dayjs().add(7, "days");
422
        const startDate = dayjs().add(5, "day");
423
        const endDate = dayjs().add(10, "days");
423
424
424
        cy.get("#period").selectFlatpickrDateRange(startDate, endDate);
425
        cy.get("#period").selectFlatpickrDateRange(startDate, endDate);
425
426
Lines 435-440 describe("Booking Modal Basic Tests", () => { Link Here
435
        );
436
        );
436
    });
437
    });
437
438
439
    it("should successfully submit an 'Any item' booking with server-side optimal item selection", () => {
440
        /**
441
         * TEST: Bug 40134 - Server-Side Optimal Item Selection for "Any Item" Bookings
442
         *
443
         * This test validates that:
444
         * 1. "Any item" bookings can be successfully submitted with itemtype_id
445
         * 2. The server performs optimal item selection based on future availability
446
         * 3. An appropriate item is automatically assigned by the server
447
         *
448
         * When submitting an "any item" booking, the client sends itemtype_id
449
         * (or item_id if only one item is available) and the server selects
450
         * the optimal item with the longest future availability.
451
         */
452
453
        cy.visit(
454
            `/cgi-bin/koha/catalogue/detail.pl?biblionumber=${testData.biblio.biblio_id}`
455
        );
456
457
        // Open the modal
458
        cy.get('[data-bs-target="#placeBookingModal"]').first().click();
459
        cy.get("#placeBookingModal").should("be.visible");
460
461
        // Step 1: Select patron
462
        cy.selectFromSelect2(
463
            "#booking_patron_id",
464
            `${testData.patron.surname}, ${testData.patron.firstname}`,
465
            testData.patron.cardnumber
466
        );
467
468
        // Step 2: Select pickup location
469
        cy.get("#pickup_library_id").should("not.be.disabled");
470
        cy.selectFromSelect2ByIndex("#pickup_library_id", 0);
471
472
        // Step 3: Select itemtype (to enable "Any item" for that type)
473
        cy.get("#booking_itemtype").should("not.be.disabled");
474
        cy.selectFromSelect2ByIndex("#booking_itemtype", 0); // Select first itemtype
475
476
        // Step 4: Select "Any item" option (index 0)
477
        cy.get("#booking_item_id").should("not.be.disabled");
478
        cy.selectFromSelect2ByIndex("#booking_item_id", 0); // "Any item" option
479
480
        // Verify "Any item" is selected
481
        cy.get("#booking_item_id").should("have.value", "0");
482
483
        // Step 5: Set dates using flatpickr
484
        cy.get("#period").should("not.be.disabled");
485
486
        // Note: Add enough days to account for lead period (3 days) to avoid past-date constraint
487
        const startDate = dayjs().add(5, "day");
488
        const endDate = dayjs().add(10, "days");
489
490
        cy.get("#period").selectFlatpickrDateRange(startDate, endDate);
491
492
        // Wait a moment for onChange handlers to populate hidden fields
493
        cy.wait(500);
494
495
        // Step 6: Submit the form
496
        // This will send either item_id (if only one available) or itemtype_id
497
        // to the server for optimal item selection
498
        cy.get("#placeBookingForm button[type='submit']")
499
            .should("not.be.disabled")
500
            .click();
501
502
        // Verify success - modal should close without errors
503
        cy.get("#placeBookingModal", { timeout: 10000 }).should(
504
            "not.be.visible"
505
        );
506
507
        // Verify that a booking was created and the server assigned an optimal item
508
        cy.task("query", {
509
            sql: `SELECT * FROM bookings
510
                  WHERE biblio_id = ?
511
                  AND patron_id = ?
512
                  AND start_date = ?
513
                  ORDER BY booking_id DESC
514
                  LIMIT 1`,
515
            values: [
516
                testData.biblio.biblio_id,
517
                testData.patron.patron_id,
518
                startDate.format("YYYY-MM-DD"),
519
            ],
520
        }).then(result => {
521
            expect(result).to.have.length(1);
522
            const booking = result[0];
523
524
            // Verify the booking has an item_id assigned (not null)
525
            expect(booking.item_id).to.not.be.null;
526
            expect(booking.item_id).to.be.oneOf([
527
                testData.items[0].item_id,
528
                testData.items[1].item_id,
529
            ]);
530
531
            // Verify booking dates match what we selected
532
            expect(booking.start_date).to.include(
533
                startDate.format("YYYY-MM-DD")
534
            );
535
            expect(booking.end_date).to.include(endDate.format("YYYY-MM-DD"));
536
537
            // Clean up the test booking
538
            cy.task("query", {
539
                sql: "DELETE FROM bookings WHERE booking_id = ?",
540
                values: [booking.booking_id],
541
            });
542
        });
543
544
        cy.log("✓ CONFIRMED: Any item booking submitted successfully");
545
        cy.log("✓ CONFIRMED: Server-side optimal item selection completed");
546
        cy.log("✓ CONFIRMED: Optimal item automatically assigned by server");
547
    });
548
438
    it("should handle basic form interactions correctly", () => {
549
    it("should handle basic form interactions correctly", () => {
439
        cy.visit(
550
        cy.visit(
440
            `/cgi-bin/koha/catalogue/detail.pl?biblionumber=${testData.biblio.biblio_id}`
551
            `/cgi-bin/koha/catalogue/detail.pl?biblionumber=${testData.biblio.biblio_id}`
Lines 1079-1082 describe("Booking Modal Basic Tests", () => { Link Here
1079
            "✓ Validated: API errors, user feedback, form preservation, and retry functionality"
1190
            "✓ Validated: API errors, user feedback, form preservation, and retry functionality"
1080
        );
1191
        );
1081
    });
1192
    });
1193
1194
    it("should maximize booking window by dynamically reducing available items during overlaps", () => {
1195
        /**
1196
         * COMPREHENSIVE TEST: Dynamic Item Assignment & Date Restriction
1197
         *
1198
         * This test validates the core "smart window maximization" algorithm for "any item of itemtype X" bookings.
1199
         *
1200
         * KEY ALGORITHM PRINCIPLE: "Never Re-add Items to Pool"
1201
         * Once an item is removed from the available pool because it becomes unavailable,
1202
         * it is NEVER re-added even if it becomes available again later in the booking period.
1203
         * This ensures optimal resource allocation and maximum booking windows.
1204
         *
1205
         * TEST SCENARIOS COVERED:
1206
         * 1. Day 5 start: Tests item pool reduction as items become unavailable
1207
         * 2. Day 8 start: Tests maximum window with all items initially available
1208
         * 3. Day 14 start: Tests window maximization with reduced initial pool
1209
         * 4. Day 19 start: Tests multi-item window extension through non-re-addition principle
1210
         * 5. Cross-scenario consistency validation
1211
         *
1212
         * EXPECTED ALGORITHM BEHAVIOR:
1213
         * - Start with items available on the selected start date
1214
         * - Walk through each day from start to potential end date
1215
         * - Remove items from pool when they become unavailable (bookings start)
1216
         * - NEVER re-add items even if they become available again (bookings end)
1217
         * - Return false (disable date) when no items remain in pool
1218
         *
1219
         * This maximizes booking windows by ensuring optimal resource utilization.
1220
         */
1221
1222
        const today = dayjs();
1223
1224
        // Create custom test data with 4 items of the same itemtype for this specific test
1225
        let testItems = [];
1226
        let testBiblio = null;
1227
        let testPatron = null;
1228
1229
        // Setup: Create biblio with 4 TABLET items
1230
        cy.task("insertSampleBiblio", {
1231
            item_count: 4,
1232
        })
1233
            .then(objects => {
1234
                testBiblio = objects.biblio;
1235
                testItems = objects.items;
1236
1237
                // Update all 4 items to be TABLET itemtype and bookable
1238
                // Set enumchron to control API ordering (A, B, C, D)
1239
                const itemUpdates = testItems.map((item, index) => {
1240
                    const enumchron = String.fromCharCode(65 + index); // A, B, C, D
1241
                    return cy.task("query", {
1242
                        sql: "UPDATE items SET bookable = 1, itype = 'BK', homebranch = 'CPL', enumchron = ?, dateaccessioned = ? WHERE itemnumber = ?",
1243
                        values: [
1244
                            enumchron,
1245
                            `2024-12-0${4 - index}`, // Newest to oldest
1246
                            item.item_id,
1247
                        ],
1248
                    });
1249
                });
1250
1251
                return Promise.all(itemUpdates);
1252
            })
1253
            .then(() => {
1254
                // Create a test patron
1255
                return cy.task("buildSampleObject", {
1256
                    object: "patron",
1257
                    values: {
1258
                        firstname: "John",
1259
                        surname: "Doe",
1260
                        cardnumber: `TEST${Date.now()}`,
1261
                        category_id: "PT",
1262
                        library_id: "CPL",
1263
                    },
1264
                });
1265
            })
1266
            .then(mockPatron => {
1267
                testPatron = mockPatron;
1268
1269
                // Insert the patron into the database
1270
                return cy.task("query", {
1271
                    sql: `INSERT INTO borrowers (borrowernumber, firstname, surname, cardnumber, categorycode, branchcode, dateofbirth)
1272
                          VALUES (?, ?, ?, ?, ?, ?, ?)`,
1273
                    values: [
1274
                        mockPatron.patron_id,
1275
                        mockPatron.firstname,
1276
                        mockPatron.surname,
1277
                        mockPatron.cardnumber,
1278
                        mockPatron.category_id,
1279
                        mockPatron.library_id,
1280
                        "1990-01-01",
1281
                    ],
1282
                });
1283
            })
1284
            .then(() => {
1285
                /**
1286
                 * STRATEGIC BOOKING PATTERN DESIGN:
1287
                 *
1288
                 * This booking pattern creates a perfect test case for validating the
1289
                 * "never re-add items to pool" algorithm across multiple scenarios:
1290
                 *
1291
                 * ITEM 0 (enumchron A): Available 5-9, BOOKED 10-15, Available again 16+
1292
                 * ITEM 1 (enumchron B): Available 5-12, BOOKED 13-20, Available again 21+
1293
                 * ITEM 2 (enumchron C): Available 5-17, BOOKED 18-25, Available again 26+
1294
                 * ITEM 3 (enumchron D): BOOKED 1-7, Available 8-22, BOOKED 23-30
1295
                 *
1296
                 * This pattern allows testing:
1297
                 * - Progressive item removal from pool (items become unavailable at different times)
1298
                 * - Non-re-addition principle (items that become available again are not re-added)
1299
                 * - Window maximization through optimal item selection
1300
                 * - Cross-scenario consistency (different start dates should produce consistent results)
1301
                 */
1302
1303
                // Create strategic bookings in the database
1304
                const bookingInserts = [
1305
                    // ITEM 0: Available days 5-9, then booked 10-15, then available again 16+
1306
                    // Algorithm should NOT re-add this item to pool after day 15 even though it becomes available
1307
                    cy.task("query", {
1308
                        sql: `INSERT INTO bookings (biblio_id, patron_id, item_id, pickup_library_id, start_date, end_date, status)
1309
                              VALUES (?, ?, ?, ?, ?, ?, ?)`,
1310
                        values: [
1311
                            testBiblio.biblio_id,
1312
                            testPatron.patron_id,
1313
                            testItems[0].item_id,
1314
                            "CPL",
1315
                            today.add(10, "day").format("YYYY-MM-DD"),
1316
                            today.add(15, "day").format("YYYY-MM-DD"),
1317
                            "new",
1318
                        ],
1319
                    }),
1320
                    // ITEM 1: Available days 5-12, then booked 13-20, then available 21+
1321
                    // Tests item removal during active booking period
1322
                    cy.task("query", {
1323
                        sql: `INSERT INTO bookings (biblio_id, patron_id, item_id, pickup_library_id, start_date, end_date, status)
1324
                              VALUES (?, ?, ?, ?, ?, ?, ?)`,
1325
                        values: [
1326
                            testBiblio.biblio_id,
1327
                            testPatron.patron_id,
1328
                            testItems[1].item_id,
1329
                            "CPL",
1330
                            today.add(13, "day").format("YYYY-MM-DD"),
1331
                            today.add(20, "day").format("YYYY-MM-DD"),
1332
                            "new",
1333
                        ],
1334
                    }),
1335
                    // ITEM 2: Available days 5-17, then booked 18-25, then available 26+
1336
                    // Tests final item removal that should trigger date disabling
1337
                    cy.task("query", {
1338
                        sql: `INSERT INTO bookings (biblio_id, patron_id, item_id, pickup_library_id, start_date, end_date, status)
1339
                              VALUES (?, ?, ?, ?, ?, ?, ?)`,
1340
                        values: [
1341
                            testBiblio.biblio_id,
1342
                            testPatron.patron_id,
1343
                            testItems[2].item_id,
1344
                            "CPL",
1345
                            today.add(18, "day").format("YYYY-MM-DD"),
1346
                            today.add(25, "day").format("YYYY-MM-DD"),
1347
                            "new",
1348
                        ],
1349
                    }),
1350
                    // ITEM 3: Booked early 1-7, then available 8-22, then booked 23-30
1351
                    // Tests item that starts unavailable but becomes available within the test period
1352
                    cy.task("query", {
1353
                        sql: `INSERT INTO bookings (biblio_id, patron_id, item_id, pickup_library_id, start_date, end_date, status)
1354
                              VALUES (?, ?, ?, ?, ?, ?, ?)`,
1355
                        values: [
1356
                            testBiblio.biblio_id,
1357
                            testPatron.patron_id,
1358
                            testItems[3].item_id,
1359
                            "CPL",
1360
                            today.add(1, "day").format("YYYY-MM-DD"),
1361
                            today.add(7, "day").format("YYYY-MM-DD"),
1362
                            "new",
1363
                        ],
1364
                    }),
1365
                    // ITEM 3 second booking: Tests item that has gaps in availability
1366
                    cy.task("query", {
1367
                        sql: `INSERT INTO bookings (biblio_id, patron_id, item_id, pickup_library_id, start_date, end_date, status)
1368
                              VALUES (?, ?, ?, ?, ?, ?, ?)`,
1369
                        values: [
1370
                            testBiblio.biblio_id,
1371
                            testPatron.patron_id,
1372
                            testItems[3].item_id,
1373
                            "CPL",
1374
                            today.add(23, "day").format("YYYY-MM-DD"),
1375
                            today.add(30, "day").format("YYYY-MM-DD"),
1376
                            "new",
1377
                        ],
1378
                    }),
1379
                ];
1380
1381
                return Promise.all(bookingInserts);
1382
            })
1383
            .then(() => {
1384
                // Setup API intercepts for this test
1385
                cy.intercept(
1386
                    "GET",
1387
                    `/api/v1/biblios/${testBiblio.biblio_id}/pickup_locations*`
1388
                ).as("getPickupLocations");
1389
                cy.intercept("GET", "/api/v1/circulation_rules*").as(
1390
                    "getCirculationRules"
1391
                );
1392
1393
                // Navigate to the biblio detail page
1394
                cy.visit(
1395
                    `/cgi-bin/koha/catalogue/detail.pl?biblionumber=${testBiblio.biblio_id}`
1396
                );
1397
1398
                // Open the booking modal
1399
                cy.get('[data-bs-target="#placeBookingModal"]').first().click();
1400
                cy.get("#placeBookingModal").should("be.visible");
1401
1402
                // Select patron
1403
                cy.selectFromSelect2(
1404
                    "#booking_patron_id",
1405
                    `${testPatron.surname}, ${testPatron.firstname}`,
1406
                    testPatron.cardnumber
1407
                );
1408
                cy.wait("@getPickupLocations");
1409
1410
                // Ensure pickup location field is enabled
1411
                cy.get("#pickup_library_id").should("not.be.disabled");
1412
1413
                // Select pickup location
1414
                cy.selectFromSelect2ByIndex("#pickup_library_id", 0);
1415
1416
                // Ensure itemtype field is enabled
1417
                cy.get("#booking_itemtype").should("not.be.disabled");
1418
1419
                // Select BK itemtype (all our test items are BK)
1420
                cy.selectFromSelect2ByIndex("#booking_itemtype", 0); // Select first available itemtype
1421
                cy.wait("@getCirculationRules");
1422
1423
                // Select "Any item" option (index 0)
1424
                cy.selectFromSelect2ByIndex("#booking_item_id", 0);
1425
                cy.get("#period").should("not.be.disabled");
1426
1427
                cy.get("#period").as("flatpickrInput");
1428
1429
                /**
1430
                 * TEST SCENARIO 1: Start Date Day 5 - Progressive Item Pool Reduction
1431
                 *
1432
                 * This scenario tests the core "never re-add items to pool" principle.
1433
                 * Starting from day 5, we expect the algorithm to:
1434
                 *
1435
                 * 1. Begin with initial pool: ITEM0, ITEM1, ITEM2
1436
                 *    (ITEM3 excluded because it's booked days 1-7)
1437
                 *
1438
                 * 2. Days 5-9: All 3 items remain available in pool
1439
                 *
1440
                 * 3. Day 10: ITEM0 becomes unavailable (booking starts)
1441
                 *    → Remove ITEM0 from pool (never to be re-added)
1442
                 *    → Pool now: ITEM1, ITEM2
1443
                 *
1444
                 * 4. Day 13: ITEM1 becomes unavailable (booking starts)
1445
                 *    → Remove ITEM1 from pool (never to be re-added)
1446
                 *    → Pool now: ITEM2 only
1447
                 *
1448
                 * 5. Day 18: ITEM2 becomes unavailable (booking starts)
1449
                 *    → Remove ITEM2 from pool
1450
                 *    → Pool now: EMPTY → Disable all dates from day 18 onwards
1451
                 *
1452
                 * CRITICAL: Even though ITEM0 becomes available again on day 16,
1453
                 * it should NOT be re-added to the pool. This is the key algorithm principle.
1454
                 *
1455
                 * Expected result: Can book from day 5 through day 17, day 18+ disabled
1456
                 */
1457
1458
                cy.log(
1459
                    "=== Testing Start Date Day 5 (Maximize through item pool reduction) ==="
1460
                );
1461
1462
                cy.get("@flatpickrInput").openFlatpickr();
1463
1464
                const startDate1 = today.add(5, "day");
1465
                cy.log("Selecting start date of ", startDate1.toString());
1466
                cy.get("@flatpickrInput")
1467
                    .getFlatpickrDate(startDate1.toDate())
1468
                    .click();
1469
1470
                cy.log(
1471
                    "Checking maximized end date availability through item reduction"
1472
                );
1473
1474
                // Days 6-9 should be available (3 items available: ITEM0, ITEM1, ITEM2)
1475
                for (let day = 6; day <= 9; day++) {
1476
                    const endDate = today.add(day, "day");
1477
                    cy.get("@flatpickrInput")
1478
                        .getFlatpickrDate(endDate.toDate())
1479
                        .should("not.have.class", "flatpickr-disabled")
1480
                        .should("be.visible");
1481
                }
1482
1483
                // Days 10-12 should still be available (3 items: ITEM1, ITEM2, ITEM3)
1484
                for (let day = 10; day <= 12; day++) {
1485
                    const endDate = today.add(day, "day");
1486
                    cy.get("@flatpickrInput")
1487
                        .getFlatpickrDate(endDate.toDate())
1488
                        .should("not.have.class", "flatpickr-disabled");
1489
                }
1490
1491
                // Days 13-17 should still be available (1 item: ITEM2 only)
1492
                for (let day = 13; day <= 17; day++) {
1493
                    const endDate = today.add(day, "day");
1494
                    cy.get("@flatpickrInput")
1495
                        .getFlatpickrDate(endDate.toDate())
1496
                        .should("not.have.class", "flatpickr-disabled");
1497
                }
1498
1499
                // Days 18+ should be disabled (no items available - ITEM2 becomes unavailable)
1500
                for (let day = 18; day <= 20; day++) {
1501
                    const endDate = today.add(day, "day");
1502
                    cy.get("@flatpickrInput")
1503
                        .getFlatpickrDate(endDate.toDate())
1504
                        .should("have.class", "flatpickr-disabled");
1505
                }
1506
1507
                /**
1508
                 * TEST SCENARIO 2: Start Date Day 8 - All Items Available, Maximum Window
1509
                 *
1510
                 * Expected availability progression from day 8:
1511
                 * Days 8-9:   Items available: ALL 4 items
1512
                 * Days 10-12: Items available: ITEM1, ITEM2, ITEM3 (lose ITEM0)
1513
                 * Days 13-17: Items available: ITEM2, ITEM3 (lose ITEM1)
1514
                 * Days 18-22: Items available: ITEM3 only (lose ITEM2)
1515
                 * Days 23+:   No items available
1516
                 *
1517
                 * Maximum window should extend to day 22
1518
                 */
1519
1520
                cy.log(
1521
                    "=== Testing Start Date Day 8 (All items available initially) ==="
1522
                );
1523
1524
                cy.get("@flatpickrInput").clearFlatpickr();
1525
                cy.get("@flatpickrInput").openFlatpickr();
1526
1527
                const startDate2 = today.add(8, "day");
1528
                cy.log("Selecting start date of ", startDate2.toString());
1529
                cy.get("@flatpickrInput")
1530
                    .getFlatpickrDate(startDate2.toDate())
1531
                    .click();
1532
1533
                cy.log("Checking maximum window from optimal start date");
1534
1535
                // Should be able to book all the way to day 22
1536
                for (let day = 9; day <= 22; day++) {
1537
                    const endDate = today.add(day, "day");
1538
                    cy.get("@flatpickrInput")
1539
                        .getFlatpickrDate(endDate.toDate())
1540
                        .should("not.have.class", "flatpickr-disabled");
1541
                }
1542
1543
                // Days 23+ should be disabled
1544
                for (let day = 23; day <= 25; day++) {
1545
                    const endDate = today.add(day, "day");
1546
                    cy.get("@flatpickrInput")
1547
                        .getFlatpickrDate(endDate.toDate())
1548
                        .should("have.class", "flatpickr-disabled");
1549
                }
1550
1551
                /**
1552
                 * TEST SCENARIO 3: Start Date Day 14 - Reduced Initial Pool, Still Maximize
1553
                 *
1554
                 * Expected availability progression from day 14:
1555
                 * Days 14-17: Items available: ITEM2, ITEM3 (ITEM0 & ITEM1 booked)
1556
                 * Days 18-22: Items available: ITEM3 only (ITEM2 becomes unavailable)
1557
                 * Days 23+:   No items available
1558
                 *
1559
                 * Should still extend to day 22 by using ITEM3
1560
                 */
1561
1562
                cy.log(
1563
                    "=== Testing Start Date Day 14 (Reduced pool but maximize window) ==="
1564
                );
1565
1566
                cy.get("@flatpickrInput").clearFlatpickr();
1567
                cy.get("@flatpickrInput").openFlatpickr();
1568
1569
                const startDate3 = today.add(14, "day");
1570
                cy.log("Selecting start date of ", startDate3.toString());
1571
                cy.get("@flatpickrInput")
1572
                    .getFlatpickrDate(startDate3.toDate())
1573
                    .click();
1574
1575
                cy.log(
1576
                    "Checking window maximization with reduced initial pool"
1577
                );
1578
1579
                // Days 15-22 should all be available
1580
                for (let day = 15; day <= 22; day++) {
1581
                    const endDate = today.add(day, "day");
1582
                    cy.get("@flatpickrInput")
1583
                        .getFlatpickrDate(endDate.toDate())
1584
                        .should("not.have.class", "flatpickr-disabled");
1585
                }
1586
1587
                // Days 23+ should be disabled
1588
                for (let day = 23; day <= 25; day++) {
1589
                    const endDate = today.add(day, "day");
1590
                    cy.get("@flatpickrInput")
1591
                        .getFlatpickrDate(endDate.toDate())
1592
                        .should("have.class", "flatpickr-disabled");
1593
                }
1594
1595
                /**
1596
                 * TEST SCENARIO 4: Start Date Day 19 - Multi-Item Window
1597
                 *
1598
                 * Expected availability from day 19:
1599
                 * Days 19-22: Items available: ITEM0, ITEM3 (ITEM0 booking 10-15 is over)
1600
                 * Days 23+:   Items available: ITEM0 only (ITEM3 becomes booked 23-30)
1601
                 *
1602
                 * Algorithm should keep ITEM0 available throughout since it was available
1603
                 * on start date and never becomes unavailable again
1604
                 */
1605
1606
                cy.log(
1607
                    "=== Testing Start Date Day 19 (Multi-item available) ==="
1608
                );
1609
1610
                cy.get("@flatpickrInput").clearFlatpickr();
1611
                cy.get("@flatpickrInput").openFlatpickr();
1612
1613
                const startDate4 = today.add(19, "day");
1614
                cy.log("Selecting start date of ", startDate4.toString());
1615
                cy.get("@flatpickrInput")
1616
                    .getFlatpickrDate(startDate4.toDate())
1617
                    .click();
1618
1619
                cy.log("Checking multi-item window maximization from day 19");
1620
1621
                // Days 20-22 should be available (ITEM0 and ITEM3 both available)
1622
                for (let day = 20; day <= 22; day++) {
1623
                    const endDate = today.add(day, "day");
1624
                    cy.get("@flatpickrInput")
1625
                        .getFlatpickrDate(endDate.toDate())
1626
                        .should("not.have.class", "flatpickr-disabled");
1627
                }
1628
1629
                // Days 23-25 should still be available (ITEM0 remains in pool)
1630
                for (let day = 23; day <= 25; day++) {
1631
                    const endDate = today.add(day, "day");
1632
                    cy.get("@flatpickrInput")
1633
                        .getFlatpickrDate(endDate.toDate())
1634
                        .should("not.have.class", "flatpickr-disabled");
1635
                }
1636
1637
                /**
1638
                 * TEST SCENARIO 5: Cross-Scenario Consistency Validation
1639
                 *
1640
                 * Verify that changing start dates properly recalculates maximum windows
1641
                 */
1642
1643
                cy.log("=== Verifying cross-scenario consistency ===");
1644
1645
                cy.get("@flatpickrInput").clearFlatpickr();
1646
                cy.get("@flatpickrInput").openFlatpickr();
1647
1648
                // Go to day 16 where ITEM0, ITEM2 and ITEM3 are available initially
1649
                const startDate6 = today.add(16, "day");
1650
                cy.get("@flatpickrInput")
1651
                    .getFlatpickrDate(startDate6.toDate())
1652
                    .click();
1653
1654
                // Should be able to book until day 22 via multiple items
1655
                const endDate6 = today.add(22, "day");
1656
                cy.get("@flatpickrInput")
1657
                    .getFlatpickrDate(endDate6.toDate())
1658
                    .should("not.have.class", "flatpickr-disabled");
1659
1660
                // Day 23+ should still be available because ITEM0 remains in pool
1661
                const endDate6_available = today.add(23, "day");
1662
                cy.get("@flatpickrInput")
1663
                    .getFlatpickrDate(endDate6_available.toDate())
1664
                    .should("not.have.class", "flatpickr-disabled");
1665
1666
                cy.log(
1667
                    "Dynamic item pool reduction and window maximization test completed"
1668
                );
1669
            });
1670
1671
        // Cleanup: Delete test data
1672
        cy.then(() => {
1673
            if (testBiblio) {
1674
                cy.task("query", {
1675
                    sql: "DELETE FROM bookings WHERE biblio_id = ?",
1676
                    values: [testBiblio.biblio_id],
1677
                });
1678
                cy.task("deleteSampleObjects", {
1679
                    biblio: testBiblio,
1680
                    items: testItems,
1681
                });
1682
            }
1683
            if (testPatron) {
1684
                cy.task("query", {
1685
                    sql: "DELETE FROM borrowers WHERE borrowernumber = ?",
1686
                    values: [testPatron.patron_id],
1687
                });
1688
            }
1689
        });
1690
    });
1082
});
1691
});
1083
- 

Return to bug 40134