All files / server/src/features/assignment/utils removeOverlapLotterySignups.ts

86.36% Statements 38/44
84.61% Branches 22/26
100% Functions 10/10
86.36% Lines 38/44

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138                                  48x         84x 84x   84x 84x             84x   530x 46x     84x 84x 83x             84x 299x   6231x     299x                 299x 3854x   299x                   299x 187x   1298x   62895x   1298x       45x   1253x         1253x       187x 94x     274x             299x   111x         39x     111x 1x               84x 9x 9x         84x    
import { addMinutes } from "date-fns";
import { config } from "shared/config";
import { RemoveLotterySignupsStrategy } from "shared/config/eventConfigTypes";
import { MongoDbError } from "shared/types/api/errors";
import { ProgramItem } from "shared/types/models/programItem";
import { UserAssignmentResult } from "shared/types/models/result";
import { Result, makeSuccessResult } from "shared/utils/result";
import { lotteryRanForProgramItem } from "shared/utils/signupTimes";
import { isBetweenExclusive } from "shared/utils/timeComparison";
import { getUpcomingLotterySignupProgramItemIds } from "server/features/assignment/utils/getUpcomingLotterySignups";
import {
  DeleteLotterySignupsParams,
  delLotterySignups,
} from "server/features/user/lottery-signup/lotterySignupRepository";
import { findUsers } from "server/features/user/userRepository";
import { logger } from "server/utils/logger";
 
export const removeOverlapLotterySignups = async (
  results: readonly UserAssignmentResult[],
  programItems: readonly ProgramItem[],
  assignmentTime: string,
): Promise<Result<void, MongoDbError>> => {
  logger.debug("Find overlapping lottery signups");
  const usersToUpdate: DeleteLotterySignupsParams[] = [];
 
  const usersResult = await findUsers();
  Iif (!usersResult.ok) {
    return usersResult;
  }
 
  // A sign-up whose lottery has run records that the attendee entered it, so only one still
  // waiting for its lottery is given up for the spot just won. The mark is what says which: the
  // sign-up window is derived from the current start time, which a move reopens.
  const lotteriedProgramItemIds = new Set(
    programItems
      .filter((programItem) => lotteryRanForProgramItem(programItem))
      .map((programItem) => programItem.programItemId),
  );
 
  const strategy = config.event().removeLotterySignupsStrategy;
  if (strategy !== RemoveLotterySignupsStrategy.NONE) {
    logger.info(
      strategy === RemoveLotterySignupsStrategy.OVERLAP
        ? "Remove overlapping signups"
        : "Remove upcoming signups",
    );
  }
 
  results.flatMap((result) => {
    const assignmentSignupProgramItem = programItems.find(
      (programItem) =>
        programItem.programItemId === result.assignmentSignup.programItemId,
    );
 
    Iif (!assignmentSignupProgramItem) {
      logger.error(
        new Error(
          `removeOverlapLotterySignups: Error finding assignment signup: ${result.assignmentSignup.programItemId}`,
        ),
      );
      return [];
    }
 
    const signedUser = usersResult.value.find(
      (user) => user.username === result.username,
    );
    Iif (!signedUser) {
      logger.error(
        new Error(
          `removeOverlapLotterySignups: Error finding signed user: ${result.username}`,
        ),
      );
      return [];
    }
 
    // Cancel all lottery sign-ups that start during the lottery direct sign-up
    if (strategy === RemoveLotterySignupsStrategy.OVERLAP) {
      const overlappingLotterySignups = signedUser.lotterySignups.filter(
        (lotterySignup) => {
          const foundProgramItem = programItems.find(
            (programItem) =>
              programItem.programItemId === lotterySignup.programItemId,
          );
          if (
            !foundProgramItem ||
            lotteriedProgramItemIds.has(foundProgramItem.programItemId)
          ) {
            return false;
          }
          const startsDuring = isBetweenExclusive(
            new Date(foundProgramItem.startTime),
            addMinutes(new Date(assignmentSignupProgramItem.startTime), 1),
            new Date(assignmentSignupProgramItem.endTime),
          );
          return startsDuring;
        },
      );
 
      if (overlappingLotterySignups.length > 0) {
        usersToUpdate.push({
          username: signedUser.username,
          lotterySignupProgramItemIds: overlappingLotterySignups.map(
            (signup) => signup.programItemId,
          ),
        });
      }
    }
 
    // Cancel all upcoming lottery sign-ups
    if (strategy === RemoveLotterySignupsStrategy.ALL_UPCOMING) {
      const upcomingLotterySignupProgramItemIds =
        getUpcomingLotterySignupProgramItemIds(
          signedUser.lotterySignups,
          programItems,
          new Date(assignmentTime),
        ).filter(
          (programItemId) => !lotteriedProgramItemIds.has(programItemId),
        );
 
      if (upcomingLotterySignupProgramItemIds.length > 0) {
        usersToUpdate.push({
          username: signedUser.username,
          lotterySignupProgramItemIds: upcomingLotterySignupProgramItemIds,
        });
      }
    }
  });
 
  if (usersToUpdate.length > 0) {
    const delLotterySignupsResult = await delLotterySignups(usersToUpdate);
    Iif (!delLotterySignupsResult.ok) {
      return delLotterySignupsResult;
    }
  }
 
  return makeSuccessResult();
};