{"record":{"id":"183bcda4f4f01746","repo":"calcom/cal.diy","slug":"please-specify-both-ooo-start-and-end-time","errorCode":null,"errorMessage":"Please specify both ooo start and end time.","messagePattern":"Please specify both ooo start and end time\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"apps/api/v2/src/modules/ooo/services/ooo.service.ts","lineNumber":46,"sourceCode":"@Injectable()\nexport class UserOOOService {\n  constructor(\n    private readonly oooRepository: UserOOORepository,\n    private readonly usersRepository: UsersRepository\n  ) {}\n\n  formatOooReason(ooo: OutOfOfficeEntry) {\n    return {\n      ...ooo,\n      reason: ooo.reasonId\n        ? OOO_REASON_ID_TO_REASON[ooo.reasonId as keyof typeof OOO_REASON_ID_TO_REASON]\n        : OOO_REASON_ID_TO_REASON[1],\n    };\n  }\n\n  isStartBeforeEnd(start?: Date, end?: Date) {\n    if ((end && !start) || (start && !end)) {\n      throw new BadRequestException(\"Please specify both ooo start and end time.\");\n    }\n\n    if (start && end) {\n      if (start.getTime() > end.getTime()) {\n        throw new BadRequestException(\"Start date must be before end date.\");\n      }\n    }\n    return true;\n  }\n\n  async checkUserEligibleForRedirect(userId: number, toUserId?: number) {\n    if (toUserId) {\n      const user = await this.usersRepository.findUserOOORedirectEligible(userId, toUserId);\n      if (!user) {\n        throw new BadRequestException(\"Cannot redirect to this user.\");\n      }\n    }\n  }","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/modules/ooo/services/ooo.service.ts#L28-L64","documentation":"Thrown by OooService.isStartBeforeEnd() when exactly one of start or end is present ((end && !start) || (start && !end)). The OOO contract requires both bounds together; a partial date range is rejected as ambiguous. Both-absent is allowed (used by no-op updates) and is the only way to skip the check.","triggerScenarios":"PATCH /v2/ooo/:id sending only { \"start\": \"...\" } or only { \"end\": \"...\" }; a create/update caller that conditionally includes one bound based on UI state but forgets the other.","commonSituations":"A 'reschedule OOO' feature that lets the user move only the end date; merging a partial patch object via spread that drops an undefined field; deserialization that omits null fields.","solutions":["Always send start and end together in any OOO create/update payload.","When patching, if the user only changed one bound, re-read the existing entry and send both the new and the unchanged bound.","Omit both start and end from the patch body entirely if you do not intend to change the time window."],"exampleFix":"// before\nawait api.patch(`/ooo/${id}`, { end: newEnd }); // missing start → 400\n// after\nconst current = await api.get(`/ooo/${id}`);\nawait api.patch(`/ooo/${id}`, { start: current.start, end: newEnd });","handlingStrategy":"validation","validationCode":"function buildOooWindow(start?: Date, end?: Date) {\n  if ((start && !end) || (end && !start))\n    throw new Error('Send both start and end, or neither.');\n  const out: { start?: string; end?: string } = {};\n  if (start && end) { out.start = start.toISOString(); out.end = end.toISOString(); }\n  return out;\n}","typeGuard":"const hasBothOrNeither = (start?: Date, end?: Date): boolean =>\n  (start && end ? true : !start && !end);","tryCatchPattern":"try { await api.patch(`/ooo/${id}`, patch); }\ncatch (e) { if (/both ooo start and end/i.test(e.message)) { /* re-send both bounds */ } else throw e; }","preventionTips":["Disable submit until either both or neither date bound is set.","When patching, re-read the resource and resend the unchanged bound alongside the changed one.","Treat the date window as one atomic value in the UI, not two independent fields."],"tags":["business-rule","ooo","nestjs","validation","partial-update"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}