Commit 57bbfe52 authored by Kaspar Vollenweider's avatar Kaspar Vollenweider 👻
Browse files

Merge branch 'features/client_beenden' into 'develop'

Features/client beenden

See merge request !467
parents 3ab62797 c0a5c71d
Pipeline #13964 passed with stage
in 22 minutes and 36 seconds
...@@ -3,15 +3,14 @@ class ClientsController < ApplicationController ...@@ -3,15 +3,14 @@ class ClientsController < ApplicationController
include NestedAttributes include NestedAttributes
include ContactAttributes include ContactAttributes
before_action :set_client, only: [:show, :edit, :update, :destroy] before_action :set_client, only: [:show, :edit, :update, :set_terminated]
before_action :set_social_worker_collection before_action :set_social_worker_collection, only: [:new, :edit]
def index def index
authorize Client authorize Client
@q = policy_scope(Client).ransack(params[:q]) @q = policy_scope(Client).ransack(default_filter)
@q.sorts = ['created_at desc'] if @q.sorts.empty? @q.sorts = ['created_at desc'] if @q.sorts.empty?
@clients = @q.result @clients = @q.result
activity_filter
respond_to do |format| respond_to do |format|
format.xlsx format.xlsx
format.html do format.html do
...@@ -47,34 +46,65 @@ class ClientsController < ApplicationController ...@@ -47,34 +46,65 @@ class ClientsController < ApplicationController
@client.involved_authority ||= current_user if current_user.social_worker? @client.involved_authority ||= current_user if current_user.social_worker?
authorize @client authorize @client
if @client.save if @client.save
if @client.user.social_worker? && ClientNotification.active.any? redirect_to @client, create_success_notice
redirect_to @client, notice: ClientNotification.active.pluck(:body).to_sentence
else
redirect_to @client, make_notice
end
else else
render :new render :new
end end
end end
def update def update
if @client.update(client_params) @client.assign_attributes(client_params)
redirect_to @client, make_notice @client.resigned_by = current_user if @client.will_save_change_to_acceptance?(to: :resigned)
if @client.save
redirect_to @client, create_update_redirect_notice
else else
@custom_notice = resigned_fail_notice if @client.errors.messages[:acceptance].present?
render :edit render :edit
end end
end end
def destroy def set_terminated
@client.destroy if @client.update(acceptance: :resigned, resigned_by: current_user)
redirect_to clients_url, make_notice redirect_back(fallback_location: client_path(@client),
notice: 'Klient/in wurde erfolgreich beendet.')
else
redirect_back(fallback_location: client_path(@client), notice: resigned_fail_notice)
end
end end
private private
def activity_filter def create_success_notice
return unless params[:q] && params[:q][:active_eq] if @client.user.social_worker? && ClientNotification.active.any?
@clients = params[:q][:active_eq] == 'true' ? @clients.active : @clients.inactive { notice: ClientNotification.active.pluck(:body).to_sentence }
else
make_notice
end
end
def create_update_redirect_notice
if @client.saved_change_to_acceptance?(to: :resigned)
{ notice: 'Klient/in wurde erfolgreich beendet.' }
else
make_notice
end
end
def resigned_fail_notice
{
message: 'Beenden fehlgeschlagen.', model_message: @client.errors.messages[:acceptance].first,
action_link: { text: 'Begleitung bearbeiten', path: edit_assignment_path(@client.assignment) }
}
end
def default_filter
return { acceptance_not_eq: 2 } if params[:q].blank?
filters = params.to_unsafe_hash[:q]
if filters[:acceptance_eq].present? || filters[:contact_full_name_cont].present?
filters.except(:acceptance_not_eq)
else
filters.merge(acceptance_not_eq: 2)
end
end end
def set_client def set_client
......
module NotificationHelper
def notification_warning_bubble(text)
tag.div(class: 'alert alert-warning alert-dismissible', role: 'alert') do
concat notification_close_button
concat text
end
end
def notification_close_button(text = nil)
tag.button(
class: 'close', aria: { label: 'Schliessen' }, data: { dismiss: 'alert' }, type: 'button'
) do
if text.present?
concat text
else
tag.span('&times;', aria: { hidden: 'true' })
end
end
end
end
...@@ -5,6 +5,8 @@ class Client < ApplicationRecord ...@@ -5,6 +5,8 @@ class Client < ApplicationRecord
include ZuerichScopes include ZuerichScopes
include ImportRelation include ImportRelation
before_update :record_acceptance_change, if: :going_to_change_to_resigned?
enum acceptance: { accepted: 0, rejected: 1, resigned: 2 } enum acceptance: { accepted: 0, rejected: 1, resigned: 2 }
enum cost_unit: { city: 0, municipality: 1, canton: 2 } enum cost_unit: { city: 0, municipality: 1, canton: 2 }
...@@ -15,6 +17,7 @@ class Client < ApplicationRecord ...@@ -15,6 +17,7 @@ class Client < ApplicationRecord
belongs_to :user, -> { with_deleted } belongs_to :user, -> { with_deleted }
belongs_to :involved_authority, class_name: 'User', optional: true belongs_to :involved_authority, class_name: 'User', optional: true
belongs_to :resigned_by, class_name: 'User', optional: true
has_one :assignment, dependent: :destroy has_one :assignment, dependent: :destroy
has_many :assignment_logs has_many :assignment_logs
...@@ -33,14 +36,18 @@ class Client < ApplicationRecord ...@@ -33,14 +36,18 @@ class Client < ApplicationRecord
validates :salutation, presence: true validates :salutation, presence: true
scope :need_accompanying, lambda { validates :acceptance, exclusion: {
inactive.order(created_at: :asc) in: ['resigned'],
} message: 'Klient/in kann nicht beendet werden, solange noch ein laufendes Tandem existiert.'
}, unless: :terminatable?
scope :with_assignment, (-> { joins(:assignment) }) scope :with_assignment, (-> { joins(:assignment) })
scope :with_active_assignment, (-> { with_assignment.merge(Assignment.active) }) scope :with_active_assignment, (-> { with_assignment.merge(Assignment.active) })
scope :need_accompanying, lambda {
inactive.order(created_at: :asc)
}
scope :with_active_assignment_between, lambda { |start_date, end_date| scope :with_active_assignment_between, lambda { |start_date, end_date|
with_assignment.merge(Assignment.active_between(start_date, end_date)) with_assignment.merge(Assignment.active_between(start_date, end_date))
} }
...@@ -62,6 +69,18 @@ class Client < ApplicationRecord ...@@ -62,6 +69,18 @@ class Client < ApplicationRecord
accepted.without_assignment.or(with_inactive_assignment) accepted.without_assignment.or(with_inactive_assignment)
} }
def terminatable?
assignment.blank? || assignment.no_period? || assignment.ended?
end
def self.acceptences_restricted
acceptances.except('resigned')
end
def self.acceptance_collection_restricted
acceptences_restricted.keys.map(&:to_sym)
end
def self.acceptance_collection def self.acceptance_collection
acceptances.keys.map(&:to_sym) acceptances.keys.map(&:to_sym)
end end
...@@ -88,4 +107,19 @@ class Client < ApplicationRecord ...@@ -88,4 +107,19 @@ class Client < ApplicationRecord
def german_missing? def german_missing?
language_skills.german.blank? language_skills.german.blank?
end end
# allow ransack to use defined scopes
def self.ransackable_scopes(auth_object = nil)
['active', 'inactive']
end
private
def going_to_change_to_resigned?
will_save_change_to_acceptance?(to: 'resigned')
end
def record_acceptance_change
self.resigned_on = Time.zone.today
end
end end
...@@ -30,7 +30,9 @@ module GroupAssignmentAndAssignmentCommon ...@@ -30,7 +30,9 @@ module GroupAssignmentAndAssignmentCommon
scope :start_after, ->(date) { where("#{model_name.plural}.period_start > ?", date) } scope :start_after, ->(date) { where("#{model_name.plural}.period_start > ?", date) }
scope :start_at_or_after, ->(date) { where("#{model_name.plural}.period_start >= ?", date) } scope :start_at_or_after, ->(date) { where("#{model_name.plural}.period_start >= ?", date) }
scope :start_within, ->(date_range) { where(period_start: date_range) } scope :start_within, ->(date_range) { where(period_start: date_range) }
scope :started_six_months_ago, (-> { where("#{model_name.plural}.period_start < ?", 6.months.ago) }) scope :started_six_months_ago, lambda {
where("#{model_name.plural}.period_start < ?", 6.months.ago)
}
scope :started_ca_six_weeks_ago, lambda { scope :started_ca_six_weeks_ago, lambda {
start_at_or_after(8.weeks.ago).start_at_or_before(6.weeks.ago) start_at_or_after(8.weeks.ago).start_at_or_before(6.weeks.ago)
} }
...@@ -103,12 +105,16 @@ module GroupAssignmentAndAssignmentCommon ...@@ -103,12 +105,16 @@ module GroupAssignmentAndAssignmentCommon
period_start.present? && period_start <= Time.zone.today period_start.present? && period_start <= Time.zone.today
end end
def ending?
period_start.present? && period_end.present?
end
def ended? def ended?
ending? && period_end <= Time.zone.today ending? && period_end <= Time.zone.today
end end
def ending? def no_period?
period_start.present? && period_end.present? period_start.blank? && period_end.blank?
end end
end end
end end
...@@ -49,6 +49,8 @@ class User < ApplicationRecord ...@@ -49,6 +49,8 @@ class User < ApplicationRecord
has_many :group_offer_terminations_verified, class_name: 'GroupOffer', has_many :group_offer_terminations_verified, class_name: 'GroupOffer',
foreign_key: 'termination_verified_by_id', inverse_of: 'termination_verified_by' foreign_key: 'termination_verified_by_id', inverse_of: 'termination_verified_by'
has_many :resigned_clients, class_name: 'Client', foreign_key: 'resigned_by_id'
# Mailing process done relation # Mailing process done relation
has_many :process_submitted_by, class_name: 'ReminderMailingVolunteer' has_many :process_submitted_by, class_name: 'ReminderMailingVolunteer'
......
...@@ -84,8 +84,12 @@ class ApplicationPolicy ...@@ -84,8 +84,12 @@ class ApplicationPolicy
superadmin? || user_owns_record? superadmin? || user_owns_record?
end end
def department_managers_record?
department_manager? && user_owns_record?
end
def superadmin_or_department_managers_record? def superadmin_or_department_managers_record?
superadmin? || department_manager? && user_owns_record? superadmin? || department_managers_record?
end end
def superadmin_or_department_managers_registration? def superadmin_or_department_managers_registration?
......
...@@ -8,16 +8,23 @@ class ClientPolicy < ApplicationPolicy ...@@ -8,16 +8,23 @@ class ClientPolicy < ApplicationPolicy
end end
# controller action policies # controller action policies
alias_method :index?, :superadmin_or_department_manager_or_social_worker? alias_method :index?, :superadmin_or_department_manager_or_social_worker?
alias_method :search?, :superadmin_or_department_manager_or_social_worker? alias_method :search?, :superadmin_or_department_manager_or_social_worker?
alias_method :new?, :superadmin_or_department_manager_or_social_worker? alias_method :new?, :superadmin_or_department_manager_or_social_worker?
alias_method :create?, :superadmin_or_department_manager_or_social_worker? alias_method :create?, :superadmin_or_department_manager_or_social_worker?
alias_method :show?, :superadmin_or_department_manager_or_social_worker? alias_method :show?, :superadmin_or_department_manager_or_social_worker?
alias_method :edit?, :superadmin_or_record_owner? alias_method :edit?, :superadmin_or_record_owner?
alias_method :update?, :superadmin_or_record_owner? alias_method :update?, :superadmin_or_record_owner?
alias_method :termination?, :superadmin_or_department_managers_record? alias_method :set_terminated?, :superadmin_or_department_managers_record?
alias_method :destroy?, :superadmin?
# suplementary policies # suplementary policies
alias_method :superadmin_privileges?, :superadmin? alias_method :superadmin_privileges?, :superadmin?
def acceptance_collection
if superadmin_or_department_managers_record?
Client.acceptance_collection
else
Client.acceptance_collection_restricted
end
end
end end
- if notice.present? || alert.present? - if alert.present?
.alert.alert-warning.alert-dismissible role='alert' = notification_warning_bubble(alert)
button.close aria-label='Close' data-dismiss='alert' type='button' - elsif notice.present?
span aria-hidden='true' &times; - if notice.is_a? String
= notice || alert = notification_warning_bubble(notice)
- elsif notice.is_a? Hash
= render 'notification_alert_with_action', notice: notice
- elsif @custom_notice.present?
- @custom_notice[:action_link].stringify_keys!
= render 'notification_alert_with_action', notice: @custom_notice.stringify_keys
.alert.alert-danger.alert-dismissible role='alert'
= notification_close_button
h4= notice['message']
- if notice['model_message'].present?
p= notice['model_message']
p
= notification_close_button('Abbrechen')
- if notice['action_link'].present?
= link_to(notice['action_link']['text'], notice['action_link']['path'], target: '_blank', class: 'btn btn-default')
...@@ -22,4 +22,9 @@ tr ...@@ -22,4 +22,9 @@ tr
td.button-acceptance = link_to t(".acceptance.#{client.acceptance}"), '#', td.button-acceptance = link_to t(".acceptance.#{client.acceptance}"), '#',
class: "btn btn-xs btn-acceptance-#{client.acceptance}" class: "btn btn-xs btn-acceptance-#{client.acceptance}"
td= client.comments td= client.comments
= render 'index_actions', subject: client td.index-action-cell.hidden-print
= link_to t_action(:show), client_path(client)
- if policy(client).edit?
= link_to t_action(:edit), edit_client_path(client)
- if policy(client).set_terminated?
= link_to 'Beenden', set_terminated_client_path(client), method: :patch, data: { confirm: 'Klient/in wirklich beenden?' }
...@@ -47,8 +47,9 @@ ...@@ -47,8 +47,9 @@
fieldset fieldset
legend Interne Kriterien legend Interne Kriterien
- if policy(@client).set_terminated?
= f.input :acceptance, collection: policy(@client).acceptance_collection, include_blank: false
- if policy(Client).superadmin_privileges? - if policy(Client).superadmin_privileges?
= f.input :acceptance, collection: Client.acceptance_collection, include_blank: false
= f.association :involved_authority, collection: @social_workers = f.association :involved_authority, collection: @social_workers
= f.input :competent_authority = f.input :competent_authority
......
...@@ -12,7 +12,9 @@ nav.navbar.section-navigation.hidden-print ...@@ -12,7 +12,9 @@ nav.navbar.section-navigation.hidden-print
= list_filter_dropdown(:salutation, Client::SALUTATIONS) = list_filter_dropdown(:salutation, Client::SALUTATIONS)
- if policy(Client).superadmin_privileges? - if policy(Client).superadmin_privileges?
= enum_filter_dropdown(:acceptance, Client.acceptances) = enum_filter_dropdown(:acceptance, Client.acceptances)
= boolean_toggler_filter_dropdown(:active, 'Tandem', 'Aktiv', 'Inaktiv') = custom_filter_dropdown('Tandem',
{ q: :active, text: 'Aktiv', value: 'true' },
{ q: :inactive, text: 'Inktiv', value: 'true' })
li= button_link t('clear_filters'), clients_path, dimension: :sm li= button_link t('clear_filters'), clients_path, dimension: :sm
li= button_link navigation_fa_icon(:xlsx), url_for(format: :xlsx, q: search_parameters), dimension: :sm li= button_link navigation_fa_icon(:xlsx), url_for(format: :xlsx, q: search_parameters), dimension: :sm
li= link_to navigation_glyph(:print), url_for(print: :true, q: search_parameters), { class: 'btn btn-default btn-sm', target: '_blank' } li= link_to navigation_glyph(:print), url_for(print: :true, q: search_parameters), { class: 'btn btn-default btn-sm', target: '_blank' }
......
nav.navbar.section-navigation nav.navbar.section-navigation
ul.list-inline ul.list-inline
- if policy(Client).superadmin_privileges? - if policy(Client).superadmin_privileges?
li.button-acceptance = link_to t(".acceptance.#{@client.acceptance}"), '#', li.button-acceptance = link_to t_attr(@client.acceptance, Client), '#',
class: "btn btn-acceptance-#{@client.acceptance}" class: "btn btn-acceptance-#{@client.acceptance}"
- if policy(@client).edit? - if policy(@client).edit?
li= button_link t_action(:edit), edit_client_path(@client) li= button_link t_action(:edit), edit_client_path(@client)
li= button_link navigation_glyph(:print), client_path(@client, print: true) li= button_link navigation_glyph(:print), client_path(@client, print: true)
- if policy(Client).destroy? - if !@client.resigned? && policy(@client).set_terminated?
li= link_to navigation_glyph(:delete), @client, confirm_deleting(@client, 'btn btn-default') li= link_to 'Beenden', set_terminated_client_path(@client), class: 'btn btn-default', method: :patch, data: { confirm: 'Klient/in wirklich beenden?' }
li= button_link navigation_glyph(:back), clients_path li= button_link navigation_glyph(:back), clients_path
- if policy(Journal).index? - if policy(Journal).index?
ul.list-inline.pull-right ul.list-inline.pull-right
......
...@@ -56,8 +56,9 @@ Rails.application.routes.draw do ...@@ -56,8 +56,9 @@ Rails.application.routes.draw do
resources :assignments, concerns: [:update_submitted_at, :search, :termination_actions] resources :assignments, concerns: [:update_submitted_at, :search, :termination_actions]
resources :client_notifications, :departments, :performance_reports, :email_templates, :users resources :client_notifications, :departments, :performance_reports, :email_templates, :users
resources :clients, concerns: :search do resources :clients, except: [:destroy], concerns: :search do
resources :journals, except: [:show] resources :journals, except: [:show]
patch :set_terminated, on: :member
end end
resources :feedbacks, only: [:new, :create] resources :feedbacks, only: [:new, :create]
......
class AddTerminationFieldsToClient < ActiveRecord::Migration[5.1]
def change
change_table :clients do |t|
t.date :resigned_on
t.references :resigned_by, references: :users, index: true
end
end
end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180126101119) do ActiveRecord::Schema.define(version: 20180130140401) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -166,8 +166,11 @@ ActiveRecord::Schema.define(version: 20180126101119) do ...@@ -166,8 +166,11 @@ ActiveRecord::Schema.define(version: 20180126101119) do
t.integer "acceptance", default: 0 t.integer "acceptance", default: 0
t.integer "cost_unit" t.integer "cost_unit"
t.bigint "involved_authority_id" t.bigint "involved_authority_id"
t.date "resigned_on"
t.bigint "resigned_by_id"
t.index ["deleted_at"], name: "index_clients_on_deleted_at" t.index ["deleted_at"], name: "index_clients_on_deleted_at"
t.index ["involved_authority_id"], name: "index_clients_on_involved_authority_id" t.index ["involved_authority_id"], name: "index_clients_on_involved_authority_id"
t.index ["resigned_by_id"], name: "index_clients_on_resigned_by_id"
t.index ["user_id"], name: "index_clients_on_user_id" t.index ["user_id"], name: "index_clients_on_user_id"
end end
......
...@@ -82,7 +82,7 @@ end ...@@ -82,7 +82,7 @@ end
puts_model_counts('After Volunteer created', User, Volunteer, Client) puts_model_counts('After Volunteer created', User, Volunteer, Client)
# Create clients for each acceptance type # Create clients for each acceptance type
Client.acceptance_collection.each do |acceptance| Client.acceptance_collection_restricted.each do |acceptance|
FactoryBot.create(:client, acceptance: acceptance, user: User.superadmins.first) FactoryBot.create(:client, acceptance: acceptance, user: User.superadmins.first)
end end
puts_model_counts('After Client created', User, Volunteer, Client) puts_model_counts('After Client created', User, Volunteer, Client)
......
...@@ -7,54 +7,50 @@ class ClientActivityFilterTest < ActionDispatch::IntegrationTest ...@@ -7,54 +7,50 @@ class ClientActivityFilterTest < ActionDispatch::IntegrationTest
create :assignment_active, client: @client_accepted create :assignment_active, client: @client_accepted
@client_rejected = create :client, acceptance: 'rejected' @client_rejected = create :client, acceptance: 'rejected'
create :assignment_active, client: @client_rejected create :assignment_active, client: @client_rejected
@client_resigned = create :client, acceptance: 'resigned' @client_resigned = create :client, acceptance: 'accepted'
create :assignment_active, client: @client_resigned create :assignment_active, client: @client_resigned, period_end: 2.days.ago
@client_resigned.resigned!
login_as @superadmin login_as @superadmin
end end
test 'client_tandem_active_filter_returns_accepted_active_client' do test 'client_tandem_active_filter_returns_accepted_active_client' do
get clients_path(q: { active_eq: 'true' }) get clients_path(q: { active: 'true' })
assert response.body.include? @client_accepted.contact.full_name assert response.body.include? @client_accepted.contact.full_name
refute response.body.include? @client_rejected.contact.full_name refute response.body.include? @client_rejected.contact.full_name
refute response.body.include? @client_resigned.contact.full_name refute response.body.include? @client_resigned.contact.full_name
end end
test 'client_tandem_inactive_filter_returns_accepted_inactive_client' do test 'client_tandem_inactive_filter_returns_accepted_inactive_client' do
get clients_path(q: { active_eq: 'false' }) get clients_path(q: { inactive: 'true' })
refute response.body.include? @client_accepted.contact.full_name refute response.body.include? @client_accepted.contact.full_name
refute response.body.include? @client_rejected.contact.full_name refute response.body.include? @client_rejected.contact.full_name
refute response.body.include? @client_resigned.contact.full_name refute response.body.include? @client_resigned.contact.full_name
end end
test 'client_acceptance_tandem_filters_work_together' do test 'client_acceptance_tandem_filters_work_together' do
get clients_path(q: { acceptance_eq: '0', active_eq: 'true' }) get clients_path(q: { acceptance_eq: '0', active: 'true' })
assert response.body.include? @client_accepted.contact.full_name assert response.body.include? @client_accepted.contact.full_name
refute response.body.include? @client_rejected.contact.full_name refute response.body.include? @client_rejected.contact.full_name
refute response.body.include? @client_resigned.contact.full_name refute response.body.include? @client_resigned.contact.full_name
get clients_path(q: { acceptance_eq: '0', active_eq: 'false' }) get clients_path(q: { acceptance_eq: '0', inactive: 'true' })
refute response.body.include? @client_accepted.contact.full_name refute response.body.include? @client_accepted.contact.full_name