diff --git a/Gemfile b/Gemfile index 98193f8e82..6b3a172fd3 100644 --- a/Gemfile +++ b/Gemfile @@ -152,7 +152,7 @@ group :development, :test do # Debugger which supports rdbg and Shopify Ruby LSP VSCode extension gem "debug", ">= 1.0.0" # RSpec behavioral testing framework for Rails. - gem "rspec-rails", "~> 6.1.4" + gem "rspec-rails", "~> 7.0.1" # Static analysis / linter. gem "rubocop" # Rails add-on for static analysis. @@ -201,7 +201,7 @@ group :test do # More concise test ("should") matchers gem 'shoulda-matchers', '~> 6.2' # Mock HTTP requests and ensure they are not called during tests. - gem "webmock", "~> 3.23" + gem "webmock", "~> 3.24" # Interface capybara to chrome headless gem "cuprite" # Read PDF files for tests diff --git a/Gemfile.lock b/Gemfile.lock index 67666e4c23..b2cab74685 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -76,8 +76,8 @@ GEM minitest (>= 5.1) mutex_m tzinfo (~> 2.0) - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) afm (0.2.2) annotate (3.2.0) activerecord (>= 3.2, < 8.0) @@ -185,9 +185,9 @@ GEM discard (1.3.0) activerecord (>= 4.2, < 8) docile (1.4.0) - dotenv (3.1.2) - dotenv-rails (3.1.2) - dotenv (= 3.1.2) + dotenv (3.1.4) + dotenv-rails (3.1.4) + dotenv (= 3.1.4) railties (>= 6.1) drb (2.2.1) dry-core (1.0.1) @@ -225,7 +225,7 @@ GEM railties (>= 5.0.0) faker (3.4.2) i18n (>= 1.8.11, < 2) - faraday (1.10.3) + faraday (1.10.4) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -294,14 +294,14 @@ GEM guard (~> 2.1) guard-compat (~> 1.1) rspec (>= 2.99.0, < 4.0) - hashdiff (1.1.0) + hashdiff (1.1.1) hashery (2.1.2) hashie (5.0.0) httparty (0.22.0) csv mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) - i18n (1.14.5) + i18n (1.14.6) concurrent-ruby (~> 1.0) icalendar (2.10.2) ice_cube (~> 0.16) @@ -315,14 +315,14 @@ GEM activesupport (>= 6.0.0) railties (>= 6.0.0) io-console (0.7.2) - irb (1.14.0) + irb (1.14.1) rdoc (>= 4.0.0) reline (>= 0.4.2) - jbuilder (2.12.0) + jbuilder (2.13.0) actionview (>= 5.0.0) activesupport (>= 5.0.0) json (2.7.2) - jwt (2.8.2) + jwt (2.9.1) base64 kaminari (1.2.2) activesupport (>= 4.1.0) @@ -423,8 +423,8 @@ GEM hashie (>= 3.4.6) rack (>= 2.2.3) rack-protection - omniauth-google-oauth2 (1.1.3) - jwt (>= 2.0) + omniauth-google-oauth2 (1.2.0) + jwt (>= 2.9) oauth2 (~> 2.0) omniauth (~> 2.0) omniauth-oauth2 (~> 1.8) @@ -481,7 +481,7 @@ GEM pry (~> 0.13) psych (5.1.2) stringio - public_suffix (5.1.0) + public_suffix (6.0.1) puma (6.4.2) nio4r (~> 2.0) racc (1.8.1) @@ -546,33 +546,32 @@ GEM redis-client (0.22.1) connection_pool regexp_parser (2.9.2) - reline (0.5.9) + reline (0.5.10) io-console (~> 0.5) request_store (1.5.1) rack (>= 1.4) responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) - rexml (3.3.6) - strscan + rexml (3.3.8) rolify (6.0.1) rouge (4.1.2) rspec (3.13.0) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) rspec-mocks (~> 3.13.0) - rspec-core (3.13.0) + rspec-core (3.13.1) rspec-support (~> 3.13.0) - rspec-expectations (3.13.2) + rspec-expectations (3.13.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-mocks (3.13.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (6.1.4) - actionpack (>= 6.1) - activesupport (>= 6.1) - railties (>= 6.1) + rspec-rails (7.0.1) + actionpack (>= 7.0) + activesupport (>= 7.0) + railties (>= 7.0) rspec-core (~> 3.13) rspec-expectations (~> 3.13) rspec-mocks (~> 3.13) @@ -666,16 +665,14 @@ GEM stringio (3.1.1) strong_migrations (1.8.0) activerecord (>= 5.2) - strscan (3.1.0) terser (1.2.3) execjs (>= 0.3.0, < 3) thor (1.3.2) tilt (2.2.0) timeout (0.4.1) ttfunk (1.7.0) - turbo-rails (2.0.6) + turbo-rails (2.0.10) actionpack (>= 6.0.0) - activejob (>= 6.0.0) railties (>= 6.0.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) @@ -689,18 +686,18 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webmock (3.23.1) + webmock (3.24.0) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.8.1) + webrick (1.8.2) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) yard (0.9.34) - zeitwerk (2.6.17) + zeitwerk (2.6.18) PLATFORMS arm64-darwin-20 @@ -784,7 +781,7 @@ DEPENDENCIES recaptcha redis (~> 5.2) rolify (~> 6.0) - rspec-rails (~> 6.1.4) + rspec-rails (~> 7.0.1) rubocop rubocop-performance rubocop-rails (~> 2.25.1) @@ -799,7 +796,7 @@ DEPENDENCIES terser turbo-rails web-console - webmock (~> 3.23) + webmock (~> 3.24) BUNDLED WITH - 2.5.16 + 2.5.20 diff --git a/app/controllers/partner_groups_controller.rb b/app/controllers/partner_groups_controller.rb index eb51430059..0ca0a04baf 100644 --- a/app/controllers/partner_groups_controller.rb +++ b/app/controllers/partner_groups_controller.rb @@ -1,4 +1,6 @@ class PartnerGroupsController < ApplicationController + before_action :set_partner_group, only: %i[edit destroy] + def new @partner_group = current_organization.partner_groups.new @item_categories = current_organization.item_categories @@ -31,8 +33,23 @@ def update end end + def destroy + if @partner_group.partners.any? + redirect_to partners_path + "#nav-partner-groups", alert: "Partner Group cannot be deleted." + else + @partner_group.destroy + respond_to do |format| + format.html { redirect_to partners_path + "#nav-partner-groups", notice: "Partner Group was successfully deleted." } + end + end + end + private + def set_partner_group + @partner_group = current_organization.partner_groups.find(params[:id]) + end + def partner_group_params params.require(:partner_group).permit(:name, :send_reminders, :reminder_schedule, :deadline_day, :by_month_or_week, :day_of_month, :day_of_week, :every_nth_day, item_category_ids: []) diff --git a/app/events/inventory_aggregate.rb b/app/events/inventory_aggregate.rb index 2bc6204cb6..ef1fe6f5cb 100644 --- a/app/events/inventory_aggregate.rb +++ b/app/events/inventory_aggregate.rb @@ -12,6 +12,8 @@ def on(*event_types, &block) # @param event_time [DateTime] # @param validate [Boolean] # @return [EventTypes::Inventory] + # This method can take a block so that you can build up the history of a particular item over + # time, for instance def inventory_for(organization_id, event_time: nil, validate: false) last_snapshot = Event.most_recent_snapshot(organization_id) @@ -35,11 +37,13 @@ def inventory_for(organization_id, event_time: nil, validate: false) # don't do grouping for UpdateExistingEvents if event_batch.any? { |e| e.is_a?(UpdateExistingEvent) } handle(last_grouped_event, inventory, validate: validate) + yield last_grouped_event, inventory if block_given? next end - previous_event = event_hash[last_grouped_event.eventable] - event_hash[last_grouped_event.eventable] = last_grouped_event + previous_event = event_hash[[last_grouped_event.eventable_type, last_grouped_event.eventable_id]] + event_hash[[last_grouped_event.eventable_type, last_grouped_event.eventable_id]] = last_grouped_event handle(last_grouped_event, inventory, validate: validate, previous_event: previous_event) + yield last_grouped_event, inventory if block_given? end inventory end diff --git a/app/javascript/controllers/confirmation_controller.js b/app/javascript/controllers/confirmation_controller.js index 308009ca0e..bb04894b7f 100644 --- a/app/javascript/controllers/confirmation_controller.js +++ b/app/javascript/controllers/confirmation_controller.js @@ -102,6 +102,9 @@ export default class extends Controller { } submitForm() { + $(this.modalTarget).find('#modalClose').prop('disabled', true); + $(this.modalTarget).find('#modalYes').prop('disabled', true); + $(this.modalTarget).find('#modalNo').prop('disabled', true); $(this.modalTarget).modal("hide"); this.formTarget.requestSubmit(); } diff --git a/app/mailers/distribution_mailer.rb b/app/mailers/distribution_mailer.rb index c84dadf90c..8ef47f4a11 100644 --- a/app/mailers/distribution_mailer.rb +++ b/app/mailers/distribution_mailer.rb @@ -27,7 +27,11 @@ def partner_mailer(current_organization, distribution, subject, distribution_cha pdf = DistributionPdf.new(current_organization, @distribution).compute_and_render attachments[format("%s %s.pdf", @partner.name, @distribution.created_at.strftime("%Y-%m-%d"))] = pdf cc = [@partner.email] - cc.push(@partner.profile&.pick_up_email) if distribution.pick_up? + if distribution.pick_up? && @partner.profile&.pick_up_email + pick_up_emails = @partner.profile.split_pick_up_emails + cc.push(pick_up_emails) + end + cc.flatten! cc.compact! cc.uniq! diff --git a/app/models/donation.rb b/app/models/donation.rb index 2100b738af..6fb1466d78 100644 --- a/app/models/donation.rb +++ b/app/models/donation.rb @@ -136,6 +136,10 @@ def storage_view storage_location.nil? ? "N/A" : storage_location.name end + def in_kind_value_money + Money.new(value_per_itemizable) + end + private def combine_duplicates diff --git a/app/models/partner.rb b/app/models/partner.rb index b45edd9d39..9ac073d186 100644 --- a/app/models/partner.rb +++ b/app/models/partner.rb @@ -51,7 +51,7 @@ class Partner < ApplicationRecord validates :email, presence: true, uniqueness: { case_sensitive: false }, format: { with: URI::MailTo::EMAIL_REGEXP, on: :create } - validates :quota, numericality: true, allow_blank: true + validates :quota, numericality: { greater_than_or_equal_to: 0 }, allow_blank: true validate :correct_document_mime_type diff --git a/app/models/partners/profile.rb b/app/models/partners/profile.rb index af6416a83c..5c2de0381f 100644 --- a/app/models/partners/profile.rb +++ b/app/models/partners/profile.rb @@ -98,6 +98,7 @@ class Profile < Base validate :client_share_is_0_or_100 validate :has_at_least_one_request_setting + validate :pick_up_email_addresses self.ignored_columns = %w[ evidence_based_description @@ -118,6 +119,12 @@ def client_share_total served_areas.map(&:client_share).compact.sum end + def split_pick_up_emails + return nil if pick_up_email.nil? + + pick_up_email.split(/,|\s+/).compact_blank + end + private def check_social_media @@ -144,5 +151,22 @@ def has_at_least_one_request_setting errors.add(:base, "At least one request type must be set") end end + + def pick_up_email_addresses + # pick_up_email is a string of comma-separated emails, check specs for details + return if pick_up_email.nil? + + emails = split_pick_up_emails + if emails.size > 3 + errors.add(:pick_up_email, "There can't be more than three pick up email addresses.") + nil + end + if emails.uniq.size != emails.size + errors.add(:pick_up_email, "There should not be repeated email addresses.") + end + emails.each do |e| + errors.add(:pick_up_email, "Invalid email address for '#{e}'.") unless e.match? URI::MailTo::EMAIL_REGEXP + end + end end end diff --git a/app/services/exports/export_donations_csv_service.rb b/app/services/exports/export_donations_csv_service.rb index 254ebcd196..6e600ea09b 100644 --- a/app/services/exports/export_donations_csv_service.rb +++ b/app/services/exports/export_donations_csv_service.rb @@ -80,6 +80,9 @@ def base_table "Variety of Items" => ->(donation) { donation.line_items.map(&:name).uniq.size }, + "In-Kind Value" => ->(donation) { + donation.in_kind_value_money + }, "Comments" => ->(donation) { donation.comment } diff --git a/app/views/distributions/validate.html.erb b/app/views/distributions/validate.html.erb index cf2af814b6..3153d37808 100644 --- a/app/views/distributions/validate.html.erb +++ b/app/views/distributions/validate.html.erb @@ -2,7 +2,7 @@ diff --git a/app/views/partners/_partner_groups_table.html.erb b/app/views/partners/_partner_groups_table.html.erb index 0fa3730f48..458e606e17 100644 --- a/app/views/partners/_partner_groups_table.html.erb +++ b/app/views/partners/_partner_groups_table.html.erb @@ -54,8 +54,9 @@ No <% end %> - + <%= edit_button_to edit_partner_group_path(pg) %> + <%= delete_button_to(partner_group_path(pg),{confirm: confirm_delete_msg(pg.name)}) if pg.partners.none? %> <% end %> diff --git a/app/views/partners/profiles/edit/_pick_up_person.html.erb b/app/views/partners/profiles/edit/_pick_up_person.html.erb index f64b038baa..1c9319213e 100644 --- a/app/views/partners/profiles/edit/_pick_up_person.html.erb +++ b/app/views/partners/profiles/edit/_pick_up_person.html.erb @@ -9,7 +9,7 @@
<%= form.input :pick_up_name, label: "Pick Up Person Name", class: "form-control", wrapper: :input_group %> <%= form.input :pick_up_phone, label: "Pick Up Person's Phone #", class: "form-control", wrapper: :input_group %> - <%= form.input :pick_up_email, label: "Pick Up Person's Email", class: "form-control", wrapper: :input_group %> + <%= form.input :pick_up_email, label: "Pick Up Person's Email (comma-separated if multiple addresses)", placeholder: "example1@org.com, example2@org.com", class: "form-control", wrapper: :input_group %>
diff --git a/app/views/partners/requests/validate.html.erb b/app/views/partners/requests/validate.html.erb index 37d53adebc..b596194994 100644 --- a/app/views/partners/requests/validate.html.erb +++ b/app/views/partners/requests/validate.html.erb @@ -2,7 +2,7 @@ diff --git a/app/views/reports/annual_reports/show.html.erb b/app/views/reports/annual_reports/show.html.erb index 26eeb8b87d..d8e254e6f8 100644 --- a/app/views/reports/annual_reports/show.html.erb +++ b/app/views/reports/annual_reports/show.html.erb @@ -2,7 +2,7 @@
- <% content_for :title, "Audits - Inventory - #{current_organization.name}" %> + <% content_for :title, "Annual Survey - #{current_organization.name}" %>

<%= "#{params[:year]} NDBN Annual Report" %> for <%= current_organization.name %> diff --git a/config/routes.rb b/config/routes.rb index 17e4398dc0..fc1ed88259 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -226,7 +226,7 @@ def set_up_flipper end end - resources :partner_groups, only: [:new, :create, :edit, :update] + resources :partner_groups, only: %i(new create edit update destroy) resources :product_drives diff --git a/docs/user_guide/bank/community_donation_sites.md b/docs/user_guide/bank/community_donation_sites.md index 7c00a56292..fd2c1f2a78 100644 --- a/docs/user_guide/bank/community_donation_sites.md +++ b/docs/user_guide/bank/community_donation_sites.md @@ -1 +1,45 @@ -Not yet written \ No newline at end of file +# Donation Sites + +Donation sites are places where people drop off donations. You can manage the sites' information on the "Donation Sites" page under the "Community" section. + +![Donation Sites](images/community/donation_sites/donation_sites.jpg) + +Previously recorded information about donation sites appears on this page including the name of the donation site name, address, contact name, e-mail and phone number. + +### Adding a Donation Site + +Create a new site by populating the donation site, address, contact name, e-mail and phone fields and clicking the "Create" button. + +![Create Donation_Site](images/community/donation_sites/create_donation_site.jpg) + +Note that the donation site and address fields are mandatory while the contact name, email and phone are optional. + +You can also use the "+ New Donation Site" button which renders a form for you to fill in details of a new donation site. + +![Add Donation_Site](images/community/donation_sites/add_new_donation_site.jpg) + +After saving the site's details there will be a new row on the Donation Sites page. + +## Viewing Donation Site information + +Clicking on the "view" button beside a donation site will show detailed information for that site, including the donation site name, address, contact name, e-mail, phone number, storage location. It also shows a list of the donations for that site including the quantity of items and variety of items. You can drill down to see the full details of each donation by clicking "View donation details". + +![Donation Sites Details](images/community/donation_sites/donation_sites_details.jpg) + +## Editing Donation Site information + +Clicking on the "Edit" button beside a donation site in the donation site list lets you edit the name, address, contact name, email and phone number. + +![Edit Donation Site Details](images/community/donation_sites/edit_donation_site.jpg) + +## Deactivating a Donation Site + +Use the "Deactivate" button to delete information about a donation site that is no longer active. + +![Deactivate Donation Sites](images/community/donation_sites/deactivate_donation_site.jpg) + +## Exporting Donation Sites + +You can export the active donation sites by clicking on the "Export Donation Sites" button. This will provide a .csv file containing the name, address, and contact information for each active donation site. + +![Export Donation Sites](images/community/donation_sites/export_donation_sites.jpg) \ No newline at end of file diff --git a/docs/user_guide/bank/getting_started_choices.md b/docs/user_guide/bank/getting_started_choices.md new file mode 100644 index 0000000000..3f1d8107c3 --- /dev/null +++ b/docs/user_guide/bank/getting_started_choices.md @@ -0,0 +1,21 @@ +DRAFT USER GUIDE + +This page is a work in progress until we are all done. + +# Some up front things to think about + +Human Essentials is designed to help you manage your inventory and your relationship with partners, while also providing at least some information to help with your grant writing. + +Some questions that will help you when you are setting up Human Essentials. This is not a complete list. + +- Do you (your bank) want to start out immediately with my partners making requests, or do you want to just track our distributions for awhile first? +- Do you run on a monthly cycle, or more on an adhoc basis? +- What information do you need from our partners (see [Partner Profile](pm_partner_profiles.md) for what you *can* collect in the system - this is somewhat configurable)? +- Do you need to get all that information from your partners before they can enter requests? +- Do you have different groups of partners allowed to request different items? (if so, you'll want to look at [Partner Groups](pm_)) +- Who should have admin access? (See [Access Levels](getting_started_access_levels.md)) +- Do you repackage items into 'kits'? (common for period-focused banks) +- How do you want your partners requesting from you? (By # of individuals for each item, by quantity of each item, or by specific child (we don't recommend that unless they really have the need.) This is covered more in ["The Request Distribution Cycle"](pm_request_distribution_cycle.md)) +- If quantity, do you want to have our partners request in # of items, or in some other units (for instance packs, for diapers ) + +[TODO: This is a work in progress until we have completed everything else.] \ No newline at end of file diff --git a/docs/user_guide/bank/images/community/donation_sites/add_new_donation_site.jpg b/docs/user_guide/bank/images/community/donation_sites/add_new_donation_site.jpg new file mode 100644 index 0000000000..b9c3749701 Binary files /dev/null and b/docs/user_guide/bank/images/community/donation_sites/add_new_donation_site.jpg differ diff --git a/docs/user_guide/bank/images/community/donation_sites/create_donation_site.jpg b/docs/user_guide/bank/images/community/donation_sites/create_donation_site.jpg new file mode 100644 index 0000000000..22816b0618 Binary files /dev/null and b/docs/user_guide/bank/images/community/donation_sites/create_donation_site.jpg differ diff --git a/docs/user_guide/bank/images/community/donation_sites/deactivate_donation_site.jpg b/docs/user_guide/bank/images/community/donation_sites/deactivate_donation_site.jpg new file mode 100644 index 0000000000..c55e1098ea Binary files /dev/null and b/docs/user_guide/bank/images/community/donation_sites/deactivate_donation_site.jpg differ diff --git a/docs/user_guide/bank/images/community/donation_sites/donation_sites.jpg b/docs/user_guide/bank/images/community/donation_sites/donation_sites.jpg new file mode 100644 index 0000000000..f561ca1b55 Binary files /dev/null and b/docs/user_guide/bank/images/community/donation_sites/donation_sites.jpg differ diff --git a/docs/user_guide/bank/images/community/donation_sites/donation_sites_details.jpg b/docs/user_guide/bank/images/community/donation_sites/donation_sites_details.jpg new file mode 100644 index 0000000000..586e4fd678 Binary files /dev/null and b/docs/user_guide/bank/images/community/donation_sites/donation_sites_details.jpg differ diff --git a/docs/user_guide/bank/images/community/donation_sites/edit_donation_site.jpg b/docs/user_guide/bank/images/community/donation_sites/edit_donation_site.jpg new file mode 100644 index 0000000000..6aa6323dc5 Binary files /dev/null and b/docs/user_guide/bank/images/community/donation_sites/edit_donation_site.jpg differ diff --git a/docs/user_guide/bank/images/community/donation_sites/export_donation_sites.jpg b/docs/user_guide/bank/images/community/donation_sites/export_donation_sites.jpg new file mode 100644 index 0000000000..bc5cce1120 Binary files /dev/null and b/docs/user_guide/bank/images/community/donation_sites/export_donation_sites.jpg differ diff --git a/docs/user_guide/bank/images/partners/partners_announcements_1.png b/docs/user_guide/bank/images/partners/partners_announcements_1.png new file mode 100644 index 0000000000..6ba970ddee Binary files /dev/null and b/docs/user_guide/bank/images/partners/partners_announcements_1.png differ diff --git a/docs/user_guide/bank/images/partners/partners_announcements_2.png b/docs/user_guide/bank/images/partners/partners_announcements_2.png new file mode 100644 index 0000000000..cffa85af84 Binary files /dev/null and b/docs/user_guide/bank/images/partners/partners_announcements_2.png differ diff --git a/docs/user_guide/bank/images/partners/partners_groups_1.png b/docs/user_guide/bank/images/partners/partners_groups_1.png new file mode 100644 index 0000000000..4192424224 Binary files /dev/null and b/docs/user_guide/bank/images/partners/partners_groups_1.png differ diff --git a/docs/user_guide/bank/images/partners/partners_groups_2.png b/docs/user_guide/bank/images/partners/partners_groups_2.png new file mode 100644 index 0000000000..9ace79c32d Binary files /dev/null and b/docs/user_guide/bank/images/partners/partners_groups_2.png differ diff --git a/docs/user_guide/bank/images/partners/partners_importing_1.png b/docs/user_guide/bank/images/partners/partners_importing_1.png new file mode 100644 index 0000000000..836b8cc912 Binary files /dev/null and b/docs/user_guide/bank/images/partners/partners_importing_1.png differ diff --git a/docs/user_guide/bank/images/partners/partners_importing_2.png b/docs/user_guide/bank/images/partners/partners_importing_2.png new file mode 100644 index 0000000000..86c99d66fe Binary files /dev/null and b/docs/user_guide/bank/images/partners/partners_importing_2.png differ diff --git a/docs/user_guide/bank/pm_adding_a_partner.md b/docs/user_guide/bank/pm_adding_a_partner.md index 569b4b5aff..5c8afddc99 100644 --- a/docs/user_guide/bank/pm_adding_a_partner.md +++ b/docs/user_guide/bank/pm_adding_a_partner.md @@ -1,2 +1,7 @@ # Adding a single partner To add a single partner, you can either Click on the "Add a Partner" button in the "Getting Started" section of your dashboard (if you are, indeed, just getting started), or click "Partner Agencies" in the left-hand menu, then "All Partners", then "New Partner Agency". +[TODO: Needs screenshots of navigation] + +This just sets up the partner in your bank so you can distribute to them. If you want them to be able to make requests, you'll need to [invite](pm_inviting_a_partner.md) them. + +[Prior - Importing Partners](pm_importing_partners.md) [Next - Partner Groups](pm_partner_groups.md) \ No newline at end of file diff --git a/docs/user_guide/bank/pm_announcements.md b/docs/user_guide/bank/pm_announcements.md index 7c00a56292..f9eb061b1f 100644 --- a/docs/user_guide/bank/pm_announcements.md +++ b/docs/user_guide/bank/pm_announcements.md @@ -1 +1,33 @@ -Not yet written \ No newline at end of file +DRAFT USER GUIDE + +# Partner announcements + +Partner announcements are a great way to let your partners know about temporary situations -- like when you need to put some limits on what you can give out of a particular size. Some banks also use them to point to other resources. +These announcements appear on the partner's dashboard, so they'll be there whenever the partner logs in. + +## How to create an announcement + +Click on "Partner Agencies" in the left hand menu, then "Partner Announcement". This will bring up a list of all your partner announcements. + +![all announcments screen](images/partners/partners_announcements_1.png) + +Click the "New Announcements" button to bring up a form for a new announcement + +[TODO: Also check that the "SEnt by" actually has meaning. As org_admin1, it just says "N/A".] +![new announcement screen](images/partners/partners_announcements_2.png) + +Here you can fill in +- Up to 500 characters for your announcement. +- a URL for more information (useful for pointing to resources on your site) +- an expiry date. This is optional - you can leave announcements up 'forever' if you want. The expiry date is the last day the announcement will be shown [TODO: confirm that it's not the first day it's *not* shown.] + +Then, as soon as you click "Broadcast announcement", the partners will see it when they log in. + +## Changing an announcment +Simply click "edit" in the actions column beside the announcement you want to change, make your changes, and click "Broadcast announcement" + +## Deleting an announcement +You may want to clear out your older announcements. You can click "delete" beside any announcement to remove it from your view. It also will be deleted from the partner's view, if it was not yet expired. + + +[Prior: Other partner information](pm_other_information.md) [Next: Inventory -- Items](inventory_items.md) diff --git a/docs/user_guide/bank/pm_approving_a_partner.md b/docs/user_guide/bank/pm_approving_a_partner.md index 7c00a56292..40f7667f1f 100644 --- a/docs/user_guide/bank/pm_approving_a_partner.md +++ b/docs/user_guide/bank/pm_approving_a_partner.md @@ -1 +1,36 @@ -Not yet written \ No newline at end of file +DRAFT USER GUIDE + + +[TODO: Upcoming change review -- terminology. I feel like these are going to be done before the first pass is through so I'm leaving the screenshots out for the nonce.] + +# Approving partners + +There are three paths in to reviewing a partner's information before approving them to make requests -- + +1/ There is a list of partners awaiting approvals on the dashboard. To start the approval process, click "Review Application"; + +[TODO: Screenshot] + +2/ Click "Partner Agencies" and "All Partners" in the left-hand menu, then "Review Application" beside the partner. + +[TODO: Screenshot] + +or +3/ Click "Partner Agencies" and "All Partners" in the left-hand menu, then click the partner name. Scroll down to see the partner profile information, which starts just below the "Approve Partner" button + +[TODO: Screenshot] + +---------------------------------- + +In any of these cases, you can view all the answers in the profile. If something needs changing, you can do it yourself by clicking the "Edit Information" button, or the partner can make the change. + +See [Partner profiles](partner_profiles.md) for details on the partner profile. +[TODO: Link should go to the part of partner profiles that lists all the fields] + +Once you have reviewed the partner's info, and want to approve them to make requests, click the "Approve Partner " button. +[TODO: Screenshot] + +You should see a "Partner Approved!" message, and the status of the partner will show as "Approved" +[TODO: Screenshot] + +[Prior - Partner Profiles](pm_partner_profiles.md) [Next - Requesting Recertification](pm_requesting_recertification.md) diff --git a/docs/user_guide/bank/pm_editing_a_partner.md b/docs/user_guide/bank/pm_editing_a_partner.md new file mode 100644 index 0000000000..53f0a37cac --- /dev/null +++ b/docs/user_guide/bank/pm_editing_a_partner.md @@ -0,0 +1,19 @@ +DRAFT USER GUIDE + +# Editing a partner + +Note - this is editing a partner's basic information, not their [profile](pm_partner_profiles.md) + +You can edit a partner's basic information by clicking [Partner Agencies] in the left-hand menu, then [All Partners], then click on the partner you want to work with. + +That will bring up this screen: + +[TODO: Screenshot] + +Click "Edit Partner Information" to bring up this screen: + +[TODO: Screenshot] + +Make your changes, and click "Update Partner" to save. + +[Prior: Partner Groups](pm_partner_groups.md)[Next: Inviting a partner](pm_inviting_a_partner.md) diff --git a/docs/user_guide/bank/pm_importing_partners.md b/docs/user_guide/bank/pm_importing_partners.md index 7c00a56292..f44ddaa43d 100644 --- a/docs/user_guide/bank/pm_importing_partners.md +++ b/docs/user_guide/bank/pm_importing_partners.md @@ -1 +1,25 @@ -Not yet written \ No newline at end of file +DRAFT USER GUIDE +# Importing partners + +**N.B.** We've set up importing partners as a process you can only run once to prevent accidental imports and writing over existing partners. + +To import partners, you will need a .csv file containing their information -- this will just be the partner business name and primary contact email. + +We do provide an example file -- to get it, click on "Partner Agencies", "All Partners", then "Import Partners". (If you are a brand-new bank you might have gotten here through the "Getting Started" instructions on the dashboard.) +![Navigation to import](images/partners/partners_importing_1.png) +You will see a pop-up with a "Download example CSV" button (A) on it. Clicking that will download an example file for partner uploads. +![Partners import popup screen with instructions and buttons for downloading example, choosing import file, and importing the CSV](images/partners/partners_importing_2.png) + +That file is named partners_template.csv, and you should be able to find it in your downloads directory. + +You can edit this in your favourite spreadsheet program, or just as a text file. + + +When you have the information completed, navigate back to that same pop-up ( click on "Partner Agencies", "All Partners", then "Import Partners" ) +Now, follow the instructions under "2. Upload your CSV file " -- click "Choose File" (B), and pick the .csv file you've edited. The file name will appear after the "Choose File" button. Then, click "Import CSV". + +If you see "Partners were imported successfully!", that's good! You should review the list of partners on that page to make sure that all of the partners were imported -- if there was a badly formatted email, that partner will not appear. +If you should get a "500" error, it most likely means that your file is not formatted properly. + +[Prior - The request/distribution cycle](pm_request_distribution_cycle.md) +[Next - Adding a partner](pm_adding_a_partner.md) \ No newline at end of file diff --git a/docs/user_guide/bank/pm_inviting_a_partner.md b/docs/user_guide/bank/pm_inviting_a_partner.md index 7c00a56292..9b6f2feee3 100644 --- a/docs/user_guide/bank/pm_inviting_a_partner.md +++ b/docs/user_guide/bank/pm_inviting_a_partner.md @@ -1 +1,59 @@ -Not yet written \ No newline at end of file +Draft User Guide +# Inviting a partner + +Before a partner can make requests, they have to be invited and approved. + +When they are invited, they will receive an email with a link so that they can set up their password. +These links expire[TODO: How long?], but if they don't respond in time, +you can direct them to use the "Forgot your password?" function on the sign-in page +(https://humanessentials.app/signin) to get a new link to set their password. + +## Invite or Invite-and-Approve? +There are two options for enabling partners to request. + +The first, default, option is: + +1/ You invite the partner. + +2/ They click on the link and set up their password. + +3/ They then sign in and go into their "My Organization", update the profile information you need (se [profile](pm_partner_profiles.md). + +4/ They submit it for your approval + +5/ You approve it (there may be some back and forth here!) + +6/ They can now make requests. + +If you choose "Use One step Partner invite and approve process?" in ["My Organization"](getting_started_customization.md), +then the sequence is: + +1/ You invite and approve the partner. + +2/ They click on the link and set up their password. + +3/ They can now make requests. + +The *disadvantage* to the second method is that it may be harder to extract any +information you do need from your partners which can impact your grant-writing and +your annual reports. + +# How to invite a partner + +1/ Click on "Partner Agencies" in the left hand menu, then "All Partners" +2/ Find the partner you wish to invite. +3/ Click on the "Invite and Approve" button for that partner. + +[TODO: Add screenshot] + +# How to invite and approve a partner +First, make sure that you have "Use one-step partner invate and approve process" chosen in ["My Organization"](getting_started_customization.md). +Then... + +1/ Click on "Partner Agencies" in the left hand menu, then "All Partners" +2/ Find the partner you wish to invite. +3/ Click on the "Invite and Approve" button for that partner. + +[TODO: Add screenshot] + +[Prior: Partner Groups ](pm_partner_groups.md) [ Next: Partner Profiles](pm_partner_profiles.md) diff --git a/docs/user_guide/bank/pm_making_a_partner_inactive.md b/docs/user_guide/bank/pm_making_a_partner_inactive.md index 7c00a56292..000e7b3847 100644 --- a/docs/user_guide/bank/pm_making_a_partner_inactive.md +++ b/docs/user_guide/bank/pm_making_a_partner_inactive.md @@ -1 +1,35 @@ -Not yet written \ No newline at end of file +DRAFT USER GUIDE + +# Making a partner inactive + +Unfortunately, sometimes a partner will cease operations. Should that occur, you can make them inactive. This does not remove their historical information, but they won't appear in your selections, or in the all partners list (unless you specifically choose "Deactivated") + +To deactivate a partner: + +Go to the All Partners list (click on "Partner Agencies", then "All Partners" in the left hand menu),then click on the partner name to bring up that partner's screen. + +[TODO: Screenshot] + +Then click on "Deactivate Partner". A confirmation screen will appear. Click "OK". + +[TODO: Screenshot] + +You'll see a message that the partner is successfully deactivated, and they will not appear in the all partners list. + +## But what if I need to see them? + +If you need to see their information, go to the all partners list, and click on the "Deactivated" link. +This will bring up a list of all the deactivated partners. You can then click on them to view their information. + +[TODO: screenshot] + +## Can I reactivate them? + +Yes. Bring up the list of deactivated partners, then click on the "Reactivate" button beside the partner. Click "Ok" on the confirmation window that pops up. + + +[TODO: screenshot] + +This will bring them up in the same status as they were when you deactivated them. + +[Prior: Requesting recertification](pm_requesting_recertification.md) [Next: reactivating a partner](pm_partner_reactivation.md) diff --git a/docs/user_guide/bank/pm_other_information.md b/docs/user_guide/bank/pm_other_information.md new file mode 100644 index 0000000000..1cfb695e12 --- /dev/null +++ b/docs/user_guide/bank/pm_other_information.md @@ -0,0 +1,14 @@ +DRAFT USER GUIDE + +There are a few other things with partners that didn't fit into any of the sections above: + +1/ You can see the families served, children served and zipcodes served +How do we get these numbers? +[TODO: confirm how we determine families, children, zipcode in this case] +- Families served -- if the partner is using child requests, this is a count of the families they have entered. +- Children served -- if the partner is using child requests, this is a count of active children (confirm), +- Zipcodes - these are directly from the zipcodes entered in the partner profiles +- [TODO: Should we also show counties served?] +2/ You can see the prior distributions for this partner, at the bottom of the view of the partner. + +[Prior: Administering partner users](pm_partner_user_admin.md)[Next: Partner announcements](pm_announcements.md) diff --git a/docs/user_guide/bank/pm_partner_groups.md b/docs/user_guide/bank/pm_partner_groups.md new file mode 100644 index 0000000000..d7d977f6d3 --- /dev/null +++ b/docs/user_guide/bank/pm_partner_groups.md @@ -0,0 +1,42 @@ +DRAFT USER GUIDE +# Partner Groups + +You may have reasons that some partners are treated differently than others. Reasons we've seen for this include: +- Some banks have 'tiers' of partners, where some partners have different rules about what and when they can request. +- Some banks have grants that are tied to specific geographic areas, and set up specific items for those grants (that only partners in those geographic areas can request) + +Partner groups allow you to manage that. They allow you to set the item categories (see [Item Categories](inventory_items.md)) that a group can request +[TODO: Point at the precise place in Inventory Items that that is at.] + +If you are going to use partner groups, you should +1/ Set up your item categories +2/ Assign items to them +3/ Set up the groups, +4/ Then assign a group to the partners on an individual basis -- so you may want to set up your groups before adding your partners. + +# Adding a partner group +In the left-hand menu, click on "Partner Agencies", then "All Partners". The Partner Agencies list will appear. There are two tabs in this list "Partners" and "Groups". Click on "Groups". +Then click on "New Partner Group" +![Navigation for adding a partner group](images/partners/partners_groups_1.png) +This will bring up a form like this (the categories will be different): +![New partner group form](images/partners/partners_groups_2.png) +## Fields in the partner group form +### Name +This is the name your bank will use to refer to the partner group. It is not visible to the partners, and is not used in any reports. It does have to be unique among your partner groups. +### Which Item Categories Can They Request? +This lists the item categories you entered (in (see [Item Categories](inventory_items.md))) + +The partners who are in this group will only be able to request the items in the categories you check here. Note that they will not be able +they will not be able to request any items that are not in a category + +For clarity - if you do not choose any categories, they will not be able to choose any items, so if you are using partner groups, you have to use item categories. + +### Do you want to send deadline reminders to them every month? +This works in conjunction with "Reminder day" and "Deadline day", which is set on an organization level (see [Getting Started - Customization](getting_started_customization.md)) + +# What if a partner isn't in a group? +If a partner is not in a group, they can request any item that is visible to partners. +If a partner is not in a group, they will receive reminder emails, if they have been set up on an organization level [TODO: double-check the code for that] + +[Prior - Adding a partner](pm_adding_a_partner.md) +[Next - Inviting a partner](pm_inviting_a_partner.md) \ No newline at end of file diff --git a/docs/user_guide/bank/pm_partner_profiles.md b/docs/user_guide/bank/pm_partner_profiles.md index 7c00a56292..489ccefcb4 100644 --- a/docs/user_guide/bank/pm_partner_profiles.md +++ b/docs/user_guide/bank/pm_partner_profiles.md @@ -1 +1,170 @@ -Not yet written \ No newline at end of file +DRAFT USER GUIDE + +[TODO: Screenshots throughout. Waiting for Partner Profiles rework.] + +# Partner Profiles + +Partner profiles allow you to gather and hold a lot of information about your partners that may be useful for meeting your regulatory requirements, managing their experience, and for grant-writing. + +There is a *lot* of information in the profile, and the information that is needed varies from bank to bank. Once you decide what information you need from your partners, you can set up the partner profiles to show the sections that you need, customizing it through ["My organization"](getting_started_customization.md). + +## How does a partner fill in and submit their profile + +In the partner's view of the system, they can click on "Edit My Profile" to fill in all the information you want. +Once they have save this, they will also see a "Submit for Approval" button. Clicking that changes their status to "Waiting for Approval", and makes them appear in your [dashboard](essentials_dashboard.md) list of partners waiting for approval, as well as making a "Review partner's application" button appear beside them in your view of all the partners. + +# Viewing a partner's profile +The partner's profile is viewable by clicking Partner Agencies in the left hand menu, then All Partners, then "view" beside the partner in question. Scroll down to "Partner Profile" +# Editing a partner's profile +You can edit a partner's profile clicking Partner Agencies in the left hand menu, then All Partners, then "view" beside the partner in question. Scroll down to "Partner Profile", then click "Edit Information." + +# The sections +The high level sections of the partner profile are: +- Agency Information (not configurable) +- Program / Delivery Address (if different) +- Media Information +- Agency Stability +- Organization Capacity +- Sources of Funding +- Area Served (County/Client Share %) +- Population Served +- Executive Director +- Primary Contact +- Pick up Person +- Race/Ethnicity of Client Base +- Agency Distribution Information +- Additional Documents +- Settings (not configurable) + +## Agency Information (not configurable) +This section contains basic agency information that most, if not all, partners will need to provide: +- Agency Name +- Agency Type +- Other Agency Type (i.e, details if "other" is chosen in Agency Type) +- 501(c)(3) IRS Determination Letter -- this is a file to upload said letter. +- Agency Mission +- Address +- City +- State +- Zip Code + +## Program / Delivery Address (if different) +Sometimes, a large agency may have a separate address for a specific program or for deliveries/shipping. +- Address +- City +- State +- Zip Code + +## Media Information +This provides a place for the agency to indicate their major communication outlets. + +*If* the bank chooses to ask for this information, the partner must fill in at least one of the fields: + +- Website +- Facebook +- Twitter (yes, we know it's called X now) +- Instagram +- No Social Media Presence + + + +## Agency Stability +This section is about the maturity of the agency. + +None of these fields are mandatory. + +- Year founded +- Form 990 +- Program Name(s) +- Program Description(s) +- Agency Age +- Evidence Based? +- Case Management +- How Are Essentials (e.g. diapers, period supplies) Used in Your Program? +- Do You Receive Essentials From Other Sources? +- Currently Providing Diapers? + +## Organization Capacity +This section has specific questions about the volume of the agency's needs. +- Client Capacity +- Storage Space (yes/no) +- Storage Space Description + +## Sources of Funding +How does the agency get its funding? +- Sources of Funding +- How do you currently obtain diapers? +- Essentials Budget +- Essentials Funding Source + +## Area Served (County/Client Share %) +This asks what county/county equivalents the agency serves and what proportion of their client share is in which area. + +At this time, this only covers U.S. counties. We believe the list covers *all* of the U.S, including some areas that are not counties. +Let us know if you need more! + +The sum of the client share has to be either 0 or 100, and the numbers have to be positive whole numbers. + +You start out with 1 county, but can add more with the "Add Another County button" + +## Population Served +This section has only two questions: +- Clients Have An Income Requirement to Work With You? +- Do You Verify The Income Of Your Clients? +- +## Race/Ethnicity of Client Base +This section is comprised of questions about the race/ethnicity of the client base and the poverty level of the client base. +There is no check on whether the numbers add up to 100 -- because there may be overlap. + +### Race/Ethnicity: +- % African American; % Caucasian; % Hispanic; % Asian; % American Indian; % Pacific Island; % Multi-racial; % Other +- Zip Codes Served + +### Poverty Information of those Served: +- % at FPL or Below (FPL is Federal Poverty Line) +- % above 1-2 times FPL +- % Greater than 2 times FPL +- % Poverty Unknown + +## Executive Director +Contact information for the head of the agency: +- Executive Director Name +- Executive Director Phone +- Executive Director Email +[TODO: Check if this is ever used for outgoing email] +## Primary Contact +Contact information for your bank's primary contact +- Primary Contact Name +- Primary Contact Phone +- Primary Contact Cell +- Primary Contact Email +[TODO: Check if 1/ Primary Contact email is defaulted on partner creation, and 2/ confirm that this is used for outgoing emails. Also what about multiple emails -- we need to either restrict to a single, or do the same thing as with the pickup person about them.] + +## Pick Up Person +The Pick Up person will receive an email when the distribution is scheduled. +[TODO: Check -- is it only on pickups, or do they get it for deliveries too?] + +- Pick Up Person Name +- Pick Up Person's Phone # +- Pick Up Person's Email +Note this can be multiple, comma-separated emails. + +## Agency Distribution Information +This section is about the agency's practices when distributing to their clients. +- Distribution Times +- New Client Times +- More Docs Required + - This is meant to be the documentation required for new clients + +## Additional Documents +This is a place to upload additional documents that you need from your partner. + +## Settings (not configurable) +Many banks restrict the partners to one kind of request (see [Customization](getting_started_customization.md)). If you don't, the partner can still simplify their request experience by unclicking the items that don't apply. +Only the options that you haven't already turned off will show up here, and at least one has to be checked. +- Enable Quantity-based Requests +- Enable Child-based Requests +- Enable Requests for Individuals + + +[Prior: inviting a partner](pm_inviting_a_partner.md) [Next: Approving a partner](pm_approving_a_partner.md) \ No newline at end of file diff --git a/docs/user_guide/bank/pm_partner_reactivation.md b/docs/user_guide/bank/pm_partner_reactivation.md new file mode 100644 index 0000000000..a1f3de7bd2 --- /dev/null +++ b/docs/user_guide/bank/pm_partner_reactivation.md @@ -0,0 +1,18 @@ +DRAFT USER GUIDE + +# Partner reactivation + +To reactivate a partner: + +Go to the all partners list, and click on the "Deactivated" link. +This will bring up a list of all the deactivated partners. You can then click on them to view their information. + +Then click on the "Reactivate" button beside the partner. Click "Ok" on the confirmation window that pops up. + + +[TODO: screenshot] + +This will bring them up in the same status as they were when you deactivated them. + + +[Prior: Making a partner inactive](pm_making_a_partner_inactive.md) [Next: administering partner users](pm_partner_user_admin.md) diff --git a/docs/user_guide/bank/pm_partner_user_admin.md b/docs/user_guide/bank/pm_partner_user_admin.md index 7c00a56292..f0a0dea0a0 100644 --- a/docs/user_guide/bank/pm_partner_user_admin.md +++ b/docs/user_guide/bank/pm_partner_user_admin.md @@ -1 +1,18 @@ -Not yet written \ No newline at end of file +DRAFT USER GUIDE + +# Administering partner users + +Partners can add users themselves, but occasionally a bank may need to step in and administer partner users (for instance, if the only person who uses the system leaves the partner.) + +## Where do you administer partner users? + +If you need to administer a partner's users, click on "Partner Agencies" in the left-hand menu, then "All Partners", +then click on the specific partner you want to administer, and then "Manage Users" near the top of that screen. + +[TODO: Navigation screenshots] + +This will bring you to a list of users for that partner. Here you can resend invitations (A), start a reset password process (B) (they will receive an email with a link to reset their password), Remove access to that partner from the user(C), or invite a new user to that partner (D). + +[TODO: Annotated screenshot for partner user admin screen] + +[Prior - Partner reactivation](pm_partner_reactivation.md) [Next: Other partner information](pm_other_information.md) \ No newline at end of file diff --git a/docs/user_guide/bank/pm_request_distribution_cycle.md b/docs/user_guide/bank/pm_request_distribution_cycle.md index 7c00a56292..a4c33f7e96 100644 --- a/docs/user_guide/bank/pm_request_distribution_cycle.md +++ b/docs/user_guide/bank/pm_request_distribution_cycle.md @@ -1 +1,36 @@ -Not yet written \ No newline at end of file +DRAFT USER GUIDE +# The Request/Distribution Cycle + +You can distribute materials to your partners as soon as you have them entered into the system. However, many banks find it useful to allow their partners to request products through the system. + +To allow partners to make requests, you will have to [invite](pm_inviting_a_partner.md) them, and [approve](pm_approving_a_partner.md) them, based on a [profile](pm_partner_profiles.md) they fill out and submit. There is a shortcut to do both at once. + +For clarity - you can make distributions without requests. + +So, how does this work? + +1/ The partner fills in a request form on the system and saves it. + +2/ They get an email confirming that the request has been sent to you. + +3/ This requests appears in the system in the following places: a) your [dashbboard](essentials_dashboard.md), under "Outstanding Requests", and in the [Requests](essentials_requests.md) view. + +4/ You view the request, and click "Fulfill request" (you so have the option of cancelling it) + +5/ That marks the request as "started" -- so other staff don't grab the same request to start working on it, and brings up a new [Distribution] for the partner, with the information we can pre-fill, pre-filled. + +6/ You fill in the rest of the information, and make any adjustments required. + +7/ Clicking "Save" creates the distribution in the system, adjusting the inventory, and sends an email to the partner with the details attached. They can also see the distribution in their "Upcoming Distributions" list. + +8/ You set aside the products for pick-up / deliver them / ship them + +9/ You can mark the distribution as complete. + +Please note that you can edit distributions (e.g. if you have more product come in before pickup). The partner does not get a new email if you do that.[TODO: Confirm] + + +[Prior: Pick ups and deliveries](essentials_pick_ups.md) [Next: importing partners](pm_importing_partners.md) + + + diff --git a/docs/user_guide/bank/pm_requesting_recertification.md b/docs/user_guide/bank/pm_requesting_recertification.md index 7c00a56292..13c6f38c9e 100644 --- a/docs/user_guide/bank/pm_requesting_recertification.md +++ b/docs/user_guide/bank/pm_requesting_recertification.md @@ -1 +1,23 @@ -Not yet written \ No newline at end of file +DRAFT USER GUIDE + +# Requesting Recertification + +From time to time, perhaps annually, you may want to confirm that the information your partner has provided is correct. That is what "request recertification" is for. + + +#### *** N.B. Once you request recertification, the partner will not be able to enter requests until you have approved them again!! *** + +If you request recertification from a partner, they will receive an email requesting that they update their information +[TODO: provide text of email], and their status will change to "Recertification required". + +To request recertification, go to the All Partners screen (click on "Partner Agencies", then "All Partners" in the left hand menu), then click the red "Request Recertification" button beside the partner. A confirmation window will pop up - check that you picked the right partner, and click "OK". + +[TODO: provide screenshot -- that screen is likely to change before the end of writing.] + +This will change the status of the partner to "Recertification required". + +Once they have made their changes, you can [approve](pm_approving_a_partner.md) them again, which will restore their ability to make requests. + +(Note: They have a "Submit for Approval" button, which would change their status to "Waiting for Approval", and make them appear in your dashboard, but you can bypass that step) + +[Prior -- Approving a partner](pm_approving_a_partner.md) [Next -- making a partner inactive ](pm_making_a_partner_inactive.md) diff --git a/docs/user_guide/bank/readme.md b/docs/user_guide/bank/readme.md index e6bd8a70a1..e9905ecd44 100644 --- a/docs/user_guide/bank/readme.md +++ b/docs/user_guide/bank/readme.md @@ -6,12 +6,13 @@ This user guide is meant for users of the human essentials app at essentials ban 1. [Is Human Essentials right for you?](intro_i.md) (what we help with and what we don't) 2. [Support](intro_ii.md) 2. Getting started - 1. [Storage Locations](getting_started_storage_locations.md) - 2. [Partner Agencies](getting_started_partners.md) - 3. [Donation sites](getting_started_donation_sites.md) - 4. [Inventory](getting_started_inventory.md) - 5. [Customization and other organization-level info](getting_started_customization.md) - 6. Adding your staff + 1. [Some choices you'll want to think about at the start. ](getting_started_choices.md) + 3. [Storage Locations](getting_started_storage_locations.md) + 3. [Partner Agencies](getting_started_partners.md) + 4. [Donation sites](getting_started_donation_sites.md) + 5. [Inventory](getting_started_inventory.md) + 6. [Customization and other organization-level info](getting_started_customization.md) + 7. Adding your staff 1. [Levels of access](getting_started_access_levels.md) 2. [User management](getting_started_user_management.md) 3. Everyday essentials @@ -23,16 +24,20 @@ This user guide is meant for users of the human essentials app at essentials ban 6. [Pick Ups and Deliveries](essentials_pick_ups.md) 4. Partner Management 1. [The request/distribution cycle](pm_request_distribution_cycle.md) - 2. [Adding a partner](pm_adding_a_partner.md) - 3. [Importing partners](pm_importing_partners.md) - 4. [Administering partner users](pm_partner_user_admin.md) - 4. The partner approval cycle + 2. [Importing partners](pm_importing_partners.md) + 3. [Adding a partner](pm_adding_a_partner.md) + 4. [Partner Groups](pm_partner_groups.md) + 5. [Editing a Partner](pm_editing partners.md) + 5. The partner approval cycle 1. [Inviting a partner](pm_inviting_a_partner.md) 2. [Partner profiles](pm_partner_profiles.md) 3. [Approving a partner](pm_approving_a_partner.md) 4. [Requesting recertification](pm_requesting_recertification.md) - 5. [Making a partner inactive](pm_making_a_partner_inactive.md) - 5. [Announcements](pm_announcements.md) + 6. [Making a partner inactive](pm_making_a_partner_inactive.md) + 7. [Reactivating a partner](pm_parter_reactivation.md) + 7. [Administering partner users](pm_partner_user_admin.md) + 8. [Other partner information](pm_other_information.md) + 9. [Announcements](pm_announcements.md) 5. Inventory 1. [Items](inventory_items.md) 2. [Storage Locations](inventory_storage_locations.md) diff --git a/docs/user_guide/bank/reports_annual_survey.md b/docs/user_guide/bank/reports_annual_survey.md index 71dd6e6b69..f9702437f3 100644 --- a/docs/user_guide/bank/reports_annual_survey.md +++ b/docs/user_guide/bank/reports_annual_survey.md @@ -1,7 +1,7 @@ DRAFT USER GUIDE ## Annual Survey The annual survey contains information useful for completing the NDBN or Alliance for Period Supplies annual survey, but also for grant writing. -Each year's annual survey becomes available January 1 of the following +Each year's annual survey becomes available January 1 of the following year. ## How to get the report diff --git a/docs/user_guide/pm_partner_other.md b/docs/user_guide/pm_partner_other.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spec/mailers/distribution_mailer_spec.rb b/spec/mailers/distribution_mailer_spec.rb index a44254c8b1..0ebd95d9e0 100644 --- a/spec/mailers/distribution_mailer_spec.rb +++ b/spec/mailers/distribution_mailer_spec.rb @@ -5,7 +5,8 @@ let(:distribution) { create(:distribution, organization: user.organization, comment: "Distribution comment", partner: partner) } let(:request) { create(:request, distribution: distribution) } - let(:pick_up_email) { 'pick_up@org.com' } + let(:pick_up_email) { 'pick_up@org.com, second_pick_up@org.com' } + let(:pick_up_emails) { ['pick_up@org.com', 'second_pick_up@org.com'] } before do organization.default_email_text = "Default email text example\n\n%{delivery_method} %{distribution_date}\n\n%{partner_name}\n\n%{comment}" @@ -23,7 +24,7 @@ expect(mail.body.encoded).to match("Default email text example") expect(mail.html_part.body).to match(%(From: me@org.com)) expect(mail.to).to eq([distribution.request.user_email]) - expect(mail.cc).to eq([distribution.partner.email, pick_up_email]) + expect(mail.cc).to eq([distribution.partner.email, pick_up_emails.first, pick_up_emails.second]) expect(mail.from).to eq(["no-reply@humanessentials.app"]) expect(mail.subject).to eq("test subject from TEST ORG") end @@ -43,10 +44,11 @@ expect(mail.body.encoded).to match("picked up") end - context 'when parners profile pick_up_email is present' do - it 'sends email to the primary contact and partner`s pickup person' do + context 'when partners profile pick_up_email is present' do + it 'sends email to the primary contact and partner`s pickup persons' do expect(mail.cc.first).to match(partner.email) - expect(mail.cc.second).to match(pick_up_email) + expect(mail.cc.second).to match(pick_up_emails.first) + expect(mail.cc.third).to match(pick_up_emails.second) end context 'when pickup person happens to be the same as the primary contact' do diff --git a/spec/models/partner_spec.rb b/spec/models/partner_spec.rb index 1eac977304..c632759004 100644 --- a/spec/models/partner_spec.rb +++ b/spec/models/partner_spec.rb @@ -70,6 +70,12 @@ end it { should validate_numericality_of(:quota).allow_nil } + + it "validates that the quota is greater than or equal to 0" do + expect(build(:partner, quota: -1)).not_to be_valid + expect(build(:partner, quota: 0)).to be_valid + expect(build(:partner, quota: 1)).to be_valid + end end context "callbacks" do diff --git a/spec/models/partners/profile_spec.rb b/spec/models/partners/profile_spec.rb index 0ecc60220a..76aaac100e 100644 --- a/spec/models/partners/profile_spec.rb +++ b/spec/models/partners/profile_spec.rb @@ -167,6 +167,85 @@ end end + describe "split pick up email" do + let(:profile) { build(:partner_profile, pick_up_email: "pick_up@org.com, pick_up2@org.com") } + it "should disregard commas at the beginning or end of the string" do + profile.update(pick_up_email: ", pick_up@org.com, pick_up2@org.com,") + expect(profile.split_pick_up_emails).to match_array(["pick_up@org.com", "pick_up2@org.com"]) + end + + it "should allow optional whitespace between email addresses" do + profile.update(pick_up_email: "pick_up@org.com, pick_up2@org.com") + expect(profile.split_pick_up_emails).to match_array(["pick_up@org.com", "pick_up2@org.com"]) + profile.update(pick_up_email: "pick_up@org.com,pick_up2@org.com") + expect(profile.split_pick_up_emails).to match_array(["pick_up@org.com", "pick_up2@org.com"]) + end + + it "should handle nil value" do + profile.update(pick_up_email: nil) + expect(profile.split_pick_up_emails).to be_nil + end + + it "should return empty array if for when pick_up_email is an empty string" do + profile.update(pick_up_email: "") + expect(profile.split_pick_up_emails).to match_array([]) + end + + it "should correctly split strings" do + profile.update(pick_up_email: "test me, pick_up@org.com, pick_up2@org.com, test me") + expect(profile.split_pick_up_emails).to match_array(["test", "me", "pick_up@org.com", "pick_up2@org.com", "test", "me"]) + profile.update(pick_up_email: "test me. pick_up@org.com, pick_up2@org.com. test me") + expect(profile.split_pick_up_emails).to match_array(["test", "me.", "pick_up@org.com", "pick_up2@org.com.", "test", "me"]) + end + end + + describe "pick up email address validation" do + context "number of email addresses" do + let(:profile) { build(:partner_profile, pick_up_email: "pick_up@org.com, pick_up2@org.com, pick_up3@org.com, pick_up4@org.com") } + it "should not allow more than three email addresses" do + expect(profile).to_not be_valid + profile.update(pick_up_email: "pick_up@org.com, pick_up2@org.com, pick_up3@org.com") + expect(profile).to be_valid + end + + it "should not allow repeated email addresses" do + profile.update(pick_up_email: "pick_up@org.com, pick_up2@org.com, pick_up@org.com") + expect(profile).to_not be_valid + end + end + + context "invalid emails" do + let(:profile) { build(:partner_profile, pick_up_email: "pick_up@org.com, pick_up2@org.com, asdf") } + it "should not allow invalid email addresses" do + expect(profile).to_not be_valid + profile.update(pick_up_email: "test me, pick_up@org.com, pick_up2@org.com, test me") + expect(profile).to_not be_valid + profile.update(pick_up_email: "pick_up@org.com, pick_up2@org.com") + expect(profile).to be_valid + end + + it "should not allow input having emails separated by non-word characters" do + profile.update(pick_up_email: "test me. pick_up@org.com, pick_up2@org.com. test me") + expect(profile).to_not be_valid + profile.update(pick_up_email: "pick_up@org.com. pick_up2@org.com") + expect(profile).to_not be_valid + profile.update(pick_up_email: "pick_up@org.com.pick_up2@org.com") + expect(profile).to_not be_valid + profile.update(pick_up_email: "pick_up@org.com/ pick_up2@org.com/ pick_up3@org.com") + expect(profile).to_not be_valid + profile.update(pick_up_email: "pick_up@org.com- pick_up2@org.com- pick_up3@org.com") + expect(profile).to_not be_valid + profile.update(pick_up_email: "pick_up@org.com' pick_up2@org.com' pick_up3@org.com") + expect(profile).to_not be_valid + end + + it "should handle nil value" do + profile.update(pick_up_email: nil) + expect(profile).to be_valid + end + end + end + describe "client share behaviour" do context "no served areas" do let(:profile) { build(:partner_profile) } diff --git a/spec/requests/partner_group_spec.rb b/spec/requests/partner_group_spec.rb new file mode 100644 index 0000000000..f91dbc1dcf --- /dev/null +++ b/spec/requests/partner_group_spec.rb @@ -0,0 +1,46 @@ +RSpec.describe "PartnerGroups", type: :request do + let(:user) { create(:user) } + let(:partner_group) { create(:partner_group) } + + before do + sign_in(user) + end + + describe "DELETE #destroy" do + context "when partner group has no partners" do + let!(:partner_group) { create(:partner_group) } + before { get partners_path + "#nav-partner-groups" } + it "destroys the partner group" do + within "#nav-partner-groups" do + expect(response.body).to have_link("Delete") + end + expect { + delete partner_group_path(partner_group) + }.to change(PartnerGroup, :count).by(-1) + + expect(flash[:notice]).to eq("Partner Group was successfully deleted.") + expect(response).to redirect_to(partners_path + "#nav-partner-groups") + end + end + + context "when partner group has partners" do + let!(:partner_group) { create(:partner_group) } + + before do + create(:partner, partner_group: partner_group) + get partners_path + "#nav-partner-groups" + end + it "does not destroy the partner group" do + within "#nav-partner-groups" do + expect(reponse.body).not_to have_link("Delete") + end + expect { + delete partner_group_path(partner_group) + }.not_to change(PartnerGroup, :count) + + expect(flash[:alert]).to eq("Partner Group cannot be deleted.") + expect(response).to redirect_to(partners_path + "#nav-partner-groups") + end + end + end +end diff --git a/spec/services/exports/export_donations_csv_service_spec.rb b/spec/services/exports/export_donations_csv_service_spec.rb index 237c1a4513..7990929f63 100644 --- a/spec/services/exports/export_donations_csv_service_spec.rb +++ b/spec/services/exports/export_donations_csv_service_spec.rb @@ -51,6 +51,7 @@ "Storage Location", "Quantity of Items", "Variety of Items", + "In-Kind Value", "Comments" ] + expected_item_headers end @@ -84,6 +85,7 @@ donation.storage_view, donation.line_items.total, total_item_quantity.count(&:positive?), + donation.in_kind_value_money, donation.comment ] diff --git a/spec/system/distribution_system_spec.rb b/spec/system/distribution_system_spec.rb index 05ebb31492..0b310d76f5 100644 --- a/spec/system/distribution_system_spec.rb +++ b/spec/system/distribution_system_spec.rb @@ -130,6 +130,34 @@ expect(page.find(".alert-info")).to have_content "created" end + # Issue #4644 + it "Disables confirmation and modal close buttons after clicking confirm" do + item = View::Inventory.new(organization.id).items_for_location(storage_location.id).first.db_item + item.update!(on_hand_minimum_quantity: 5) + TestInventory.create_inventory(organization, + { + storage_location.id => { item.id => 20 } + }) + + visit new_distribution_path + select "Test Partner", from: "Partner" + select "Test Storage Location", from: "From storage location" + select2(page, 'distribution_line_items_item_id', item.name, position: 1) + select "Test Storage Location", from: "distribution_storage_location_id" + fill_in "distribution_line_items_attributes_0_quantity", with: 15 + + click_button "Save" + + # Disable form submission so form doesn't immediately submit and we can check button state + page.execute_script("$('form#new_distribution').attr('action', 'javascript: void(0);');") + + click_button(id: "modalYes") + + expect(page).to have_button(id: "modalYes", visible: false, disabled: true) + expect(page).to have_button(id: "modalNo", visible: false, disabled: true) + expect(page).to have_button(id: "modalClose", visible: false, disabled: true) + end + it "Displays a complete form after validation errors" do visit new_distribution_path diff --git a/spec/system/partners/family_requests_system_spec.rb b/spec/system/partners/family_requests_system_spec.rb index 44c632442c..f6c5642e4c 100644 --- a/spec/system/partners/family_requests_system_spec.rb +++ b/spec/system/partners/family_requests_system_spec.rb @@ -66,6 +66,22 @@ click_link "Your Previous Requests" expect(page).to have_text("Request History") end + + # Issue #4644 + it "disables confirmation and modal close buttons after clicking confirm" do + visit partners_requests_path + find('a[aria-label="Create a request for a child or family"]').click + click_button("Submit Essentials Request") + + # Disable form submission so form doesn't immediately submit and we can check button state + page.execute_script("$(\"form[action='/partners/family_requests']\").attr('action', 'javascript: void(0);');") + + click_button(id: "modalYes") + + expect(page).to have_button(id: "modalYes", visible: false, disabled: true) + expect(page).to have_button(id: "modalNo", visible: false, disabled: true) + expect(page).to have_button(id: "modalClose", visible: false, disabled: true) + end end describe "filtering children" do