Skip to content

feat(substitution): add conflict check for teacher substitutions#239

Merged
Dasa122 merged 5 commits into
mainfrom
substitution-check
Jun 22, 2026
Merged

feat(substitution): add conflict check for teacher substitutions#239
Dasa122 merged 5 commits into
mainfrom
substitution-check

Conversation

@Dasa122

@Dasa122 Dasa122 commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • Bug Fixes

    • Added validation to prevent teacher substitution overlap conflicts across lesson periods.
    • Improved conflict handling with clear error responses for conflicting substitutions.
  • Improvements

    • Strengthened creation and update workflows using safer, atomic transaction handling.
    • Refined notification cancellation/dispatch so notifications reflect the latest substitution details.

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Dasa122, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 29 minutes and 55 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3c52fb4c-0b14-41a8-ac8e-42deff1e9a29

📥 Commits

Reviewing files that changed from the base of the PR and between d48709c and 6826e84.

📒 Files selected for processing (1)
  • apps/chronos/src/routes/timetable/substitution.ts
📝 Walkthrough

Walkthrough

Adds teacher-substitution period-overlap validation to substitution.ts. Two new internal helpers (checkTeacherSubstitutionConflict, validateUpdateTeacherConflict) detect same-date period conflicts and throw 409. Both createSubstitution and updateSubstitution are refactored to call these helpers inside serializable transactions, with updateSubstitution also gaining an explicit not-found guard and pre-transaction notification cancellation.

Changes

Teacher Substitution Conflict Detection

Layer / File(s) Summary
Conflict detection helpers
apps/chronos/src/routes/timetable/substitution.ts
Defines TxOrDb type, checkTeacherSubstitutionConflict (queries same-date/substituter substitutions for period overlap, throws HTTPException(409)), and validateUpdateTeacherConflict (resolves effective substituter and lesson IDs from update payload vs. existing record and delegates to the conflict checker). Adds ne to the drizzle-orm import for the inequality filter.
Create and update flow integration
apps/chronos/src/routes/timetable/substitution.ts
Wraps createSubstitution insert in a serializable transaction and calls the conflict checker before inserting the substitution and its MTM lesson relations. Refactors updateSubstitution to use limit(1) fetch with a not-found guard, cancel pending notifications before the transaction, call validateUpdateTeacherConflict inside a serializable transaction, conditionally refresh MTM lesson links, and dispatch notifications with updated-or-fallback field values.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly and accurately describes the main change: adding conflict validation for teacher substitutions, which is the primary focus of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/chronos/src/routes/timetable/substitution.ts`:
- Line 738: The call to cancelPendingNotification(id, 'substitution') is
executed before the transaction begins, which means if the transaction fails
(due to conflict detection or database errors), the notification is already
cancelled but the substitution remains unchanged, creating an inconsistent
state. Move the cancelPendingNotification call to execute after the transaction
completes successfully, either by placing it after the transaction block or
within the transaction after all write operations are confirmed.
- Around line 774-779: The dispatchPendingNotification call for substitution
uses body.lessonIds ?? [] which defaults to an empty array when lessonIds is not
provided in the update, but validateUpdateTeacherConflict correctly uses
body.lessonIds ?? existingLessonIds to preserve the actual lesson IDs. Update
the notification dispatch to use the same fallback logic as the validation
function, either by fetching the existing lesson IDs separately or by
refactoring validateUpdateTeacherConflict to return the existingLessonRecords so
it can be reused in the notification payload, ensuring the notification includes
the actual associated lessons instead of an empty array.
- Around line 754-766: The condition checking `if (body.lessonIds)` evaluates to
true even when body.lessonIds is an empty array, since empty arrays are truthy
in JavaScript. This causes the code to delete existing substitutionLessonMTM
relationships and then attempt to insert an empty values array, which throws an
error. Fix this by modifying the condition to check both that body.lessonIds
exists and that it has at least one element, such as changing the condition to
check body.lessonIds?.length or body.lessonIds && body.lessonIds.length > 0,
ensuring the insert operation only runs when there are actual lessons to insert.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f9b76eca-6192-4f49-a0d7-75697ba52e95

📥 Commits

Reviewing files that changed from the base of the PR and between a333961 and 50e371f.

📒 Files selected for processing (1)
  • apps/chronos/src/routes/timetable/substitution.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (3)
  • GitHub Check: typecheck
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
apps/chronos/src/routes/timetable/substitution.ts (4)

3-3: LGTM!


215-329: LGTM!


610-650: LGTM!


714-720: LGTM!

Comment thread apps/chronos/src/routes/timetable/substitution.ts Outdated
Comment thread apps/chronos/src/routes/timetable/substitution.ts Outdated
Comment thread apps/chronos/src/routes/timetable/substitution.ts Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 21, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/chronos/src/routes/timetable/substitution.ts (1)

562-567: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Require at least one lesson in create schema to prevent empty insert.

The schema allows empty lessonIds arrays, but tx.insert(substitutionLessonMTM).values([]) at line 645 will fail with an empty array. Adding .min(1) provides a clear API contract and early validation.

Proposed fix
 const createSchema = createInsertSchema(substitution)
   .omit({ id: true })
   .extend({
     date: z.coerce.date<Date>(),
-    lessonIds: z.string().array(),
+    lessonIds: z.string().array().min(1, 'At least one lesson is required'),
   });

Also applies to: 639-645

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/chronos/src/routes/timetable/substitution.ts` around lines 562 - 567,
The lessonIds field in the createSchema validation allows empty arrays, which
will cause the tx.insert(substitutionLessonMTM).values([]) call to fail. Add
.min(1) constraint to the lessonIds field definition to require at least one
lesson ID in the array, ensuring early validation and providing a clear API
contract that substitutions must include at least one lesson.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/chronos/src/routes/timetable/substitution.ts`:
- Around line 788-792: The nullish coalescing operator `??` in the substituter
field of the dispatchPendingNotification call treats both null and undefined
identically. When a user explicitly sets substituter to null to cancel a
substitution, the expression `body.substituter ?? existing.substituter` will
incorrectly fall back to the existing substituter value instead of respecting
the explicit cancellation. Fix this by checking whether substituter is
explicitly present in the body object (using hasOwnProperty or the in operator)
to distinguish between an explicitly provided null value and an
undefined/unprovided value, then use the explicit null when present or the
existing value when not.

---

Outside diff comments:
In `@apps/chronos/src/routes/timetable/substitution.ts`:
- Around line 562-567: The lessonIds field in the createSchema validation allows
empty arrays, which will cause the tx.insert(substitutionLessonMTM).values([])
call to fail. Add .min(1) constraint to the lessonIds field definition to
require at least one lesson ID in the array, ensuring early validation and
providing a clear API contract that substitutions must include at least one
lesson.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c9250ee3-b041-4b61-ad6d-dd8ee140b78e

📥 Commits

Reviewing files that changed from the base of the PR and between 50e371f and d48709c.

📒 Files selected for processing (1)
  • apps/chronos/src/routes/timetable/substitution.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (3)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (6)
apps/chronos/src/routes/timetable/substitution.ts (6)

3-3: LGTM!


300-328: LGTM!


714-736: LGTM!


738-771: LGTM!


773-786: LGTM!


220-236: 🩺 Stability & Availability

The proposed guard is unnecessary for the current Drizzle version.

The project uses drizzle-orm version 0.45.2, which safely handles empty arrays in inArray. Since Drizzle 0.32.1, empty arrays are handled gracefully—inArray(column, []) returns false rather than causing a SQL error. The code works correctly without the guard.

An early return when lessonIds is empty could be added for clarity and to avoid an unnecessary database query, but it is not required for correctness or stability.

			> Likely an incorrect or invalid review comment.

Comment thread apps/chronos/src/routes/timetable/substitution.ts
@Dasa122 Dasa122 merged commit 1200ddc into main Jun 22, 2026
7 checks passed
@Dasa122 Dasa122 deleted the substitution-check branch June 22, 2026 21:35
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Filc Issue Tracker Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Do not allow more than one substitution to be assigned to a teacher in a specific period

1 participant