diff --git a/app/controllers/api/v1/late_policies_controller.rb b/app/controllers/api/v1/late_policies_controller.rb new file mode 100644 index 000000000..c6066962c --- /dev/null +++ b/app/controllers/api/v1/late_policies_controller.rb @@ -0,0 +1,124 @@ +class Api::V1::LatePoliciesController < ApplicationController + include AuthorizationHelper + + before_action :set_late_policy, only: %i[show edit update destroy] + + def action_allowed? + case params[:action] + when 'new', 'create', 'index' + current_user_has_ta_privileges? + when 'edit', 'update', 'destroy' + current_user_has_ta_privileges? && + current_user.instructor_id == check_if_instructor + else + false + end + end + + def index + @penalty_policies = LatePolicy.where(['instructor_id = ? OR private = 0', check_if_instructor]) + render json: @penalty_policies + end + + def show + render json: @penalty_policy + end + + def new + @penalty_policy = LatePolicy.new + render json: @penalty_policy + end + + def edit + render json: @penalty_policy + end + + def create + valid_penalty, error_message = validate_input + if error_message + render json: { error: error_message }, status: :unprocessable_entity and return + end + + if valid_penalty + @late_policy = LatePolicy.new(late_policy_params) + @late_policy.instructor_id = check_if_instructor + if @late_policy.save + render json: @late_policy, status: :created + else + render json: { error: 'The following error occurred while saving the late policy.' }, status: :unprocessable_entity + end + else + render json: { error: 'Validation failed' }, status: :unprocessable_entity + end + end + + def update + valid_penalty, error_message = @penalty_policy.duplicate_name_check(check_if_instructor, params[:id]) + + if error_message + render json: { error: error_message }, status: :unprocessable_entity and return + end + + if valid_penalty + if @penalty_policy.update(late_policy_params) + LatePolicy.update_calculated_penalty_objects(@penalty_policy) + render json: @penalty_policy, status: :ok + else + render json: { error: 'The following error occurred while updating the late policy.' }, status: :unprocessable_entity + end + else + render json: { error: 'Validation failed' }, status: :unprocessable_entity + end + end + + def destroy + if @penalty_policy.destroy + head :no_content + else + render json: { error: 'This policy is in use and hence cannot be deleted.' }, status: :unprocessable_entity + end + end + + private + + def set_late_policy + @penalty_policy = LatePolicy.find(params[:id]) + end + + def late_policy_params + params.require(:late_policy).permit(:policy_name, :penalty_per_unit, :penalty_unit, :max_penalty) + end + + def check_if_instructor + late_policy.try(:instructor_id) || current_user.instructor_id + end + + def late_policy + @penalty_policy ||= @late_policy || LatePolicy.find(params[:id]) if params[:id] + end + + def validate_input(is_update = false) + max_penalty = params[:late_policy][:max_penalty].to_i + penalty_per_unit = params[:late_policy][:penalty_per_unit].to_i + + valid_penalty, error_message = duplicate_name_check(is_update) + prefix = is_update ? "Cannot edit the policy. " : "" + + if max_penalty < penalty_per_unit + error_message = prefix + 'The maximum penalty cannot be less than penalty per unit.' + valid_penalty = false + end + + if penalty_per_unit < 0 + error_message = 'Penalty per unit cannot be negative.' + valid_penalty = false + end + + if max_penalty >= 100 + error_message = prefix + 'Maximum penalty cannot be greater than or equal to 100' + valid_penalty = false + end + + return valid_penalty, error_message + end + end diff --git a/app/models/late_policy.rb b/app/models/late_policy.rb new file mode 100644 index 000000000..9f86790e9 --- /dev/null +++ b/app/models/late_policy.rb @@ -0,0 +1,41 @@ +class LatePolicy < ApplicationRecord + belongs_to :user + + has_many :assignments, dependent: :nullify + + validates :policy_name, presence: true, format: { with: /\A[A-Za-z0-9][A-Za-z0-9\s'._-]+\z/i } + validates :instructor_id, presence: true + validates :max_penalty, presence: true, numericality: { greater_than: 0, less than: 100 } + validates :penalty_per_unit, presence: true, numericality: { greater_than: 0 } + validates :penalty_unit, presence: true + + def duplicate_name_check(instructor_id, current_policy_id = nil) + existing_policy = LatePolicy.find_by(policy_name: self.policy_name, instructor_id: instructor_id) + + if existing_policy && (current_policy_id.nil? || existing_policy.id != current_policy_id) + error_message = "A policy with the same name #{self.policy_name} already exists." + return false, error_message + end + + return true, nil + end + + def self.update_calculated_penalty_objects(late_policy) + CalculatedPenalty.find_each do |pen| + participant = AssignmentParticipant.find(pen.participant_id) + assignment = participant.assignment + next unless assignment.late_policy_id == late_policy.id + + penalties = calculate_penalty(pen.participant_id) + total_penalty = penalties.values.sum + case pen.deadline_type_id.to_i + when 1 + pen.update(penalty_points: penalties[:submission]) + when 2 + pen.update(penalty_points: penalties[:review]) + when 5 + pen.update(penalty_points: penalties[:meta_review]) + end + end + end +end diff --git a/app/models/ta.rb b/app/models/ta.rb index 7ced545ff..95d9f47c2 100644 --- a/app/models/ta.rb +++ b/app/models/ta.rb @@ -1,11 +1,77 @@ class Ta < User - # Get all users whose parent is the TA - # @return [Array] all users that belongs to courses that is mapped to the TA - def managed_users - User.where(parent_id: id).to_a + has_many :ta_mappings, dependent: :destroy + + QUESTIONNAIRE = [['My questionnaires', 'list_mine'], + ['All public questionnaires', 'list_all']].freeze + + ASSIGNMENT = [['My assignments', 'list_mine'], + ['All public assignments', 'list_all']].freeze + + def courses_assisted_with + Course.where(id: ta_mappings.select(:course_id)) + end + + def is_instructor_or_co_ta?(questionnaire) + return false if questionnaire.nil? + + instructor_ids = self.class.get_my_instructors(id) + return true if instructor_ids.include?(questionnaire.instructor_id) + + co_tas = Ta.where(id: courses_assisted_with.joins(:tas).select(:id)) + co_tas.include?(Ta.find(questionnaire.instructor_id)) + end + + def list_all(object_type, user_id) + object_type.where('instructor_id = ? OR private = 0', user_id) + end + + def list_mine(object_type, user_id) + if object_type.to_s == 'Assignment' + Assignment.joins(:ta_mappings).where('ta_mappings.ta_id = ? OR assignments.instructor_id = ?', user_id, user_id) + else + object_type.where(instructor_id: user_id) + end + end + + def get(object_type, id, user_id) + object_type.where('id = ? AND (instructor_id = ? OR private = 0)', id, user_id).first + end + + def self.get_my_instructors(user_id) + TaMapping.where(ta_id: user_id).pluck(:course_id).uniq.map do |course_id| + Course.find(course_id).instructor_id + end + end + + def self.get_mapped_instructor_ids(user_id) + TaMapping.where(ta_id: user_id).includes(:course).map { |mapping| mapping.course.instructor.id } + end + + def self.get_mapped_courses(user_id) + TaMapping.where(ta_id: user_id).pluck(:course_id).uniq + end + + def get_instructor + self.class.get_my_instructors(id).first + end + + def set_instructor(new_assign) + new_assign.instructor_id = get_instructor + new_assign.course_id = TaMapping.find_by(ta_id: id).course_id + end + + def assign_courses_to_assignment + TaMapping.where(ta_id: id).pluck(:course_id) + end + + def teaching_assistant? + true end - def my_instructor - # code here + def self.get_user_list(user) + courses = get_mapped_courses(user.id) + participants = courses.flat_map { |course_id| Course.find(course_id).get_participants } + participants.select { |participant| user.role.has_all_privileges_of?(participant.user.role) } + .map(&:user) end end \ No newline at end of file