Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removing partner addresses #2218

Merged
merged 14 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ Style/HashSyntax:
Rails/ActionOrder:
Enabled: false

Metrics/ClassLength:
Exclude:
- "test/**/*.rb"
ivan-kocienski-gfsc marked this conversation as resolved.
Show resolved Hide resolved



Rails/RootPathnameMethods:
Enabled: false
GraphQL/MaxComplexitySchema:
Expand Down
15 changes: 14 additions & 1 deletion app/controllers/admin/partners_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions app/javascript/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
67 changes: 67 additions & 0 deletions app/javascript/controllers/partner_address_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
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) {
console.log("do_clear_address");
ivan-kocienski-gfsc marked this conversation as resolved.
Show resolved Hide resolved

event.preventDefault();
console.log("do_clear_address");
ivan-kocienski-gfsc marked this conversation as resolved.
Show resolved Hide resolved

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 =
"<p>Address has been cleared</p>";
});
}
}
43 changes: 42 additions & 1 deletion app/models/partner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
ivan-kocienski-gfsc marked this conversation as resolved.
Show resolved Hide resolved

has_many :partner_tags, dependent: :destroy
has_many :tags, through: :partner_tags
Expand Down Expand Up @@ -247,6 +247,47 @@ def has_service_areas?
service_areas.any?
end

def can_clear_address?(user = nil)
ivan-kocienski-gfsc marked this conversation as resolved.
Show resolved Hide resolved
return false if address.blank?
return false if service_areas.empty?

return false if user.blank?
return true if user.root?
return true if user.admin_for_partner?(id)

return true if user.neighbourhood_admin?
return true if user.partnership_admin?
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
# this shouldn't happen (?) - a user has no neighbourhoods
# but can still remove a partners address? if that is the case
# then they aren't seeing the partner by neighbourhood anyway
# so warning them that removing the address will remove the
# partner seems wrong
return true if user_hood_ids.empty?
ivan-kocienski-gfsc marked this conversation as resolved.
Show resolved Hide resolved

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
Expand Down
4 changes: 4 additions & 0 deletions app/policies/partner_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ def destroy?
return true if user.only_neighbourhood_admin_for_partner?(record.id)
end

def clear_address?
index?
ivan-kocienski-gfsc marked this conversation as resolved.
Show resolved Hide resolved
end

def setup?
create?
end
Expand Down
7 changes: 0 additions & 7 deletions app/views/admin/application/_address_fields.html.erb

This file was deleted.

40 changes: 40 additions & 0 deletions app/views/admin/partners/_address_fields.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<%= form.fields_for :address do |address_form| %>
<div class='nested-fields'
data-controller="partner-address"
data-partner-address-partner-id-value="<%= partner.id %>"
data-partner-address-warn-of-delisting-value="<%= partner.warn_user_clear_address?(current_user) ? 'true' : 'false' %>">

<%# this view is augmented by the /app/javascript/controllers/parter_address_controller.js %>

<%= address_form.input :street_address,
class: "form-control address_1 address_field",
label: 'Street address' %>

<%= address_form.input :street_address2,
class: "form-control address_2 address_field",
label: 'Street address 2' %>

<%= address_form.input :street_address3,
class: "form-control address_3 address_field",
label: 'Street address 3' %>

<%= address_form.input :city,
class: "form-control city address_field" %>

<%= address_form.input :postcode,
class: "form-control postcode address_field" %>

<% if partner.can_clear_address?(current_user) %>
<div data-partner-address-target="addressInfoArea">
<p>Address in neighbourhood <%= link_to_neighbourhood(partner.address.neighbourhood) %>.</p>
<p>
<%= link_to 'Clear Address',
'#',
class: "btn btn-secondary btn-sm",
data: { action: "click->partner-address#do_clear_address" } %>
<strong>WARNING:</strong> clicking this button will permanently clear this partners address!
ivan-kocienski-gfsc marked this conversation as resolved.
Show resolved Hide resolved
</p>
</div>
<% end %>
</div>
<% end %>
8 changes: 5 additions & 3 deletions app/views/admin/partners/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@
<div id='address'>
<div class="row">
<div class="col-md-6">
<%= f.fields_for :address, @partner.address || Address.new do |a| %>
<%= render 'address_fields', f: a %>
<% end %>

<%= render 'address_fields',
form: f,
partner: @partner %>

<% if @partner&.address&.neighbourhood&.legacy_neighbourhood? %>
<p>
The address for this partner is assigned to an out of date neighbourhood.
Expand Down
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
collection do
match :setup, via: %i[get post]
end
member do
delete :clear_address
end
end
resources :tags
resources :sites
Expand Down
14 changes: 14 additions & 0 deletions test/controllers/admin/partners_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
80 changes: 80 additions & 0 deletions test/models/partner_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -423,4 +423,84 @@ 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

partner = build(:bare_partner)
partner.address = create(:address)
partner.service_areas.build(neighbourhood: neighbourhood)
partner.save!

assert partner.can_clear_address?(citizen)
end

test 'can_clear_address? for partnership admin' do
tag = create(:partnership)
citizen = create(:citizen)
citizen.tags << tag

partner = build(:bare_partner)
partner.address = create(:address)

neighbourhood = create(:neighbourhood)
partner.service_areas.build(neighbourhood: neighbourhood)

partner.save!

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
Loading