Conversation
…In addition, create start_date and end_date to course_class
| class_enrollments.each do |class_enrollment| | ||
| course_class = class_enrollment.course_class | ||
| unless course_class.end_date < self.start_date || course_class.start_date > self.end_date | ||
| class_enrollment.delete |
There was a problem hiding this comment.
Me parece estranho ter um delete dentro de um validate. Acho que seria melhor num after_update ou after_create
…, now it's a after_commit function, not a validate function of enrollment hold
…in class_enrolments and class_enrollment_requests
… and other to search for class_enrollments in a period. Cancel_class_enrollments is deleted
…llment_request form when the enroll is held
…ollment_requests
There was a problem hiding this comment.
Pull request overview
This PR (Issue 286) extends the “enrollment hold” concept across the enrollment flow: it blocks class enrollments/requests that overlap a hold period, invalidates existing class enrollment requests when a hold is created for the same semester, and updates the student enrollment UI + translations to reflect the held state.
Changes:
- Add EnrollmentHold validations/callbacks to (a) prevent creating holds that overlap existing class enrollments and (b) invalidate class enrollment requests for the same semester.
- Enforce “held enrollment” validation in
ClassEnrollmentandClassEnrollmentRequest, and surface held status in the student enrollment UI (styles + message). - Add model + feature specs and new pt-BR i18n keys for the new validations/messages.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| spec/models/enrollment_hold_spec.rb | Adds coverage for blocking holds when class enrollments exist in the hold period. |
| spec/models/class_enrollment_spec.rb | Adds validation spec ensuring class enrollments can’t be created during a hold. |
| spec/models/class_enrollment_request_spec.rb | Adds validation spec ensuring class enrollment requests can’t be created during a hold. |
| spec/features/enrollment_holds_spec.rb | Feature spec for invalidating requests when a hold is created via UI. |
| spec/factories/factory_enrollment.rb | Adjusts default admission_date used by many tests. |
| config/locales/student_enrollment.pt-BR.yml | Adds student-facing message when enrollment is held. |
| config/locales/enrollment_hold.pt-BR.yml | Adds validation message for “class enrollments exist in hold period”. |
| config/locales/class_enrollment_request.pt-BR.yml | Adds labels and held-validation message for requests. |
| config/locales/class_enrollment.pt-BR.yml | Adds label + held-validation message for enrollments. |
| app/views/student_enrollment/_show_enroll.html.erb | Changes UI to show a held banner/message and disable enrollment links while held. |
| app/models/enrollment_hold.rb | Implements overlap checks, request invalidation, and date-range hold detection. |
| app/models/course_class.rb | Adds start_date/end_date helpers based on year/semester. |
| app/models/class_enrollment_request.rb | Adds hold validation to block requests in held periods. |
| app/models/class_enrollment.rb | Adds hold validation to block enrollments in held periods. |
| app/controllers/class_enrollment_requests_controller.rb | Changes flash handling on set_status failure to show validation errors. |
| app/assets/stylesheets/custom/student_enrollment.scss | Adds held-state styling. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "M#{number}" | ||
| end | ||
| admission_date { 5.days.ago.to_date } | ||
| admission_date { 10.days.ago.to_date } |
There was a problem hiding this comment.
Setting the default admission_date to 10.days.ago is still date-dependent and can fall after YearSemester.current.semester_begin (e.g., during most of the first semester), which makes creating an EnrollmentHold for the current semester fail the existing before_admission_date validation. Consider using a deterministic default (e.g., relative to YearSemester.current.semester_begin) or a trait so tests opt into a recent admission date explicitly.
| admission_date { 10.days.ago.to_date } | |
| admission_date { YearSemester.current.semester_begin - 1.day } |
| @destroy_later << cer = FactoryBot.create(:enrollment_request, enrollment: e) | ||
| class_enrollment_request.enrollment_request = cer |
There was a problem hiding this comment.
In this spec, cer is used as a variable name for an EnrollmentRequest, but elsewhere cer commonly reads as ClassEnrollmentRequest. Renaming the local to enrollment_request (or similar) would reduce confusion when reading failures.
| @destroy_later << cer = FactoryBot.create(:enrollment_request, enrollment: e) | |
| class_enrollment_request.enrollment_request = cer | |
| @destroy_later << enrollment_request = FactoryBot.create(:enrollment_request, enrollment: e) | |
| class_enrollment_request.enrollment_request = enrollment_request |
| def invalidate_enrollment_requests | ||
| enrollment_requests = EnrollmentRequest.where(enrollment: self.enrollment) | ||
| enrollment_requests.each do |enrollment_request| | ||
| if self.year == enrollment_request.year && self.semester == enrollment_request.semester | ||
| enrollment_request.class_enrollment_requests.each do |class_enrollment_request| | ||
| class_enrollment_request.set_status!(ClassEnrollmentRequest::INVALID) | ||
| end | ||
| end | ||
| end | ||
| end |
There was a problem hiding this comment.
invalidate_enrollment_requests runs on every create/update and will invalidate requests even when the hold is being deactivated (active toggled to false) or when the updated record should not apply. It should likely be gated on active == true (and/or on relevant changes like year/semester/number_of_semesters) to avoid unintended invalidations.
| def verify_class_enrollments | ||
| class_enrollments = ClassEnrollment.where(enrollment: self.enrollment) | ||
| class_enrollments.each do |class_enrollment| | ||
| course_class = class_enrollment.course_class | ||
| unless course_class.end_date < self.start_date || course_class.start_date > self.end_date | ||
| errors.add(:base, :class_enrollments_exist) | ||
| end | ||
| end |
There was a problem hiding this comment.
verify_class_enrollments also ignores the active flag. As written, it will prevent saving an inactive hold even though inactive holds are treated elsewhere as not in effect; consider skipping this validation when active is false (or clarify intended behavior).
| def self.hold_in_date(enrollment, course_start, course_end) | ||
| enrollment_holds = EnrollmentHold.where(enrollment: enrollment) | ||
| unless enrollment_holds.empty? | ||
| enrollment_holds.each do |hold| | ||
| return true unless course_end < hold.start_date || course_start > hold.end_date | ||
| end | ||
| end | ||
| false |
There was a problem hiding this comment.
hold_in_date currently checks all holds for the enrollment, including active = false. The rest of the codebase treats inactive holds as not applicable (e.g., enrollment hold search filters by active = true), so this will incorrectly block enrollments/requests when a hold is inactive. Filter to active holds (or explicitly decide how inactive holds should behave).
| enrollment_requests = EnrollmentRequest.where(enrollment: self.enrollment) | ||
| enrollment_requests.each do |enrollment_request| | ||
| if self.year == enrollment_request.year && self.semester == enrollment_request.semester | ||
| enrollment_request.class_enrollment_requests.each do |class_enrollment_request| | ||
| class_enrollment_request.set_status!(ClassEnrollmentRequest::INVALID) | ||
| end |
There was a problem hiding this comment.
invalidate_enrollment_requests does an N+1 iteration (EnrollmentRequest.where(...).each then class_enrollment_requests.each). This can be reduced to a single query by scoping to the relevant year/semester in SQL and eager-loading (or updating) the associated class_enrollment_requests, which will matter if an enrollment has many requests.
| enrollment_requests = EnrollmentRequest.where(enrollment: self.enrollment) | |
| enrollment_requests.each do |enrollment_request| | |
| if self.year == enrollment_request.year && self.semester == enrollment_request.semester | |
| enrollment_request.class_enrollment_requests.each do |class_enrollment_request| | |
| class_enrollment_request.set_status!(ClassEnrollmentRequest::INVALID) | |
| end | |
| enrollment_requests = EnrollmentRequest.where( | |
| enrollment: enrollment, | |
| year: year, | |
| semester: semester | |
| ).includes(:class_enrollment_requests) | |
| enrollment_requests.each do |enrollment_request| | |
| enrollment_request.class_enrollment_requests.each do |class_enrollment_request| | |
| class_enrollment_request.set_status!(ClassEnrollmentRequest::INVALID) |


No description provided.