diff --git a/.rubocop.yml b/.rubocop.yml index da1449aeb..7cb3c5baf 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -55,6 +55,12 @@ Style/HashSyntax: Rails/ActionOrder: Enabled: false +Metrics/ClassLength: + Exclude: + - "test/**/*.rb" + + + Rails/RootPathnameMethods: Enabled: false GraphQL/MaxComplexitySchema: diff --git a/app/controllers/admin/partners_controller.rb b/app/controllers/admin/partners_controller.rb index 9101d407f..730cd34d1 100644 --- a/app/controllers/admin/partners_controller.rb +++ b/app/controllers/admin/partners_controller.rb @@ -3,7 +3,7 @@ module Admin class PartnersController < Admin::ApplicationController include LoadUtilities - before_action :set_partner, only: %i[show edit update destroy] + before_action :set_partner, only: %i[show edit update destroy clear_address] before_action :set_tags, only: %i[new create edit] before_action :set_neighbourhoods, only: %i[new edit] before_action :set_partner_tags_controller, only: %i[new edit update] @@ -108,6 +108,19 @@ def destroy end end + def clear_address + authorize @partner + + if @partner.can_clear_address?(current_user) + @partner.clear_address! + render json: { message: 'Address cleared' } + + else + render json: { message: 'Could not clear address' }, + status: :unprocessable_entity + end + end + def setup @partner = Partner.new authorize @partner diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index e5c0360d5..45e885b6b 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -21,3 +21,6 @@ application.register("user-partners", UserPartnersController); import PartnerTagsController from "./partner_tags_controller.js"; application.register("partner-tags", PartnerTagsController); + +import PartnerAddressController from "./partner_address_controller.js"; +application.register("partner-address", PartnerAddressController); diff --git a/app/javascript/controllers/partner_address_controller.js b/app/javascript/controllers/partner_address_controller.js new file mode 100644 index 000000000..a199da1ef --- /dev/null +++ b/app/javascript/controllers/partner_address_controller.js @@ -0,0 +1,64 @@ +import { Controller } from "@hotwired/stimulus"; + +export default class extends Controller { + static values = { + partnerId: String, + warnOfDelisting: String, // "true" or "false" + }; + + static targets = ["addressInfoArea"]; + + static addressFieldIds = [ + "partner_address_attributes_street_address", + "partner_address_attributes_street_address2", + "partner_address_attributes_street_address3", + "partner_address_attributes_city", + "partner_address_attributes_postcode", + ]; + + connect() {} + + disconnect() {} + + do_clear_address(event) { + event.preventDefault(); + + let warning_text = "Please confirm you want to clear this partners address"; + if (this.warnOfDelistingValue === "true") { + warning_text = `This address links to you to this partner and by clearing this address you will no longer be able to access this partner,\n\n${warning_text}`; + } + + if (!confirm(warning_text)) { + return; + } + + const csrfToken = document + .querySelector('meta[name="csrf-token"]') + .getAttribute("content"); + + const url = `/partners/${this.partnerIdValue}/clear_address`; + + const payload = { + method: "DELETE", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + "X-CSRF-Token": csrfToken, + }, + body: "", + }; + + fetch(url, payload) + .then((response) => response.json()) + .then((data) => { + this.constructor.addressFieldIds.forEach((id) => { + let node = document.getElementById(id); + node.value = ""; + node.classList.remove("is-valid"); + }); + + this.addressInfoAreaTarget.innerHTML = + "
Address has been cleared
"; + }); + } +} diff --git a/app/models/address.rb b/app/models/address.rb index 411bc2a0c..3d64882c1 100644 --- a/app/models/address.rb +++ b/app/models/address.rb @@ -31,6 +31,14 @@ def prepend_room_number(room_number_string) self end + def missing_values? + street_address.blank? && + street_address2.blank? && + street_address3.blank? && + city.blank? && + postcode.blank? + end + def first_address_line street_address end diff --git a/app/models/partner.rb b/app/models/partner.rb index 4598eadbb..0e0885e83 100644 --- a/app/models/partner.rb +++ b/app/models/partner.rb @@ -16,7 +16,7 @@ class Partner < ApplicationRecord has_and_belongs_to_many :users has_many :calendars, dependent: :destroy has_many :events - belongs_to :address, optional: true + belongs_to :address, optional: true, dependent: :destroy has_many :partner_tags, dependent: :destroy has_many :tags, through: :partner_tags @@ -247,6 +247,43 @@ def has_service_areas? service_areas.any? end + def can_clear_address?(user = nil) + return false if address.blank? || address.missing_values? + return false if service_areas.empty? + + return false if user.blank? + return true if user.root? + return true if user.admin_for_partner?(id) + + # must admin for this address specifically + user_hood_ids = user.owned_neighbourhood_ids + user_hood_ids.include?(address.neighbourhood_id) + end + + def warn_user_clear_address?(user) + return false if user.root? + return false if user.admin_for_partner?(id) + + user_hood_ids = user.owned_neighbourhood_ids + return true if user_hood_ids.empty? + + sa_hood_ids = service_areas.pluck(:neighbourhood_id) + + any_service_areas = Set.new(user_hood_ids).any?(Set.new(sa_hood_ids)) + + # is the only way this user is tied to this partner through the address? + any_service_areas == false + end + + def clear_address! + Partner.transaction do + old_address = address + update! address_id: nil + + old_address&.destroy + end + end + def permalink "https://placecal.org/partners/#{id}" end diff --git a/app/policies/partner_policy.rb b/app/policies/partner_policy.rb index b58ae0804..201ff4f97 100644 --- a/app/policies/partner_policy.rb +++ b/app/policies/partner_policy.rb @@ -37,6 +37,10 @@ def destroy? return true if user.only_neighbourhood_admin_for_partner?(record.id) end + def clear_address? + edit? + end + def setup? create? end diff --git a/app/views/admin/application/_address_fields.html.erb b/app/views/admin/application/_address_fields.html.erb deleted file mode 100644 index 8cee20785..000000000 --- a/app/views/admin/application/_address_fields.html.erb +++ /dev/null @@ -1,7 +0,0 @@ -Address in neighbourhood <%= link_to_neighbourhood(partner.address.neighbourhood) %>.
+ <% end %> ++ <%= link_to 'Clear Address', + '#', + class: "btn btn-secondary btn-sm", + data: { action: "click->partner-address#do_clear_address" } %> +
+The address for this partner is assigned to an out of date neighbourhood. diff --git a/config/routes.rb b/config/routes.rb index 90c0e4083..3aef1141d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -37,6 +37,9 @@ collection do match :setup, via: %i[get post] end + member do + delete :clear_address + end end resources :tags resources :sites diff --git a/test/controllers/admin/partners_controller_test.rb b/test/controllers/admin/partners_controller_test.rb index 72208dcc5..75b57fcf6 100644 --- a/test/controllers/admin/partners_controller_test.rb +++ b/test/controllers/admin/partners_controller_test.rb @@ -265,4 +265,18 @@ class Admin::PartnersControllerTest < ActionDispatch::IntegrationTest assert_response :redirect assert_equal 'Partners must have an address or a service area inside your neighbourhood', flash[:danger] end + + test 'root user clear address on partner clears address' do + sign_in @root + + delete clear_address_admin_partner_path(@partner) + assert_response :success + + @partner.reload + assert_nil @partner.address + + # will not work if no address is set + delete clear_address_admin_partner_path(@partner) + assert_response :unprocessable_entity + end end diff --git a/test/models/partner_test.rb b/test/models/partner_test.rb index 0ebb2cf37..53eca1eed 100644 --- a/test/models/partner_test.rb +++ b/test/models/partner_test.rb @@ -423,4 +423,73 @@ class PartnerTest < ActiveSupport::TestCase partner = create(:partner, :accessed_by_user => pa, :tags => [pa.tags.first]) assert_predicate partner, :valid? end + + test 'can_clear_address? for root' do + partner = build(:bare_partner) + # has no address + assert_not partner.can_clear_address? + + partner.address = create(:address) + # missing service area + assert_not partner.can_clear_address? + + partner.service_areas.build(neighbourhood: create(:neighbourhood)) + # is not root + assert_not partner.can_clear_address? + + root = create(:root) + assert partner.can_clear_address?(root) + end + + test 'can_clear_address? for partner admin' do + partner = build(:bare_partner) + partner.address = create(:address) + partner.service_areas.build(neighbourhood: create(:neighbourhood)) + partner.save! + + citizen = create(:citizen) + citizen.partners << partner + + assert partner.can_clear_address?(citizen) + end + + test 'can_clear_address? for neighbourhood admin' do + neighbourhood = create(:neighbourhood) + citizen = create(:citizen) + citizen.neighbourhoods << neighbourhood + + # cannot clear address if admin does not "own" address + partner = build(:bare_partner) + partner.address = create(:address) + partner.service_areas.build(neighbourhood: neighbourhood) + partner.save! + + assert_not partner.can_clear_address?(citizen) + + # now partner is in admins neighbourhood pool, can clear + partner.address.neighbourhood = neighbourhood + assert partner.can_clear_address?(citizen) + end + + test 'warn_user_clear_address?' do + partner = build(:bare_partner) + partner.address = create(:address) + partner.save! + + # am root + root = create(:root) + assert_not partner.warn_user_clear_address?(root) + + # am owner + citizen = create(:citizen) + citizen.partners << partner + assert_not partner.warn_user_clear_address?(citizen) + + # not root or owner, so warn + other_neighbourhood = create(:neighbourhood) + other_citizen = create(:citizen) + other_citizen.neighbourhoods << other_neighbourhood + + assert partner.warn_user_clear_address?(other_citizen) + end end