From d206e1c37ee0cbd39db6d0dc2f289e66db5fb8df Mon Sep 17 00:00:00 2001 From: cxt <853663049@qq.com> Date: Wed, 8 Jan 2020 18:56:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=99=BA=E8=83=BD=E7=BB=84=E5=8D=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ination_intelligent_settings_controller.rb | 85 +++++++++++++++++-- app/controllers/item_banks_controller.rb | 9 +- app/controllers/item_baskets_controller.rb | 35 +++++--- .../save_exam_setting_form.rb | 11 +++ app/forms/item_banks/save_item_form.rb | 4 +- app/models/examination_intelligent_setting.rb | 3 + app/models/examination_type_setting.rb | 1 + app/models/item_basket.rb | 11 +-- app/models/user.rb | 2 + app/queries/optional_item_query.rb | 37 ++++---- .../save_setting_service.rb | 63 ++++++++++++++ .../item_baskets/save_item_basket_service.rb | 24 ++++-- config/routes.rb | 5 ++ ...930_add_user_id_to_intelligent_settings.rb | 5 ++ ..._intelligent_setting_id_to_item_baskets.rb | 5 ++ 15 files changed, 247 insertions(+), 53 deletions(-) create mode 100644 app/forms/examination_intelligent_settings/save_exam_setting_form.rb create mode 100644 app/services/examination_intelligent_settings/save_setting_service.rb create mode 100644 db/migrate/20200108030930_add_user_id_to_intelligent_settings.rb create mode 100644 db/migrate/20200108061139_add_intelligent_setting_id_to_item_baskets.rb diff --git a/app/controllers/examination_intelligent_settings_controller.rb b/app/controllers/examination_intelligent_settings_controller.rb index 8a69a5c70..13d412da8 100644 --- a/app/controllers/examination_intelligent_settings_controller.rb +++ b/app/controllers/examination_intelligent_settings_controller.rb @@ -1,14 +1,89 @@ class ExaminationIntelligentSettingsController < ApplicationController + before_action :require_login + before_action :find_exam, only: [:exchange_one_item, :exchange_items] def optinal_items - items = OptionalItemQuery.call(params) - @single_question_count = items[:single_question_count] - @multiple_question_count = items[:multiple_question_count] - @judgement_question_count = items[:judgement_question_count] - @program_question_count = items[:program_question_count] + sub_discipline_id = params[:sub_discipline_id] + tag_discipline_id = params[:tag_discipline_id] + difficulty = params[:difficulty] + source = params[:source] + + items = OptionalItemQuery.call(sub_discipline_id, tag_discipline_id, difficulty, source) + @single_question_count = items.select{ |item| item.item_type == "SINGLE" }.size + @multiple_question_count = items.select{ |item| item.item_type == "MULTIPLE" }.size + @judgement_question_count = items.select{ |item| item.item_type == "JUDGMENT" }.size + @program_question_count = items.select{ |item| item.item_type == "PROGRAM" }.size end def create + ActiveRecord::Base.transaction do + exam = ExaminationIntelligentSetting.new(user: current_user) + # 保存试卷基础信息 + exam = ExaminationIntelligentSettings::SaveSettingService.call(exam, form_params) + render_ok({exam_setting_id: exam.id}) + end + rescue ApplicationService::Error => ex + render_error(ex.message) + end + + def exchange_one_item + item = @exam.item_baskets.find_by!(id: params[:item_id]) + exam_type_setting = @exam.examination_type_settings.find_by!(item_type: item.item_type) + + # 获取可选的题 + items = OptionalItemQuery.call(@exam.sub_discipline_id, @exam.tag_discipline_containers.pluck(:tag_discipline_id), @exam.difficulty, @exam.public) + type_items = items.select{ |t_item| t_item.item_type == item.item_type } + # 如果可选的题数小于等于设置的题数则提示无可换的题 + tip_exception("无可换的题") if type_items.size <= exam_type_setting.count + # 可选题中去掉已组卷的同题型试题 + optional_item_ids = type_items.pluck(:id) - @exam.item_baskets.where(item_type: item.item_type).pluck(:item_bank_id) + new_item = ItemBank.find optional_item_ids.sample(1).first + ActiveRecord::Base.transaction do + @exam.item_baskets << ItemBasket.new(item_bank_id: new_item.id, position: item.position, score: item.score, item_type: new_item.item_type) + item.destroy! + end + render_ok + end + + def exchange_items + exam_type_setting = @exam.examination_type_settings.find_by!(item_type: params[:item_type]) + choosed_items = @exam.item_baskets.where(item_type: params[:item_type]) + + # 获取可选的题 + items = OptionalItemQuery.call(@exam.sub_discipline_id, @exam.tag_discipline_containers.pluck(:tag_discipline_id), @exam.difficulty, @exam.public) + type_items = items.select{ |t_item| t_item.item_type == params[:item_type] } + # 如果可选的题数小于等于设置的题数则提示无可换的题 + tip_exception("无可换的题") if type_items.size <= exam_type_setting.count + # 可选题中去掉已组卷的同题型试题 + choosed_item_ids = choosed_items.pluck(:item_bank_id) + optional_item_ids = type_items.pluck(:id) - choosed_item_ids + + # 如果可选题数小于设置的题数n,则在原来的选题中随机选n个,确保换题时能选到新的题 + if optional_item_ids.size < exam_type_setting.count + absence_count = exam_type_setting.count - optional_item_ids.size + optional_item_ids = optional_item_ids + choosed_item_ids.sample(absence_count) + end + + ActiveRecord::Base.transaction do + # 取试题分数 + score = choosed_items.first&.score || (params[:item_type] == "PROGRAM" ? 10 : 5) + choosed_items.destroy_all + optional_item_ids.sample(exam_type_setting.count).each_with_index do |item_id, index| + new_item = ItemBank.find item_id + @exam.item_baskets << ItemBasket.new(item_bank_id: new_item.id, position: index+1, score: score, item_type: new_item.item_type) + end + end + render_ok + end + + private + + def find_exam + @exam = ExaminationIntelligentSetting.find_by!(id: params[:id]) + tip_exception(403,"无权限编辑") unless current_user.admin_or_business? || @exam.user_id == current_user.id + end + def form_params + params.permit(:discipline_id, :sub_discipline_id, :difficulty, :source, tag_discipline_id: [], question_settings: %i[item_type count]) end end \ No newline at end of file diff --git a/app/controllers/item_banks_controller.rb b/app/controllers/item_banks_controller.rb index 24d9c44a9..10dd10e07 100644 --- a/app/controllers/item_banks_controller.rb +++ b/app/controllers/item_banks_controller.rb @@ -9,7 +9,14 @@ class ItemBanksController < ApplicationController @items_count = items.size @items = paginate items.includes(:item_analysis, :user, :container) exam = ExaminationBank.find_by(id: params[:exam_id]) if params[:exam_id].present? - @item_basket_ids = exam ? exam.examination_items.pluck(:item_bank_id) : current_user.item_baskets.pluck(:item_bank_id) + exam_setting = ExaminationIntelligentSetting.find_by(id: params[:exam_setting_id]) if params[:exam_setting_id].present? + @item_basket_ids = if exam + exam.examination_items.pluck(:item_bank_id) + elsif exam_setting + exam_setting.item_baskets.pluck(:item_bank_id) + else + current_user.item_baskets.pluck(:item_bank_id) + end end def create diff --git a/app/controllers/item_baskets_controller.rb b/app/controllers/item_baskets_controller.rb index 21203346b..dc5367378 100644 --- a/app/controllers/item_baskets_controller.rb +++ b/app/controllers/item_baskets_controller.rb @@ -4,7 +4,7 @@ class ItemBasketsController < ApplicationController helper_method :current_basket def index - @item_baskets = current_user.item_baskets + @item_baskets = basket_items @single_questions = @item_baskets.where(item_type: "SINGLE") @multiple_questions = @item_baskets.where(item_type: "MULTIPLE") @judgement_questions = @item_baskets.where(item_type: "JUDGMENT") @@ -22,41 +22,41 @@ class ItemBasketsController < ApplicationController end def create - ItemBaskets::SaveItemBasketService.call(current_user, create_params) + ItemBaskets::SaveItemBasketService.call(current_user, create_params, exam_setting) render_ok rescue ApplicationService::Error => ex render_error(ex.message) end def destroy - item = current_user.item_baskets.find_by!(item_bank_id: params[:id]) + item = basket_items.find_by!(item_bank_id: params[:id]) ActiveRecord::Base.transaction do - current_user.item_baskets.where(item_type: item.item_type).where("position > #{item.position}").update_all("position = position -1") + basket_items.where(item_type: item.item_type).where("position > #{item.position}").update_all("position = position -1") item.destroy! end render_ok end def delete_item_type - baskets = ItemBasket.where(item_type: params[:item_type]) + baskets = basket_items.where(item_type: params[:item_type]) baskets.destroy_all render_ok end def set_score current_basket.update_attributes!(score: params[:score]) - @questions_score = current_user.item_baskets.where(item_type: current_basket.item_type).pluck(:score).sum - @all_score = current_user.item_baskets.pluck(:score).sum + @questions_score = basket_items.where(item_type: current_basket.item_type).pluck(:score).sum + @all_score = basket_items.pluck(:score).sum end def batch_set_score - current_user.item_baskets.where(item_type: params[:item_type]).update_all(score: params[:score]) - @questions_score = current_user.item_baskets.where(item_type: params[:item_type]).pluck(:score).sum - @all_score = current_user.item_baskets.pluck(:score).sum + basket_items.where(item_type: params[:item_type]).update_all(score: params[:score]) + @questions_score = basket_items.where(item_type: params[:item_type]).pluck(:score).sum + @all_score = basket_items.pluck(:score).sum end def adjust_position - same_items = current_user.item_baskets.where(item_type: current_basket.item_type) + same_items = basket_items.where(item_type: current_basket.item_type) max_position = same_items.size tip_exception("position超出范围") unless params[:position].present? && params[:position].to_i <= max_position && params[:position].to_i >= 1 ActiveRecord::Base.transaction do @@ -79,8 +79,19 @@ class ItemBasketsController < ApplicationController params.permit(item_ids: []) end + def exam_setting + @_exam_setting = ExaminationIntelligentSetting.find_by(id: params[:exam_setting_id]) + end + + def basket_items + @_items = params[:exam_setting_id] ? exam_setting.item_baskets : current_user.item_baskets + end + def current_basket - @_current_basket = current_user.item_baskets.find_by!(id: params[:id]) + @_current_basket = ItemBasket.find_by!(id: params[:id]) + tip_exception(403, "无权限编辑") unless current_user.admin_or_business? || @_current_basket.user_id.to_i == current_user.id || + @_current_basket.examination_intelligent_setting&.user_id.to_i == current_user.id + @_current_basket end def validate_score diff --git a/app/forms/examination_intelligent_settings/save_exam_setting_form.rb b/app/forms/examination_intelligent_settings/save_exam_setting_form.rb new file mode 100644 index 000000000..bbfb9eee8 --- /dev/null +++ b/app/forms/examination_intelligent_settings/save_exam_setting_form.rb @@ -0,0 +1,11 @@ +class ExaminationIntelligentSettings::SaveExamSettingForm + include ActiveModel::Model + + attr_accessor :discipline_id, :sub_discipline_id, :source, :difficulty, :tag_discipline_id, :question_settings + + validates :discipline_id, presence: true + validates :sub_discipline_id, presence: true + validates :source, presence: true + validates :difficulty, presence: true, inclusion: {in: 1..3}, numericality: { only_integer: true } + validates :question_settings, presence: true +end \ No newline at end of file diff --git a/app/forms/item_banks/save_item_form.rb b/app/forms/item_banks/save_item_form.rb index 7e788067e..d6d3a3e28 100644 --- a/app/forms/item_banks/save_item_form.rb +++ b/app/forms/item_banks/save_item_form.rb @@ -15,9 +15,9 @@ class ItemBanks::SaveItemForm return unless errors.blank? choices.each { |item| SubForm.new(item).validate! } if %W(SINGLE MULTIPLE JUDGMENT).include?(item_type) return unless errors.blank? - if [0, 2].include?(item_type) && choices.pluck(:is_answer).select{|item| item == 1}.length > 1 + if ["SINGLE", "JUDGMENT"].include?(item_type) && choices.pluck(:is_answer).select{|item| item == 1}.length > 1 raise("正确答案只能有一个") - elsif item_type == 1 && choices.pluck(:is_answer).select{|item| item == 1}.length <= 1 + elsif item_type == "MULTIPLE" && choices.pluck(:is_answer).select{|item| item == 1}.length <= 1 raise("多选题至少有两个正确答案") end end diff --git a/app/models/examination_intelligent_setting.rb b/app/models/examination_intelligent_setting.rb index 16dd88300..38c7fbe4b 100644 --- a/app/models/examination_intelligent_setting.rb +++ b/app/models/examination_intelligent_setting.rb @@ -1,4 +1,7 @@ class ExaminationIntelligentSetting < ApplicationRecord belongs_to :sub_discipline + belongs_to :user has_many :examination_type_settings, dependent: :destroy + has_many :tag_discipline_containers, as: :container, dependent: :destroy + has_many :item_baskets, dependent: :destroy end diff --git a/app/models/examination_type_setting.rb b/app/models/examination_type_setting.rb index c985e578e..30f3e80b4 100644 --- a/app/models/examination_type_setting.rb +++ b/app/models/examination_type_setting.rb @@ -1,3 +1,4 @@ class ExaminationTypeSetting < ApplicationRecord + enum item_type: { SINGLE: 0, MULTIPLE: 1, JUDGMENT: 2, COMPLETION: 3, SUBJECTIVE: 4, PRACTICAL: 5, PROGRAM: 6 } belongs_to :examination_intelligent_setting end diff --git a/app/models/item_basket.rb b/app/models/item_basket.rb index a3ff865ba..eb849d403 100644 --- a/app/models/item_basket.rb +++ b/app/models/item_basket.rb @@ -2,13 +2,6 @@ class ItemBasket < ApplicationRecord enum item_type: { SINGLE: 0, MULTIPLE: 1, JUDGMENT: 2, COMPLETION: 3, SUBJECTIVE: 4, PRACTICAL: 5, PROGRAM: 6 } belongs_to :item_bank - belongs_to :user - - def all_score - User.current.item_baskets.map(&:score).sum - end - - def question_count - User.current.item_baskets.size - end + belongs_to :user, optional: true + belongs_to :examination_intelligent_setting, optional: true end diff --git a/app/models/user.rb b/app/models/user.rb index 54aa8d85e..34be9615e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -156,6 +156,8 @@ class User < ApplicationRecord # 题库 has_many :item_banks, dependent: :destroy has_many :item_baskets, -> { order("item_baskets.position ASC") }, dependent: :destroy + has_many :examination_banks, dependent: :destroy + has_many :examination_intelligent_settings, dependent: :destroy # Groups and active users scope :active, lambda { where(status: STATUS_ACTIVE) } diff --git a/app/queries/optional_item_query.rb b/app/queries/optional_item_query.rb index 456a74c6a..54cd0eed0 100644 --- a/app/queries/optional_item_query.rb +++ b/app/queries/optional_item_query.rb @@ -1,32 +1,35 @@ class OptionalItemQuery < ApplicationQuery - attr_reader :params + attr_reader :sub_discipline_id, :tag_discipline_id, :difficulty, :source - def initialize(params) - @params = params + def initialize(sub_discipline_id, tag_discipline_id, difficulty, source) + @sub_discipline_id = sub_discipline_id + @tag_discipline_id = tag_discipline_id + @difficulty = difficulty + @source = source end def call items = ItemBank.all - if params[:tag_discipline_id].present? && params[:sub_discipline_id].present? - items = items.joins(:tag_discipline_containers).where(tag_discipline_containers: {tag_discipline_id: params[:tag_discipline_id]}) - hacks = Hack.joins(:tag_discipline_containers).where(tag_discipline_containers: {tag_discipline_id: params[:tag_discipline_id]}) - elsif params[:sub_discipline_id].present? - items = items.where(sub_discipline_id: params[:sub_discipline_id]) - hacks = Hack.where(sub_discipline_id: params[:sub_discipline_id]) + if tag_discipline_id.present? && sub_discipline_id.present? + items = items.joins(:tag_discipline_containers).where(tag_discipline_containers: {tag_discipline_id: tag_discipline_id}) + hacks = Hack.joins(:tag_discipline_containers).where(tag_discipline_containers: {tag_discipline_id: tag_discipline_id}) + elsif sub_discipline_id.present? + items = items.where(sub_discipline_id: sub_discipline_id) + hacks = Hack.where(sub_discipline_id: sub_discipline_id) + end + + if hacks.present? + items = ItemBank.where(container_id: hacks.pluck(:id), container_type: "Hack").or(ItemBank.where(id: items.pluck(:id))) end # 来源 - source = params[:source].present? ? params[:source].to_i : 1 - public = source == 3 ? [0, 1] : source + public = source.present? ? source.to_i : 1 + public = public == 2 ? [0, 1] : public items = items.where(public: public) # 难度 - difficulty = params[:difficulty] ? params[:difficulty].to_i : 1 + difficulty = difficulty ? difficulty.to_i : 1 items = items.where(difficulty: difficulty) - single_question_count = items.select{ |item| item.item_type == "SINGLE" }.size - multiple_question_count = items.select{ |item| item.item_type == "MULTIPLE" }.size - judgement_question_count = items.select{ |item| item.item_type == "JUDGMENT" }.size - program_question_count = hacks.present? ? hacks.pluck(:item_bank_id).reject(&:blank?).size : items.select{ |item| item.item_type == "PROGRAM" }.size - {single_question_count: single_question_count, multiple_question_count: multiple_question_count, judgement_question_count: judgement_question_count, program_question_count: program_question_count} + items end end \ No newline at end of file diff --git a/app/services/examination_intelligent_settings/save_setting_service.rb b/app/services/examination_intelligent_settings/save_setting_service.rb new file mode 100644 index 000000000..af22b3234 --- /dev/null +++ b/app/services/examination_intelligent_settings/save_setting_service.rb @@ -0,0 +1,63 @@ +class ExaminationIntelligentSettings::SaveSettingService < ApplicationService + attr_reader :exam, :params + + def initialize(exam, params) + @exam = exam + @params = params + end + + def call + ExaminationIntelligentSettings::SaveExamSettingForm.new(params).validate! + items = OptionalItemQuery.call(params[:sub_discipline_id], params[:tag_discipline_id], params[:difficulty], params[:source]) + params[:question_settings].each do |setting| + raise "超出可选题数范围" if items.select{ |item| item.item_type == setting[:item_type] }.size.to_i < setting[:count].to_i + end + + exam.difficulty = params[:difficulty] + exam.sub_discipline_id = params[:sub_discipline_id] + exam.public = params[:source].present? ? params[:source].to_i : 1 + exam.save! + + # 知识点的创建 + params[:tag_discipline_id].each do |tag_id| + exam.tag_discipline_containers << TagDisciplineContainer.new(tag_discipline_id: tag_id) + end + + # 智能选题的设置 + params[:question_settings].each do |setting| + if setting[:count].to_i > 0 + exam.examination_type_settings << ExaminationTypeSetting.new(item_type: setting[:item_type], count: setting[:count].to_i) + end + end + + # 选题 + choose_question items + + exam + end + + private + + def choose_question items + exam.examination_type_settings.each do |setting| + questions = items.select{ |item| item.item_type == setting.item_type } + questions.pluck(:id).sample(setting.count).each_with_index do |item_id, index| + item = ItemBank.find item_id + exam.item_baskets << ItemBasket.new(item_bank_id: item.id, position: index+1, score: item_score(item.item_type), item_type: item.item_type) + end + end + end + + def item_score item_type + score = + case item_type + when "SINGLE", "MULTIPLE", "JUDGMENT" + 5 + when "PROGRAM" + 10 + else + 5 + end + score + end +end \ No newline at end of file diff --git a/app/services/item_baskets/save_item_basket_service.rb b/app/services/item_baskets/save_item_basket_service.rb index cf6d3738b..debc7e2ed 100644 --- a/app/services/item_baskets/save_item_basket_service.rb +++ b/app/services/item_baskets/save_item_basket_service.rb @@ -1,9 +1,10 @@ class ItemBaskets::SaveItemBasketService < ApplicationService - attr_reader :user, :params + attr_reader :user, :params, :exam_setting - def initialize(user, params) + def initialize(user, params, exam_setting) @user = user @params = params + @exam_setting = exam_setting end def call @@ -13,9 +14,14 @@ class ItemBaskets::SaveItemBasketService < ApplicationService items = ItemBank.where(public: 1).or(ItemBank.where(user_id: user.id)) # 已选到过试题篮的不重复选用 - item_ids = params[:item_ids] - user.item_baskets.pluck(:item_bank_id) + item_ids = params[:item_ids] - basket_items.pluck(:item_bank_id) items.where(id: item_ids).each do |item| - new_item = ItemBasket.new(user_id: user.id, item_bank_id: item.id, item_type: item.item_type) + new_item = ItemBasket.new(item_bank_id: item.id, item_type: item.item_type) + if exam_setting.present? + new_item.examination_intelligent_setting_id = exam_setting.id + else + new_item.user_id = user.id + end new_item.score = item_score item.item_type new_item.position = item_position item.item_type new_item.save! @@ -25,8 +31,8 @@ class ItemBaskets::SaveItemBasketService < ApplicationService private def item_score item_type - if user.item_baskets.where(item_type: item_type).last.present? - score = user.item_baskets.where(item_type: item_type).last.score + if basket_items.where(item_type: item_type).last.present? + score = basket_items.where(item_type: item_type).last.score else score = case item_type @@ -42,6 +48,10 @@ class ItemBaskets::SaveItemBasketService < ApplicationService end def item_position item_type - user.item_baskets.where(item_type: item_type).last&.position.to_i + 1 + basket_items.where(item_type: item_type).last&.position.to_i + 1 + end + + def basket_items + exam_setting.present? ? exam_setting.item_baskets : user.item_baskets end end \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 198dbc39f..225909f8e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -100,6 +100,11 @@ Rails.application.routes.draw do collection do get :optinal_items end + + member do + post :exchange_one_item + post :exchange_items + end end resources :hacks, path: :problems, param: :identifier do diff --git a/db/migrate/20200108030930_add_user_id_to_intelligent_settings.rb b/db/migrate/20200108030930_add_user_id_to_intelligent_settings.rb new file mode 100644 index 000000000..83d3b96b4 --- /dev/null +++ b/db/migrate/20200108030930_add_user_id_to_intelligent_settings.rb @@ -0,0 +1,5 @@ +class AddUserIdToIntelligentSettings < ActiveRecord::Migration[5.2] + def change + add_column :examination_intelligent_settings, :user_id, :integer, index: true + end +end diff --git a/db/migrate/20200108061139_add_intelligent_setting_id_to_item_baskets.rb b/db/migrate/20200108061139_add_intelligent_setting_id_to_item_baskets.rb new file mode 100644 index 000000000..9de17ee48 --- /dev/null +++ b/db/migrate/20200108061139_add_intelligent_setting_id_to_item_baskets.rb @@ -0,0 +1,5 @@ +class AddIntelligentSettingIdToItemBaskets < ActiveRecord::Migration[5.2] + def change + add_column :item_baskets, :examination_intelligent_setting_id, :integer, index: true + end +end