diff --git a/app/assets/stylesheets/member-app-waiting-reason.scss b/app/assets/stylesheets/member-app-waiting-reason.scss index e98f13bf4..63cd939bd 100644 --- a/app/assets/stylesheets/member-app-waiting-reason.scss +++ b/app/assets/stylesheets/member-app-waiting-reason.scss @@ -1,7 +1,34 @@ -.member_app_waiting_reasons { - input.wpcf7-form-control.is-custom { - width: 3rem; +$reason-border-color: #ff7f50; // coral; picked sort of at random + + +#member-app-waiting-reasons { + + + border-bottom: 1pt solid $reason-border-color; + margin-bottom: 1rem; + + .row { + + margin-bottom: 0; + margin-top: 0; + padding-bottom: 0.5rem; + padding-top: 0.5rem; + + } + + + select { + height: 110%; + } + + label { + font-weight: normal; + } + + input[type="text"] { + padding: 0.3rem; + width: 35rem; } } diff --git a/app/controllers/membership_applications_controller.rb b/app/controllers/membership_applications_controller.rb index e84d97cf4..88f34dcaf 100644 --- a/app/controllers/membership_applications_controller.rb +++ b/app/controllers/membership_applications_controller.rb @@ -3,6 +3,7 @@ class MembershipApplicationsController < ApplicationController before_action :get_membership_application, except: [:information, :index, :new, :create] before_action :authorize_membership_application, only: [:update, :show, :edit] + before_action :set_other_waiting_reason, only: [:show, :edit, :update] def new @@ -16,7 +17,7 @@ def index authorize MembershipApplication action_params, @items_count, items_per_page = - process_pagination_params('membership_application') + process_pagination_params('membership_application') @search_params = MembershipApplication.ransack(action_params) @@ -47,15 +48,11 @@ def create helpers.flash_message(:notice, t('.success')) redirect_to root_path else - helpers.flash_message(:alert, t('.error')) - current_user.membership_applications.reload - render :new + create_error(t('.error')) end else - helpers.flash_message(:alert, t('.error')) - current_user.membership_applications.reload - render :new + create_error(t('.error')) end end @@ -67,18 +64,26 @@ def update check_and_mark_if_ready_for_review params['membership_application'] if params.fetch('membership_application', false) - helpers.flash_message(:notice, t('.success')) - render :show + respond_to do |format| + format.js do + head :ok # just let the receiver know everything is OK. no need to render anything + end + + format.html do + helpers.flash_message(:notice, t('.success')) + render :show + end + + end else - helpers.flash_message(:alert, t('.error')) - redirect_to edit_membership_application_path(@membership_application) + update_error(t('.error')) end else - helpers.flash_message(:alert, t('.error')) - redirect_to edit_membership_application_path(@membership_application) + update_error(t('.error')) end + end @@ -95,7 +100,7 @@ def information def destroy - @membership_application = MembershipApplication.find(params[:id]) # we don't need to fetch the categories + @membership_application = MembershipApplication.find(params[:id]) # we don't need to fetch the categories @membership_application.destroy redirect_to membership_applications_url, notice: t('membership_applications.application_deleted') end @@ -132,21 +137,30 @@ def need_info def cancel_need_info + + # empty out the reason for waiting info + @membership_application.waiting_reason = nil + @membership_application.custom_reason_text = nil + simple_state_change(:cancel_waiting_for_applicant!, t('.success'), t('.error')) end + def need_payment simple_state_change(:ask_for_payment!, t('.success'), t('.error')) end + def cancel_need_payment simple_state_change(:cancel_waiting_for_payment!, t('.success'), t('.error')) end + def received_payment simple_state_change(:received_payment!, t('.success'), t('.error')) end + private def membership_application_params params.require(:membership_application).permit(*policy(@membership_application || MembershipApplication).permitted_attributes) @@ -164,6 +178,11 @@ def authorize_membership_application end + def set_other_waiting_reason + @other_waiting_reason_text = t('admin_only.member_app_waiting_reasons.other_custom_reason') + end + + def new_file_uploaded(params) successful = true @@ -205,4 +224,23 @@ def simple_state_change(state_method, success_msg, error_msg) end + def create_error(error_message) + helpers.flash_message(:alert, error_message) + current_user.membership_applications.reload + render :new + end + + + def update_error(error_message) + + if request.xhr? + render json: @membership_application.errors.full_messages, status: :unprocessable_entity if request.xhr? + else + helpers.flash_message(:alert, error_message) + redirect_to edit_membership_application_path(@membership_application) + end + + end + + end diff --git a/app/helpers/membership_applications_helper.rb b/app/helpers/membership_applications_helper.rb index 3a6780929..474f81d53 100644 --- a/app/helpers/membership_applications_helper.rb +++ b/app/helpers/membership_applications_helper.rb @@ -4,8 +4,54 @@ def can_edit_state? policy(@membership_application).permitted_attributes_for_edit.include? :state end + def member_full_name @membership_application ? "#{@membership_application.first_name} #{@membership_application.last_name}" : '..' end + + + # the method to use to get the name for a reason, given the locale + # If no locale is given, the current locale is used. + # If the locale given isn't found or defined, the default name method is used + def reason_name_method(locale = I18n.locale) + reason_method 'name', locale + end + + + # the method to use to get the description for a reason, given the locale + # If no locale is given, the current locale is used. + # If the locale given isn't found or defined, the default name method is used + def reason_desc_method(locale = I18n.locale) + reason_method 'description', locale + end + + + # a collection of arrays with [the name of the reasons for waiting, the reason (object)] + # in the locale + def reasons_for_waiting_names(use_locale = I18n.locale) + reasons_for_waiting_info('name', use_locale) + end + + + # a collection of arrays with [the descriptions of the reasons for waiting, the reason (object)] + # in the locale + def reasons_for_waiting_descs(use_locale = I18n.locale) + reasons_for_waiting_info('description', use_locale) + end + + + private + + def reason_method(method_prefix, locale) + possible_method = "#{method_prefix}_#{locale}".to_sym + (AdminOnly::MemberAppWaitingReason.new.respond_to?(possible_method) ? possible_method : AdminOnly::MemberAppWaitingReason.send("default_#{method_prefix}_method".to_sym)) + end + + + def reasons_for_waiting_info(method_prefix, locale) + method_name = reason_method(method_prefix, locale) + AdminOnly::MemberAppWaitingReason.all.map { |r| [r.id, r.send(method_name)] } + end + end diff --git a/app/models/admin_only/member_app_waiting_reason.rb b/app/models/admin_only/member_app_waiting_reason.rb index 6147f2436..4d41f88a0 100644 --- a/app/models/admin_only/member_app_waiting_reason.rb +++ b/app/models/admin_only/member_app_waiting_reason.rb @@ -12,15 +12,25 @@ def other_reason_placeholder? # This is effectively a CONSTANT that is used in the UI - def self.other_reason_name - I18n.t('admin_only.member_app_waiting_reasons.other_custom_reason') + def self.other_reason_name(locale = I18n.locale) + I18n.t('admin_only.member_app_waiting_reasons.other_custom_reason', locale) end + def self.other_reason_desc(locale = I18n.locale) + I18n.t('admin_only.member_app_waiting_reasons.other_custom_reason_desc', locale) + end + + + def self.default_name_method :name_sv end + def self.default_description_method + :description_sv + end + end end diff --git a/app/models/membership_application.rb b/app/models/membership_application.rb index 9f5d5c2aa..7eddb5c87 100644 --- a/app/models/membership_application.rb +++ b/app/models/membership_application.rb @@ -23,6 +23,11 @@ class MembershipApplication < ApplicationRecord has_and_belongs_to_many :business_categories has_many :uploaded_files + belongs_to :waiting_reason, optional: true, + foreign_key: "member_app_waiting_reasons_id", + class_name: 'AdminOnly::MemberAppWaitingReason' + + validates_presence_of :first_name, :last_name, :company_number, diff --git a/app/policies/membership_application_policy.rb b/app/policies/membership_application_policy.rb index 6fba76c0f..e6492beab 100644 --- a/app/policies/membership_application_policy.rb +++ b/app/policies/membership_application_policy.rb @@ -97,7 +97,7 @@ def user_owner_attributes def all_attributes - owner_attributes + [:membership_number] + owner_attributes + [:membership_number, :waiting_reason, :custom_reason_text, :member_app_waiting_reasons_id] end diff --git a/app/views/membership_applications/_application_status_form.html.haml b/app/views/membership_applications/_application_status_form.html.haml index 6055fa318..de536a134 100644 --- a/app/views/membership_applications/_application_status_form.html.haml +++ b/app/views/membership_applications/_application_status_form.html.haml @@ -1,23 +1,31 @@ +- if @membership_application.waiting_for_applicant? + = render 'reason_waiting' + + + #admin-change-status-buttons %p.header = "#{t('membership_applications.show.change_status')}:" - = button_to t('membership_applications.start_review_btn'), start_review_membership_application_path(@membership_application), {class: 'btn start-review', disabled: !(@membership_application.may_start_review?)} + .row + = button_to t('membership_applications.start_review_btn'), start_review_membership_application_path(@membership_application), {class: 'btn start-review', disabled: !(@membership_application.may_start_review?)} + + = button_to t('membership_applications.accept_btn'), accept_membership_application_path(@membership_application), {class: 'btn accept', disabled: !(@membership_application.may_accept?)} - = button_to t('membership_applications.accept_btn'), accept_membership_application_path(@membership_application), {class: 'btn accept', disabled: !(@membership_application.may_accept?)} + = button_to t('membership_applications.reject_btn'), reject_membership_application_path(@membership_application), {class: 'btn reject', disabled: !(@membership_application.may_reject?)} - = button_to t('membership_applications.reject_btn'), reject_membership_application_path(@membership_application), {class: 'btn reject', disabled: !(@membership_application.may_reject?)} + = button_to t('membership_applications.ask_applicant_for_info_btn'), need_info_membership_application_path(@membership_application), {class: 'btn need-info', disabled: !(@membership_application.may_ask_applicant_for_info?)} - = button_to t('membership_applications.ask_applicant_for_info_btn'), need_info_membership_application_path(@membership_application), {class: 'btn need-info', disabled: !(@membership_application.may_ask_applicant_for_info?)} + .row + = button_to t('membership_applications.cancel_waiting_for_applicant_btn'), cancel_need_info_membership_application_path(@membership_application), {class: 'btn cancel-need-info', disabled: !(@membership_application.may_cancel_waiting_for_applicant?)} - = button_to t('membership_applications.cancel_waiting_for_applicant_btn'), cancel_need_info_membership_application_path(@membership_application), {class: 'btn cancel-need-info', disabled: !(@membership_application.may_cancel_waiting_for_applicant?)} + = button_to t('membership_applications.ask_applicant_for_payment_btn'), need_payment_membership_application_path(@membership_application), {class: 'btn need-payment', disabled: !(@membership_application.may_ask_for_payment?)} - = button_to t('membership_applications.ask_applicant_for_payment_btn'), need_payment_membership_application_path(@membership_application), {class: 'btn need-payment', disabled: !(@membership_application.may_ask_for_payment?)} + = button_to t('membership_applications.cancel_waiting_for_payment_btn'), cancel_need_payment_membership_application_path(@membership_application), {class: 'btn cancel_need-payment', disabled: !(@membership_application.may_cancel_waiting_for_payment?)} - = button_to t('membership_applications.cancel_waiting_for_payment_btn'), cancel_need_payment_membership_application_path(@membership_application), {class: 'btn cancel_need-payment', disabled: !(@membership_application.may_cancel_waiting_for_payment?)} + = button_to t('membership_applications.received_payment_btn'), received_payment_membership_application_path(@membership_application), {class: 'btn received-payment', disabled: !(@membership_application.may_received_payment?)} - = button_to t('membership_applications.received_payment_btn'), received_payment_membership_application_path(@membership_application), {class: 'btn received-payment', disabled: !(@membership_application.may_received_payment?)} %br - if !@membership_application.paid? diff --git a/app/views/membership_applications/_reason_waiting.html.haml b/app/views/membership_applications/_reason_waiting.html.haml new file mode 100644 index 000000000..f429d665b --- /dev/null +++ b/app/views/membership_applications/_reason_waiting.html.haml @@ -0,0 +1,104 @@ +-# Submit the changed reason via javascript when a member_app_waiting_reasons_id is selected from the list or custom_reason_text is changed. + Do not make user press a "save" or "submit" button for these changes. (use AJAX) + + +.row + .container + .reason-waiting-information + = label_tag :member_app_waiting_reasons, t('membership_applications.need_info.reason_title') + - collection = AdminOnly::MemberAppWaitingReason.all.to_a + - collection << AdminOnly::MemberAppWaitingReason.new(id: -1, "name_#{I18n.locale.to_s}": "#{@other_waiting_reason_text}") + - selected = ! @membership_application.custom_reason_text.blank? ? -1 : @membership_application.member_app_waiting_reasons_id + = select_tag(:member_app_waiting_reasons, + options_from_collection_for_select(collection, :id, reason_name_method, + selected), + { include_blank: t('membership_applications.need_info.select_a_reason'), + class: 'reason-waiting-list' }) + +.row + .container + #other-text-field + = label_tag :custom_reason_text, t('membership_applications.need_info.other_reason_label') + = text_field_tag :custom_reason_text, @membership_application.custom_reason_text + + +:javascript + + var reasons_list = $('#member_app_waiting_reasons'); // the select HTML element (list) + var custom_text_info = $('#other-text-field'); + var custom_text_field = $('#custom_reason_text'); + + // this option is added to the list of reasons and is only used so we can show/hide the custom reason text field + var other_reason_text = "#{@other_waiting_reason_text}"; + var other_reason_option = document.createElement("option"); + other_reason_option.text = other_reason_text; + other_reason_option.value = -1; // some value that will not be in the database or use by Rails + + + + function selected_is_customOtherReason() { + return $('#member_app_waiting_reasons option:selected').text() === other_reason_option.text; + } + + + // send the reason selected to the server unless the 'custom/other' was selected + function changed_reason() { + // do not send a request if the reason selected is the "Other/custom... enter text" reason + // wait until the text field with the custom reason is entered to send that data + + if (!selected_is_customOtherReason()) { + custom_text_field.val(''); + send_updated_reason( #{@membership_application.id}, $('#member_app_waiting_reasons').find('option:selected').val() , null ); + } + hideOrShowCustomReasonElements(); + + } + + + // Set the waiting_reason to nil because we instead have the info in the custom text field + function changed_custom_text() { + send_updated_reason( #{@membership_application.id}, null , custom_text_field.val() ); + custom_text_info.show(); + } + + + var hideOrShowCustomReasonElements = function() { + + if ( custom_text_field.val() || selected_is_customOtherReason()) { + custom_text_info.show(); + reasons_list.value = other_reason_option.value; // make sure this option is selected; important when first displaying the view + } + else { + // clear out any custom reason if the selected reason is not the custom reason + $('#custom_reason_text').val(""); + custom_text_info.hide(); + } + + }; + + + // Send information to the server + // no need to do anything if it was successful + var send_updated_reason = function(app_id, reason_id, custom_text) { + + $.ajax({ + url: "#{membership_application_path}", + type: "PUT", + data: { membership_application: { member_app_waiting_reasons_id: reason_id, + custom_reason_text: custom_text }, + id: app_id } + }).fail(function(evt, xhr, status, err) { + alert( "#{I18n.t('membership_applications.update.error')}: " + 'Status: ' + evt.statusText ); + }); + + }; + + + var initialize = (function() { + // reasons_list.options.add(other_reason_option); // add the other_reason to the list + hideOrShowCustomReasonElements(); + $('#member_app_waiting_reasons').on('change', changed_reason); + $('#custom_reason_text').on('change', changed_custom_text) + }); + + $(document).ready(initialize); diff --git a/config/locales/en.yml b/config/locales/en.yml index 061a4c84d..9904ef223 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -313,14 +313,13 @@ en: need_info: reason_title: Reason - other_reason_label: "other reason:" - submit_button_label: "Save the reason" - + select_a_reason: "Select a reason..." success: 'The application has been marked as needing info. (Send email to the applicant.)' error: "There was an error when trying to set the application to 'needing information.'" + cancel_need_info: success: The application has been changed to no longer needing information from the applicant. error: "There was an error when trying to set the application to 'no longer needing information from the applicant.'" @@ -677,6 +676,7 @@ en: list_all_member_app_waiting_reasons: "List all Waiting for Information Reasons" other_custom_reason: "Other (enter the reason)" + other_custom_reason_desc: "" delete_confirm: "Are you sure you want to delete the reason: %{name_sv} (%{name_en})?" diff --git a/config/locales/sv.yml b/config/locales/sv.yml index fd5f74357..ebf3f3a3a 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -316,13 +316,13 @@ sv: need_info: reason_title: Anledning - other_reason_label: "Annan orsak" - submit_button_label: "Spara orsaken" + select_a_reason: "Välj en anledning..." success: 'Ansökan har markerats som: "Behöver kompletteras". (Skicka e-post till den sökande.)' error: 'Det uppstod ett fel när systemet försökte sätta status: "Behöver kompletteras".' + cancel_need_info: success: Ansökan har ändrats och behöver ej längre kompletteras av sökande. error: 'Det uppstod ett fel när systemet försökte ångra status: "Behöver kompletteras".' @@ -680,6 +680,7 @@ sv: list_all_member_app_waiting_reasons: "Lista alla orsaker" other_custom_reason: "Annat (skriv in orsaken)" + other_custom_reason_desc: "" delete_confirm: "Är du säker på att du vill radera orsaken: %{name_sv} (%{name_en})?" diff --git a/db/migrate/20170615091313_add_reason_waiting_to_membership_application.rb b/db/migrate/20170615091313_add_reason_waiting_to_membership_application.rb new file mode 100644 index 000000000..306d8a860 --- /dev/null +++ b/db/migrate/20170615091313_add_reason_waiting_to_membership_application.rb @@ -0,0 +1,8 @@ +class AddReasonWaitingToMembershipApplication < ActiveRecord::Migration[5.0] + + def change + add_reference :membership_applications, :member_app_waiting_reasons, foreign_key: true + add_column :membership_applications, :custom_reason_text, :string + end + +end diff --git a/db/schema.rb b/db/schema.rb index d236ead5d..8012a5f44 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,43 +10,43 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170525201944) do +ActiveRecord::Schema.define(version: 20170615091313) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" - create_table "addresses", force: :cascade do |t| - t.string "street_address" - t.string "post_code" - t.string "city" - t.string "country", default: "Sverige", null: false - t.integer "region_id" - t.string "addressable_type" - t.integer "addressable_id" - t.integer "kommun_id" - t.float "latitude" - t.float "longitude" + create_table "addresses", id: :bigserial, force: :cascade do |t| + t.string "street_address" + t.string "post_code" + t.string "city" + t.string "country", default: "Sverige", null: false + t.bigint "region_id" + t.string "addressable_type" + t.bigint "addressable_id" + t.bigint "kommun_id" + t.float "latitude" + t.float "longitude" t.index ["addressable_type", "addressable_id"], name: "index_addresses_on_addressable_type_and_addressable_id", using: :btree t.index ["kommun_id"], name: "index_addresses_on_kommun_id", using: :btree t.index ["latitude", "longitude"], name: "index_addresses_on_latitude_and_longitude", using: :btree t.index ["region_id"], name: "index_addresses_on_region_id", using: :btree end - create_table "business_categories", force: :cascade do |t| + create_table "business_categories", id: :bigserial, force: :cascade do |t| t.string "name" t.string "description" t.datetime "created_at", null: false t.datetime "updated_at", null: false end - create_table "business_categories_membership_applications", force: :cascade do |t| - t.integer "membership_application_id" - t.integer "business_category_id" + create_table "business_categories_membership_applications", id: :bigserial, force: :cascade do |t| + t.bigint "membership_application_id" + t.bigint "business_category_id" t.index ["business_category_id"], name: "index_on_categories", using: :btree t.index ["membership_application_id"], name: "index_on_applications", using: :btree end - create_table "ckeditor_assets", force: :cascade do |t| + create_table "ckeditor_assets", id: :bigserial, force: :cascade do |t| t.string "data_file_name", null: false t.string "data_content_type" t.integer "data_file_size" @@ -56,12 +56,12 @@ t.integer "height" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.integer "company_id" + t.bigint "company_id" t.index ["company_id"], name: "index_ckeditor_assets_on_company_id", using: :btree t.index ["type"], name: "index_ckeditor_assets_on_type", using: :btree end - create_table "companies", force: :cascade do |t| + create_table "companies", id: :bigserial, force: :cascade do |t| t.string "name" t.string "company_number" t.string "phone_number" @@ -74,13 +74,13 @@ t.index ["company_number"], name: "index_companies_on_company_number", unique: true, using: :btree end - create_table "kommuns", force: :cascade do |t| + create_table "kommuns", id: :bigserial, force: :cascade do |t| t.string "name" t.datetime "created_at", null: false t.datetime "updated_at", null: false end - create_table "member_app_waiting_reasons", force: :cascade, comment: "reasons why SHF is waiting for more info from applicant. Add more columns when more locales needed." do |t| + create_table "member_app_waiting_reasons", id: :bigserial, force: :cascade, comment: "reasons why SHF is waiting for more info from applicant. Add more columns when more locales needed." do |t| t.string "name_sv", comment: "name of the reason in svenska/Swedish" t.string "description_sv", comment: "description for the reason in svenska/Swedish" t.string "name_en", comment: "name of the reason in engelsk/English" @@ -90,38 +90,41 @@ t.datetime "updated_at", null: false end - create_table "member_pages", force: :cascade do |t| + create_table "member_pages", id: :bigserial, force: :cascade do |t| t.string "filename", null: false t.string "title" t.datetime "created_at", null: false t.datetime "updated_at", null: false end - create_table "membership_applications", force: :cascade do |t| + create_table "membership_applications", id: :bigserial, force: :cascade do |t| t.string "company_number" t.string "phone_number" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.integer "user_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "user_id" t.string "first_name" t.string "last_name" t.string "contact_email" - t.integer "company_id" + t.bigint "company_id" t.string "membership_number" - t.string "state", default: "new" + t.string "state", default: "new" + t.integer "member_app_waiting_reasons_id" + t.string "custom_reason_text" t.index ["company_id"], name: "index_membership_applications_on_company_id", using: :btree + t.index ["member_app_waiting_reasons_id"], name: "index_membership_applications_on_member_app_waiting_reasons_id", using: :btree t.index ["user_id"], name: "index_membership_applications_on_user_id", using: :btree end - create_table "regions", force: :cascade do |t| + create_table "regions", id: :bigserial, force: :cascade do |t| t.string "name" t.string "code" t.datetime "created_at", null: false t.datetime "updated_at", null: false end - create_table "shf_documents", force: :cascade do |t| - t.integer "uploader_id", null: false + create_table "shf_documents", id: :bigserial, force: :cascade do |t| + t.bigint "uploader_id", null: false t.string "title" t.text "description" t.datetime "created_at", null: false @@ -133,18 +136,18 @@ t.index ["uploader_id"], name: "index_shf_documents_on_uploader_id", using: :btree end - create_table "uploaded_files", force: :cascade do |t| + create_table "uploaded_files", id: :bigserial, force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "actual_file_file_name" t.string "actual_file_content_type" t.integer "actual_file_file_size" t.datetime "actual_file_updated_at" - t.integer "membership_application_id" + t.bigint "membership_application_id" t.index ["membership_application_id"], name: "index_uploaded_files_on_membership_application_id", using: :btree end - create_table "users", force: :cascade do |t| + create_table "users", id: :bigserial, force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" @@ -165,6 +168,7 @@ add_foreign_key "addresses", "kommuns" add_foreign_key "addresses", "regions" add_foreign_key "ckeditor_assets", "companies" + add_foreign_key "membership_applications", "member_app_waiting_reasons", column: "member_app_waiting_reasons_id" add_foreign_key "membership_applications", "users" add_foreign_key "shf_documents", "users", column: "uploader_id" add_foreign_key "uploaded_files", "membership_applications" diff --git a/db/seeds.rb b/db/seeds.rb index 0c3c058cc..158420e98 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -39,6 +39,8 @@ Rake::Task['shf:load_kommuns'].invoke end + + puts 'Creating business categories' business_categories = %w(Träning Psykologi Rehab Butik Trim Friskvård Dagis Pensionat Skola) business_categories.each { |b_category| BusinessCategory.find_or_create_by(name: b_category) } diff --git a/features/membership-application-status/admin-custom-reason-waiting.feature b/features/membership-application-status/admin-custom-reason-waiting.feature new file mode 100644 index 000000000..8293b6f73 --- /dev/null +++ b/features/membership-application-status/admin-custom-reason-waiting.feature @@ -0,0 +1,68 @@ +Feature: "Other/Custom" waiting reason comes from locale file and Admin cannot edit or delete it + + As an Admin + Because the "Other/custom" reason needs to always exist so that the UI (e.g. view) can use it to display a custom text field if it's chosen, + And because the text displayed must be always translated, + The text displayed for "Other/custom" reason must always come from a locale file + + PT: https://www.pivotaltracker.com/story/show/143810729 + PT: https://www.pivotaltracker.com/epic/show/3619113 (epic) + + + This requires a different background set-up than the "admin-manage-reasons..." feature. + This requires that the system be seeded *before* any other information is entered (via other background statements). + + + + Background: + + # it is important that this statement is first so that tables are empty, so that things will be seeded + # Given the system is seeded with initial data + + + Given the following users exists + | email | admin | + | anna_waiting_for_info@nosnarkybarky.se | | + | emma@happymutts.se | | + | admin@shf.se | true | + + Given the following business categories exist + | name | description | + | rehab | physcial rehabitation | + + Given the following regions exist: + | name | + | Stockholm | + + Given the following companies exist: + | name | company_number | email | region | + | No More Snarky Barky | 5560360793 | snarky@snarkybarky.se | Stockholm | + | HappyMutts | 2120000142 | woof@happymutts.com | Stockholm | + + + And the following applications exist: + | first_name | user_email | company_number | category_name | state | + | AnnaWaiting | anna_waiting_for_info@nosnarkybarky.se | 5560360793 | rehab | waiting_for_applicant | + | EmmaAccepted | emma@happymutts.se | 2120000142 | rehab | accepted | + + + And the following member app waiting reasons exist: + | name_sv | name_en | description_sv | description_en | is_custom | + | namn 1 | name 1 | beskrivning 1 | description 1 | false | + | namn 2 | name 2 | beskrivning 2 | description 2 | false | + + + And I am logged in as "admin@shf.se" + + + @admin @javascript + Scenario: The "other/custom" reason is listed as a reason for the 'waiting for...' status + Given I am on "AnnaWaiting" application page + Then "member_app_waiting_reasons" should have t("admin_only.member_app_waiting_reasons.other_custom_reason") as an option + + + @admin + Scenario: The "other/custom" reason doesn't appear in the list of all reasons + Given I am on the "all waiting for info reasons" page + Then I should see 2 reasons listed + And I should not see t("admin_only.member_app_waiting_reasons.other_custom_reason") diff --git a/features/membership-application-status/admin-manage-reasons-waiting-for-info.feature b/features/membership-application-status/admin-manage-reasons-waiting-for-info.feature index d571c4aaa..89a1859e9 100644 --- a/features/membership-application-status/admin-manage-reasons-waiting-for-info.feature +++ b/features/membership-application-status/admin-manage-reasons-waiting-for-info.feature @@ -11,6 +11,7 @@ Feature: Admin manages the list of reasons why SHF is waiting for info from an a Background: + Given the following users exists | email | admin | | anna_waiting_for_info@nosnarkybarky.se | | @@ -244,3 +245,4 @@ Feature: Admin manages the list of reasons why SHF is waiting for info from an a Then I should see t("admin_only.member_app_waiting_reasons.update.success") And I should see t("yes") And I should not see t("no") + diff --git a/features/membership-application-status/admin-sets-custom-waiting-reason.feature b/features/membership-application-status/admin-sets-custom-waiting-reason.feature new file mode 100644 index 000000000..96c3d472b --- /dev/null +++ b/features/membership-application-status/admin-sets-custom-waiting-reason.feature @@ -0,0 +1,104 @@ +Feature: Admin sets or enters the reason they are waiting for info from a user + As an admin + so that SHF can talk with the user specifically about why they are waiting and know how long they might need to wait, + I need to set the reason why SHF is waiting + and if the reason is not available from a list, + I need to be able to type in text that describes the situation + + PT: https://www.pivotaltracker.com/story/show/143810729 + + Background: + Given the following users exists + | email | admin | + | anna_waiting_for_info@nosnarkybarky.se | | + | admin@shf.com | true | + + Given the following business categories exist + | name | description | + | rehab | physcial rehabitation | + + Given the following regions exist: + | name | + | Stockholm | + + Given the following companies exist: + | name | company_number | email | region | + | No More Snarky Barky | 5560360793 | snarky@snarkybarky.se | Stockholm | + + + And the following applications exist: + | first_name | user_email | company_number | category_name | state | + | AnnaWaiting | anna_waiting_for_info@nosnarkybarky.se | 5560360793 | rehab | waiting_for_applicant | + + + And the following member app waiting reasons exist + | name_sv | description_sv | name_en | description_en | is_custom | + | need doc | need doc | need documentation | need more documents proving qualifications | false | + | waiting for payment | still waiting for payment | waiting for payment | still waiting for payment | false | + + + + + And I am logged in as "admin@shf.com" + + + @javascript + Scenario: Admin selects 'need more documentation' as the reason SHF is waiting_for_applicant + Given I am on "AnnaWaiting" application page + When I set "member_app_waiting_reasons" to "need doc" + And I am on the list applications page + And I am on "AnnaWaiting" application page + Then "member_app_waiting_reasons" should have "need doc" selected + + @javascript + Scenario: Admin selects 'waiting for payment' as the reason SHF is waiting_for_applicant + Given I am on "AnnaWaiting" application page + When I set "member_app_waiting_reasons" to "waiting for payment" + And I am on the list applications page + And I am on "AnnaWaiting" application page + And "member_app_waiting_reasons" should have "waiting for payment" selected + + + @javascript + Scenario: Admin selects 'other' and enters text as the reason SHF is waiting_for_applicant + Given I am on "AnnaWaiting" application page + When I set "member_app_waiting_reasons" to t("admin_only.member_app_waiting_reasons.other_custom_reason") + And I fill in "custom_reason_text" with "This is my reason" + And I press enter in "custom_reason_text" + And I am on the list applications page + And I am on "AnnaWaiting" application page + #And item t("admin_only.member_app_waiting_reasons.other_custom_reason") should be visible + And I should see t("membership_applications.need_info.other_reason_label") + And the "custom_reason_text" field should be set to "This is my reason" + And "member_app_waiting_reasons" should have t("admin_only.member_app_waiting_reasons.other_custom_reason") selected + + + @javascript + Scenario: Admin selects 'other' and fills in custom text but then changes reason to something else + Given I am on "AnnaWaiting" application page + When I set "member_app_waiting_reasons" to t("admin_only.member_app_waiting_reasons.other_custom_reason") + And I fill in "custom_reason_text" with "This is my reason" + And I press enter in "custom_reason_text" + And I set "member_app_waiting_reasons" to "waiting for payment" + And I am on the list applications page + And I am on "AnnaWaiting" application page + And "member_app_waiting_reasons" should have "waiting for payment" selected + + + @javascript + Scenario: When selected reason is not 'custom other,' the custom text is saved as blank (empty string) + Given I am on "AnnaWaiting" application page + When I set "member_app_waiting_reasons" to t("admin_only.member_app_waiting_reasons.other_custom_reason") + And I fill in "custom_reason_text" with "This is my reason" + And I press enter in "custom_reason_text" + And I set "member_app_waiting_reasons" to "need doc" + # change back so the custom reason field shows. it should be blank + And I set "member_app_waiting_reasons" to t("admin_only.member_app_waiting_reasons.other_custom_reason") + Then I should not see "This is my reason" + + + @javascript + Scenario: owner cannot see the fields for changing the reason + Given I am logged in as "anna_waiting_for_info@nosnarkybarky.se" + And I am on the application page for "AnnaWaiting" + Then I should not see t("membership_applications.need_info.reason_title") diff --git a/features/step_definitions/assertion_steps.rb b/features/step_definitions/assertion_steps.rb index a4e574360..a123eaeb4 100644 --- a/features/step_definitions/assertion_steps.rb +++ b/features/step_definitions/assertion_steps.rb @@ -283,6 +283,19 @@ def current_path_without_locale(path) end +# Have to be sure to wait for any javascript to execute since it may hide or show an item +Then(/^item t\("([^"]*)"\) should( not)? be visible$/) do | item, negate| + + if negate + expect(page).to have_field(i18n_content(item), visible: false) + + else + expect( find_field(i18n_content(item)).visible? ).to be_truthy + end + +end + + # Tests that an input or button with the given label is disabled. Then /^the "([^\"]*)" (field|button|item) should( not)? be disabled$/ do |label, kind, negate| @@ -420,8 +433,7 @@ def current_path_without_locale(path) # Checks that a certain option is selected for a text field (from https://github.com/makandra/spreewald) -Then /^"([^"]*)" should( not)? have (t\()?"([^"]*)"(?:\))? selected$/ do |select_list, - negate, translate_start, expected_string| +Then /^"([^"]*)" should( not)? have t\("([^"]*)"\) selected$/ do |select_list, negate, expected_string | field = find_field(select_list) @@ -438,15 +450,52 @@ def current_path_without_locale(path) field.value end - if translate_start - expect(field_value).send( (negate ? :not_to : :to), eq(i18n_content(expected_string)) ) - else - expect(field_value).send( (negate ? :not_to : :to), eq(expected_string) ) - end + + expect(field_value).send( (negate ? :not_to : :to), eq(i18n_content(expected_string)) ) end + +# Checks that a certain option is selected for a text field (from https://github.com/makandra/spreewald) +Then /^"([^"]*)" should( not)? have "([^"]*)" selected$/ do | select_list, negate, expected_string | + + field = find_field(select_list) + + field_value = case field.tag_name + when 'select' + options = field.all('option') + selected_option = options.detect(&:selected?) || options.first + if selected_option && selected_option.text.present? + selected_option.text.strip + else + '' + end + else + field.value + end + + expect(field_value).send( (negate ? :not_to : :to), eq(expected_string) ) + +end + + + +# Checks that a certain option does or does not exist in a select list +Then /^"([^"]*)" should( not)? have t\("([^"]*)"\) as an option/ do | select_list, negate, expected_string | + + field = find_field(select_list) + + select_options= case field.tag_name + when 'select' + options = field.all('option') + end + expect(select_options.map(&:text)).send( (negate ? :not_to : :to), include( i18n_content expected_string) ) + +end + + + Then(/^I should be on the all member app waiting reasons page$/) do expect(current_path_without_locale(current_path)).to eq admin_only_member_app_waiting_reasons_path end diff --git a/features/step_definitions/basic_steps.rb b/features/step_definitions/basic_steps.rb index 50ac4eed4..30ddf127f 100644 --- a/features/step_definitions/basic_steps.rb +++ b/features/step_definitions/basic_steps.rb @@ -28,6 +28,10 @@ fill_in field, with: value end +And(/^I press enter in "([^"]*)"$/) do |field| + find_field(field).send_keys :enter +end + And(/^I fill in t\("([^"]*)"\) with "([^"]*)"$/) do |field, value| fill_in i18n_content(field), with: value end @@ -109,6 +113,11 @@ find(:select, lst).find(:option, item).select_option end + +When(/^(?:I|they) select "([^"]*)" in select list "([^"]*)"$/) do |item, lst| + find(:select, lst).find(:option, item).select_option +end + Then(/^I wait(?: for)? (\d+) second(?:s)?$/) do |seconds| sleep seconds.to_i.seconds end diff --git a/features/step_definitions/member_app_waiting_reason_steps.rb b/features/step_definitions/member_app_waiting_reason_steps.rb index 957e1a248..03d384aaa 100644 --- a/features/step_definitions/member_app_waiting_reason_steps.rb +++ b/features/step_definitions/member_app_waiting_reason_steps.rb @@ -17,8 +17,20 @@ visit path_with_locale(edit_admin_only_member_app_waiting_reason_path reason) end +Given(/^I am on the edit member app waiting reason with name_sv t\("([^"]*)"\)$/) do | locale_reason_name_sv | + reason = AdminOnly::MemberAppWaitingReason.find_by_name_sv( i18n_content(locale_reason_name_sv) ) + visit path_with_locale(edit_admin_only_member_app_waiting_reason_path reason) +end + Given(/^I am on the member app waiting reason page for name_sv "([^"]*)"$/) do | reason_name_sv | reason = AdminOnly::MemberAppWaitingReason.find_by_name_sv(reason_name_sv) visit path_with_locale(admin_only_member_app_waiting_reason_path reason) end + + +Given(/^I am on the member app waiting reason with name_sv t\("([^"]*)"\)$/) do | locale_reason_name_sv | + reason = AdminOnly::MemberAppWaitingReason.find_by_name_sv( i18n_content(locale_reason_name_sv) ) + visit path_with_locale(admin_only_member_app_waiting_reason_path reason) +end + diff --git a/features/support/hooks.rb b/features/support/hooks.rb index 40bbd5012..63499cac9 100644 --- a/features/support/hooks.rb +++ b/features/support/hooks.rb @@ -2,6 +2,7 @@ Capybara.current_driver = :poltergeist end + After('@javascript, @poltergeist') do Capybara.reset_sessions! Capybara.current_driver = :rack_test diff --git a/log/.keep b/log/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/spec/helpers/membership_applications_helper_spec.rb b/spec/helpers/membership_applications_helper_spec.rb index 3a77c5d16..2807beb56 100644 --- a/spec/helpers/membership_applications_helper_spec.rb +++ b/spec/helpers/membership_applications_helper_spec.rb @@ -2,6 +2,12 @@ RSpec.describe MembershipApplicationsHelper, type: :helper do + before(:all) do + # ensure MembershipAppWaitingReason.all is empty + AdminOnly::MemberAppWaitingReason.delete_all + end + + describe '#member_full_name' do it 'appends first and last with a space inbetween' do assign(:membership_application, create(:membership_application, first_name: 'Kitty', last_name: 'Kat', user: create(:user))) @@ -10,4 +16,133 @@ end + describe 'returns a list of reasons_for_waiting for the right locale' do + + before(:all) do + FactoryGirl.create(:member_app_waiting_reason, name_sv: 'name_sv1', name_en: 'name_en1', description_sv: 'desc_sv1', description_en: 'desc_en1') + FactoryGirl.create(:member_app_waiting_reason, name_sv: 'name_sv2', name_en: 'name_en2', description_sv: 'desc_sv2', description_en: 'desc_en2') + FactoryGirl.create(:member_app_waiting_reason, name_sv: 'name_sv3', name_en: 'name_en3', description_sv: 'desc_sv3', description_en: 'desc_en3') + end + + let(:reason1) { AdminOnly::MemberAppWaitingReason.find_by_name_sv('name_sv1') } + let(:reason2) { AdminOnly::MemberAppWaitingReason.find_by_name_sv('name_sv2') } + let(:reason3) { AdminOnly::MemberAppWaitingReason.find_by_name_sv('name_sv3') } + + + describe '#reasons_for_waiting_names' do + + let(:expected_sv_names) { [ [reason1.id, 'name_sv1'], [reason2.id, 'name_sv2'], [reason3.id, 'name_sv3'] ] } + let(:expected_en_names) { [ [reason1.id, 'name_en1'], [reason2.id, 'name_en2'], [reason3.id, 'name_en3'] ] } + + it 'calls name_sv if locale == sv' do + name_list = helper.reasons_for_waiting_names(:sv) + expect(name_list).to match expected_sv_names + end + + it 'calls name_en if locale == en' do + name_list = helper.reasons_for_waiting_names(:en) + expect(name_list).to match expected_en_names + end + + it 'calls default_name_method if there is no name method for that locale' do + name_list = helper.reasons_for_waiting_names(:blorf) + expect(name_list).to match expected_sv_names + end + + it 'calls default_name_method if no locale is specified' do + name_list = helper.reasons_for_waiting_names + expect(name_list).to match expected_sv_names + end + + + it 'returns a list of [id, text]' do + name_list = helper.reasons_for_waiting_names + first_item = name_list.first + expect(first_item.is_a?(Enumerable)).to be_truthy + expect(first_item.first.is_a?(Numeric)).to be_truthy + expect(first_item.last.is_a?(String)).to be_truthy + end + + end + + + describe '#reason_name_method' do + + it 'is :name_sv if locale == :sv' do + expect(helper.reason_name_method(:sv)).to eq :name_sv + end + + it 'is :name_en if locale == :en' do + expect(helper.reason_name_method(:en)).to eq :name_en + end + + it 'is default name method if there is no desc method for that locale' do + expect(helper.reason_name_method(:blorf)).to eq AdminOnly::MemberAppWaitingReason.default_name_method + end + + it 'is default name method if no locale is specified' do + expect(helper.reason_name_method).to eq AdminOnly::MemberAppWaitingReason.default_name_method + end + + end + + + describe '#reason_description_method' do + + it 'is :desc_sv if locale == :sv' do + expect(helper.reason_desc_method(:sv)).to eq :description_sv + end + + it 'is :desc_en if locale == :en' do + expect(helper.reason_desc_method(:en)).to eq :description_en + end + + it 'is default desc method if there is no desc method for that locale' do + expect(helper.reason_desc_method(:blorf)).to eq AdminOnly::MemberAppWaitingReason.default_description_method + end + + it 'is default desc method if no locale is specified' do + expect(helper.reason_desc_method).to eq AdminOnly::MemberAppWaitingReason.default_description_method + end + + end + + + describe '#reasons_for_waiting_descs' do + + let(:expected_sv_descs) { [ [reason1.id, 'desc_sv1'], [reason2.id, 'desc_sv2'], [reason3.id, 'desc_sv3'] ] } + let(:expected_en_descs) { [ [reason1.id, 'desc_en1'], [reason2.id, 'desc_en2'], [reason3.id, 'desc_en3'] ] } + + it 'calls description_sv if locale == sv' do + desc_list = helper.reasons_for_waiting_descs(:sv) + expect(desc_list).to match expected_sv_descs + end + + it 'calls description_en if locale == en' do + desc_list = helper.reasons_for_waiting_descs(:en) + expect(desc_list).to match expected_en_descs + end + + it 'calls default_description_method if there is no desc method for that locale' do + desc_list = helper.reasons_for_waiting_descs(:blorf) + expect(desc_list).to match expected_sv_descs + end + + it 'calls default_description_method if no locale is specified' do + desc_list = helper.reasons_for_waiting_descs + expect(desc_list).to match expected_sv_descs + end + + + it 'returns a list of [id, text]' do + desc_list = helper.reasons_for_waiting_descs + first_item = desc_list.first + expect(first_item.is_a?(Enumerable)).to be_truthy + expect(first_item.first.is_a?(Numeric)).to be_truthy + expect(first_item.last.is_a?(String)).to be_truthy + end + + end + + end end diff --git a/spec/models/member_app_waiting_reason_spec.rb b/spec/models/member_app_waiting_reason_spec.rb index ff20ff828..1d0813307 100644 --- a/spec/models/member_app_waiting_reason_spec.rb +++ b/spec/models/member_app_waiting_reason_spec.rb @@ -24,30 +24,5 @@ end - describe '#other_reason_placeholder?' do - - let(:reason) { subject } - - it 'false if name_sv is empty' do - reason.name_sv = '' - expect(reason.other_reason_placeholder?).to be_falsey - end - - it 'false if name_sv is nil' do - reason.name_sv = nil - expect(reason.other_reason_placeholder?).to be_falsey - end - - it "false if name_sv is not the 'other reason' name" do - reason.name_sv = "blorf" - expect(reason.other_reason_placeholder?).to be_falsey - end - - it "true if name_sv is equal to the 'other reason' name" do - reason.name_sv = reason.class.other_reason_name - expect(reason.other_reason_placeholder?).to be_truthy - end - - end end diff --git a/spec/models/membership_application_spec.rb b/spec/models/membership_application_spec.rb index f563ff077..593f1bb79 100644 --- a/spec/models/membership_application_spec.rb +++ b/spec/models/membership_application_spec.rb @@ -48,6 +48,7 @@ it {is_expected.to have_db_column :contact_email} it {is_expected.to have_db_column :membership_number} it {is_expected.to have_db_column :state} + it {is_expected.to have_db_column :custom_reason_text} end describe 'Validations' do @@ -81,6 +82,7 @@ it {is_expected.to belong_to :user} it {is_expected.to have_and_belong_to_many :business_categories} it {is_expected.to belong_to :company} + it {is_expected.to belong_to :waiting_reason} end describe "Uploaded Files" do