Allow people to set enterprise latitude/longitude manually or automatically.

This for new changes to the enterprise registration/signup flow where a map will be displayed when people are filling in their address. On this map people can check the geocoder has geocoded their address correctly and if not they can manually adjust their latitude/longitude on the map.

But currently every time someone changes their address in the Admin > Enterprise > Address section the address would automatically be geocoded so this could overwrite the latitude/longitude that was set during sign up. To prevent the latitude/longitude from being overwritten this add's a checkbox which people need to explicity click if they want their address to be automatically geocoded, otherwise it will just use the manually configured latitude/longitude.

Note this new feature which allows people to select their location on a map during registration only works with Google maps so far. So if an instance is using Open Street Map this change also adds support for passing a :use_geocoder parameter to the Api::EnterprisesController during registration so that the address will be geocoded on the backend without the use of a map.
This commit is contained in:
Cillian O'Ruanaidh
2021-02-12 14:36:45 +00:00
parent 971971803e
commit f20cea7e4f
20 changed files with 199 additions and 68 deletions

View File

@@ -59,4 +59,4 @@
$ ->
# Hacky fix for issue - http://foundation.zurb.com/forum/posts/2112-foundation-5100-syntax-error-in-js
Foundation.set_namespace ""
$(document).foundation()
$(document).foundation()

View File

@@ -6,7 +6,6 @@ Darkswarm.controller "RegistrationCtrl", ($scope, RegistrationService, Enterpris
$scope.latLong = null
$scope.addressConfirmed
$scope.steps = ['details', 'contact', 'type', 'about', 'images', 'social']
$scope.enableMapConfirm = GmapsGeo and GmapsGeo.OK
# Filter countries without states since the form requires a state to be selected.
# Consider changing the form to require a state only if a country requires them (Spree option).

View File

@@ -21,6 +21,7 @@ Darkswarm.factory "EnterpriseRegistrationService", ($http, RegistrationService,
url: "/api/enterprises"
data:
enterprise: @prepare()
use_geocoder: @useGeocoder()
params:
token: spreeApiKey
).success((data) =>
@@ -45,6 +46,7 @@ Darkswarm.factory "EnterpriseRegistrationService", ($http, RegistrationService,
url: "/api/enterprises/#{@enterprise.id}"
data:
enterprise: @prepare()
use_geocoder: @useGeocoder()
params:
token: spreeApiKey
).success((data) ->
@@ -63,3 +65,7 @@ Darkswarm.factory "EnterpriseRegistrationService", ($http, RegistrationService,
enterprise.address_attributes = @enterprise.address if @enterprise.address?
enterprise.address_attributes.country_id = @enterprise.country.id if @enterprise.country?
enterprise
useGeocoder: =>
if @enterprise.address? && !@enterprise.address.latitude? && !@enterprise.address.longitude?
return "1"

View File

@@ -4,6 +4,8 @@ require 'open_food_network/order_cycle_permissions'
module Admin
class EnterprisesController < Admin::ResourceController
include GeocodeEnterpriseAddress
# These need to run before #load_resource so that @object is initialised with sanitised values
prepend_before_action :override_owner, only: :create
prepend_before_action :override_sells, only: :create
@@ -22,6 +24,9 @@ module Admin
before_action :load_properties, only: [:edit, :update]
before_action :setup_property, only: [:edit]
update.after :geocode_address_if_use_geocoder
create.after :geocode_address_if_use_geocoder
helper 'spree/products'
include OrderCyclesHelper

View File

@@ -1,5 +1,7 @@
module Api
class EnterprisesController < Api::BaseController
include GeocodeEnterpriseAddress
before_action :override_owner, only: [:create, :update]
before_action :check_type, only: :update
before_action :override_sells, only: [:create, :update]
@@ -14,6 +16,7 @@ module Api
user_ids = enterprise_params.delete(:user_ids)
@enterprise = Enterprise.new(enterprise_params)
if @enterprise.save
geocode_address_if_use_geocoder
@enterprise.user_ids = user_ids
render json: @enterprise.id, status: :created
else
@@ -26,6 +29,7 @@ module Api
authorize! :update, @enterprise
if @enterprise.update(enterprise_params)
geocode_address_if_use_geocoder
render json: @enterprise.id, status: :ok
else
invalid_resource!(@enterprise)

View File

@@ -0,0 +1,9 @@
# frozen_string_literal: true
module GeocodeEnterpriseAddress
extend ActiveSupport::Concern
def geocode_address_if_use_geocoder
AddressGeocoder.new(@enterprise.address).geocode if params[:use_geocoder] == "1"
end
end

View File

@@ -1,2 +1,13 @@
module MapHelper
def using_google_maps?
ENV["GOOGLE_MAPS_API_KEY"].present? || google_maps_configured_with_geocoder_api_key?
end
private
def google_maps_configured_with_geocoder_api_key?
ENV["GEOCODER_API_KEY"].present? && (
ENV["GEOCODER_SERVICE"].to_s == "google" || ENV["GEOCODER_SERVICE"].blank?
)
end
end

View File

@@ -410,10 +410,6 @@ class Enterprise < ActiveRecord::Base
address.firstname = address.lastname = address.phone = 'unused' if address.present?
end
def geocode_address
address.geocode if address.andand.changed?
end
def ensure_owner_is_manager
users << owner unless users.include?(owner)
end

View File

@@ -10,24 +10,18 @@ module Spree
has_one :enterprise, dependent: :restrict_with_exception
has_many :shipments
before_validation :geocode, if: :use_geocoder?
validates :firstname, :lastname, :address1, :city, :country, presence: true
validates :zipcode, presence: true, if: :require_zipcode?
validates :phone, presence: true, if: :require_phone?
validate :state_validate
attr_accessor :use_geocoder
after_save :touch_enterprise
alias_attribute :first_name, :firstname
alias_attribute :last_name, :lastname
delegate :name, to: :state, prefix: true, allow_nil: true
geocoded_by :geocode_address
def self.default
country = begin
Spree::Country.find(Spree::Config[:default_country_id])
@@ -93,10 +87,6 @@ module Spree
}
end
def geocode_address
render_address([address1, address2, zipcode, city, country.andand.name, state.andand.name])
end
def full_address
render_address([address1, address2, city, zipcode, state.andand.name])
end
@@ -111,10 +101,6 @@ module Spree
private
def use_geocoder?
@use_geocoder == true || @use_geocoder == 'true' || @use_geocoder == '1'
end
def require_phone?
true
end

View File

@@ -0,0 +1,30 @@
# frozen_string_literal: true
class AddressGeocoder
def initialize(address)
@address = address
end
def geocode
latitude, longitude = Geocoder.coordinates(geocode_address)
return unless latitude.present? && longitude.present?
address.update(latitude: latitude, longitude: longitude)
end
private
attr_reader :address
def geocode_address
[
address.address1,
address.address2,
address.zipcode,
address.city,
address.country.andand.name,
address.state.andand.name
].select(&:present?).join(', ')
end
end

View File

@@ -7,7 +7,7 @@ module PermittedAttributes
:firstname, :lastname, :address1, :address2,
:city, :country_id, :state_id, :zipcode,
:phone, :state_name, :alternative_phone, :company,
:latitude, :longitude, :use_geocoder
:latitude, :longitude
]
end
end

View File

@@ -113,12 +113,11 @@
.three.columns.alpha
= "&nbsp;".html_safe
.five.columns.omega
= af.check_box :use_geocoder
= af.label :use_geocoder, t('use_geocoder')
= check_box_tag "use_geocoder"
= label_tag "use_geocoder", t('use_geocoder')
.row
.twelve.columns.alpha
&nbsp;
.row
.twelve.columns.alpha
= render partial: "spree/admin/shared/new_resource_links"
= render partial: "spree/admin/shared/new_resource_links"

View File

@@ -51,5 +51,5 @@
.three.columns.alpha
= "&nbsp;".html_safe
.five.columns.omega
= af.check_box :use_geocoder
= af.label :use_geocoder, t('use_geocoder')
= check_box_tag "use_geocoder"
= label_tag "use_geocoder", t('use_geocoder')

View File

@@ -35,5 +35,4 @@
= yield :injection_data
= render "layouts/i18n_script"
= render "layouts/bugherd_script"
= render "layouts/bugherd_script"

View File

@@ -56,25 +56,26 @@
%select.chunky{ id: 'enterprise_country', name: 'country', required: true, ng: { init: "setDefaultCountry(#{Spree::Config[:default_country_id]})", model: 'enterprise.country', options: 'c as c.name for c in countries' } }
%span.error{ ng: { show: "details.country.$error.required && submitted" } }
= t(".country_field_error")
%div{ ng: {if: "enableMapConfirm"}}
.center{ ng: {if: "latLong"}}
%strong {{'registration.steps.details.drag_pin' | t}}
.small-12.medium-9.large-12.columns.end
.map-container--registration{id: "registration-map", ng: {if: "!!map"}}
%ui-gmap-google-map{ center: "map.center", zoom: "map.zoom"}
%ui-gmap-marker{idKey: 1, coords: "latLong", options: '{ draggable: true}'}
.center
%input.button.primary{ type: "button", value: "{{'registration.steps.details.locate_address' | t}}", ng: { click: "locateAddress()" } }
.center{ ng: {if: "latLong"}}
.field
%input{ type: 'checkbox', id: 'confirm_address', name: 'confirm_address', ng: { model: 'addressConfirmed', change: 'confirmAddressChange(confirmAddress)'} }
%label{ for: 'confirm_address' } {{'registration.steps.details.confirm_address' | t}}
.small-12.medium-9.large-12.columns.end{ ng: {if: "latLong"}}
.field
%strong {{'registration.steps.details.drag_map_marker' | t}}
- if using_google_maps?
%div
.center{ ng: {if: "latLong"}}
%strong {{'registration.steps.details.drag_pin' | t}}
.small-12.medium-9.large-12.columns.end
.map-container--registration{id: "registration-map", ng: {if: "!!map"}}
%ui-gmap-google-map{ center: "map.center", zoom: "map.zoom"}
%ui-gmap-marker{idKey: 1, coords: "latLong", options: '{ draggable: true}'}
.center
%input.button.primary{ type: "button", value: "{{'registration.steps.details.locate_address' | t}}", ng: { click: "locateAddress()" } }
.center{ ng: {if: "latLong"}}
.field
%input{ type: 'checkbox', id: 'confirm_address', name: 'confirm_address', ng: { model: 'addressConfirmed', change: 'confirmAddressChange(confirmAddress)'} }
%label{ for: 'confirm_address' } {{'registration.steps.details.confirm_address' | t}}
.small-12.medium-9.large-12.columns.end{ ng: {if: "latLong"}}
.field
%strong {{'registration.steps.details.drag_map_marker' | t}}
.row.buttons
.small-12.columns
%hr
%input.button.primary.right{ type: "submit", value: "{{'continue' | t}}" }

View File

@@ -13,13 +13,14 @@ describe Admin::EnterprisesController, type: :controller do
let(:distributor) { create(:distributor_enterprise, owner: distributor_owner ) }
let(:supplier) { create(:supplier_enterprise, owner: supplier_owner) }
let(:country) { Spree::Country.find_by name: 'Australia' }
let(:state) { Spree::State.find_by name: 'Victoria' }
let(:address_params) { { address1: 'a', city: 'a', zipcode: 'a', country_id: country.id, state_id: state.id } }
before { @request.env['HTTP_REFERER'] = 'http://test.com/' }
describe "creating an enterprise" do
let(:country) { Spree::Country.find_by name: 'Australia' }
let(:state) { Spree::State.find_by name: 'Victoria' }
let(:enterprise_params) { { enterprise: { name: 'zzz', permalink: 'zzz', is_primary_producer: '0', address_attributes: { address1: 'a', city: 'a', zipcode: 'a', country_id: country.id, state_id: state.id } } } }
let(:enterprise_params) { { enterprise: { name: 'zzz', permalink: 'zzz', is_primary_producer: '0', address_attributes: address_params } } }
it "grants management permission if the current user is an enterprise user" do
allow(controller).to receive_messages spree_current_user: distributor_manager
@@ -99,6 +100,27 @@ describe Admin::EnterprisesController, type: :controller do
expect(enterprise.sells).to eq('any')
end
end
context "geocoding" do
before do
allow(controller).to receive_messages spree_current_user: admin_user
enterprise_params[:enterprise][:owner_id] = admin_user
end
it "geocodes the address when the :use_geocoder parameter is set" do
expect_any_instance_of(AddressGeocoder).to receive(:geocode)
enterprise_params[:use_geocoder] = "1"
spree_put :create, enterprise_params
end
it "doesn't geocode the address when the :use_geocoder parameter is not set" do
expect_any_instance_of(AddressGeocoder).not_to receive(:geocode)
enterprise_params[:use_geocoder] = "0"
spree_put :create, enterprise_params
end
end
end
describe "updating an enterprise" do
@@ -291,6 +313,26 @@ describe Admin::EnterprisesController, type: :controller do
expect(distributor.users).to include user
end
end
context "geocoding" do
before do
allow(controller).to receive_messages spree_current_user: profile_enterprise.owner
end
it "geocodes the address when the :use_geocoder parameter is set" do
expect_any_instance_of(AddressGeocoder).to receive(:geocode)
enterprise_params = { id: profile_enterprise, enterprise: {}, use_geocoder: "1" }
spree_put :update, enterprise_params
end
it "doesn't geocode the address when the :use_geocoder parameter is not set" do
expect_any_instance_of(AddressGeocoder).not_to receive(:geocode)
enterprise_params = { id: profile_enterprise, enterprise: {}, use_geocoder: "0" }
spree_put :update, enterprise_params
end
end
end
describe "register" do

View File

@@ -49,6 +49,20 @@ describe Api::EnterprisesController, type: :controller do
enterprise = Enterprise.last
expect(enterprise.user_ids).to match_array([enterprise_owner.id, manager1.id, manager2.id])
end
context "geocoding" do
it "geocodes the address when the :use_geocoder parameter is set" do
expect_any_instance_of(AddressGeocoder).to receive(:geocode)
api_post :create, { enterprise: new_enterprise_params, use_geocoder: "1" }
end
it "doesn't geocode the address when the :use_geocoder parameter is not set" do
expect_any_instance_of(AddressGeocoder).not_to receive(:geocode)
api_post :create, { enterprise: new_enterprise_params, use_geocoder: "0" }
end
end
end
end

View File

@@ -15,6 +15,7 @@ feature "Registration", js: true do
albania = Spree::Country.create!({ name: "Albania", iso3: "ALB", iso: "AL", iso_name: "ALBANIA", numcode: "8" })
Spree::State.create!({ name: "Berat", abbr: "BRA", country: albania })
Spree::Country.create!({ name: "Chad", iso3: "TCD", iso: "TD", iso_name: "CHAD", numcode: "148" })
AddressGeocoder.any_instance.stub(:geocode)
end
after do

View File

@@ -42,24 +42,6 @@ describe Spree::Address do
end
end
describe "geocode address" do
it "should include address1, address2, zipcode, city, state and country" do
expect(address.geocode_address).to include(address.address1)
expect(address.geocode_address).to include(address.address2)
expect(address.geocode_address).to include(address.zipcode)
expect(address.geocode_address).to include(address.city)
expect(address.geocode_address).to include(address.state.name)
expect(address.geocode_address).to include(address.country.name)
end
it "should not include empty fields" do
address.address2 = nil
address.city = ""
expect(address.geocode_address.split(',').length).to eql(4)
end
end
describe "full address" do
let(:address) { FactoryBot.build(:address) }

View File

@@ -0,0 +1,47 @@
# frozen_string_literal: true
require 'spec_helper'
describe AddressGeocoder do
let(:australia) { Spree::Country.find_or_create_by!(name: "Australia") }
let(:victoria) { Spree::State.find_or_create_by(name: "Victoria", country: australia) }
let(:address) do
create(:address,
address1: "12 Galvin Street",
address2: "Unit 1",
city: "Altona",
country: australia,
state: victoria,
zipcode: 3018,
latitude: nil,
longitude: nil)
end
it "formats the address into a single comma separated string when passing it to the geocoder" do
expect(Geocoder).to receive(:coordinates).with("12 Galvin Street, Unit 1, 3018, Altona, Australia, Victoria")
AddressGeocoder.new(address).geocode
end
describe "when the geocoder can determine the latitude and longitude" do
it "updates the address's latitude and longitude" do
allow(Geocoder).to receive(:coordinates).and_return([-37.47, 144.78])
AddressGeocoder.new(address).geocode
expect(address.latitude).to eq(-37.47)
expect(address.longitude).to eq(144.78)
end
end
describe "when the geocoder cannot determine the latitude and longitude" do
it "doesn't update the address's latitude and longitude" do
allow(Geocoder).to receive(:coordinates).and_return([nil, nil])
AddressGeocoder.new(address).geocode
expect(address.latitude).to be_nil
expect(address.longitude).to be_nil
end
end
end