<commit_before>require 'csv'
require 'bcrypt'
require 'json'
require 'moss_ruby'
require 'csv_helper'

class Unit < ActiveRecord::Base
  include ApplicationHelper
  include FileHelper
  include LogHelper
  include MimeCheckHelpers
  include CsvHelper

  validates :description, length: { maximum: 4095, allow_blank: true }
  #
  # Permissions around unit data
  #
  def self.permissions
    # What can students do with units?
    student_role_permissions = [
      :get_unit
    ]
    # What can tutors do with units?
    tutor_role_permissions = [
      :get_unit,
      :get_students,
      :enrol_student,
      :provide_feedback,
      :download_stats,
      :download_unit_csv,
      :download_grades
    ]

    # What can convenors do with units?
    convenor_role_permissions = [
      :get_unit,
      :get_students,
      :enrol_student,
      :upload_csv,
      :download_unit_csv,
      :update,
      :employ_staff,
      :add_tutorial,
      :add_task_def,
      :provide_feedback,
      :change_project_enrolment,
      :download_stats,
      :download_grades,
      :rollover_unit
    ]

    # What can other users do with units?
    nil_role_permissions = [

    ]

    # Return permissions hash
    {
      student: student_role_permissions,
      tutor: tutor_role_permissions,
      convenor: convenor_role_permissions,
      nil: nil_role_permissions
    }
  end

  def role_for(user)
    if convenors.where('unit_roles.user_id=:id', id: user.id).count == 1
      Role.convenor
    elsif tutors.where('unit_roles.user_id=:id', id: user.id).count == 1
      Role.tutor
    elsif active_projects.where('projects.user_id=:id', id: user.id).count == 1
      Role.student
    end
  end

  validates :name, :description, :start_date, :end_date, presence: true

  # Model associations.
  # When a Unit is destroyed, any TaskDefinitions, Tutorials, and ProjectConvenor instances will also be destroyed.
  has_many :task_definitions, -> { order 'start_date ASC, abbreviation ASC' }, dependent: :destroy
  has_many :projects, dependent: :destroy
  has_many :tutorials, dependent: :destroy
  has_many :unit_roles, dependent: :destroy
  has_many :learning_outcomes, dependent: :destroy
  has_many :tasks, through: :projects
  has_many :group_sets, dependent: :destroy
  has_many :task_engagements, through: :projects
  has_many :comments, through: :projects
  has_many :groups, through: :group_sets

  has_many :learning_outcome_task_links, through: :task_definitions

  has_many :convenors, -> { joins(:role).where('roles.name = :role', role: 'Convenor') }, class_name: 'UnitRole'
  has_many :staff, ->     { joins(:role).where('roles.name = :role_convenor or roles.name = :role_tutor', role_convenor: 'Convenor', role_tutor: 'Tutor') }, class_name: 'UnitRole'

  # Unit has a teaching period
  belongs_to :teaching_period
  validates :start_date, presence: true
  validates :end_date, presence: true

  validate :validate_end_date_after_start_date
  validate :ensure_teaching_period_dates_match, if: :has_teaching_period?

  scope :current,               -> { current_for_date(Time.zone.now) }
  scope :current_for_date,      ->(date) { where('start_date <= ? AND end_date >= ?', date, date) }
  scope :not_current,           -> { not_current_for_date(Time.zone.now) }
  scope :not_current_for_date,  ->(date) { where('start_date > ? OR end_date < ?', date, date) }
  scope :set_active,            -> { where('active = ?', true) }
  scope :set_inactive,          -> { where('active = ?', false) }

  def teaching_period_id=(teaching_period_id)
    if teaching_period_id.present?
      tp = TeachingPeriod.find(teaching_period_id)
      write_attribute(:start_date, tp.start_date)
      write_attribute(:end_date, tp.end_date)
    end
    super(teaching_period_id)
  end

  def has_teaching_period?
    self.teaching_period.present?
  end

  def ensure_teaching_period_dates_match
    if read_attribute(:start_date) != teaching_period.start_date
      errors.add(:start_date, "should match teaching period date")
    end
    if read_attribute(:end_date) != teaching_period.end_date
      errors.add(:end_date, "should match teaching period date")
    end
  end

  def validate_end_date_after_start_date
    if end_date < start_date
      errors.add(:end_date, "should be after the Start date")
    end
  end

  def rollover(teaching_period_id, start_date, end_date)
    new_unit = self.dup
    if teaching_period_id.present?
      new_unit.set_teaching_period(teaching_period_id)
    else
      new_unit.set_custom_dates(start_date, end_date)
    end
    new_unit.add_associations(self)
    new_unit
  end

  def set_teaching_period(teaching_period_id)
    self.teaching_period_id = teaching_period_id
    self.save!
  end

  def add_associations(unit)
    self.duplicate_task_definitions_from_existing_unit(unit)
    self.duplicate_learning_outcomes_from_existing_unit(unit)
    self.duplicate_group_sets_from_existing_unit(unit)
    self.duplicate_convenors_from_existing_unit(unit)
  end

  def duplicate_task_definitions_from_existing_unit(unit)
    diff_in_sec = (self.start_date - unit.start_date).to_i
    unit.task_definitions.each do |task_definitions|
      new_task_definitions = task_definitions.dup
      new_task_definitions.adjust_dates(diff_in_sec)
      self.task_definitions << new_task_definitions
    end
    self.task_definitions.each do |task_definitions|
      task_definitions.adjust_dates_for_breaks_in_current_teaching_period
    end
  end

  def duplicate_learning_outcomes_from_existing_unit(unit)
    unit.learning_outcomes.each do |learning_outcomes|
      self.learning_outcomes << learning_outcomes.dup
    end
  end

  def duplicate_group_sets_from_existing_unit(unit)
    unit.group_sets.each do |group_sets|
      self.group_sets << group_sets.dup
    end
  end

  def duplicate_convenors_from_existing_unit(unit)
    unit.convenors.each do |convenors|
      self.convenors << convenors.dup
    end<commit_msg>FIX: Save the task def after current break<commit_after>require 'csv'
require 'bcrypt'
require 'json'
require 'moss_ruby'
require 'csv_helper'

class Unit < ActiveRecord::Base
  include ApplicationHelper
  include FileHelper
  include LogHelper
  include MimeCheckHelpers
  include CsvHelper

  validates :description, length: { maximum: 4095, allow_blank: true }
  #
  # Permissions around unit data
  #
  def self.permissions
    # What can students do with units?
    student_role_permissions = [
      :get_unit
    ]
    # What can tutors do with units?
    tutor_role_permissions = [
      :get_unit,
      :get_students,
      :enrol_student,
      :provide_feedback,
      :download_stats,
      :download_unit_csv,
      :download_grades
    ]

    # What can convenors do with units?
    convenor_role_permissions = [
      :get_unit,
      :get_students,
      :enrol_student,
      :upload_csv,
      :download_unit_csv,
      :update,
      :employ_staff,
      :add_tutorial,
      :add_task_def,
      :provide_feedback,
      :change_project_enrolment,
      :download_stats,
      :download_grades,
      :rollover_unit
    ]

    # What can other users do with units?
    nil_role_permissions = [

    ]

    # Return permissions hash
    {
      student: student_role_permissions,
      tutor: tutor_role_permissions,
      convenor: convenor_role_permissions,
      nil: nil_role_permissions
    }
  end

  def role_for(user)
    if convenors.where('unit_roles.user_id=:id', id: user.id).count == 1
      Role.convenor
    elsif tutors.where('unit_roles.user_id=:id', id: user.id).count == 1
      Role.tutor
    elsif active_projects.where('projects.user_id=:id', id: user.id).count == 1
      Role.student
    end
  end

  validates :name, :description, :start_date, :end_date, presence: true

  # Model associations.
  # When a Unit is destroyed, any TaskDefinitions, Tutorials, and ProjectConvenor instances will also be destroyed.
  has_many :task_definitions, -> { order 'start_date ASC, abbreviation ASC' }, dependent: :destroy
  has_many :projects, dependent: :destroy
  has_many :tutorials, dependent: :destroy
  has_many :unit_roles, dependent: :destroy
  has_many :learning_outcomes, dependent: :destroy
  has_many :tasks, through: :projects
  has_many :group_sets, dependent: :destroy
  has_many :task_engagements, through: :projects
  has_many :comments, through: :projects
  has_many :groups, through: :group_sets

  has_many :learning_outcome_task_links, through: :task_definitions

  has_many :convenors, -> { joins(:role).where('roles.name = :role', role: 'Convenor') }, class_name: 'UnitRole'
  has_many :staff, ->     { joins(:role).where('roles.name = :role_convenor or roles.name = :role_tutor', role_convenor: 'Convenor', role_tutor: 'Tutor') }, class_name: 'UnitRole'

  # Unit has a teaching period
  belongs_to :teaching_period
  validates :start_date, presence: true
  validates :end_date, presence: true

  validate :validate_end_date_after_start_date
  validate :ensure_teaching_period_dates_match, if: :has_teaching_period?

  scope :current,               -> { current_for_date(Time.zone.now) }
  scope :current_for_date,      ->(date) { where('start_date <= ? AND end_date >= ?', date, date) }
  scope :not_current,           -> { not_current_for_date(Time.zone.now) }
  scope :not_current_for_date,  ->(date) { where('start_date > ? OR end_date < ?', date, date) }
  scope :set_active,            -> { where('active = ?', true) }
  scope :set_inactive,          -> { where('active = ?', false) }

  def teaching_period_id=(teaching_period_id)
    if teaching_period_id.present?
      tp = TeachingPeriod.find(teaching_period_id)
      write_attribute(:start_date, tp.start_date)
      write_attribute(:end_date, tp.end_date)
    end
    super(teaching_period_id)
  end

  def has_teaching_period?
    self.teaching_period.present?
  end

  def ensure_teaching_period_dates_match
    if read_attribute(:start_date) != teaching_period.start_date
      errors.add(:start_date, "should match teaching period date")
    end
    if read_attribute(:end_date) != teaching_period.end_date
      errors.add(:end_date, "should match teaching period date")
    end
  end

  def validate_end_date_after_start_date
    if end_date < start_date
      errors.add(:end_date, "should be after the Start date")
    end
  end

  def rollover(teaching_period_id, start_date, end_date)
    new_unit = self.dup
    if teaching_period_id.present?
      new_unit.set_teaching_period(teaching_period_id)
    else
      new_unit.set_custom_dates(start_date, end_date)
    end
    new_unit.add_associations(self)
    new_unit
  end

  def set_teaching_period(teaching_period_id)
    self.teaching_period_id = teaching_period_id
    self.save!
  end

  def add_associations(unit)
    self.duplicate_task_definitions_from_existing_unit(unit)
    self.duplicate_learning_outcomes_from_existing_unit(unit)
    self.duplicate_group_sets_from_existing_unit(unit)
    self.duplicate_convenors_from_existing_unit(unit)
  end

  def duplicate_task_definitions_from_existing_unit(unit)
    diff_in_sec = (self.start_date - unit.start_date).to_i
    unit.task_definitions.each do |task_definitions|
      new_task_definitions = task_definitions.dup
      new_task_definitions.adjust_dates(diff_in_sec)
      self.task_definitions << new_task_definitions
    end
    self.task_definitions.each do |task_definitions|
      task_definitions.adjust_dates_for_breaks_in_current_teaching_period
      task_definitions.save!
    end
  end

  def duplicate_learning_outcomes_from_existing_unit(unit)
    unit.learning_outcomes.each do |learning_outcomes|
      self.learning_outcomes << learning_outcomes.dup
    end
  end

  def duplicate_group_sets_from_existing_unit(unit)
    unit.group_sets.each do |group_sets|
      self.group_sets << group_sets.dup
    end
  end

  def duplicate_convenors_from_existing_unit(unit)
    unit.convenors.each do |convenors|
      self.convenors << convenors.dup
    end
{'fraction_non_alphanumeric': 0.08749791074711682, 'fraction_numerical': 0.001169981614574628, 'mean_word_length': 3.8137570394207563, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3781341', 'n_tokens_mistral': 4032, 'n_tokens_neox': 3941, 'n_words': 797}