# class Plan

### Constants

A4_PAGE_HEIGHT
A4_PAGE_WIDTH
FONT_HEIGHT_CONVERSION_FACTOR
FONT_WIDTH_HEIGHT_RATIO
ROUNDING

### Public Instance Methods

add_guidance_to_array(guidance_array, guidance_group, theme, guidance) click to toggle source

    # File app/models/plan.rb, line 119
    def add_guidance_to_array(guidance_array, guidance_group, theme, guidance)

            if guidance_array[guidance_group].nil? then
                    guidance_array[guidance_group] = {}
            end
            if theme.nil? then
                    if guidance_array[guidance_group]["no_theme"].nil? then
                            guidance_array[guidance_group]["no_theme"] = []
                    end
                    if !guidance_array[guidance_group]["no_theme"].include?(guidance) then
                            guidance_array[guidance_group]["no_theme"].push(guidance)
                    end
            else
                    if guidance_array[guidance_group][theme].nil? then
                            guidance_array[guidance_group][theme] = []
                    end
                    if !guidance_array[guidance_group][theme].include?(guidance) then
                            guidance_array[guidance_group][theme].push(guidance)
                    end
            end

    return guidance_array
    end

administerable_by(user_id) click to toggle source

    # File app/models/plan.rb, line 168
    def administerable_by(user_id)
            return project.readable_by(user_id)
    end

answer(qid, create_if_missing = true) click to toggle source

    # File app/models/plan.rb, line 53
    def answer(qid, create_if_missing = true)
            answer = answers.where(:question_id => qid).order("created_at DESC").first
            question = Question.find(qid)
            if answer.nil? && create_if_missing then
                    answer = Answer.new
                    answer.plan_id = id
                    answer.question_id = qid
                    answer.text = question.default_value
                    default_options = Array.new
                    question.options.each do |option|
                            if option.is_default
                                    default_options << option
                            end
                    end
                    answer.options = default_options
            end
            return answer
    end

delete_recent_locks(user_id) click to toggle source

    # File app/models/plan.rb, line 291
    def delete_recent_locks(user_id)
            plan_sections.where(:user_id => user_id).each do |lock|
                    lock.delete
            end
    end

details() click to toggle source

    # File app/models/plan.rb, line 230
    def details
            details = {
                    "project_title" => project.title,
                    "phase_title" => version.phase.title,
                    "sections" => {}
            }
            sections.sort_by(&:"number").each do |s|
                    details["sections"][s.number] = {}
                    details["sections"][s.number]["title"] = s.title
                    details["sections"][s.number]["questions"] = {}
                    s.questions.order("number").each do |q|
                            details["sections"][s.number]["questions"][q.number] = {}
                            details["sections"][s.number]["questions"][q.number]["question_text"] = q.text
                            answer = answer(q.id, false)
                            if ! answer.nil? then
                q_format = q.question_format
                                    if (q_format.title == t("helpers.checkbox") || q_format.title == t("helpers.multi_select_box") ||
                                    q_format.title == t("helpers.radio_buttons") || q_format.title == t("helpers.dropdown")) then
                                            details["sections"][s.number]["questions"][q.number]["selections"] = {}
                                            answer.options.each do |o|
                                                    details["sections"][s.number]["questions"][q.number]["selections"][o.number] = o.text
                                            end
                                    end
                                    details["sections"][s.number]["questions"][q.number]["answer_text"] = answer.text
                            end
                    end
            end
            return details
    end

dmptemplate() click to toggle source

    # File app/models/plan.rb, line 36
    def dmptemplate
            self.project.try(:dmptemplate) || Dmptemplate.new
    end

editable_by(user_id) click to toggle source

    # File app/models/plan.rb, line 156
    def editable_by(user_id)
            return project.editable_by(user_id)
    end

guidance_for_question(question) click to toggle source

    # File app/models/plan.rb, line 81
    def guidance_for_question(question)
            guidances = {}
            # If project org isn't nil, get guidance by theme from any "non-subset" groups belonging to project org

            unless project.organisation.nil? then
                    project.organisation.guidance_groups.each do |group|
                            if !group.optional_subset && (group.dmptemplates.pluck(:id).include?(project.dmptemplate_id) || group.dmptemplates.count == 0) then
                                    group.guidances.each do |guidance|
                                            guidance.themes.where("id IN (?)", question.theme_ids).each do |theme|
                                                    guidances = self.add_guidance_to_array(guidances, group, theme, guidance)
                                            end
                                    end
                            end
                    end
            end

            # Get guidance by theme from any guidance groups selected on creation

            project.guidance_groups.each do |group|
                    if group.dmptemplates.pluck(:id).include?(project.dmptemplate_id) || group.dmptemplates.count == 0 then
                            group.guidances.each do |guidance|
                                    guidance.themes.where("id IN (?)", question.theme_ids).each do |theme|
                                            guidances = self.add_guidance_to_array(guidances, group, theme, guidance)
                                    end
                            end
                    end
    end

            # Get guidance by question where guidance group was selected on creation or if group is organisation default

            question.guidances.each do |guidance|
                    guidance.guidance_groups.each do |group|
                            if (group.organisation == project.organisation && !group.optional_subset) || project.guidance_groups.include?(group) then
                                    guidances = self.add_guidance_to_array(guidances, group, nil, guidance)
                            end
        end
            end

            return guidances
    end

latest_update() click to toggle source

    # File app/models/plan.rb, line 321
    def latest_update
            if answers.any? then
                    last_answered = answers.order("updated_at DESC").first.updated_at
                    if last_answered > updated_at then
                            return last_answered
                    else
                            return updated_at
                    end
            else
                    return updated_at
            end
    end

lock_all_sections(user_id) click to toggle source

    # File app/models/plan.rb, line 279
    def lock_all_sections(user_id)
            sections.each do |s|
                    lock_section(s.id, user_id, 1800)
            end
    end

lock_section(section_id, user_id, release_time = 60) click to toggle source

    # File app/models/plan.rb, line 297
    def lock_section(section_id, user_id, release_time = 60)
            status = locked(section_id, user_id)
            if ! status["locked"] then
                    plan_section = PlanSection.new
                    plan_section.plan_id = id
                    plan_section.section_id = section_id
                    plan_section.release_time = Time.now + release_time.seconds
                    plan_section.user_id = user_id
                    plan_section.save
            elsif status["current_user"] then
                    plan_section = PlanSection.find(status["id"])
                    plan_section.release_time = Time.now + release_time.seconds
                    plan_section.save
            else
                    return false
            end
    end

locked(section_id, user_id) click to toggle source

    # File app/models/plan.rb, line 260
    def locked(section_id, user_id)
            plan_section = plan_sections.where("section_id = ? AND user_id != ? AND release_time > ?", section_id, user_id, Time.now).last
            if plan_section.nil? then
                    status = {
                            "locked" => false,
                            "locked_by" => nil,
                            "timestamp" => nil,
                            "id" => nil
                    }
            else
                    status = {
                            "locked" => true,
                            "locked_by" => plan_section.user.name,
                            "timestamp" => plan_section.updated_at,
                            "id" => plan_section.id
                    }
            end
    end

readable_by(user_id) click to toggle source

    # File app/models/plan.rb, line 160
    def readable_by(user_id)
            if project.nil?
                    return false
            else
                    return project.readable_by(user_id)
            end
    end

section_answers(section_id) click to toggle source

    # File app/models/plan.rb, line 334
    def section_answers(section_id)
            section = Section.find(section_id)
            section_questions = Array.new
            counter = 0
            section.questions.each do |q|
                    section_questions[counter] = {}
                    section_questions[counter]["id"] = q.id
                    #section_questions[counter]["multiple_choice"] = q.multiple_choice

                    q_answer = answer(q.id, false)
                    if q_answer.nil? then
                            section_questions[counter]["answer_id"] = nil
                            if q.suggested_answers.find_by_organisation_id(project.organisation_id).nil? then
                                    section_questions[counter]["answer_text"] = ""
                            else
                                    section_questions[counter]["answer_text"] = q.default_value
                            end
                            section_questions[counter]["answer_timestamp"] = nil
                            section_questions[counter]["answer_options"] = Array.new
                    else
                            section_questions[counter]["answer_id"] = q_answer.id
                            section_questions[counter]["answer_text"] = q_answer.text
                            section_questions[counter]["answer_timestamp"] = q_answer.created_at
                            section_questions[counter]["answer_options"] = q_answer.options.pluck(:id)
                    end
                    counter = counter + 1
            end
            return section_questions
    end

sections() click to toggle source

    # File app/models/plan.rb, line 72
    def sections
            unless project.organisation.nil? then
                    sections = version.global_sections + project.organisation.all_sections(version_id)
            else
                    sections = version.global_sections
            end
            return sections.uniq.sort_by &:number
    end

settings(key) click to toggle source

Proxy through to the template settings (or defaults if this plan doesn't have an associated template) if there are no settings stored for this plan. `key` is required by rails-settings, so it's required here, too.

    # File app/models/plan.rb, line 29
    def settings(key)
            self_settings = self.super_settings(key)
            return self_settings if self_settings.value?

            self.dmptemplate.settings(key)
    end

Also aliased as: super_settings
status() click to toggle source

    # File app/models/plan.rb, line 172
    def status
            status = {
                    "num_questions" => 0,
                    "num_answers" => 0,
                    "sections" => {},
                    "questions" => {},
                    "space_used" => 0 # percentage of available space in pdf used

            }

            space_used = height_of_text(self.project.title, 2, 2)

            sections.each do |s|
                    space_used += height_of_text(s.title, 1, 1)
                    section_questions = 0
                    section_answers = 0
                    status["sections"][s.id] = {}
                    status["sections"][s.id]["questions"] = Array.new
                    s.questions.each do |q|
                            status["num_questions"] += 1
                            section_questions += 1
                            status["sections"][s.id]["questions"] << q.id
                            status["questions"][q.id] = {}
                            answer = answer(q.id, false)

                            space_used += height_of_text(q.text) unless q.text == s.title
                            space_used += height_of_text(answer.try(:text) || I18n.t('helpers.plan.export.pdf.question_not_answered'))

                            if ! answer.nil? then
                                    status["questions"][q.id] = {
                                            "answer_id" => answer.id,
                                            "answer_created_at" => answer.created_at.to_i,
                                            "answer_text" => answer.text,
                                            "answer_option_ids" => answer.option_ids,
                                            "answered_by" => answer.user.name
                                    }
                q_format = q.question_format
                                    status["num_answers"] += 1 if (q_format.title == I18n.t("helpers.checkbox") || q_format.title == I18n.t("helpers.multi_select_box") ||
                                    q_format.title == I18n.t("helpers.radio_buttons") || q_format.title == I18n.t("helpers.dropdown")) || answer.text.present?
                                    section_answers += 1
                                    #TODO: include selected options in space estimate

                            else
                                    status["questions"][q.id] = {
                                            "answer_id" => nil,
                                            "answer_created_at" => nil,
                                            "answer_text" => nil,
                                            "answer_option_ids" => nil,
                                            "answered_by" => nil
                                    }
                            end
                            status["sections"][s.id]["num_questions"] = section_questions
                            status["sections"][s.id]["num_answers"] = section_answers
                    end
            end

            status['space_used'] = estimate_space_used(space_used)
            return status
    end

super_settings(key) click to toggle source
Alias for: settings
title() click to toggle source

    # File app/models/plan.rb, line 40
    def title
            logger.debug "Title in settings: #{self.settings(:export).title}"
            if self.settings(:export).title == ""
        if !self.version.nil? && !self.version.phase.nil? && !self.version.phase.title? then
            return self.version.phase.title
        else
            return "DMP title"
                    end
            else
                    return self.settings(:export).title
            end
    end

unlock_all_sections(user_id) click to toggle source

    # File app/models/plan.rb, line 285
    def unlock_all_sections(user_id)
            plan_sections.where(:user_id => user_id).order("created_at DESC").each do |lock|
                    lock.delete
            end
    end

unlock_section(section_id, user_id) click to toggle source

    # File app/models/plan.rb, line 315
    def unlock_section(section_id, user_id)
            plan_sections.where(:section_id => section_id, :user_id => user_id).order("created_at DESC").each do |lock|
                    lock.delete
            end
    end

warning(option_id) click to toggle source

    # File app/models/plan.rb, line 148
    def warning(option_id)
            if project.organisation.nil?
                    return nil
            else
                    return project.organisation.warning(option_id)
            end
    end

### Private Instance Methods

estimate_space_used(used_height) click to toggle source

Based on the height of the text gathered so far and the available vertical space of the pdf, estimate a percentage of how much space has been used. This is highly dependent on the layout in the pdf. A more accurate approach would be to render the pdf and check how much space had been used, but that could be very slow. NOTE: This is only an estimate, rounded up to the nearest 5%; it is intended for guidance when editing plan data, not to be 100% accurate.

    # File app/models/plan.rb, line 372
    def estimate_space_used(used_height)
            @formatting ||= self.settings(:export).formatting

            return 0 unless @formatting[:font_size] > 0

            margin_height    = @formatting[:margin][:top].to_i + @formatting[:margin][:bottom].to_i
            page_height      = A4_PAGE_HEIGHT - margin_height # 297mm for A4 portrait

            available_height = page_height * self.dmptemplate.settings(:export).max_pages

            percentage = (used_height / available_height) * 100
            (percentage / ROUNDING).ceil * ROUNDING # round up to nearest five

    end

height_of_text(text, font_size_inc = 0, vertical_margin = 0) click to toggle source

Take a guess at the vertical height (in mm) of the given text based on the font-size and left/right margins stored in the plan's settings. This assumes a fixed-width for each glyph, which is obviously incorrect for the font-face choices available; the idea is that they'll hopefully average out to that in the long-run. Allows for hinting different font sizes (offset from base via font_size_inc) and vertical margins (i.e. for heading text)

    # File app/models/plan.rb, line 392
    def height_of_text(text, font_size_inc = 0, vertical_margin = 0)
            @formatting     ||= self.settings(:export).formatting
            @margin_width   ||= @formatting[:margin][:left].to_i + @formatting[:margin][:right].to_i
            @base_font_size ||= @formatting[:font_size]

            return 0 unless @base_font_size > 0

            font_height = FONT_HEIGHT_CONVERSION_FACTOR * (@base_font_size + font_size_inc)
            font_width  = font_height * FONT_WIDTH_HEIGHT_RATIO # Assume glyph width averages at 2/5s the height

            leading     = font_height / 2

            chars_in_line = (A4_PAGE_WIDTH - @margin_width) / font_width # 210mm for A4 portrait

            num_lines = (text.length / chars_in_line).ceil

            (num_lines * font_height) + vertical_margin + leading
    end