diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 7597add361..662df208d9 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -2,7 +2,8 @@ # it will be more efficient to change the image. # See https://github.com/devcontainers/images/blob/main/src/ruby/history/ FROM mcr.microsoft.com/devcontainers/ruby:dev-3.2-buster -RUN apt -y update && apt install -y vim curl gpg postgresql postgresql-contrib +RUN export DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get -y install vim curl gpg postgresql postgresql-contrib RUN cd /tmp -RUN wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb -RUN apt install -y ./google-chrome-stable_current_amd64.deb \ No newline at end of file +RUN wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \ + && apt-get -y install ./google-chrome-stable_current_amd64.deb \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ee345ad95f..27e89fdc40 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,24 @@ // README at: https://github.com/devcontainers/templates/tree/main/src/postgres { "dockerComposeFile": "docker-compose.yml", - "forwardPorts": [3000, 5432], + "features": { + "ghcr.io/devcontainers/features/desktop-lite:1": {} + }, + "forwardPorts": [3000, 5432, 6080], + "portsAttributes": { + "3000": { + "label": "Application", + "onAutoForward": "silent" + }, + "5432": { + "label": "Database", + "onAutoForward": "silent" + }, + "6080": { + "label": "Desktop", + "onAutoForward": "silent" + } + }, "workspaceFolder": "/workspaces/human-essentials", "service": "app", "customizations": { @@ -21,5 +38,5 @@ "DOCKER": "true" }, - "postCreateCommand": ".devcontainer/post-create.sh" + "postCreateCommand": "bash -i .devcontainer/post-create.sh" } diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index b04277eb83..d04b16f23d 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -9,6 +9,9 @@ services: volumes: - ../..:/workspaces:cached + # Increase shared memory for Chrome to run in Fluxbox + shm_size: "2gb" + # Overrides default command so things don't shut down after the process ends. command: sleep infinity diff --git a/.devcontainer/launch.json.codespaces b/.devcontainer/launch.json.codespaces new file mode 100644 index 0000000000..dd375f5f89 --- /dev/null +++ b/.devcontainer/launch.json.codespaces @@ -0,0 +1,32 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "ruby_lsp", + "request": "launch", + "name": "Debug rspec at cursor with browser", + "program": "bundle exec rspec ${file}:${lineNumber}", + "env": { + "NOT_HEADLESS": "true" + } + }, + { + "type": "ruby_lsp", + "request": "launch", + "name": "Debug with Events rspec at cursor with browser", + "program": "bundle exec rspec ${file}:${lineNumber}", + "env": { + "NOT_HEADLESS": "true", + "EVENTS_READ": "true" + } + }, + { + "type": "ruby_lsp", + "request": "attach", + "name": "Attach to a live server" + } + ] +} \ No newline at end of file diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh index 69debb7c8b..e1dcaa2fcc 100755 --- a/.devcontainer/post-create.sh +++ b/.devcontainer/post-create.sh @@ -2,6 +2,7 @@ RUBY_VERSION="$(cat .ruby-version | tr -d '\n')" # copy the file only if it doesn't already exist cp -n .devcontainer/.env.codespaces .env +mkdir -p .vscode && cp -n .devcontainer/launch.json.codespaces .vscode/launch.json # If the project's required ruby version changes from 3.2.2, this command # will download and compile the correct version, but it will take a long time. @@ -11,4 +12,8 @@ if [ "$RUBY_VERSION" != "3.2.2" ]; then echo "Ruby $RUBY_VERSION installed" fi +nvm install node +rbenv init bash +rbenv init zsh + bin/setup diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9bb38de8e7..9fa5a6792e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -23,6 +23,9 @@ updates: - dependency-name: geocoder versions: - 1.6.7 + - dependency-name: strong_migrations + versions: + - 1.8.0 - dependency-name: devise_invitable versions: - 2.0.4 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d11de908d5..a1c7e260a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,6 +2,7 @@ We โ™ฅ contributors! By participating in this project, you agree to abide by the Ruby for Good [code of conduct](https://github.com/rubyforgood/human-essentials/blob/main/code-of-conduct.md). If you're new here, here are some things you should know: +- A great introductory overview of the application is available at the [wiki](https://github.com/rubyforgood/human-essentials/wiki/Application-Overview). - Issues tagged "Help Wanted" are self-contained and great for new contributors - Pull Requests are reviewed within a week or so - Ensure your build passes linting and tests and addresses the issue requirements @@ -77,13 +78,18 @@ You won't be yelled at for giving your best effort. The worst that can happen is ``` -## Codespaces - EXPERIMENTAL ๐Ÿ› ๏ธ +## Codespaces and Dev Container - EXPERIMENTAL ๐Ÿ› ๏ธ [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/rubyforgood/human-essentials/tree/main?quickstart=1) -1. Follow the link above or follow instructions to [create a new Codespace.](https://docs.github.com/en/codespaces/developing-in-a-codespace/creating-a-codespace-for-a-repository); You can use the web editor, or even better open the Codespace in VSCode +[![Clone and open in VSCode Dev Container](https://img.shields.io/static/v1?label=Dev%20Containers&message=Clone%20and%20Open%20in%20VSCode&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/rubyforgood/human-essentials) + +1. Create the container: + - To run the container on a Github VM, follow the Codespace link above. You can connect to the Codespace using VSCode or the VSCode web editor. + - Or follow instructions to [create a new Codespace.](https://docs.github.com/en/codespaces/developing-in-a-codespace/creating-a-codespace-for-a-repository) + - To clone this repo and run the container locally, follow instructions to [install VSCode and Docker](https://code.visualstudio.com/docs/devcontainers/containers). Click the Dev Container link above. Don't forget to add a git remote pointing to your fork once the container is setup and you want to push changes. 2. Wait for the container to start. This will take a few (10-15) minutes since Ruby needs to be installed, the database needs to be created, and the `bin/setup` script needs to run -3. Run `bin/start` and visit the URL that pops in VSCode up to see the human essentials page +3. Run `bin/start`. On the Ports tab, visit the forwarded port 3000 URL marked as Application to see the human essentials page. 4. Login as a sample user with the default [credentials](#credentials). ## Troubleshooting ๐Ÿ‘ท๐Ÿผโ€โ™€๏ธ @@ -92,7 +98,13 @@ Please let us know by opening up an issue! We have many new contributors come th - *"My RBENV installation didn't work!"* - The rbenv repository provides a [rbenv-doctor script](https://github.com/rbenv/rbenv-installer#rbenv-doctor) to verify the installation and check if a ruby version is installed -# ๐Ÿค Contributing workflow +# Wiki Contribution Workflow +1. Follow this [SO post](https://stackoverflow.com/a/56480628/13342792) to force push the main repo's Wiki to your fork's Wiki. +2. Make edits to your fork's Wiki. +3. Create a documentation issue about your changes. Make sure to note which pages you changed and link to your fork's Wiki. +4. Someone will review and approve your changes and merge them into the main Wiki following this [SO post](https://stackoverflow.com/a/56810747/13342792) + +# ๐Ÿค Code Contribution Workflow 1. **Identify an unassigned issue**. Read more [here](#issues) about how to pick a good issue. 2. **Assign it** to avoid duplicated efforts (or request assignment by adding a comment). @@ -132,6 +144,13 @@ If starting server directly, via `rail s` or `rail console`, or built-in debugge If starting via Procfile with `bin/start`, then drop a ``binding.remote_pry`` into the line where you want execution to pause at. Then run ``pry-remote`` in the terminal to connect to it. https://github.com/Mon-Ouie/pry-remote +If you want to connect via Shopify Ruby LSP VSCode extension or rdbg, start the server with `bundle exec rdbg -O -n -c -- bin/rails server -p 3000` + +### Codespaces +When running tests in browser, visit the forwarded port 6080 URL to see the browser in Codespaces. You can also visit this port to access the GUI desktop in Codespaces. + +In VSCode Run and Debug view, there are some helpful defaults for running RSpec tests in browser at your cursor as well as attaching to a live server. Make sure the Ruby LSP server is started before debugging. + ## Squashing commits Consider the balance of "polluting the git log with commit messages" vs. "providing useful detail about the history of changes in the git log". If you have several smaller commits that serve a one purpose, you are encouraged to squash them into a single commit. There's no hard and fast rule here about this (for now), just use your best judgement. Please don't squash other people's commits. Everyone who contributes here deserves credit for their work! :) @@ -150,29 +169,46 @@ If you are so inclined, you can open a draft PR as you continue to work on it. S ## Tests ๐Ÿงช ### Writing Browser/System/Feature Tests/Specs -Add a test for your change. If you are adding functionality or fixing a bug, you should add a test! - -If you are inexperienced in writing tests or get stuck on one, please reach out for help :). You probably don't need to write new tests when simple re-stylings are done (ie. the page may look slightly different but the Test suite is unaffected by those changes). - -If you need to see a browser/system spec run in the browser, you can use the following env variable: +Add a test for your change. If you are adding functionality or fixing a bug, you should add a test! + +If you are inexperienced in writing tests or get stuck on one, please reach out for help :) + +#### Guidelines +- Prefer request tests over system tests (which run much slower) unless you need to test Javascript or other interactivity +- When creating factories, in each RSpec test, hard code all values that you check with a RSpec matcher. Don't check FactoryBot default values. See [#4217](https://github.com/rubyforgood/human-essentials/issues/4217) for why. +- Write tests to pass with Event Sourcing turned both on and off, see the [Event Sourcing wiki page](https://github.com/rubyforgood/human-essentials/wiki/Event-Sourcing). +- Keep individual tests tightly scoped, only test the endpoint that you want to test. E.g. create inventory directly using `TestInventory` rather than using an additional endpoint. +- You probably don't need to write new tests when simple re-stylings are done (ie. the page may look slightly different but the Test suite is unaffected by those changes). + +#### Useful Tips +- If you need to see a browser/system spec run in the browser, you can use the following env variable + ``` + NOT_HEADLESS=true bundle exec rspec + ``` +- We've added [magic_test](https://github.com/bullet-train-co/magic_test) which makes creating browser specs much easier. It allows you to record actions on the browser running the specs and easily paste them into the spec. You can do this by adding `magic_test` within your system spec: + ```rb + it "does some browser stuff" do + magic_test + end + ``` + and run the spec using this command: + ``` + MAGIC_TEST=1 NOT_HEADLESS=true bundle exec rspec ` + ``` + **See videos of it in action [here](https://twitter.com/andrewculver/status/1366062684802846721)** +- Helpful classes for viewing and modifying inventory include `View::Inventory`, `TestInventory` and various `CreateService` services, see the [Event Sourcing wiki page](https://github.com/rubyforgood/human-essentials/wiki/Event-Sourcing). -``` -NOT_HEADLESS=true bundle exec rspec -``` - -We've added [magic_test](https://github.com/bullet-train-co/magic_test) which makes creating browser specs much easier. It allows you to record actions on the browser running the specs and easily paste them into the spec. You can do this by adding `magic_test` within your system spec: -```rb - it "does some browser stuff" do - magic_test - end -``` -and run the spec using this command: `MAGIC_TEST=1 NOT_HEADLESS=true bundle exec rspec ` +### Test before submitting pull requests +Before submitting a pull request, run all tests and lints. Fix any broken tests and lints before submitting a pull request. -**See videos of it in action [here](https://twitter.com/andrewculver/status/1366062684802846721)** +#### Continuous Integration +- There are Github Actions workflows which will run all tests with and without Event Sourcing in parallel using Knapsack and lints whenever you push a commit to your fork. +- Once your first PR has been merged, all commits pushed to an open PR will also run these workflows. -### Test before submitting pull requests -- Before submitting a pull request, run all tests and rake tasks with `bundle exec rake` and run lints with `bin/lint`. Fix any broken tests and lints before submitting a pull request. -- You can run all the tests without rake tasks with `bundle exec rspec` +#### Local testing +- Run all lints with `bin/lint`. +- Run all tests without Event Sourcing with `bundle exec rspec` +- Run all tests with Event Sourcing with `EVENTS_READ=true bundle exec rspec` - You can run a single test with `bundle exec rspec {path_to_test_name}_spec.rb` or on a specific line by appending `:LineNumber` - If you need to skip a failing test, place `pending("Reason you are skipping the test")` into the `it` block rather than skipping with `xit`. This will allow rspec to deliver the error message without causing the test suite to fail. diff --git a/Gemfile b/Gemfile index 6dcd1ef7d4..8bc1e4f459 100644 --- a/Gemfile +++ b/Gemfile @@ -10,7 +10,7 @@ end # User management and login workflow. gem "devise", '>= 4.7.1' # Postgres database adapter. -gem "pg", "~> 1.5.6" +gem "pg", "~> 1.5.7" # Web server. gem "puma" # Rails web framework. @@ -32,6 +32,8 @@ gem "paper_trail" # Associates users with roles. gem "rolify", "~> 6.0" # Enforces "safe" migrations. +# Pinned to 1.8.0 because 2.0.0 no longer support postgres v10 +# And as of now we are using postgres v10 in production gem "strong_migrations", "1.8.0" # used in events gem 'dry-struct' @@ -103,7 +105,6 @@ gem "clockwork" # These are gems that aren't used directly, only as dependencies for other gems. # Technically they don't need to be in this Gemfile at all, but we are pinning them to # specific versions for compatibility reasons. -gem "mini_racer", "~> 0.12.0" gem "nokogiri", ">= 1.10.4" gem "image_processing" gem "sprockets", "~> 4.2.1" @@ -124,7 +125,7 @@ group :development, :test, :staging do # Generate models based on factory definitions. gem 'factory_bot_rails' # Ensure the database is in a clean state on every test. - gem "database_cleaner-active_record", '~> 2.1' + gem "database_cleaner-active_record", '~> 2.2' # Generate fake data for use in tests. gem 'faker' end @@ -146,15 +147,17 @@ group :development, :test do gem "pry-remote" # Add-on for command line to create a simple debugger. gem "pry-nav" + # 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.3" # Static analysis / linter. gem "rubocop" # Rails add-on for static analysis. gem 'rubocop-performance' - gem "rubocop-rails", "~> 2.25.0" + gem "rubocop-rails", "~> 2.25.1" # Default rules for Rubocop. - gem "standard", "~> 1.37" + gem "standard", "~> 1.39" # Erb linter. gem "erb_lint" end @@ -199,6 +202,8 @@ group :test do gem "webmock", "~> 3.23" # Interface capybara to chrome headless gem "cuprite" + # Read PDF files for tests + gem "pdf-reader" end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem diff --git a/Gemfile.lock b/Gemfile.lock index fbbbbef62b..f31ef3541a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,7 @@ GEM remote: https://rubygems.org/ specs: + Ascii85 (1.1.1) actioncable (7.1.3.4) actionpack (= 7.1.3.4) activesupport (= 7.1.3.4) @@ -77,6 +78,7 @@ GEM tzinfo (~> 2.0) addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) + afm (0.2.2) annotate (3.2.0) activerecord (>= 3.2, < 8.0) rake (>= 10.4, < 14.0) @@ -118,7 +120,7 @@ GEM bugsnag (6.27.1) concurrent-ruby (~> 1.0) builder (3.3.0) - bullet (7.1.6) + bullet (7.2.0) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) capybara (3.40.0) @@ -151,11 +153,14 @@ GEM cuprite (0.15.1) capybara (~> 3.0) ferrum (~> 0.15.0) - database_cleaner-active_record (2.1.0) + database_cleaner-active_record (2.2.0) activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) date (3.3.4) + debug (1.9.2) + irb (~> 1.10) + reline (>= 0.3.8) debug_inspector (1.2.0) delayed_job (4.1.11) activesupport (>= 3.0, < 8.0) @@ -218,7 +223,7 @@ GEM factory_bot_rails (6.4.3) factory_bot (~> 6.4) railties (>= 5.0.0) - faker (3.4.1) + faker (3.4.2) i18n (>= 1.8.11, < 2) faraday (1.10.3) faraday-em_http (~> 1.0) @@ -288,6 +293,7 @@ GEM guard-compat (~> 1.1) rspec (>= 2.99.0, < 4.0) hashdiff (1.1.0) + hashery (2.1.2) hashie (5.0.0) httparty (0.22.0) csv @@ -295,9 +301,9 @@ GEM multi_xml (>= 0.5.2) i18n (1.14.5) concurrent-ruby (~> 1.0) - icalendar (2.10.1) + icalendar (2.10.2) ice_cube (~> 0.16) - ice_cube (0.16.4) + ice_cube (0.17.0) ice_nine (0.11.2) image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) @@ -328,7 +334,7 @@ GEM activerecord kaminari-core (= 1.2.2) kaminari-core (1.2.2) - knapsack_pro (7.6.1) + knapsack_pro (7.6.2) rake language_server-protocol (3.17.0.3) launchy (3.0.0) @@ -336,9 +342,6 @@ GEM childprocess (~> 5.0) letter_opener (1.10.0) launchy (>= 2.2, < 4) - libv8-node (21.7.2.0-arm64-darwin) - libv8-node (21.7.2.0-x86_64-darwin) - libv8-node (21.7.2.0-x86_64-linux) lint_roller (1.1.0) listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) @@ -367,9 +370,7 @@ GEM method_source (1.1.0) mini_magick (4.11.0) mini_mime (1.1.5) - mini_racer (0.12.0) - libv8-node (~> 21.7.2.0) - minitest (5.24.0) + minitest (5.24.1) monetize (1.12.0) money (~> 6.12) money (6.16.0) @@ -396,13 +397,13 @@ GEM timeout net-smtp (0.5.0) net-protocol - newrelic_rpm (9.10.2) + newrelic_rpm (9.12.0) nio4r (2.7.3) - nokogiri (1.16.6-arm64-darwin) + nokogiri (1.16.7-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.6-x86_64-darwin) + nokogiri (1.16.7-x86_64-darwin) racc (~> 1.4) - nokogiri (1.16.6-x86_64-linux) + nokogiri (1.16.7-x86_64-linux) racc (~> 1.4) notiffany (0.1.3) nenv (~> 0.1) @@ -441,7 +442,13 @@ GEM ast (~> 2.4.1) racc pdf-core (0.9.0) - pg (1.5.6) + pdf-reader (2.12.0) + Ascii85 (~> 1.0) + afm (~> 0.2.1) + hashery (~> 2.0) + ruby-rc4 + ttfunk + pg (1.5.7) popper_js (2.11.8) prawn (2.4.0) pdf-core (~> 0.9.0) @@ -473,7 +480,7 @@ GEM public_suffix (5.1.0) puma (6.4.2) nio4r (~> 2.0) - racc (1.8.0) + racc (1.8.1) rack (2.2.9) rack-protection (3.1.0) rack (~> 2.2, >= 2.2.4) @@ -542,7 +549,7 @@ GEM responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) - rexml (3.3.0) + rexml (3.3.1) strscan rolify (6.0.1) rouge (4.1.2) @@ -583,7 +590,7 @@ GEM rubocop-performance (1.21.1) rubocop (>= 1.48.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rails (2.25.0) + rubocop-rails (2.25.1) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) @@ -591,6 +598,7 @@ GEM ruby-graphviz (1.2.5) rexml ruby-progressbar (1.13.0) + ruby-rc4 (0.1.5) ruby-vips (2.1.4) ffi (~> 1.12) ruby2_keywords (0.0.5) @@ -636,7 +644,7 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - standard (1.37.0) + standard (1.39.1) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) rubocop (~> 1.64.0) @@ -715,7 +723,8 @@ DEPENDENCIES clockwork coverband cuprite - database_cleaner-active_record (~> 2.1) + database_cleaner-active_record (~> 2.2) + debug (>= 1.0.0) delayed_job_active_record delayed_job_web devise (>= 4.7.1) @@ -747,7 +756,6 @@ DEPENDENCIES lograge magic_test matrix - mini_racer (~> 0.12.0) money-rails newrelic_rpm nokogiri (>= 1.10.4) @@ -756,7 +764,8 @@ DEPENDENCIES omniauth-rails_csrf_protection orderly (~> 0.1) paper_trail - pg (~> 1.5.6) + pdf-reader + pg (~> 1.5.7) prawn-rails pry-doc pry-nav @@ -772,13 +781,13 @@ DEPENDENCIES rspec-rails (~> 6.1.3) rubocop rubocop-performance - rubocop-rails (~> 2.25.0) + rubocop-rails (~> 2.25.1) sass-rails shoulda-matchers (~> 6.2) simple_form simplecov sprockets (~> 4.2.1) - standard (~> 1.37) + standard (~> 1.39) stimulus-rails strong_migrations (= 1.8.0) terser @@ -787,4 +796,4 @@ DEPENDENCIES webmock (~> 3.23) BUNDLED WITH - 2.5.14 + 2.5.16 diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index c918a4663a..d2ee0e734a 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -188,8 +188,7 @@ select.selectpicker + .dropdown-toggle::after { background-color: #8282df } -div.warning { - color: #e15563; +div.low_priority_warning { margin: 5px; text-align: center; } diff --git a/app/controllers/distributions_controller.rb b/app/controllers/distributions_controller.rb index 4a748de7ff..e2a10393a9 100644 --- a/app/controllers/distributions_controller.rb +++ b/app/controllers/distributions_controller.rb @@ -176,7 +176,7 @@ def edit if Event.read_events?(current_organization) inventory = View::Inventory.new(@distribution.organization_id) @storage_locations = current_organization.storage_locations.active_locations.alphabetized.select do |storage_loc| - inventory.quantity_for(storage_location: storage_loc.id).positive? + !inventory.quantity_for(storage_location: storage_loc.id).negative? end else @storage_locations = current_organization.storage_locations.active_locations.has_inventory_items.alphabetized @@ -225,7 +225,14 @@ def itemized_breakdown # TODO: This needs a little more context. Is it JSON only? HTML? def schedule - @pick_ups = current_organization.distributions + respond_to do |format| + format.html + format.json do + start_at = params[:start].to_datetime + end_at = params[:end].to_datetime + @pick_ups = current_organization.distributions.includes(:partner).where(issued_at: start_at..end_at) + end + end end def calendar diff --git a/app/controllers/donations_controller.rb b/app/controllers/donations_controller.rb index b876af27a1..a925ab6c41 100644 --- a/app/controllers/donations_controller.rb +++ b/app/controllers/donations_controller.rb @@ -2,6 +2,19 @@ class DonationsController < ApplicationController before_action :authorize_admin, only: [:destroy] + def print + @donation = Donation.find(params[:id]) + respond_to do |format| + format.any do + pdf = DonationPdf.new(current_organization, @donation) + send_data pdf.compute_and_render, + filename: format("%s %s.pdf", @donation.source, sortable_date(@donation.created_at)), + type: "application/pdf", + disposition: "inline" + end + end + end + def index setup_date_range_picker diff --git a/app/controllers/items_controller.rb b/app/controllers/items_controller.rb index 8ed9a7c423..cd066bac84 100644 --- a/app/controllers/items_controller.rb +++ b/app/controllers/items_controller.rb @@ -4,7 +4,7 @@ class ItemsController < ApplicationController def index @items = current_organization .items - .includes(:base_item, :kit, :line_items, :request_units) + .includes(:base_item, :kit, :line_items, :request_units, :item_category) .alphabetized .class_filter(filter_params) .group('items.id') diff --git a/app/controllers/organizations_controller.rb b/app/controllers/organizations_controller.rb index 4ab62d3ac4..9a07bdfafd 100644 --- a/app/controllers/organizations_controller.rb +++ b/app/controllers/organizations_controller.rb @@ -65,18 +65,17 @@ def demote_to_user end end - def deactivate_user - user = User.with_discarded.find_by!(id: params[:user_id]) - raise ActiveRecord::RecordNotFound unless user.has_role?(Role::ORG_USER, current_organization) - user.discard! - redirect_to user_update_redirect_path, notice: "User has been deactivated." - end - - def reactivate_user - user = User.with_discarded.find_by!(id: params[:user_id]) + def remove_user + user = User.find(params[:user_id]) raise ActiveRecord::RecordNotFound unless user.has_role?(Role::ORG_USER, current_organization) - user.undiscard! - redirect_to user_update_redirect_path, notice: "User has been reactivated." + begin + RemoveRoleService.call(user_id: params[:user_id], + resource_type: Role::ORG_USER, + resource_id: current_organization.id) + redirect_to user_update_redirect_path, notice: "User has been removed!" + rescue => e + redirect_back(fallback_location: organization_path, alert: e.message) + end end private @@ -100,7 +99,8 @@ def organization_params :ytd_on_distribution_printout, :one_step_partner_invite, :hide_value_columns_on_receipt, :hide_package_column_on_receipt, :signature_for_distribution_pdf, - partner_form_fields: [] + partner_form_fields: [], + request_unit_names: [] ) end diff --git a/app/controllers/partners/children_controller.rb b/app/controllers/partners/children_controller.rb index b0e6047557..378f6abf2a 100644 --- a/app/controllers/partners/children_controller.rb +++ b/app/controllers/partners/children_controller.rb @@ -6,7 +6,7 @@ class ChildrenController < BaseController def index @filterrific = initialize_filterrific( current_partner.children - .includes(:family) + .includes(:family, :requested_items) .order(sort_order), params[:filterrific] ) || return @@ -82,11 +82,11 @@ def child_params :first_name, :gender, :health_insurance, - :item_needed_diaperid, :last_name, :race, :archived, - child_lives_with: [] + child_lives_with: [], + requested_item_ids: [] ) end diff --git a/app/controllers/partners/family_requests_controller.rb b/app/controllers/partners/family_requests_controller.rb index ff91b4b9bc..6b8dc44051 100644 --- a/app/controllers/partners/family_requests_controller.rb +++ b/app/controllers/partners/family_requests_controller.rb @@ -23,9 +23,9 @@ def create end end - children = current_partner.children.active.where(id: children_ids).where.not(item_needed_diaperid: [nil, 0]) + children = current_partner.children.active.where(id: children_ids).joins(:requested_items).select('children.*', :item_id) - children_grouped_by_item_id = children.group_by(&:item_needed_diaperid) + children_grouped_by_item_id = children.group_by(&:item_id) family_requests_attributes = children_grouped_by_item_id.map do |item_id, item_requested_children| { item_id: item_id, person_count: item_requested_children.size, children: item_requested_children } end diff --git a/app/controllers/partners/users_controller.rb b/app/controllers/partners/users_controller.rb index bb4a4eb5bf..57693186b6 100644 --- a/app/controllers/partners/users_controller.rb +++ b/app/controllers/partners/users_controller.rb @@ -23,6 +23,7 @@ def update end end + # partner user creation def create user = UserInviteService.invite(name: user_params[:name], email: user_params[:email], diff --git a/app/controllers/product_drive_participants_controller.rb b/app/controllers/product_drive_participants_controller.rb index bf1d3f5374..59a58b185e 100644 --- a/app/controllers/product_drive_participants_controller.rb +++ b/app/controllers/product_drive_participants_controller.rb @@ -60,7 +60,7 @@ def update def product_drive_participant_params params.require(:product_drive_participant) - .permit(:contact_name, :phone, :email, :business_name, :address) + .permit(:contact_name, :phone, :email, :business_name, :address, :comment) end helper_method \ diff --git a/app/controllers/storage_locations_controller.rb b/app/controllers/storage_locations_controller.rb index d2a622c42b..2a679bea92 100644 --- a/app/controllers/storage_locations_controller.rb +++ b/app/controllers/storage_locations_controller.rb @@ -79,7 +79,15 @@ def show @items_in = ItemsInQuery.new(organization: current_organization, storage_location: @storage_location).call @items_in_total = ItemsInTotalQuery.new(organization: current_organization, storage_location: @storage_location).call if Event.read_events?(current_organization) - @inventory = View::Inventory.new(current_organization.id, event_time: params[:version_date]) + if View::Inventory.within_snapshot?(current_organization.id, params[:version_date]) + @inventory = View::Inventory.new(current_organization.id, event_time: params[:version_date]) + else + @legacy_inventory = View::Inventory.legacy_inventory_for_storage_location( + current_organization.id, + @storage_location.id, + params[:version_date] + ) + end end respond_to do |format| @@ -160,6 +168,7 @@ def inventory .active @inventory_items += include_omitted_items(@inventory_items.collect(&:item_id)) if params[:include_omitted_items] == "true" + @inventory_items.to_a.sort_by! { |inventory_item| inventory_item.item.name.downcase } respond_to :json end end diff --git a/app/helpers/partners_helper.rb b/app/helpers/partners_helper.rb index 6a8e8b87b3..fcf9f498c9 100644 --- a/app/helpers/partners_helper.rb +++ b/app/helpers/partners_helper.rb @@ -1,5 +1,12 @@ # Encapsulates methods that need some business logic module PartnersHelper + def display_requested_items(partner, child) + ids = child.requested_item_ids + ids.map do |item_id| + partner.organization.item_id_to_display_string_map[item_id] + end.join(', ') + end + def show_header_column_class(partner, additional_classes: "") if partner.quota.present? "col-sm-3 col-3 #{additional_classes}" diff --git a/app/javascript/controllers/select2_controller.js b/app/javascript/controllers/select2_controller.js index e15acc3fcc..f1dc82e8b8 100644 --- a/app/javascript/controllers/select2_controller.js +++ b/app/javascript/controllers/select2_controller.js @@ -5,17 +5,29 @@ import "select2" export default class extends Controller { static values = { config: { type: Object, default: {} }, - } + hideDropdown: { type: Boolean, default: false } + }; connect() { - $(this.element).select2(this.configValue); + const select2 = $(this.element).select2(this.configValue); + + if (this.hideDropdownValue) { + select2.on('select2:open', function (e) { + $('.select2-container--open .select2-dropdown--below').css('display','none'); + }); + } /** * This is a workaround to auto focus on the select2 input when it is opened. */ $(this.element).on('select2:open', function (e) { - $(".select2-search__field")[0].focus(); - }) + let select2Instance = $(e.target).data('select2'); + if (select2Instance) { + let searchField = select2Instance.dropdown.$search || select2Instance.selection.$search; + if (searchField) { + searchField.focus(); + } + } + }); } - } diff --git a/app/javascript/utils/distributions_and_transfers.js b/app/javascript/utils/distributions_and_transfers.js index 2871accb61..24082e5c69 100644 --- a/app/javascript/utils/distributions_and_transfers.js +++ b/app/javascript/utils/distributions_and_transfers.js @@ -66,6 +66,7 @@ $(function() { $(document).on("change", "select.storage-location-source", function() { const default_item = $(".line-item-fields select"); + control = $("select.storage-location-source"); if (storage_location_required && !control.val()) { $("#__add_line_item").addClass("disabled"); } diff --git a/app/models/item.rb b/app/models/item.rb index 381505e72b..66ca08f5c7 100644 --- a/app/models/item.rb +++ b/app/models/item.rb @@ -135,19 +135,23 @@ def has_inventory?(inventory = nil) end end - def is_in_kit? - organization.kits - .active - .joins(:line_items) - .where(line_items: { item_id: id}).any? + def is_in_kit?(kits = nil) + if kits + kits.any? { |k| k.line_items.map(&:item_id).include?(id) } + else + organization.kits + .active + .joins(:line_items) + .where(line_items: { item_id: id}).any? + end end - def can_delete?(inventory = nil) - can_deactivate_or_delete?(inventory) && line_items.none? && !barcode_count&.positive? + def can_delete?(inventory = nil, kits = nil) + can_deactivate_or_delete?(inventory, kits) && line_items.none? && !barcode_count&.positive? end # @return [Boolean] - def can_deactivate_or_delete?(inventory = nil) + def can_deactivate_or_delete?(inventory = nil, kits = nil) if inventory.nil? && Event.read_events?(organization) inventory = View::Inventory.new(organization_id) end @@ -156,7 +160,7 @@ def can_deactivate_or_delete?(inventory = nil) # If an active kit includes this item, then changing kit allocations would change inventory # for an inactive item - which we said above we don't want to allow. - !has_inventory?(inventory) && !is_in_kit? + !has_inventory?(inventory) && !is_in_kit?(kits) end def validate_destroy diff --git a/app/models/organization.rb b/app/models/organization.rb index cd03a56f2a..d8f46221a6 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -135,7 +135,7 @@ def flipper_id has_one_attached :logo - accepts_nested_attributes_for :users, :account_request + accepts_nested_attributes_for :users, :account_request, :request_units include Geocodable diff --git a/app/models/partners/child.rb b/app/models/partners/child.rb index 1421bd47a1..3cc260ebdd 100644 --- a/app/models/partners/child.rb +++ b/app/models/partners/child.rb @@ -22,9 +22,10 @@ module Partners class Child < Base has_paper_trail - serialize :child_lives_with, Array + serialize :child_lives_with, type: Array belongs_to :family has_many :child_item_requests, dependent: :destroy + has_and_belongs_to_many :requested_items, class_name: 'Item' include Filterable include Exportable @@ -88,7 +89,7 @@ def display_name def self.csv_export_headers %w[ id first_name last_name date_of_birth gender child_lives_with race agency_child_id - health_insurance comments created_at updated_at family_id item_needed_diaperid active archived + health_insurance comments created_at updated_at guardian_last_name guardian_first_name requested_items active archived ].freeze end @@ -106,8 +107,9 @@ def csv_export_attributes comments, created_at, updated_at, - family_id, - item_needed_diaperid, + family.guardian_last_name, + family.guardian_first_name, + requested_items.map(&:name).join(", "), active, archived ] diff --git a/app/models/partners/family.rb b/app/models/partners/family.rb index 52413ff24c..11d73da765 100644 --- a/app/models/partners/family.rb +++ b/app/models/partners/family.rb @@ -31,7 +31,7 @@ class Family < Base belongs_to :partner, class_name: '::Partner' has_many :children, dependent: :destroy has_many :authorized_family_members, dependent: :destroy - serialize :sources_of_income, Array + serialize :sources_of_income, type: Array validates :guardian_first_name, :guardian_last_name, :guardian_zip_code, presence: true include Filterable diff --git a/app/models/product_drive_participant.rb b/app/models/product_drive_participant.rb index 150209f846..76a24e0459 100644 --- a/app/models/product_drive_participant.rb +++ b/app/models/product_drive_participant.rb @@ -25,6 +25,7 @@ class ProductDriveParticipant < ApplicationRecord validates :phone, presence: { message: "Must provide a phone or an e-mail" }, if: proc { |pdp| pdp.email.blank? } validates :email, presence: { message: "Must provide a phone or an e-mail" }, if: proc { |pdp| pdp.phone.blank? } + validates :comment, length: { maximum: 500 } scope :alphabetized, -> { order(:contact_name) } diff --git a/app/models/request_item.rb b/app/models/request_item.rb index a843f293d2..a22feb5f0d 100644 --- a/app/models/request_item.rb +++ b/app/models/request_item.rb @@ -1,5 +1,5 @@ class RequestItem - attr_accessor :item, :quantity, :on_hand, :on_hand_for_location + attr_accessor :item, :quantity, :unit, :on_hand, :on_hand_for_location include ItemQuantity def self.from_json(json, request, inventory = nil) @@ -9,6 +9,7 @@ def self.from_json(json, request, inventory = nil) item = Item.find(json['item_id']) quantity = json['quantity'] + unit = request.item_requests.find { |item_request| item_request.item_id == item.id }&.request_unit if inventory on_hand = inventory.quantity_for(item_id: item.id) on_hand_for_location = inventory.quantity_for(storage_location: location&.id, item_id: item.id) @@ -16,14 +17,15 @@ def self.from_json(json, request, inventory = nil) on_hand = request.organization.inventory_items.where(item_id: item.id).sum(:quantity) on_hand_for_location = location&.inventory_items&.where(item_id: item.id)&.sum(:quantity) end - new(item, quantity, on_hand, on_hand_for_location&.positive? ? on_hand_for_location : 'N/A') + new(item, quantity, unit, on_hand, on_hand_for_location&.positive? ? on_hand_for_location : 'N/A') end delegate :name, to: :item - def initialize(item, quantity, on_hand, on_hand_for_location) + def initialize(item, quantity, unit, on_hand, on_hand_for_location) @item = item @quantity = quantity + @unit = unit @on_hand = on_hand @on_hand_for_location = on_hand_for_location end diff --git a/app/models/unit.rb b/app/models/unit.rb index 53527345ec..cfe4731589 100644 --- a/app/models/unit.rb +++ b/app/models/unit.rb @@ -10,4 +10,5 @@ # class Unit < ApplicationRecord belongs_to :organization + validates :name, uniqueness: {scope: :organization} end diff --git a/app/models/view/inventory.rb b/app/models/view/inventory.rb index ea291eae09..08a209fe7e 100644 --- a/app/models/view/inventory.rb +++ b/app/models/view/inventory.rb @@ -13,6 +13,31 @@ class ViewInventoryItem < EventTypes::EventItem attr_accessor :inventory, :organization_id delegate :storage_locations, to: :inventory + # @param event_time [ActiveSupport::TimeWithZone] + # @return [Boolean] + def self.within_snapshot?(organization_id, event_time) + return true if event_time.blank? + + event = SnapshotEvent.where(organization_id: organization_id).first + event && event.created_at < event_time + end + + # @param organization_id [Integer] + # @param storage_location_id [Integer] + # @param event_time [ActiveSupport::TimeWithZone] + # @return [Array] + def self.legacy_inventory_for_storage_location(organization_id, storage_location_id, event_time) + items = Organization.find(organization_id).inventory_items.where(storage_location_id: storage_location_id) + items.map do |item| + ViewInventoryItem.new( + item_id: item.item_id, + quantity: item.paper_trail.version_at(event_time)&.quantity || 0, + storage_location_id: storage_location_id, + db_item: item.item + ) + end + end + # @param organization_id [Integer] # @param event_time [DateTime] def initialize(organization_id, event_time: nil) diff --git a/app/pdfs/donation_pdf.rb b/app/pdfs/donation_pdf.rb new file mode 100644 index 0000000000..773e1b9095 --- /dev/null +++ b/app/pdfs/donation_pdf.rb @@ -0,0 +1,195 @@ +# Configures a Prawn PDF template for generating Donation receipts +class DonationPdf + include Prawn::View + include ItemsHelper + + class DonorInfo + attr_reader :name, :address, :email + + def initialize(donation) + if donation.nil? + raise "Must pass a Donation object" + end + case donation.source + when Donation::SOURCES[:donation_site] + @name = donation.donation_site.name + @address = donation.donation_site.address + @email = donation.donation_site.email + when Donation::SOURCES[:manufacturer] + @name = donation.manufacturer.name + @address = nil + @email = nil + when Donation::SOURCES[:product_drive] + @name = donation.product_drive_participant.business_name + @address = donation.product_drive_participant.address + @email = donation.product_drive_participant.email + when Donation::SOURCES[:misc] + @name = "Misc. Donation" + @address = nil + @email = nil + end + end + end + + def initialize(organization, donation) + @donation = Donation.includes(line_items: [:item]).find_by(id: donation.id) + @organization = organization + @donor = DonorInfo.new(@donation) + end + + def compute_and_render + font_families["OpenSans"] = PrawnRails.config["font_families"][:OpenSans] + font "OpenSans" + font_size 10 + + logo_image = if @organization.logo.attached? + StringIO.open(@organization.logo.download) + else + Organization::DIAPER_APP_LOGO + end + + footer_height = 35 + + # Bounding box containing non-footer elements + bounding_box [bounds.left, bounds.top], width: bounds.width, height: bounds.height - footer_height do + image logo_image, fit: [250, 85] + + bounding_box [bounds.right - 225, bounds.top], width: 225, height: 85 do + text @organization.name, align: :right + text @organization.address, align: :right + text @organization.email, align: :right + end + + font_size 12 + text "Issued on:", style: :bold + text @donation.issued_at.to_fs(:distribution_date) + move_up 24 + + font_size 12 + text "Donation from:", style: :bold, align: :right + font_size 10 + text @donor.name, align: :right + text @donor.address, align: :right + text @donor.email, align: :right + move_down 10 + # Get some additional vertical distance in left column if all donor info is nil + if @donor.name.nil? && @donor.address.nil? && @donor.email.nil? + move_down 10 + end + + font_size 12 + money_raised = "$0.00" + if @donation.money_raised && @donation.money_raised > 0 + money_raised = dollar_value(@donation.money_raised) + end + text "Money Raised In Dollars: #{money_raised}", inline_format: true + + move_down 10 + font_size 12 + text "Comments:", style: :bold + text @donation.comment + + move_down 20 + + data = donation_data + + hide_columns(data) + hidden_columns_length = column_names_to_hide.length + + font_size 11 + + # Line item table + table(data) do + self.header = true + self.cell_style = { + padding: [5, 20, 5, 20] + } + self.row_colors = %w[dddddd ffffff] + + cells.borders = [] + + # Header row + row(0).borders = [:bottom] + row(0).border_width = 2 + row(0).font_style = :bold + row(0).size = 9 + row(0).column(1..-1).borders = %i[bottom left] + + # Total Items footer row + row(-1).borders = [:top] + row(-1).font_style = :bold + row(-1).column(1..-1).borders = %i[top left] + row(-1).column(1..-1).border_left_color = "aaaaaa" + + # Footer spacing row + row(-2).borders = [:top] + row(-2).padding = [2, 0, 2, 0] + + column(0).width = 190 + (hidden_columns_length * 60) + + # Quantity column + column(1..-1).row(1..-3).borders = [:left] + column(1..-1).row(1..-3).border_left_color = "aaaaaa" + column(1).style align: :right + end + end + + number_pages "Page of ", + start_count_at: 1, + at: [bounds.right - 130, 22], + align: :right + + repeat :all do + # Page footer + bounding_box [bounds.left, bounds.bottom + footer_height], width: bounds.width do + stroke_bounds + font "OpenSans" + font_size 9 + stroke_horizontal_rule + move_down(5) + + logo_offset = (bounds.width - 190) / 2 + bounding_box([logo_offset, 0], width: 190, height: 33) do + text "Lovingly created with", valign: :center + image Organization::DIAPER_APP_LOGO, width: 75, vposition: :center, position: :right + end + end + end + + render + end + + def donation_data + data = [["Items Received", + "Value/item", + "In-Kind Value", + "Quantity"]] + data += @donation.line_items.sorted.map do |c| + [c.item.name, + dollar_value(c.item.value_in_cents), + dollar_value(c.value_per_line_item), + c.quantity] + end + data + [["", "", "", ""], + ["Total Items Received", + "", + dollar_value(@donation.value_per_itemizable), + @donation.line_items.total]] + end + + def hide_columns(data) + column_names_to_hide.each do |col_name| + col_index = data.first.find_index(col_name) + data.each { |line| line.delete_at(col_index) } if col_index.present? + end + end + + private + + def column_names_to_hide + in_kind_column_name = "In-Kind Value" + columns_to_hide = [] + columns_to_hide.push("Value/item", in_kind_column_name) if @organization.hide_value_columns_on_receipt + columns_to_hide + end +end diff --git a/app/services/organization_update_service.rb b/app/services/organization_update_service.rb index 0b5b3c72c6..d49e650acf 100644 --- a/app/services/organization_update_service.rb +++ b/app/services/organization_update_service.rb @@ -12,10 +12,23 @@ class << self def update(organization, params) return false unless valid?(organization, params) - if params.has_key?("partner_form_fields") - params["partner_form_fields"].delete_if { |field| field == "" } + org_params = params.dup + + if org_params.has_key?("partner_form_fields") + org_params["partner_form_fields"] = org_params["partner_form_fields"].reject(&:blank?) + end + + if Flipper.enabled?(:enable_packs) && org_params[:request_unit_names] + # Find or create units for the organization + request_unit_ids = org_params[:request_unit_names].reject(&:blank?).map do |request_unit_name| + Unit.find_or_create_by(organization: organization, name: request_unit_name).id + end + org_params.delete(:request_unit_names) + org_params[:request_unit_ids] = request_unit_ids end - result = organization.update(params) + + result = organization.update(org_params) + return false unless result update_partner_flags(organization) true diff --git a/app/services/reports/summary_report_service.rb b/app/services/reports/summary_report_service.rb index 4052ebc093..fb5c20634a 100644 --- a/app/services/reports/summary_report_service.rb +++ b/app/services/reports/summary_report_service.rb @@ -16,7 +16,7 @@ def report entries: { '% difference in yearly donations' => percent_donations, '% difference in total money donated' => percent_money, - '% difference in diaper donations' => percent_diapers + '% difference in disposable diaper donations' => percent_diapers } } end diff --git a/app/services/transfer_create_service.rb b/app/services/transfer_create_service.rb index c4cb5cc12c..fd81089aed 100644 --- a/app/services/transfer_create_service.rb +++ b/app/services/transfer_create_service.rb @@ -9,7 +9,7 @@ def call(transfer) TransferEvent.publish(transfer) end else - raise StandardError.new(transfer.errors.full_messages.join("
")) + raise StandardError.new(transfer.errors.full_messages.join(", ")) end end end diff --git a/app/views/dashboard/_getting_started_prompt.html.erb b/app/views/dashboard/_getting_started_prompt.html.erb index b56f408dcf..8ad0f15839 100644 --- a/app/views/dashboard/_getting_started_prompt.html.erb +++ b/app/views/dashboard/_getting_started_prompt.html.erb @@ -3,7 +3,7 @@ <% location_criteria_met = org_stats.storage_locations_added > 0 %> <% donation_criteria_met = org_stats.donation_sites_added > 0 %> <% inventory_criteria_met = org_stats.locations_with_inventory.length > 0 %> -<% criterias = [ partner_criteria_met, location_criteria_met, donation_criteria_met, inventory_criteria_met ] %> +<% criterias = [ location_criteria_met, partner_criteria_met, donation_criteria_met, inventory_criteria_met ] %> <% current_step = criterias.find_index(false) %> <% if current_step.present? %> @@ -30,7 +30,7 @@ current_step: current_step, criterias: criterias, lines: ["line-right", "line-right line-left", "line-right line-left", "line-left"], - step_labels: ["Partner Agencies", "Storage Locations", "Donation Sites", "Inventory"] + step_labels: ["Storage Locations", "Partner Agencies", "Donation Sites", "Inventory"] } %> @@ -38,21 +38,21 @@
- <% if partner_criteria_met %> + <% if location_criteria_met %> <% else %> - + <% end %> -

<%= pluralize(org_stats.partners_added, 'Partner Agency') %> Added

+

<%= pluralize(org_stats.storage_locations_added, 'Storage Location') %> Added

- To start building your community in Human Essentials, import a list of your current partner agencies or add them individually. + Add details for all Storage Locations you use for your inventory.

-
- <% partner_link_text = partner_criteria_met ? "Add More Partners" : "Add a Partner" %> - <%= new_button_to new_partner_path, { text: partner_link_text, size: "md" } %> +
+ <% location_link_text = location_criteria_met ? "Add More Storage Locations" : "Add a Storage Location" %> + <%= new_button_to new_storage_location_path, { text: location_link_text, size: "md" } %>
@@ -62,21 +62,21 @@
- <% if location_criteria_met %> + <% if partner_criteria_met %> <% else %> - + <% end %> -

<%= pluralize(org_stats.storage_locations_added, 'Storage Location') %> Added

+

<%= pluralize(org_stats.partners_added, 'Partner Agency') %> Added

- Add details for all Storage Locations you use for your inventory. + To start building your community in Human Essentials, import a list of your current partner agencies or add them individually.

-
- <% location_link_text = location_criteria_met ? "Add More Storage Locations" : "Add a Storage Location" %> - <%= new_button_to new_storage_location_path, { text: location_link_text, size: "md" } %> +
+ <% partner_link_text = partner_criteria_met ? "Add More Partners" : "Add a Partner" %> + <%= new_button_to new_partner_path, { text: partner_link_text, size: "md" } %>
diff --git a/app/views/distributions/_distribution_row.html.erb b/app/views/distributions/_distribution_row.html.erb index 71fc0736a9..6e1c99ee7b 100644 --- a/app/views/distributions/_distribution_row.html.erb +++ b/app/views/distributions/_distribution_row.html.erb @@ -32,6 +32,6 @@ text: "Reclaim", icon: "undo", enabled: !distribution_row.has_inactive_item? } %> <% if distribution_row.has_inactive_item? %> -
Has Inactive Items
+
Has Inactive Items
<% end %> diff --git a/app/views/distributions/show.html.erb b/app/views/distributions/show.html.erb index da6776ff12..b37af4a02e 100644 --- a/app/views/distributions/show.html.erb +++ b/app/views/distributions/show.html.erb @@ -92,7 +92,7 @@ <%= print_button_to print_distribution_path(@distribution, format: :pdf), {size: "md"} %>
<% if @distribution.has_inactive_item? %> -
+
You can only correct distributions where all the items are active. If you need to make a correction, please make the following items active: <%= @distribution.inactive_items.map(&:name).join(", ") %>
diff --git a/app/views/donations/_donation_row.html.erb b/app/views/donations/_donation_row.html.erb index 50ea5a8618..9ce06cc75f 100644 --- a/app/views/donations/_donation_row.html.erb +++ b/app/views/donations/_donation_row.html.erb @@ -9,6 +9,7 @@ <%= truncate donation_row.comment, length: 140, separator: /\w+/ %> <%= view_button_to donation_path(donation_row) %> + <%= print_button_to print_donation_path(donation_row, format: :pdf) %> diff --git a/app/views/donations/show.html.erb b/app/views/donations/show.html.erb index 8e45413ae2..b13ed109b2 100644 --- a/app/views/donations/show.html.erb +++ b/app/views/donations/show.html.erb @@ -82,11 +82,12 @@ confirm: "Are you sure you want to permanently remove this donation?" } %> <% end %> <% if @donation.has_inactive_item? %> -
+
You can only delete or correct donations where all the items are active. If you need to delete this donation or make a correction, please make the following items active: <%= @donation.inactive_items.map(&:name).join(", ") %>
<% end %> + <%= print_button_to print_donation_path(@donation, format: :pdf), { size: "md" } %>
diff --git a/app/views/items/_item_list.html.erb b/app/views/items/_item_list.html.erb index eca1a53973..a6a0d07231 100644 --- a/app/views/items/_item_list.html.erb +++ b/app/views/items/_item_list.html.erb @@ -19,7 +19,7 @@ - <%= render partial: "items/item_row", collection: items, as: :item_row, locals: { inventory: inventory } %> + <%= render partial: "items/item_row", collection: items, as: :item_row, locals: { inventory: inventory, kits: kits } %> diff --git a/app/views/items/_item_row.html.erb b/app/views/items/_item_row.html.erb index cf67c84990..d7733bfa2d 100644 --- a/app/views/items/_item_row.html.erb +++ b/app/views/items/_item_row.html.erb @@ -12,12 +12,12 @@ <%= view_button_to item_path(item_row) %> <%= edit_button_to edit_item_path(item_row) %> <% if item_row.active? %> - <% if item_row.can_delete?(inventory) %> + <% if item_row.can_delete?(inventory, kits) %> <%= delete_button_to item_path(item_row), text: 'Delete', confirm: confirm_delete_msg(item_row.name) %> <% else %> - <% can_deactivate = item_row.can_deactivate_or_delete?(inventory) %> + <% can_deactivate = item_row.can_deactivate_or_delete?(inventory, kits) %> <%= delete_button_to deactivate_item_path(item_row), text: 'Deactivate', enabled: can_deactivate, diff --git a/app/views/items/index.html.erb b/app/views/items/index.html.erb index 23d706d6e0..169618aadc 100644 --- a/app/views/items/index.html.erb +++ b/app/views/items/index.html.erb @@ -93,7 +93,7 @@
- <%= render partial: 'item_list', locals: { items: @items, inventory: @inventory } %> + <%= render partial: 'item_list', locals: { items: @items, inventory: @inventory, kits: @kits.active } %> <%= render partial: 'item_categories', locals: { item_categories: @item_categories } %> <%= render partial: 'items_quantity_and_location' %> <%= render partial: 'items_inventory' %> diff --git a/app/views/organizations/_details.html.erb b/app/views/organizations/_details.html.erb index d92788f59f..90dc479f6f 100644 --- a/app/views/organizations/_details.html.erb +++ b/app/views/organizations/_details.html.erb @@ -125,6 +125,24 @@ <%= humanize_boolean(@organization.distribute_monthly) %>

+ <% if Flipper.enabled?(:enable_packs) %> +
+

Custom Request Units

+

+ <% if @organization.request_units.length > 0 %> + <% @organization.request_units.map do |unit| %> + <%= fa_icon "angle-right" %> + + <%= unit.name.titlecase %> +
+ <% end %> + <% else %> + <%= fa_icon "angle-right" %> + None + <% end %> +

+
+ <% end %>

Child Based Requests?

diff --git a/app/views/organizations/edit.html.erb b/app/views/organizations/edit.html.erb index 4c7cc0928d..b67acaec03 100644 --- a/app/views/organizations/edit.html.erb +++ b/app/views/organizations/edit.html.erb @@ -40,7 +40,9 @@

-<%= simple_form_for current_organization, url: {controller: "organizations", action: "update"} do |f| %> +<%= simple_form_for current_organization, + data: { controller: "form-input" }, + url: {controller: "organizations", action: "update"} do |f| %>
@@ -100,6 +102,27 @@ <%= f.input :repackage_essentials, label: 'Does your bank repackage essentials?', as: :radio_buttons, collection: [[true, 'Yes'], [false, 'No']], label_method: :second, value_method: :first %> <%= f.input :distribute_monthly, label: 'Does your bank distribute monthly?', as: :radio_buttons, collection: [[true, 'Yes'], [false, 'No']], label_method: :second, value_method: :first %> + + <% if Flipper.enabled?(:enable_packs) %> + <%= label_tag "organization[request_unit_names]", 'Custom request units used (please use singular form -- e.g. pack, not packs)' %> + <%= select_tag( + "organization[request_unit_names]", + options_from_collection_for_select( + current_organization.request_units, + 'name', + 'name', + ->(_) { true } # Select all of the current request units + ), + { + multiple: true, + class: 'form-control custom-select', + 'data-controller': 'select2', + 'data-select2-hide-dropdown-value': true, + 'data-select2-config-value': '{"selectOnClose": "true", "tags": "true", "tokenSeparators": [",", "\t"]}' + } + ) %> + <% end %> + <%= f.input :enable_child_based_requests, label: 'Enable partners to make child-based requests?', as: :radio_buttons, collection: [[true, 'Yes'], [false, 'No']], label_method: :second, value_method: :first %> <%= f.input :enable_individual_requests, label: 'Enable partners to make requests for individuals?', as: :radio_buttons, collection: [[true, 'Yes'], [false, 'No']], label_method: :second, value_method: :first %> <%= f.input :enable_quantity_based_requests, label: 'Enable partners to make quantity-based requests?', as: :radio_buttons, collection: [[true, 'Yes'], [false, 'No']], label_method: :second, value_method: :first %> diff --git a/app/views/partners/children/_child.json.jbuilder b/app/views/partners/children/_child.json.jbuilder index 63ae89cc65..98be8b3f55 100644 --- a/app/views/partners/children/_child.json.jbuilder +++ b/app/views/partners/children/_child.json.jbuilder @@ -1,2 +1,2 @@ -json.extract! child, :id, :first_name, :last_name, :date_of_birth, :gender, :child_lives_with, :race, :agency_child_id, :health_insurance, :item_needed_diaperid, :comments, :created_at, :updated_at +json.extract! child, :id, :first_name, :last_name, :date_of_birth, :gender, :child_lives_with, :race, :agency_child_id, :health_insurance, :requested_item_ids, :comments, :created_at, :updated_at json.url child_url(child, format: :json) diff --git a/app/views/partners/children/_form.html.erb b/app/views/partners/children/_form.html.erb index 8bd2ac2725..3f11457e9f 100644 --- a/app/views/partners/children/_form.html.erb +++ b/app/views/partners/children/_form.html.erb @@ -16,11 +16,22 @@ <%= form.label :last_name, "Last Name" %> <%= form.text_field :last_name, class: "form-control" %> - <%= form.label :item_needed, "Diaper/Item Used" %> - <%= form.select :item_needed_diaperid, @requestable_items, - {include_blank: 'Select an item'}, - {class: 'form-control'} %> + <%= @requestable_items.map(&:last).intersect?(child.requested_item_ids) %> +
+ <%= form.label :item_needed, "Item(s) Requested" %> + <%= form.select( + :requested_item_ids, + @requestable_items, + { include_hidden: true }, + { + multiple: true, + class: "form-control custom-select", + "data-controller": "select2", + "data-select2-config-value": "{}", + }, + ) %>
+
<%= form.input :date_of_birth, as: :date, start_year: 20.years.ago.year, end_year: Time.current.year, label: "Date of Birth" %> <%= form.input :gender, collection: ["Male", "Female"], class: "form-control" %> @@ -53,16 +64,15 @@ <%= form.label "Archived?" %>  <%= form.check_box :archived %> -
- <% end %> -
- +
+ - - - + + + + diff --git a/app/views/partners/children/show.html.erb b/app/views/partners/children/show.html.erb index 45e28aa0ed..3b5eb21949 100644 --- a/app/views/partners/children/show.html.erb +++ b/app/views/partners/children/show.html.erb @@ -58,8 +58,8 @@
Health insurance:
<%= @child.health_insurance %>
-
Item needed:
-
<%= current_partner.organization.item_id_to_display_string_map[@child.item_needed_diaperid] %>
+
Item(s) needed:
+
<%= display_requested_items(current_partner, @child) %>
Comments:
<%= @child.comments %>
diff --git a/app/views/partners/family_requests/_list.html.erb b/app/views/partners/family_requests/_list.html.erb index 2b350f25b1..9be1448a55 100644 --- a/app/views/partners/family_requests/_list.html.erb +++ b/app/views/partners/family_requests/_list.html.erb @@ -18,15 +18,15 @@ <%= child.first_name %> <%= child.last_name %> - <% if child.item_needed_diaperid %> - <%= current_partner.organization.item_id_to_display_string_map[child.item_needed_diaperid] %> + <% if child.requested_item_ids.any? %> + <%= display_requested_items(current_partner, child) %> <% else %> <%= "N/A" %> <% end %>
- <% if child.item_needed_diaperid %> + <% if child.requested_item_ids.any? %> <%= check_box_tag "child-#{child.id}", child.active, child.active, class: "custom-control-input", id: "child-#{child.id}" %> diff --git a/app/views/partners/requests/show.html.erb b/app/views/partners/requests/show.html.erb index 21458ef03a..000b700f59 100644 --- a/app/views/partners/requests/show.html.erb +++ b/app/views/partners/requests/show.html.erb @@ -54,7 +54,14 @@ Requested Items:
    <% @partner_request.item_requests.each do |item| %> -
  • <%= item.quantity %> of <%= item.name %>
  • +
  • + <%= item.quantity %> + <% if Flipper.enabled?(:enable_packs) && item.request_unit %> + <%= item.request_unit.pluralize(item.quantity.to_i) %> + <% end %> + of + <%= item.name %> +
  • <% end %>
diff --git a/app/views/product_drive_participants/_form.html.erb b/app/views/product_drive_participants/_form.html.erb index 84ae393483..84f1256943 100644 --- a/app/views/product_drive_participants/_form.html.erb +++ b/app/views/product_drive_participants/_form.html.erb @@ -34,10 +34,14 @@ <%= f.input_field :address, class: "form-control" %> <% end %> + <%= f.input :comment, label: "Comment", wrapper: :input_group do %> + + <%= f.input_field :comment, as: :text, class: "form-control" %> + <% end %> <% end %> diff --git a/app/views/product_drive_participants/show.html.erb b/app/views/product_drive_participants/show.html.erb index 4d7afc02d8..d133a122b2 100644 --- a/app/views/product_drive_participants/show.html.erb +++ b/app/views/product_drive_participants/show.html.erb @@ -37,6 +37,7 @@ Phone Email Address + Comment @@ -46,6 +47,7 @@ <%= @product_drive_participant.phone %> <%= @product_drive_participant.email %> <%= @product_drive_participant.address %> + <%= @product_drive_participant.comment %> diff --git a/app/views/purchases/show.html.erb b/app/views/purchases/show.html.erb index b4be293459..d75fb18684 100644 --- a/app/views/purchases/show.html.erb +++ b/app/views/purchases/show.html.erb @@ -77,7 +77,7 @@ confirm: "Are you sure you want to permanently remove this purchase?" } %> <% end %> <% if @purchase.has_inactive_item? %> -
+
You can only delete or correct purchases where all the items are active. If you need to delete this purchase or make a correction, please make the following items active: <%= @purchase.inactive_items.map(&:name).join(", ") %>
diff --git a/app/views/requests/show.html.erb b/app/views/requests/show.html.erb index 7d5c48c57b..89478380fa 100644 --- a/app/views/requests/show.html.erb +++ b/app/views/requests/show.html.erb @@ -65,6 +65,11 @@ Item Quantity + <% custom_units = Flipper.enabled?(:enable_packs) && + @request.item_requests.any? { |item| item.request_unit } %> + <% if custom_units %> + Units (if applicable) + <% end %> <% default_storage_location = @request.partner.default_storage_location_id || @request.organization.default_storage_location %> <% if default_storage_location %> @@ -78,6 +83,9 @@ <%= item.name %> <%= item.quantity %> + <% if custom_units %> + <%= item.unit&.capitalize&.pluralize(item.quantity) %> + <% end %> <% if default_storage_location %> <%= item.on_hand_for_location %> <% end %> diff --git a/app/views/storage_locations/show.html.erb b/app/views/storage_locations/show.html.erb index caa56c27bf..4baf8b3d35 100644 --- a/app/views/storage_locations/show.html.erb +++ b/app/views/storage_locations/show.html.erb @@ -102,6 +102,13 @@ <%= number_with_delimiter(item.quantity) %> <% end %> + <% elsif @legacy_inventory %> + <% @legacy_inventory.each do |item| %> + + <%= link_to item.name, item_path(item.item_id) %> + <%= number_with_delimiter(item.quantity) %> + + <% end %> <% else %> <%= render partial: "inventory_item_row", collection: @storage_location.inventory_items.joins(:item).where(items: { active: true }), @@ -111,7 +118,15 @@ Total - <%= @inventory ? @inventory.quantity_for(storage_location: @storage_location.id) : @storage_location.size %> + + <% if @inventory %> + <%= number_with_delimiter(@inventory.quantity_for(storage_location: @storage_location.id)) %> + <% elsif @legacy_inventory %> + <%= number_with_delimiter(@legacy_inventory.map(&:quantity).sum) %> + <% else %> + <%= number_with_delimiter(@storage_location.size) %> + <% end %> + diff --git a/app/views/users/_organization_user.html.erb b/app/views/users/_organization_user.html.erb index c4711e50cd..9e882b4e4b 100644 --- a/app/views/users/_organization_user.html.erb +++ b/app/views/users/_organization_user.html.erb @@ -1,9 +1,6 @@ <%= user.display_name %> - <% if user.discarded? %> - Deactivated - <% end %> <%= user.email %> <%= user.kind %> @@ -18,30 +15,25 @@
<% end %> <% if current_user.has_role?(Role::ORG_ADMIN, current_organization) && user.has_role?(Role::ORG_ADMIN, current_organization) %> <%= edit_button_to demote_to_user_organization_path(user_id: user.id), - {text: 'Make User'}, + {text: 'Demote to User'}, {method: :post, rel: "nofollow", data: {confirm: 'This will demote the admin to user status. Are you sure that you want to submit this?', size: 'xs'}} unless user.id == current_user.id %> <% end %> diff --git a/bin/setup b/bin/setup index ec12c74c41..a87b0356f6 100755 --- a/bin/setup +++ b/bin/setup @@ -54,8 +54,10 @@ FileUtils.chdir APP_ROOT do system!("bundle check") || system!("bundle install") unless File.exist?('.env') - log "== Setup .env file from .env.example ==" + log "== Setting up .env file from .env.example ==" system!('cp .env.example .env') + log "Check .env to see if your database credentials are correct.\nPress Enter to continue...", color: :red + gets end log "== Preparing database ==" diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index b17625ca9c..3951cd3f08 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -326,3 +326,10 @@ # so you need to do it manually. For the users scope, it would be: # config.omniauth_path_prefix = '/my_engine/users/auth' end + +Warden::Manager.after_set_user do |user, auth, opts| + if user.roles.empty? + auth.logout + throw(:warden) + end +end diff --git a/config/routes.rb b/config/routes.rb index b44654f925..399168e4ce 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -50,6 +50,9 @@ def set_up_flipper resources :distributions, only: [:index] do get :print, on: :member end + resources :donations, only: [:index] do + get :print, on: :member + end end # This is where a superadmin CRUDs all the things @@ -89,8 +92,7 @@ def set_up_flipper resource :organization, path: :manage, only: %i(edit update) do collection do post :invite_user - put :deactivate_user - put :reactivate_user + post :remove_user post :resend_user_invitation post :promote_to_org_admin post :demote_to_user @@ -221,6 +223,7 @@ def set_up_flipper resources :product_drives resources :donations do + get :print, on: :member patch :add_item, on: :member patch :remove_item, on: :member end diff --git a/db/migrate/20240624185108_reactivate_users_and_remove_org_user_roles.rb b/db/migrate/20240624185108_reactivate_users_and_remove_org_user_roles.rb new file mode 100644 index 0000000000..cea79ade07 --- /dev/null +++ b/db/migrate/20240624185108_reactivate_users_and_remove_org_user_roles.rb @@ -0,0 +1,15 @@ +class ReactivateUsersAndRemoveOrgUserRoles < ActiveRecord::Migration[7.1] + def up + users = User.unscoped.where.not(discarded_at: nil) + users.each do |user| + user.transaction do + user.update!(discarded_at: nil) + user.roles.delete_all + end + end + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/migrate/20240703174254_create_join_table_children_items.rb b/db/migrate/20240703174254_create_join_table_children_items.rb new file mode 100644 index 0000000000..6250f20a04 --- /dev/null +++ b/db/migrate/20240703174254_create_join_table_children_items.rb @@ -0,0 +1,8 @@ +class CreateJoinTableChildrenItems < ActiveRecord::Migration[7.1] + def change + create_join_table :children, :items do |t| + t.index [:child_id, :item_id], unique: true + t.index [:item_id, :child_id], unique: true + end + end +end diff --git a/db/migrate/20240704214509_backfill_partner_child_requested_items.rb b/db/migrate/20240704214509_backfill_partner_child_requested_items.rb new file mode 100644 index 0000000000..ed418cc5c8 --- /dev/null +++ b/db/migrate/20240704214509_backfill_partner_child_requested_items.rb @@ -0,0 +1,13 @@ +class BackfillPartnerChildRequestedItems < ActiveRecord::Migration[7.1] + def up + safety_assured do + execute <<-SQL + INSERT INTO children_items (child_id, item_id) + SELECT children.id, items.id + FROM children + LEFT JOIN items ON children.item_needed_diaperid = items.id + WHERE items.id IS NOT NULL AND children.item_needed_diaperid IS NOT NULL + SQL + end + end +end diff --git a/db/migrate/20240711020808_add_issued_at_index_to_distribution.rb b/db/migrate/20240711020808_add_issued_at_index_to_distribution.rb new file mode 100644 index 0000000000..05f43f895f --- /dev/null +++ b/db/migrate/20240711020808_add_issued_at_index_to_distribution.rb @@ -0,0 +1,7 @@ +class AddIssuedAtIndexToDistribution < ActiveRecord::Migration[7.1] + disable_ddl_transaction! + + def change + add_index :distributions, :issued_at, algorithm: :concurrently + end +end diff --git a/db/migrate/20240718010905_drop_partner_users_table.rb b/db/migrate/20240718010905_drop_partner_users_table.rb new file mode 100644 index 0000000000..4cfba2a386 --- /dev/null +++ b/db/migrate/20240718010905_drop_partner_users_table.rb @@ -0,0 +1,9 @@ +class DropPartnerUsersTable < ActiveRecord::Migration[7.1] + def up + drop_table :partner_users + + end + def down + fail ActiveRecord::IrreversibleMigration + end +end diff --git a/db/schema.rb b/db/schema.rb index 3ac916c5e4..bcc773347a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_06_01_155348) do +ActiveRecord::Schema[7.1].define(version: 2024_07_18_010905) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -188,6 +188,13 @@ t.index ["family_id"], name: "index_children_on_family_id" end + create_table "children_items", id: false, force: :cascade do |t| + t.bigint "child_id", null: false + t.bigint "item_id", null: false + t.index ["child_id", "item_id"], name: "index_children_items_on_child_id_and_item_id", unique: true + t.index ["item_id", "child_id"], name: "index_children_items_on_item_id_and_child_id", unique: true + end + create_table "counties", force: :cascade do |t| t.string "name" t.string "region" @@ -237,6 +244,7 @@ t.boolean "reminder_email_enabled", default: false, null: false t.integer "delivery_method", default: 0, null: false t.decimal "shipping_cost", precision: 8, scale: 2 + t.index ["issued_at"], name: "index_distributions_on_issued_at" t.index ["organization_id"], name: "index_distributions_on_organization_id" t.index ["partner_id"], name: "index_distributions_on_partner_id" t.index ["storage_location_id"], name: "index_distributions_on_storage_location_id" @@ -623,38 +631,6 @@ t.index ["partner_profile_id"], name: "index_partner_served_areas_on_partner_profile_id" end - create_table "partner_users", force: :cascade do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" - t.datetime "reset_password_sent_at", precision: nil - t.datetime "remember_created_at", precision: nil - t.integer "sign_in_count", default: 0, null: false - t.datetime "current_sign_in_at", precision: nil - t.datetime "last_sign_in_at", precision: nil - t.inet "current_sign_in_ip" - t.inet "last_sign_in_ip" - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.string "invitation_token" - t.datetime "invitation_created_at", precision: nil - t.datetime "invitation_sent_at", precision: nil - t.datetime "invitation_accepted_at", precision: nil - t.integer "invitation_limit" - t.string "invited_by_type" - t.bigint "invited_by_id" - t.integer "invitations_count", default: 0 - t.bigint "partner_id" - t.string "name" - t.index ["email"], name: "index_partner_users_on_email", unique: true - t.index ["invitation_token"], name: "index_partner_users_on_invitation_token", unique: true - t.index ["invitations_count"], name: "index_partner_users_on_invitations_count" - t.index ["invited_by_id"], name: "index_partner_users_on_invited_by_id" - t.index ["invited_by_type", "invited_by_id"], name: "index_partner_users_on_invited_by" - t.index ["partner_id"], name: "index_partner_users_on_partner_id" - t.index ["reset_password_token"], name: "index_partner_users_on_reset_password_token", unique: true - end - create_table "partners", id: :serial, force: :cascade do |t| t.string "name" t.string "email" diff --git a/db/seeds.rb b/db/seeds.rb index 0a138b6301..230509de1e 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -292,6 +292,8 @@ def random_record_for_org(org, klass) ) end + requestable_items = PartnerFetchRequestableItemsService.new(partner_id: p.id).call.map(&:last) + families.each do |family| Partners::AuthorizedFamilyMember.create!( first_name: Faker::Name.first_name, @@ -316,7 +318,7 @@ def random_record_for_org(org, klass) comments: Faker::Lorem.paragraph, active: Faker::Boolean.boolean, archived: false, - item_needed_diaperid: p.organization.item_id_to_display_string_map.key(Partners::Child::CHILD_ITEMS.sample) + requested_item_ids: requestable_items.sample(rand(4)) ) end @@ -334,7 +336,7 @@ def random_record_for_org(org, klass) comments: Faker::Lorem.paragraph, active: Faker::Boolean.boolean, archived: false, - item_needed_diaperid: p.organization.item_id_to_display_string_map.key(Partners::Child::CHILD_ITEMS.sample) + requested_item_ids: requestable_items.sample(rand(4)) ) end end @@ -422,6 +424,23 @@ def random_record_for_org(org, klass) end Organization.all.each { |org| SnapshotEvent.publish(org) } +# Set minimum and recomended inventory levels for items at the Pawnee Diaper Bank Organization +half_items_count = (pdx_org.items.count/2).to_i +low_items = pdx_org.items.left_joins(:inventory_items) + .select('items.*, SUM(inventory_items.quantity) AS total_quantity') + .group('items.id') + .order('total_quantity') + .limit(half_items_count) + +min_qty = low_items.first.total_quantity +max_qty = low_items.last.total_quantity + +low_items.each do |item| + min_value = rand((min_qty / 10).floor..(max_qty/10).ceil) * 10 + recomended_value = rand((min_value/10).ceil..1000) * 10 + item.update(on_hand_minimum_quantity: min_value, on_hand_recommended_quantity: recomended_value) +end + # ---------------------------------------------------------------------------- # Product Drives # ---------------------------------------------------------------------------- diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000000..ca35be08d4 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +_site diff --git a/docs/Gemfile b/docs/Gemfile new file mode 100644 index 0000000000..99e5ef1009 --- /dev/null +++ b/docs/Gemfile @@ -0,0 +1,5 @@ +source "https://rubygems.org" + +gem "github-pages", group: :jekyll_plugins + +gem "webrick", "~> 1.8" diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock new file mode 100644 index 0000000000..4051349e82 --- /dev/null +++ b/docs/Gemfile.lock @@ -0,0 +1,303 @@ +GEM + remote: https://rubygems.org/ + specs: + activesupport (7.1.3.4) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + minitest (>= 5.1) + mutex_m + tzinfo (~> 2.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + base64 (0.2.0) + bigdecimal (3.1.8) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.12.2) + colorator (1.1.0) + commonmarker (0.23.10) + concurrent-ruby (1.3.3) + connection_pool (2.4.1) + dnsruby (1.72.2) + simpleidn (~> 0.2.1) + drb (2.2.1) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + ethon (0.16.0) + ffi (>= 1.15.0) + eventmachine (1.2.7) + execjs (2.9.1) + faraday (2.10.0) + faraday-net_http (>= 2.0, < 3.2) + logger + faraday-net_http (3.1.1) + net-http + ffi (1.17.0-aarch64-linux-gnu) + ffi (1.17.0-aarch64-linux-musl) + ffi (1.17.0-arm-linux-gnu) + ffi (1.17.0-arm-linux-musl) + ffi (1.17.0-arm64-darwin) + ffi (1.17.0-x86-linux-gnu) + ffi (1.17.0-x86-linux-musl) + ffi (1.17.0-x86_64-darwin) + ffi (1.17.0-x86_64-linux-gnu) + ffi (1.17.0-x86_64-linux-musl) + forwardable-extended (2.6.0) + gemoji (4.1.0) + github-pages (231) + github-pages-health-check (= 1.18.2) + jekyll (= 3.9.5) + jekyll-avatar (= 0.8.0) + jekyll-coffeescript (= 1.2.2) + jekyll-commonmark-ghpages (= 0.4.0) + jekyll-default-layout (= 0.1.5) + jekyll-feed (= 0.17.0) + jekyll-gist (= 1.5.0) + jekyll-github-metadata (= 2.16.1) + jekyll-include-cache (= 0.2.1) + jekyll-mentions (= 1.6.0) + jekyll-optional-front-matter (= 0.3.2) + jekyll-paginate (= 1.1.0) + jekyll-readme-index (= 0.3.0) + jekyll-redirect-from (= 0.16.0) + jekyll-relative-links (= 0.6.1) + jekyll-remote-theme (= 0.4.3) + jekyll-sass-converter (= 1.5.2) + jekyll-seo-tag (= 2.8.0) + jekyll-sitemap (= 1.4.0) + jekyll-swiss (= 1.0.0) + jekyll-theme-architect (= 0.2.0) + jekyll-theme-cayman (= 0.2.0) + jekyll-theme-dinky (= 0.2.0) + jekyll-theme-hacker (= 0.2.0) + jekyll-theme-leap-day (= 0.2.0) + jekyll-theme-merlot (= 0.2.0) + jekyll-theme-midnight (= 0.2.0) + jekyll-theme-minimal (= 0.2.0) + jekyll-theme-modernist (= 0.2.0) + jekyll-theme-primer (= 0.6.0) + jekyll-theme-slate (= 0.2.0) + jekyll-theme-tactile (= 0.2.0) + jekyll-theme-time-machine (= 0.2.0) + jekyll-titles-from-headings (= 0.5.3) + jemoji (= 0.13.0) + kramdown (= 2.4.0) + kramdown-parser-gfm (= 1.1.0) + liquid (= 4.0.4) + mercenary (~> 0.3) + minima (= 2.5.1) + nokogiri (>= 1.13.6, < 2.0) + rouge (= 3.30.0) + terminal-table (~> 1.4) + github-pages-health-check (1.18.2) + addressable (~> 2.3) + dnsruby (~> 1.60) + octokit (>= 4, < 8) + public_suffix (>= 3.0, < 6.0) + typhoeus (~> 1.3) + html-pipeline (2.14.3) + activesupport (>= 2) + nokogiri (>= 1.4) + http_parser.rb (0.8.0) + i18n (1.14.5) + concurrent-ruby (~> 1.0) + jekyll (3.9.5) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (>= 0.7, < 2) + jekyll-sass-converter (~> 1.0) + jekyll-watch (~> 2.0) + kramdown (>= 1.17, < 3) + liquid (~> 4.0) + mercenary (~> 0.3.3) + pathutil (~> 0.9) + rouge (>= 1.7, < 4) + safe_yaml (~> 1.0) + jekyll-avatar (0.8.0) + jekyll (>= 3.0, < 5.0) + jekyll-coffeescript (1.2.2) + coffee-script (~> 2.2) + coffee-script-source (~> 1.12) + jekyll-commonmark (1.4.0) + commonmarker (~> 0.22) + jekyll-commonmark-ghpages (0.4.0) + commonmarker (~> 0.23.7) + jekyll (~> 3.9.0) + jekyll-commonmark (~> 1.4.0) + rouge (>= 2.0, < 5.0) + jekyll-default-layout (0.1.5) + jekyll (>= 3.0, < 5.0) + jekyll-feed (0.17.0) + jekyll (>= 3.7, < 5.0) + jekyll-gist (1.5.0) + octokit (~> 4.2) + jekyll-github-metadata (2.16.1) + jekyll (>= 3.4, < 5.0) + octokit (>= 4, < 7, != 4.4.0) + jekyll-include-cache (0.2.1) + jekyll (>= 3.7, < 5.0) + jekyll-mentions (1.6.0) + html-pipeline (~> 2.3) + jekyll (>= 3.7, < 5.0) + jekyll-optional-front-matter (0.3.2) + jekyll (>= 3.0, < 5.0) + jekyll-paginate (1.1.0) + jekyll-readme-index (0.3.0) + jekyll (>= 3.0, < 5.0) + jekyll-redirect-from (0.16.0) + jekyll (>= 3.3, < 5.0) + jekyll-relative-links (0.6.1) + jekyll (>= 3.3, < 5.0) + jekyll-remote-theme (0.4.3) + addressable (~> 2.0) + jekyll (>= 3.5, < 5.0) + jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) + rubyzip (>= 1.3.0, < 3.0) + jekyll-sass-converter (1.5.2) + sass (~> 3.4) + jekyll-seo-tag (2.8.0) + jekyll (>= 3.8, < 5.0) + jekyll-sitemap (1.4.0) + jekyll (>= 3.7, < 5.0) + jekyll-swiss (1.0.0) + jekyll-theme-architect (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-cayman (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-dinky (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-hacker (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-leap-day (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-merlot (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-midnight (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-minimal (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-modernist (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-primer (0.6.0) + jekyll (> 3.5, < 5.0) + jekyll-github-metadata (~> 2.9) + jekyll-seo-tag (~> 2.0) + jekyll-theme-slate (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-tactile (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-time-machine (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-titles-from-headings (0.5.3) + jekyll (>= 3.3, < 5.0) + jekyll-watch (2.2.1) + listen (~> 3.0) + jemoji (0.13.0) + gemoji (>= 3, < 5) + html-pipeline (~> 2.2) + jekyll (>= 3.0, < 5.0) + kramdown (2.4.0) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.4) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.6.0) + mercenary (0.3.6) + minima (2.5.1) + jekyll (>= 3.5, < 5.0) + jekyll-feed (~> 0.9) + jekyll-seo-tag (~> 2.1) + minitest (5.24.1) + mutex_m (0.2.0) + net-http (0.4.1) + uri + nokogiri (1.16.7-aarch64-linux) + racc (~> 1.4) + nokogiri (1.16.7-arm-linux) + racc (~> 1.4) + nokogiri (1.16.7-arm64-darwin) + racc (~> 1.4) + nokogiri (1.16.7-x86-linux) + racc (~> 1.4) + nokogiri (1.16.7-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.16.7-x86_64-linux) + racc (~> 1.4) + octokit (4.25.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + public_suffix (5.1.1) + racc (1.8.0) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rexml (3.3.2) + strscan + rouge (3.30.0) + rubyzip (2.3.2) + safe_yaml (1.0.5) + sass (3.7.4) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sawyer (0.9.2) + addressable (>= 2.3.5) + faraday (>= 0.17.3, < 3) + simpleidn (0.2.3) + strscan (3.1.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + typhoeus (1.4.1) + ethon (>= 0.9.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (1.8.0) + uri (0.13.0) + webrick (1.8.1) + +PLATFORMS + aarch64-linux + aarch64-linux-gnu + aarch64-linux-musl + arm-linux + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86-linux + x86-linux-gnu + x86-linux-musl + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + github-pages + webrick (~> 1.8) + +BUNDLED WITH + 2.5.14 diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000000..d4d4e35d30 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,10 @@ +remote_theme: pages-themes/slate@v0.2.0 +plugins: +- jekyll-remote-theme + +title: Human Essentials +description: Documentation + +github: + is_project_page: false +show_downloads: false diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html new file mode 100644 index 0000000000..b65b4d99d3 --- /dev/null +++ b/docs/_layouts/default.html @@ -0,0 +1,56 @@ + + + + + + + + + +{% seo %} + {% include head-custom.html %} + + + + + +
+
+ {% if site.github.is_project_page %} + View on GitHub + {% endif %} + +

{{ site.title | default: site.github.repository_name }}

+

{{ site.description | default: site.github.project_tagline }}

+ + {% if site.show_downloads %} +
+ Download this project as a .zip file + Download this project as a tar.gz file +
+ {% endif %} +
+
+ + +
+
+ {{ content }} +
+
+ + + + + diff --git a/doc/architecture/barcode-retrieval.svg b/docs/architecture/barcode-retrieval.svg similarity index 100% rename from doc/architecture/barcode-retrieval.svg rename to docs/architecture/barcode-retrieval.svg diff --git a/doc/architecture/decisions/0001-record-architecture-decisions.md b/docs/architecture/decisions/0001-record-architecture-decisions.md similarity index 100% rename from doc/architecture/decisions/0001-record-architecture-decisions.md rename to docs/architecture/decisions/0001-record-architecture-decisions.md diff --git a/doc/architecture/decisions/0002-items-are-tracked-using-associative-models-as-they-move-through-the-system.md b/docs/architecture/decisions/0002-items-are-tracked-using-associative-models-as-they-move-through-the-system.md similarity index 100% rename from doc/architecture/decisions/0002-items-are-tracked-using-associative-models-as-they-move-through-the-system.md rename to docs/architecture/decisions/0002-items-are-tracked-using-associative-models-as-they-move-through-the-system.md diff --git a/doc/architecture/decisions/0003-multitenancy-instead-of-multiple-instances.md b/docs/architecture/decisions/0003-multitenancy-instead-of-multiple-instances.md similarity index 100% rename from doc/architecture/decisions/0003-multitenancy-instead-of-multiple-instances.md rename to docs/architecture/decisions/0003-multitenancy-instead-of-multiple-instances.md diff --git a/doc/architecture/decisions/0005-extract-all-partner-operations-into-a-separate-application-intended-for-partners.md b/docs/architecture/decisions/0005-extract-all-partner-operations-into-a-separate-application-intended-for-partners.md similarity index 100% rename from doc/architecture/decisions/0005-extract-all-partner-operations-into-a-separate-application-intended-for-partners.md rename to docs/architecture/decisions/0005-extract-all-partner-operations-into-a-separate-application-intended-for-partners.md diff --git a/doc/architecture/decisions/0006-instantiating-items-from-base-items.md b/docs/architecture/decisions/0006-instantiating-items-from-base-items.md similarity index 100% rename from doc/architecture/decisions/0006-instantiating-items-from-base-items.md rename to docs/architecture/decisions/0006-instantiating-items-from-base-items.md diff --git a/doc/architecture/decisions/0007-barcode-querying.md b/docs/architecture/decisions/0007-barcode-querying.md similarity index 100% rename from doc/architecture/decisions/0007-barcode-querying.md rename to docs/architecture/decisions/0007-barcode-querying.md diff --git a/doc/architecture/decisions/0008-merging-partner-base-and-diaper-base.md b/docs/architecture/decisions/0008-merging-partner-base-and-diaper-base.md similarity index 100% rename from doc/architecture/decisions/0008-merging-partner-base-and-diaper-base.md rename to docs/architecture/decisions/0008-merging-partner-base-and-diaper-base.md diff --git a/doc/architecture/decisions/0009-stick-with-adminlte-for-app-design.md b/docs/architecture/decisions/0009-stick-with-adminlte-for-app-design.md similarity index 100% rename from doc/architecture/decisions/0009-stick-with-adminlte-for-app-design.md rename to docs/architecture/decisions/0009-stick-with-adminlte-for-app-design.md diff --git a/doc/architecture/decisions/README.md b/docs/architecture/decisions/README.md similarity index 100% rename from doc/architecture/decisions/README.md rename to docs/architecture/decisions/README.md diff --git a/doc/architecture/multi-tenancy.svg b/docs/architecture/multi-tenancy.svg similarity index 100% rename from doc/architecture/multi-tenancy.svg rename to docs/architecture/multi-tenancy.svg diff --git a/doc/architecture/overview.md b/docs/architecture/overview.md similarity index 100% rename from doc/architecture/overview.md rename to docs/architecture/overview.md diff --git a/doc/architecture/partner-request.svg b/docs/architecture/partner-request.svg similarity index 100% rename from doc/architecture/partner-request.svg rename to docs/architecture/partner-request.svg diff --git a/doc/architecture/physical-flow.svg b/docs/architecture/physical-flow.svg similarity index 100% rename from doc/architecture/physical-flow.svg rename to docs/architecture/physical-flow.svg diff --git a/doc/architecture/timeline-flow.svg b/docs/architecture/timeline-flow.svg similarity index 100% rename from doc/architecture/timeline-flow.svg rename to docs/architecture/timeline-flow.svg diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 0000000000..58c93cac07 --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,13 @@ +* [User Guide](user_guide/bank/) +* [Developer Architecture Overview](architecture/overview) + +## Developer Notes + +To run this documentation site locally: + +``` +bundle install +bundle exec jekyll serve --livereload +``` + +Then open your browser to [localhost:4000](http://localhost:4000) diff --git a/doc/templates/release-template.md b/docs/templates/release-template.md similarity index 100% rename from doc/templates/release-template.md rename to docs/templates/release-template.md diff --git a/docs/user_guide/bank/asking_for_changes.md b/docs/user_guide/bank/asking_for_changes.md new file mode 100644 index 0000000000..490fdd3410 --- /dev/null +++ b/docs/user_guide/bank/asking_for_changes.md @@ -0,0 +1,9 @@ +# Asking for changes + +Although we are a small team with a long list of tweaks and improvements that we are working toward, we do welcome ideas for how Human Essentials can support your work better. + +The best ways to get those to us are through the Slack channel or by coming to the Stakeholder Circle and raising them there. + +The Stakeholder Circle has the advantage of being able to talk live to other banks *and* the development team in the same space. We've had a lot of "Oh, we didn't realize that" moments in these meetings that directly result in changes to the system. + +(The Slack channel, on the other hand, is open every day.) \ No newline at end of file diff --git a/docs/user_guide/bank/community_donation_sites.md b/docs/user_guide/bank/community_donation_sites.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/community_donation_sites.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/community_manufacturers.md b/docs/user_guide/bank/community_manufacturers.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/community_manufacturers.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/community_product_drive_participants.md b/docs/user_guide/bank/community_product_drive_participants.md new file mode 100644 index 0000000000..289662449b --- /dev/null +++ b/docs/user_guide/bank/community_product_drive_participants.md @@ -0,0 +1,40 @@ +# Product Drive Participants + +If you conduct product drives, the product drive participants list lets you manage the participant's contact information and see their donation history. + +To manage product drive participants, click on "Product Drive Participants" under the "Community" section. + +![Product Drive Participants](images/community/product_drive_participants/product_drive_page.jpg) + +This page lists some basic details from all previously entered product drive participants -- business, contact, phone email, and total items, letting you drill down to more information, or to edit each participant's information. + +## Adding participants + +To add a new participant, click on the "+ New Product Drive Participant" button, add their details including the business name, contact name, phone, email, address and comments. + +![Add Participant](images/community/product_drive_participants/add_participant.jpg) + +After saving the participant's details there will be a new row in the Product Drive Participants page. + +You can also add new participants "on the fly" as you enter the donations, through the [New Donation](essentials_donations.md) page. + +[TODO: link to new donations ] + +## Viewing participant information + +Click on "View" for more details about the product drive participant which also shows the date of each donation, volume, and donations details. + +![Participant Details](images/community/product_drive_participants/participant_details.jpg) + +## Editing participant information + +Click the "Edit" button to edit the participant's details. + +![Edit Participant Details](images/community/product_drive_participants/edit_participant.jpg) + +## Exporting participants + +You can export all the participants by clicking on the "Export Product Drive Paticipants" button. +Currently we are not providing all the participants' details in the export. + +![Export Drive Participants](images/community/product_drive_participants/export_participants.jpg) \ No newline at end of file diff --git a/docs/user_guide/bank/community_vendors.md b/docs/user_guide/bank/community_vendors.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/community_vendors.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/essentials_dashboard.md b/docs/user_guide/bank/essentials_dashboard.md new file mode 100644 index 0000000000..1ef9214ed3 --- /dev/null +++ b/docs/user_guide/bank/essentials_dashboard.md @@ -0,0 +1,28 @@ +DRAFT USER GUIDE +# Your Dashboard +When you log in, your initial screen will be a dashboard with information that is useful on a daily basis for most banks. + +If you haven't finished setting up your bank in the system, the "getting started" steps (as described in an earlier section) will appear. + +Otherwise, there are 4 sections to the dashboard: announcements, outstanding requests, partner approvals, and bank-wide low inventory. + +![top of dashboard page](images/essentials/dashboard/essentials_dashboard_1.png) + +#### Announcements +This section is announcements from the human resources team. This is where we tell you what changes there have been in the latest release, and let everyone know about any significant issues with the system that affect everybody (with workarounds, if we can.) +Look for new info here every Monday - as we put out new releases most Sunday mornings. + +#### Outstanding Requests +This is pretty much what it sounds like - a list of the requests from your partners that have not yet been fulfilled. You can bring up the details of each request by clicking on the date. + + +![bottom of dashboard page](images/essentials/dashboard/essentials_dashboard_2.png) + +#### Partner Approvals +This lists the partner profiles that have been submitted for approval. Partners can not submit requests until they have been approved. To review the application, click on the "Review Application" button beside the partner in the Action colum. For more details on that, see [Approving a partner](pm_approving_a_partner.md) + +#### Bank-wide Low Inventory +This lists items whose *bank-wide* inventory has fallen below the recommended or minimum quantity levels you have set on the items. If the item's level in inventory across the bank has fallen below the minimum quantity, it will appear in red. + +For help on setting those levels, see [Inventory Items](inventory_items.md). If you haven't set those levels, the items will not appear on this list, even if you have no inventory. + diff --git a/docs/user_guide/bank/essentials_distributions.md b/docs/user_guide/bank/essentials_distributions.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/essentials_distributions.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/essentials_donations.md b/docs/user_guide/bank/essentials_donations.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/essentials_donations.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/essentials_pick_ups.md b/docs/user_guide/bank/essentials_pick_ups.md new file mode 100644 index 0000000000..301be5b272 --- /dev/null +++ b/docs/user_guide/bank/essentials_pick_ups.md @@ -0,0 +1,37 @@ +# Pick Ups & Deliveries Calendar + +Pick Ups & Deliveries shows scheduled and completed distributions in a calendar format on a monthly basis. + +Click on "Pick Ups & Deliveries" in the left-hand menu to view the calendar. + +![PickUps & Delivery Calendar](images/essentials/pick_ups/pickup&delivery.jpg) + +Click on any scheduled distribution to view details on all the distributions for that day. +The Distribution Schedule page shows details including the Partner,the time of distribution, source inventory, the total number of items, and the status of the distribution. + +![Specific Day Distribution](images/essentials/pick_ups/specific_day_distribution_schedule.jpg) + +Once the partner has the distributed goods, clicking "Distribution Complete" changes the status of the distribution from "Scheduled" to "Complete", so you can track what is still in your hands and what is with your partners in the community. It does not, at this time, remove it from the calendar. + +Click "View" for details on the distribution's source location, agency representative, delivery method, shipping cost, comments, and state. This also shows a list of items included in the distribution. + +![Distribution from Source Inventory to Partner](images/essentials/pick_ups/distribution_from_source_to_partner.jpg) + +If you want to print the details of the distribution to use as a contents list or receipt, click on "Print". + +## Sync Pick Ups & Deliveries Calendar with Google Calendar + +You can sync the Pick Ups & Deliveries Calendar with Google Calendar for more convenience. + +From the calendar page, click on "Copy Calendar URL to clipboard" to get the calendar URL to be added to Google calendar. +![Copy_Calendar_URL](images/essentials/pick_ups/copy_calendar_url.png) + +Then, open your Google Calendar. On Google Calendar, in the "Other calendars" section click on the "+" which will bring up several choices. + +![Other Calendars](images/essentials/pick_ups/other_calendars.jpg) + +Select "From URL"and paste the URL you copied in the "URL of calendar" section and click on "Add Calendar" + +![Add Calendar](images/essentials/pickups/add_calendar.jpg) + +Events from the Human Essentials Pick Ups & Deliveries Calendar should be accessible to you on Google Calendar when the sync is complete. \ No newline at end of file diff --git a/docs/user_guide/bank/essentials_purchases.md b/docs/user_guide/bank/essentials_purchases.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/essentials_purchases.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/essentials_requests.md b/docs/user_guide/bank/essentials_requests.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/essentials_requests.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/exports.md b/docs/user_guide/bank/exports.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/exports.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/getting_started_access_levels.md b/docs/user_guide/bank/getting_started_access_levels.md new file mode 100644 index 0000000000..807808bd2e --- /dev/null +++ b/docs/user_guide/bank/getting_started_access_levels.md @@ -0,0 +1,47 @@ +DRAFT USER GUIDE +# Levels of Access + +There are 4 different levels of access in the system: +1. Organization Admin +2. Organization User +3. Partner +4. Super Admin + +(We are currently discussing adding a user who is restricted to a specific location, but we don't have it yet) + +## Organization Admin + +Organization Admins are the top-level user at your essentials bank. They can do everything an Organization User (see below) can do, plus: + +- Administer the users for your bank ( see [User Management](getting_started_user_management.md)) +- Administer the users for your partners ( see [Partner User Management](pm_partner_user_admin.md)) +- Finalize audits (see [Audits](inventory_audits.md)) +- Customize organization settings( see [Customization](getting_started_customization.md)) + +## Organization User +This is your basic user for your bank -- they can do all the data entry except for those things reserved for the Organization Admin, above. Organization Users have the right to: + +- Enter donations, purchases, distributions +- Fulfil requests +- View pickup and delivery calendars +- Administer Partners (excluding partner user management) +- Perform Audit data entry (finalization is reserved for the Organization Admin) +- Manage the Community information +- View all the bank-level reports + +## Partner +Partner users' access is, of course, limited to the scope of the Partner they belong to. +Within that scope, they can: +- Edit their partner profile +- Submit requests (if the Partner has been approved) +- View their distributions +- Administer families and children (only if they are allowed to make Child Requests (this can be limited by the Org Admin, see [Customization](getting_started_customization.md) )) (note, the other user types cannot see this information) + +## Super Admin +This bit is for information only -- A limited number of Human Essentials staff have a superadmin role that allows us to perform the following duties: +- Review and approve Account requests +- Make system-wide announcements +- Administer users (we need it because every once in awhile, the organization admin for a bank will leave without promoting someone else to that role.) +- Administer "Base Items" +- Administer the list of NDBN members +- Administer some organization settings [TODO: Raise the question of whether we *should* be administering most of this -- we don't have rights to change any of the more recently added values on the org.] \ No newline at end of file diff --git a/docs/user_guide/bank/getting_started_customization.md b/docs/user_guide/bank/getting_started_customization.md new file mode 100644 index 0000000000..f62a611a4e --- /dev/null +++ b/docs/user_guide/bank/getting_started_customization.md @@ -0,0 +1,174 @@ +DRAFT USER GUIDE + +# Organization information and customization + +Every essentials bank has its own way of doing things. +With that in mind, there are a number of things you can tweak. +This is done through "My Organization". +Only organization admins have access to this area. + +## Getting to the organization edit + +Scroll down to the bottom of the left-hand menu (you may have to collapse areas that you've opened)to the last item. Click on "My Organization". + +This brings up a view of the organization settings. It shows everything we are going to talk about for the rest of this section, as well as the users (more on them in the next section) +Scroll down until you see an Edit button. Click it. + +You should now be in a screen that is titled "Editing [Your bank name]" + +Here's all the fields, with a bit about the implications of each one: + +## Basic Information +### Name +The name of your essentials bank. This appears in the headings on most screens, and will appear on printouts (such as the distribution printout many banks use as a packing slip), and most reports. + +### Short name +You don't change this -- we assigned it when we set it up -- it's here for reference for support calls if we need it. + +### NDBN membership +This should be filled in already from your account request,but if it isn't, you can select it from the list. That list is updated on an irregular basis, so if you are an NDBN member, and you aren't on the list, let us know and we'll get a fresh list uploaded. +This is included on the Annual survey report. That's the only effect. + +### Url +Your essentials bank's website address. This is mostly used during the account request process, so we can check if you are a good fit before you invest a lot of time and energy into the system. + +### Email +Your essential bank's email address. This is shown to the partners on their help page, and is included in reminder emails, so please use an email that is monitored. This email is also included on distribution and donation printouts and the annual survey [TODO: Confirm each of those.] + +### Address +Your essential bank's primary address. This is shown on the distribution and donation printouts, and the annual survey [TODO: Confirm annual survey] + +## Reminder Emails (optional) +You can opt, on a partner by partner basis, to have reminder emails sent. +There is also a check-box on the partner that must be checked for the partner to get these emails. + + +The text of this email will be: + +Hello [Partner's name], + +This is a friendly reminder that [Your bank's name] requires your human essentials requests to be submitted by [the deadline date, including month and year] +if you would like to receive a distribution next month. + +Please log into Human Essentials at https://humanessentials.app before this date and submit your request if you are intending to submit an essentials request. + +Please contact [Your bank's name] at <%= @organization.email %> +if you have any questions about this! + + + + + + +### Reminder day (Day of month an e-mail reminder is sent to partners to submit requests) + At this point, we send those emails once a month on the day of the month you indicate here. +If you do not pick a day, no reminder emails are sent. + +### Deadline day (Final day of the month to submit requests) +This day will be included in the reminder email message, + + +## Default Intake Location + +This is the default storage location for donations and purchases. +If you specify this, it will be pre-populated as the storage location when you are adding new donations or purchases. + +## Partner Profile Sections +The [Partner Profile](pm_partner_profiles.md) is a very large form that includes a lot of information. You might not care about all of it. +This field lets you specify which of the sub-sections of that form will be used. +The Agency Information subsection is always included. +If you do not specify any sections, they will all be included. +The sections are: +- Media Information +- Agency Stability +- Organizational Capacity +- Sources of Funding +- Area Served +- Population Served +- Agency Distribution Information +- Attached Documents + +## Default Storage Location + +The bank-wide default storage location for donations and purchases. +You can also specify a different default storage location on any partner, which will override this default. +If you specify a default storage location, it will be pre-populated as the storage location when you are adding new distributions. + +## Custom Partner Invitation Message +[TODO: Ensure that this is working!] + +When you invite a partner, they get an email. This field lets you specify the message you are sending to them. Just text -- we don't have any personalization capability for this email at this time. + +If you do not specify a message, the invitation will contain: + +Hello [partner's email] + +You've been invited to become a partner with Pawnee Diaper Bank! + +Please click the link below to accept your invitation and create an account and you'll be able to begin requesting distributions. + +Please contact [bank's email] if you are encountering any issues. + +Accept Invitation +For security reasons these invitations expire. This invitation will expire in 8 hours or if a new password reset is triggered. + +If your invitation has an expired message, go here(link to the log in page) and enter your email address to reset your password. + +Feel free to ignore this email if you are not interested or if you feel it was sent by mistake. + + +## Questions for the annual survey +These fields are only here to be reported on the annual survey. + +### Does your bank repackage essentials? +### Does your bank distribute monthly + +## Custom Units +The number of items throughout the bank's view of the system is the number of units (e.g. diapers), but +partners often think in terms of packs of diapers. Because banks were getting a lot of partners requesting the number of packs of diapers, instead of the number of diapers, we have introduced the ability for banks to allow the partners to request other units (e.g. packs) + +This deserves a page of it's own - but in short, you can specify units here, that you can add to your items. The partners then can ask for, say, 'packs' of diapers. You will still have to translate those to the number of items when distributing +Because there is a lot of variety in pack size across brands. + +[TODO: This is actually a good candidate for a video showing the whole process] + +## Controlling what kind of request a partner can make + +There are three different ways a partner can request essentials -- a "Child based" request, a request by number of individuals, and a straight quantity-based request. Some banks want to limit which requests the partners can make, in order to minimize partner confusion. +These three fields allow you to control which requests the partners can use. +If you allow more than one kind, the partner can also limit their own. +Note that if any partner limits themselves to a single type, you won't be able to remove that type. So, if you think you only want to allow quantity-based requests, doing that up front is a fine idea. + +### Enable partners to make child-based requests +### Enable partners to make requests for individuals? +### Enable partners to make quantity-based requests? + +## Customizing the distribution printout +There are four fields that allow you to tweak the appearance of the distribution printout + + +### Show Year-to-date values on the distribution printout? +Some banks don't want to show year-to-date values on the receipt (1, below) because their fiscal year is not the calendar year. +### Include Signature Lines on Distribution Printout +If "yes", this will include a space for someone from the bank and from the partner to sign the distribution printout (2, below) - which can be useful as a receipt acknowledgement. +### Hide both value columns +The default is to show the in-kind value of the items on the receipt (3, below). Many banks don't need to show this information on the distribution printout. +Note: Hiding this also hides the corresponding values on the single donation printout. +### Hide the package column on distribution receipts? +This hides the packages column on the distribution printout (4, below). Because different brands of essentials use different size packages, this +column is useful mainly for banks that repackage their essentials into uniform package sizes. If you have a uniform package size, you can specify that on the item (see [Inventory Items](inventory_items.md)) + +![distribution printout marked up with customizable sections](images/getting_started/customization/gs_customization_distribution_printout_customizable_sections.png) + +## Use One Step Invite and Approve partner process? +Partners can't submit requests until they are approved by the bank. +The full partner approval process requires the partner to fill in their profile and submit it for approval. Some banks handle that for their partners, gather the information through other means (such as a phone conversation). +Checking this will change the process so that the partners are automatically approved when they are invited. Note that any invited partners that are not yet approved will still need to be approved by the bank. + +## Distribution Email Content +Note that there is a checkbox on the partner for them to receive distribution emails. We recommend you do customize this content, as the default text is abrupt. +You can customize this quite a bit! [TODO: expand. Maybe provide a real life example.] + +## Logo + +The logo that you upload here will appear several places throughout the system, including on your distribution and donation printouts. Larger logos will impact your performace -- the 763 x 188 size is a good guideline. diff --git a/docs/user_guide/bank/getting_started_donation_sites.md b/docs/user_guide/bank/getting_started_donation_sites.md new file mode 100644 index 0000000000..15a028ac76 --- /dev/null +++ b/docs/user_guide/bank/getting_started_donation_sites.md @@ -0,0 +1 @@ +Not yet written - we are reviewing the getting started material and donation sites are an optional component. \ No newline at end of file diff --git a/docs/user_guide/bank/getting_started_inventory.md b/docs/user_guide/bank/getting_started_inventory.md new file mode 100644 index 0000000000..51751ee73e --- /dev/null +++ b/docs/user_guide/bank/getting_started_inventory.md @@ -0,0 +1,26 @@ +DRAFT USER GUIDE +# Getting started -- inventory +When we set up your organization, we provide a default set of items ranging from adult briefs to kids (newborn) +to tampons. You can modify this list at any time, through the [Inventory Items](inventory_items.md) feature. +This section of the guide will take you through a couple of ways to get your initial inventory in. +[TODO: Rewrite once we have audits available as a starting strategy] +[TODO: Really -- this should just be inventory adjustment front and center -- but that's not how the system works atm] +## Adding a past donation +One easy way to get your current inventory set up is to just enter a donation per storage location with all of the current inventory, and put a +comment on it to indicate that this was your starting inventory. The values you enter there will be included in your donation reports for the year, +so if you don't want that, either backdate the donation to the previous year or use the inventory adjustments method, below. + +To enter a past donation, simply click on the "Add past donation" button, which takes you to the New Donation screen, which is described in a lot of detail [here](essentials_donations.md) + +![navigation to enter_past_donation](images/getting_started/inventory/gs_inventory_1.png) + +## Adding a past purchase +Similarly, if you've started your bank with a purchase of goods, you might want to enter the details as a purchase. The values you enter here will be included in your purchase reports for the year chosen. + +![navigation to enter_past_purchase](images/getting_started/inventory/gs_inventory_2.png) +## Inventory adjustment +Another (some say superior) way to set up your bank is through inventory adjustments. This has the advantage of keeping your initial +inventory, however it was acquired, out of donation and purchase reports. The details on entering an inventory adjustment can be found [here](inventory_adjustments.md) + +![navigation to enter new inventory adjustment](images/getting_started/inventory/gs_inventory_3.png) + diff --git a/docs/user_guide/bank/getting_started_partners.md b/docs/user_guide/bank/getting_started_partners.md new file mode 100644 index 0000000000..6fca0940c0 --- /dev/null +++ b/docs/user_guide/bank/getting_started_partners.md @@ -0,0 +1,25 @@ +DRAFT USER GUIDE +# Getting started -- partners + +## Things you need to know about partners before deciding how you are handling them + +1/ You need to have your partners in the system to be able to record distributions to them. + +2/ However, if you're not ready to have your partners make requests yet, that's ok -- you can put them in the system without inviting them. You'll still be able to record what you are distributing to them. + +3/ You can import all your partners at once. You can only import partners once, though -- this is a precaution to make sure we don't accidently create duplicates. + +4/ The usual way to handle bringing on partners to be able to make requests is to invite them, then have them fill in their profile before approving them, so that you get the information from them that your bank needs for grants, etc. However, if that's not how you want to work, it is also possible to invite and approve them in one step. + +5/ A lot of banks set up a partner as a proxy for their direct distribution. This is allowed, and you can switch back and forth between being a bank and a partner with the same login + +## Adding a single partner +Click on the "Add a Partner" button on your "Getting started" screen +(you can also click on "Partner Agencies", then "All Partners", then "Add a Partner") + +![navigation](images/getting_started/partners/gs_just_starting_step_2.png) + +Further details on adding a partner can be found [here](pm_adding_a_partner.md) + +## Importing partners +For details on how to do a bulk import of your partners, please click [here](pm_importing_partners.md) diff --git a/docs/user_guide/bank/getting_started_storage_locations.md b/docs/user_guide/bank/getting_started_storage_locations.md new file mode 100644 index 0000000000..2966b5f331 --- /dev/null +++ b/docs/user_guide/bank/getting_started_storage_locations.md @@ -0,0 +1,35 @@ +DRAFT USER GUIDE +# Getting Started -- Storage Locations + +A bank can have multiple storage locations -- these range from warehouses down to space in people's houses. You need at least one storage location in the system. + +Each storage location has its own inventory of a common list of items (e.g. different sizes of diapers) + +When you are inputting donations, or purchases, you will specify the storage location, and the donated/purchased items will be added to that storage location's inventory. Similarly, when you make distributions, you choose a storage location, and can see the inventory levels for each item before committing to the distribution. + +How do you enter a storage location? If you haven't any storage locations yet, you can click on "Add a Storage Location" in the "Just Starting?" sequence at the top of your dashboard. + +![navigation](images/getting_started/storage_locations/gs_just_starting_step_1.png) + +You can also click on "Inventory", then "Storage Locations" in the left-hand menu, then "New Storage Locations" + +![navigation](images/getting_started/storage_locations/gs_storage_locations_navigation.png) + +Either of these will bring up the new storage location screen + +![navigation](images/getting_started/storage_locations/new_storage_location.png) + +Fill in the information: +- Name and Address are mandatory +- Square Footage and Warehouse Type are used for the Annual Survey (our collection of information corresponding to the NDBN Annual Survey) +- Set Time Zone to the local time zone for the storage location. This impacts the Pickup & Deliveries calendar but not much else. + +[TODO: Understand *How* timezone affects the P&D calendar] + +Click "Save" + +You'll see a list of your storage locations. Click "New Storage Location" if you want to enter another locations. + +![storage location index](images/getting_started/storage_locations/storage_location_index.png) + +Next step: [Entering or uploading your initial partner list](getting_started_partners.md) diff --git a/docs/user_guide/bank/getting_started_user_management.md b/docs/user_guide/bank/getting_started_user_management.md new file mode 100644 index 0000000000..e586f929fd --- /dev/null +++ b/docs/user_guide/bank/getting_started_user_management.md @@ -0,0 +1,36 @@ +DRAFT USER GUIDE +# Essentials Bank User Administration + +If you're not the sole worker at your essentials bank, you'll likely want other staff to have some access to Human Essentials. + +Note that only people with admin status can administer users, and that partners are administered on a partner-by-partner basis (see [Partner User Admin](pm_partner_user_admin.md)). + +To manage the rights for your essentials bank users: + +Click on the My Organisation view, then scroll down to the bottom. There is a user administration section there. + +![navigation users in organization](images/getting_started/user_admin/gs_user_admin_navigation.png) + +## Inviting new users +Clicking "Invite User to this Organization" will open a popup where you will enter the name and email of the user. Once you click "Invite User", they will receive an email with a link to follow to set up their password. +Please note that this link expires. If they don't click on the link in time, the workaround is for them to go to the login screen (http::/human_essentials.app) and click on "reset password". + +![invite user pop_up](images/getting_started/user_admin/gs_user_admin_invite_user.png) + +## Promote a user to admin +To promote a user to admin, just click on the "actions" button beside their information, and then click on "Promote to Admin" +![promote_user](images/getting_started/user_admin/gs_user_admin_promote_user.png) + +## Demote an admin +To remove a admin's admin rights, just click on the "Demote to User" button beside their information. +This is a necessary step if you want to remove their rights entirely. [TODO: Discuss if this really is necessary -- shouldn't we be able to do this as one step?] +Also note that you can't demote yourself. This reduces the risk of leaving the bank without an admin accidentally! +![demote_user](images/getting_started/user_admin/gs_user_admin_demote_admin.png) +## Remove a user +To remove a user, just click on the "actions" button beside their information, and then click on "Remove User" (note that if they are an admin, you have to demote them first) +![remove_user](images/getting_started/user_admin/gs_user_admin_remove_user.png) +## What to do if your admin has left without assigning a new one. +Send an email to info@humanessentials.app (or DM one of the human essentials staff on slack) explaining the situation, and we'll walk you through our process for getting a new admin set up. + + + diff --git a/docs/user_guide/bank/images/community/product_drive_participants/add_participant.jpg b/docs/user_guide/bank/images/community/product_drive_participants/add_participant.jpg new file mode 100644 index 0000000000..6e23e0dccb Binary files /dev/null and b/docs/user_guide/bank/images/community/product_drive_participants/add_participant.jpg differ diff --git a/docs/user_guide/bank/images/community/product_drive_participants/edit_participant.jpg b/docs/user_guide/bank/images/community/product_drive_participants/edit_participant.jpg new file mode 100644 index 0000000000..c4096bed63 Binary files /dev/null and b/docs/user_guide/bank/images/community/product_drive_participants/edit_participant.jpg differ diff --git a/docs/user_guide/bank/images/community/product_drive_participants/export_participants.jpg b/docs/user_guide/bank/images/community/product_drive_participants/export_participants.jpg new file mode 100644 index 0000000000..6e75f2e871 Binary files /dev/null and b/docs/user_guide/bank/images/community/product_drive_participants/export_participants.jpg differ diff --git a/docs/user_guide/bank/images/community/product_drive_participants/participant_details.jpg b/docs/user_guide/bank/images/community/product_drive_participants/participant_details.jpg new file mode 100644 index 0000000000..9f6725817d Binary files /dev/null and b/docs/user_guide/bank/images/community/product_drive_participants/participant_details.jpg differ diff --git a/docs/user_guide/bank/images/community/product_drive_participants/product_drive_page.jpg b/docs/user_guide/bank/images/community/product_drive_participants/product_drive_page.jpg new file mode 100644 index 0000000000..ac3de186e3 Binary files /dev/null and b/docs/user_guide/bank/images/community/product_drive_participants/product_drive_page.jpg differ diff --git a/docs/user_guide/bank/images/essentials/dashboard/essentials_dashboard_1.png b/docs/user_guide/bank/images/essentials/dashboard/essentials_dashboard_1.png new file mode 100644 index 0000000000..dd75480f4d Binary files /dev/null and b/docs/user_guide/bank/images/essentials/dashboard/essentials_dashboard_1.png differ diff --git a/docs/user_guide/bank/images/essentials/dashboard/essentials_dashboard_2.png b/docs/user_guide/bank/images/essentials/dashboard/essentials_dashboard_2.png new file mode 100644 index 0000000000..c9f52833c4 Binary files /dev/null and b/docs/user_guide/bank/images/essentials/dashboard/essentials_dashboard_2.png differ diff --git a/docs/user_guide/bank/images/essentials/pick_ups/add_calendar.jpg b/docs/user_guide/bank/images/essentials/pick_ups/add_calendar.jpg new file mode 100644 index 0000000000..d4efa247fe Binary files /dev/null and b/docs/user_guide/bank/images/essentials/pick_ups/add_calendar.jpg differ diff --git a/docs/user_guide/bank/images/essentials/pick_ups/copy_calendar_url.png b/docs/user_guide/bank/images/essentials/pick_ups/copy_calendar_url.png new file mode 100644 index 0000000000..4747a3a1e9 Binary files /dev/null and b/docs/user_guide/bank/images/essentials/pick_ups/copy_calendar_url.png differ diff --git a/docs/user_guide/bank/images/essentials/pick_ups/distribution_from_source_to_partner.jpg b/docs/user_guide/bank/images/essentials/pick_ups/distribution_from_source_to_partner.jpg new file mode 100644 index 0000000000..db4ed094ca Binary files /dev/null and b/docs/user_guide/bank/images/essentials/pick_ups/distribution_from_source_to_partner.jpg differ diff --git a/docs/user_guide/bank/images/essentials/pick_ups/other_calendars.jpg b/docs/user_guide/bank/images/essentials/pick_ups/other_calendars.jpg new file mode 100644 index 0000000000..c90b70ff9f Binary files /dev/null and b/docs/user_guide/bank/images/essentials/pick_ups/other_calendars.jpg differ diff --git a/docs/user_guide/bank/images/essentials/pick_ups/pickup&delivery.jpg b/docs/user_guide/bank/images/essentials/pick_ups/pickup&delivery.jpg new file mode 100644 index 0000000000..f4bcd24a9c Binary files /dev/null and b/docs/user_guide/bank/images/essentials/pick_ups/pickup&delivery.jpg differ diff --git a/docs/user_guide/bank/images/essentials/pick_ups/specific_day_distribution_schedule.jpg b/docs/user_guide/bank/images/essentials/pick_ups/specific_day_distribution_schedule.jpg new file mode 100644 index 0000000000..29a7001642 Binary files /dev/null and b/docs/user_guide/bank/images/essentials/pick_ups/specific_day_distribution_schedule.jpg differ diff --git a/docs/user_guide/bank/images/getting_started/customization/gs_customization_distribution_printout_customizable_sections.png b/docs/user_guide/bank/images/getting_started/customization/gs_customization_distribution_printout_customizable_sections.png new file mode 100644 index 0000000000..f584cf229b Binary files /dev/null and b/docs/user_guide/bank/images/getting_started/customization/gs_customization_distribution_printout_customizable_sections.png differ diff --git a/docs/user_guide/bank/images/getting_started/inventory/gs_inventory_1.png b/docs/user_guide/bank/images/getting_started/inventory/gs_inventory_1.png new file mode 100644 index 0000000000..c3477aec35 Binary files /dev/null and b/docs/user_guide/bank/images/getting_started/inventory/gs_inventory_1.png differ diff --git a/docs/user_guide/bank/images/getting_started/inventory/gs_inventory_2.png b/docs/user_guide/bank/images/getting_started/inventory/gs_inventory_2.png new file mode 100644 index 0000000000..1a01d57328 Binary files /dev/null and b/docs/user_guide/bank/images/getting_started/inventory/gs_inventory_2.png differ diff --git a/docs/user_guide/bank/images/getting_started/inventory/gs_inventory_3.png b/docs/user_guide/bank/images/getting_started/inventory/gs_inventory_3.png new file mode 100644 index 0000000000..964e8dbb57 Binary files /dev/null and b/docs/user_guide/bank/images/getting_started/inventory/gs_inventory_3.png differ diff --git a/docs/user_guide/bank/images/getting_started/partners/gs_just_starting_step_2.png b/docs/user_guide/bank/images/getting_started/partners/gs_just_starting_step_2.png new file mode 100644 index 0000000000..778466ff43 Binary files /dev/null and b/docs/user_guide/bank/images/getting_started/partners/gs_just_starting_step_2.png differ diff --git a/docs/user_guide/bank/images/getting_started/storage_locations/gs_just_starting_step_1.png b/docs/user_guide/bank/images/getting_started/storage_locations/gs_just_starting_step_1.png new file mode 100644 index 0000000000..aa20ac4adb Binary files /dev/null and b/docs/user_guide/bank/images/getting_started/storage_locations/gs_just_starting_step_1.png differ diff --git a/docs/user_guide/bank/images/getting_started/storage_locations/gs_storage_locations_navigation.png b/docs/user_guide/bank/images/getting_started/storage_locations/gs_storage_locations_navigation.png new file mode 100644 index 0000000000..83a2e5db7e Binary files /dev/null and b/docs/user_guide/bank/images/getting_started/storage_locations/gs_storage_locations_navigation.png differ diff --git a/docs/user_guide/bank/images/getting_started/storage_locations/new_storage_location.png b/docs/user_guide/bank/images/getting_started/storage_locations/new_storage_location.png new file mode 100644 index 0000000000..73f662108b Binary files /dev/null and b/docs/user_guide/bank/images/getting_started/storage_locations/new_storage_location.png differ diff --git a/docs/user_guide/bank/images/getting_started/storage_locations/storage_location_index.png b/docs/user_guide/bank/images/getting_started/storage_locations/storage_location_index.png new file mode 100644 index 0000000000..69f9a706f7 Binary files /dev/null and b/docs/user_guide/bank/images/getting_started/storage_locations/storage_location_index.png differ diff --git a/docs/user_guide/bank/images/getting_started/user_admin/gs_user_admin_demote_admin.png b/docs/user_guide/bank/images/getting_started/user_admin/gs_user_admin_demote_admin.png new file mode 100644 index 0000000000..3cc5937287 Binary files /dev/null and b/docs/user_guide/bank/images/getting_started/user_admin/gs_user_admin_demote_admin.png differ diff --git a/docs/user_guide/bank/images/getting_started/user_admin/gs_user_admin_invite_user.png b/docs/user_guide/bank/images/getting_started/user_admin/gs_user_admin_invite_user.png new file mode 100644 index 0000000000..cb5095b044 Binary files /dev/null and b/docs/user_guide/bank/images/getting_started/user_admin/gs_user_admin_invite_user.png differ diff --git a/docs/user_guide/bank/images/getting_started/user_admin/gs_user_admin_navigation.png b/docs/user_guide/bank/images/getting_started/user_admin/gs_user_admin_navigation.png new file mode 100644 index 0000000000..474ff87265 Binary files /dev/null and b/docs/user_guide/bank/images/getting_started/user_admin/gs_user_admin_navigation.png differ diff --git a/docs/user_guide/bank/images/getting_started/user_admin/gs_user_admin_promote_user.png b/docs/user_guide/bank/images/getting_started/user_admin/gs_user_admin_promote_user.png new file mode 100644 index 0000000000..a32843706e Binary files /dev/null and b/docs/user_guide/bank/images/getting_started/user_admin/gs_user_admin_promote_user.png differ diff --git a/docs/user_guide/bank/images/getting_started/user_admin/gs_user_admin_remove_user.png b/docs/user_guide/bank/images/getting_started/user_admin/gs_user_admin_remove_user.png new file mode 100644 index 0000000000..790a931244 Binary files /dev/null and b/docs/user_guide/bank/images/getting_started/user_admin/gs_user_admin_remove_user.png differ diff --git a/docs/user_guide/bank/intro_i.md b/docs/user_guide/bank/intro_i.md new file mode 100644 index 0000000000..edab63a9f3 --- /dev/null +++ b/docs/user_guide/bank/intro_i.md @@ -0,0 +1,36 @@ +DRAFT USER GUIDE + +# Is Human Essentials right for you? (what we help with and what we don't) +## Human Essentials is a free system for essentials banks +Human Essentials is for essentials banks -- by which we mean organizations that distribute essentials (diapers, period products, etc) to other organizations that work directly with people in need. + +If you *only* deal directly with people in need, this is probably not the system for you. + +## We are an all-volunteer organization + +We can offer this system for free because we are an all-volunteer organization. We have +sponsors who provide funding or in-kind services for the servers that the system runs on, but changes/support can take time. + +## What we help with +- managing your partners + - gathering the information about your partners that you need + - your request / distribution cycle (partner agencies can provide requests to you through the system, and you fulfill them) + - monthly reminders to the partners re request deadlines + - calendar that shows when you've scheduled pickups and deliveries +- inventory + - when you record donations, purchases, and distributions, your inventory record is automatically adjusted as of the date/time you enter the information + - audits and inventory adjustments + - handles multiple storage locations + - making up kits for distribution from existing inventory (this is mostly used by our period supply focused banks) +- community management + - keeping track of your donation sites, product drives (and participants) +- reporting + - yearly report with much of the information the NDBN annual survey requires drawn from your activity in the system + - itemized distribution breakdown over time + - trend reporting +## Things we don't help with +- general business needs (like payroll and office supplies and the like) +- volunteer coordination +- donor tax receipts +- direct distribution -- there are some things that help with that in the partner side, and some banks run a partner for their direct distribution, but it is not our strength. + diff --git a/docs/user_guide/bank/intro_ii.md b/docs/user_guide/bank/intro_ii.md new file mode 100644 index 0000000000..1bf7b4eb93 --- /dev/null +++ b/docs/user_guide/bank/intro_ii.md @@ -0,0 +1,29 @@ +DRAFT USER GUIDE + +# Support + +There are a few ways to get support -- some are better than others. +## For banks: +### Slack +We highly recommend that you become a member of the human essentials Slack. Here's a link to an invite: https://human-essential.slack.com/join/shared_invite/zt-bfa8tymd-d8Ks3Mq000COcRe~nfs~zg#/shared-invite/email + + +It's a good place to ask about how other banks *actually* use the system + -- any workarounds for things that don't 100% match how the system is set up. + (for instance, 1 bank has added an item that they use for the partners to report how many children are served by a bulk request.) + +It's often the quickest way to get support from the human essentials team as well, and the easiest way to interact with the development team. +### Support tickets +There is a link to submit a support ticket is at the bottom of the "Need Help?" screen. This will get to us, + but it will take longer. We generally look at these about once a week, on Sundays. +### Email + +You can email info@humanessentials.app. We generally look at these about once a week, on Sundays. + +### Stakeholder circle +We hold a stakeholder circle the first Wednesday of every month at 6:00pm Eastern Time. There will be other banks there, plus a couple members of the Human Essentials team. +We publish the zoom, and (when we remember) in the Announcements in Human Essentials. + +## For partners: +We recommend that the partners seek support through the banks. Each bank has their own particular way of doing things, and the development team doesn't want to mess that up. + diff --git a/docs/user_guide/bank/inventory_adjustments.md b/docs/user_guide/bank/inventory_adjustments.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/inventory_adjustments.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/inventory_audits.md b/docs/user_guide/bank/inventory_audits.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/inventory_audits.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/inventory_barcodes.md b/docs/user_guide/bank/inventory_barcodes.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/inventory_barcodes.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/inventory_items.md b/docs/user_guide/bank/inventory_items.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/inventory_items.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/inventory_kits.md b/docs/user_guide/bank/inventory_kits.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/inventory_kits.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/inventory_storage_locations.md b/docs/user_guide/bank/inventory_storage_locations.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/inventory_storage_locations.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/inventory_transfers.md b/docs/user_guide/bank/inventory_transfers.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/inventory_transfers.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/pm_adding_a_partner.md b/docs/user_guide/bank/pm_adding_a_partner.md new file mode 100644 index 0000000000..569b4b5aff --- /dev/null +++ b/docs/user_guide/bank/pm_adding_a_partner.md @@ -0,0 +1,2 @@ +# 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". diff --git a/docs/user_guide/bank/pm_announcements.md b/docs/user_guide/bank/pm_announcements.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/pm_announcements.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/pm_approving_a_partner.md b/docs/user_guide/bank/pm_approving_a_partner.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/pm_approving_a_partner.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/pm_importing_partners.md b/docs/user_guide/bank/pm_importing_partners.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/pm_importing_partners.md @@ -0,0 +1 @@ +Not yet written \ 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 new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/pm_inviting_a_partner.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/pm_making_a_partner_inactive.md b/docs/user_guide/bank/pm_making_a_partner_inactive.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/pm_making_a_partner_inactive.md @@ -0,0 +1 @@ +Not yet written \ 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 new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/pm_partner_profiles.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/pm_partner_user_admin.md b/docs/user_guide/bank/pm_partner_user_admin.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/pm_partner_user_admin.md @@ -0,0 +1 @@ +Not yet written \ 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 new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/pm_request_distribution_cycle.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/pm_requesting_recertification.md b/docs/user_guide/bank/pm_requesting_recertification.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/pm_requesting_recertification.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/readme.md b/docs/user_guide/bank/readme.md new file mode 100644 index 0000000000..e6bd8a70a1 --- /dev/null +++ b/docs/user_guide/bank/readme.md @@ -0,0 +1,62 @@ +DRAFT USER GUIDE + +This user guide is meant for users of the human essentials app at essentials banks. As of July 21, 2024, it is a work in progress. If you are interested in helping out, please reach out to @cielf. + +1. Introduction + 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. [Levels of access](getting_started_access_levels.md) + 2. [User management](getting_started_user_management.md) +3. Everyday essentials + 1. [Your dashboard](essentials_dashboard.md) + 2. [Donations](essentials_donations.md) + 3. [Purchases](essentials_purchases.md) + 4. [Requests](essentials_requests.md) + 5. [Distributions](essentials_distributions.md) + 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 + 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) +5. Inventory + 1. [Items](inventory_items.md) + 2. [Storage Locations](inventory_storage_locations.md) + 2. [Audits](inventory_audits.md) + 3. [Kits](inventory_kits.md) + 4. [Barcodes](inventory_barcodes.md) + 5. [Inventory Adjustments](inventory_barcodes.md) + 6. [Transfers](inventory_transfers.md) +6. Your community + 1. [Product Drive Participants](community_product_drive_participants.md) + 2. [Donation Sites](community_donation_sites.md) + 3. [Vendors](community_vendors.md) + 4. [Manufacturers](community_manufacturers.md) +7. Getting information out of the system + 1. [Exports](exports.md) + 2. Reports + 1. [Summary reports](reports_summary_reports.md) + 2. [Itemized reports](reports_itemized_reports.md) + 3. [Trends](reports_trends.md) + 4. Specialty reports + 1. [Annual Survey](reports_annual_survey.md) + 2. [Distributions by county](reports_distribution_by_county.md) + 3. [Manufacturer donations](reports_manufacturers_donations.md) + 4. [Activity graph](reports_activity_graph.md) +8. [User management](user_management.md) +9. [Signing out, and other things you can access by clicking on your name](top_right_menu.md) +[But I need to do something different!](asking_for_changes.md) diff --git a/docs/user_guide/bank/reports_activity_graph.md b/docs/user_guide/bank/reports_activity_graph.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/reports_activity_graph.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/reports_annual_survey.md b/docs/user_guide/bank/reports_annual_survey.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/reports_annual_survey.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/reports_distribution_by_county.md b/docs/user_guide/bank/reports_distribution_by_county.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/reports_distribution_by_county.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/reports_itemized_reports.md b/docs/user_guide/bank/reports_itemized_reports.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/reports_itemized_reports.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/reports_manufacturers_donations.md b/docs/user_guide/bank/reports_manufacturers_donations.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/reports_manufacturers_donations.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/reports_summary_reports.md b/docs/user_guide/bank/reports_summary_reports.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/reports_summary_reports.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/reports_trends.md b/docs/user_guide/bank/reports_trends.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/reports_trends.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/top_right_menu.md b/docs/user_guide/bank/top_right_menu.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/top_right_menu.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/docs/user_guide/bank/user_management.md b/docs/user_guide/bank/user_management.md new file mode 100644 index 0000000000..7c00a56292 --- /dev/null +++ b/docs/user_guide/bank/user_management.md @@ -0,0 +1 @@ +Not yet written \ No newline at end of file diff --git a/lib/tasks/fetch_latest_db.rake b/lib/tasks/fetch_latest_db.rake index c8f44aeeb9..fab4e2fd6a 100644 --- a/lib/tasks/fetch_latest_db.rake +++ b/lib/tasks/fetch_latest_db.rake @@ -2,8 +2,8 @@ desc "Update the development db to what is being used in prod" BACKUP_CONTAINER_NAME = 'backups' PASSWORD_REPLACEMENT = 'password' -task :fetch_latest_db => :environment do - if Rails.env.production? +task :fetch_latest_db do + if ENV["RAILS_ENV"] == "production" raise "You may not run this backup script in production!" end @@ -11,13 +11,14 @@ task :fetch_latest_db => :environment do puts "Recreating databases..." system("bin/rails db:environment:set RAILS_ENV=development") - system("rails db:drop db:create") + system("bin/rails db:drop db:create") puts "Restoring the database with #{backup.name}" backup_filepath = fetch_file_path(backup) db_username = ENV["PG_USERNAME"].presence || ENV["USER"].presence || "postgres" db_host = ENV["PG_HOST"].presence || "localhost" - system("pg_restore --clean --no-acl --no-owner -h #{db_host} -d diaper_dev -U #{db_username} #{backup_filepath}") + db_password = ENV["PG_PASSWORD"].presence + system("PGPASSWORD='#{db_password}' pg_restore --clean --no-acl --no-owner -h #{db_host} -d diaper_dev -U #{db_username} #{backup_filepath}") puts "Done!" @@ -30,14 +31,23 @@ task :fetch_latest_db => :environment do # environment. system("bin/rails jobs:clear") - ActiveRecord::Base.connection.reconnect! - puts "Replacing all the passwords with the replacement for ease of use: '#{PASSWORD_REPLACEMENT}'" - replace_user_passwords + system("bin/rails db:replace_user_passwords") puts "DONE!" end +namespace :db do + desc "Replace all user passwords with the replacement password" + task :replace_user_passwords => :environment do + if Rails.env.production? + raise "You may not run this backup script in production!" + end + + replace_user_passwords + end +end + private def fetch_latest_backups diff --git a/lib/tasks/kill_postgres_connections.rake b/lib/tasks/kill_postgres_connections.rake deleted file mode 100644 index 3004dd8545..0000000000 --- a/lib/tasks/kill_postgres_connections.rake +++ /dev/null @@ -1,15 +0,0 @@ -task :kill_postgres_connections => :environment do - db_name = Rails.configuration.database_configuration[Rails.env]['database'] - sh = < :kill_postgres_connections \ No newline at end of file diff --git a/spec/controllers/admins_controller_spec.rb b/spec/controllers/admins_controller_spec.rb index 9e80ce77a0..1b7692acc3 100644 --- a/spec/controllers/admins_controller_spec.rb +++ b/spec/controllers/admins_controller_spec.rb @@ -1,5 +1,4 @@ =begin -require "rails_helper" RSpec.describe AdminsController, type: :controller do let(:organization) { create(:organization) } diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 54775e99d9..3ff2f7af64 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -8,7 +8,7 @@ end context "As a partner user" do - let(:user) { create(:partners_user) } + let(:user) { create(:partner_user) } it "should return nil" do expect(controller.current_organization).to eq(nil) end @@ -52,7 +52,7 @@ context "As a partner user" do let(:partner) { create(:partner, organization: organization) } let(:partner2) { create(:partner, organization: organization) } - let(:user) { create(:partners_user, partner: partner) } + let(:user) { create(:partner_user, partner: partner) } before(:each) do user.add_role(Role::PARTNER, partner2) # add a second role end diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb index 280df99e65..32411eaede 100644 --- a/spec/controllers/help_controller_spec.rb +++ b/spec/controllers/help_controller_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe HelpController, type: :controller do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/factories/donation_site.rb b/spec/factories/donation_site.rb deleted file mode 100644 index 017bfc10d7..0000000000 --- a/spec/factories/donation_site.rb +++ /dev/null @@ -1,11 +0,0 @@ -FactoryBot.define do - factory :donation_site do - organization { Organization.try(:first) || create(:organization) } - name { Faker::Company.name } - address { "1500 Remount Road, Front Royal, VA 22630" } - active { true } - contact_name { Faker::Name.name } - email { Faker::Internet.email } - phone { Faker::PhoneNumber.phone_number } - end -end diff --git a/spec/factories/donation_sites.rb b/spec/factories/donation_sites.rb new file mode 100644 index 0000000000..3470a4f487 --- /dev/null +++ b/spec/factories/donation_sites.rb @@ -0,0 +1,28 @@ +# == Schema Information +# +# Table name: donation_sites +# +# id :integer not null, primary key +# active :boolean default(TRUE) +# address :string +# contact_name :string +# email :string +# latitude :float +# longitude :float +# name :string +# phone :string +# created_at :datetime not null +# updated_at :datetime not null +# organization_id :integer +# +FactoryBot.define do + factory :donation_site do + organization { Organization.try(:first) || create(:organization) } + name { Faker::Company.name } + address { "1500 Remount Road, Front Royal, VA 22630" } + active { true } + contact_name { Faker::Name.name } + email { Faker::Internet.email } + phone { Faker::PhoneNumber.phone_number } + end +end diff --git a/spec/factories/partner_user.rb b/spec/factories/partner_user.rb deleted file mode 100644 index 7c719c7088..0000000000 --- a/spec/factories/partner_user.rb +++ /dev/null @@ -1,16 +0,0 @@ -FactoryBot.define do - factory :partner_user, class: ::User do - name { "Partner User" } - sequence(:email) { |n| "partner___user_#{n}@example.com" } - password { "password!" } - password_confirmation { "password!" } - invitation_sent_at { Time.current - 1.day } - last_sign_in_at { Time.current } - transient do - partner { Partner.first || create(:partner) } - end - after(:create) do |user, evaluator| - user.add_role(Role::PARTNER, evaluator.partner) - end - end -end diff --git a/spec/factories/partners.rb b/spec/factories/partners.rb index 64812a6762..ba34f27bbe 100644 --- a/spec/factories/partners.rb +++ b/spec/factories/partners.rb @@ -45,7 +45,7 @@ # Create associated records create(:partner_profile, partner_id: partner.id) - create(:partners_user, email: partner.email, name: partner.name, partner: partner) + create(:partner_user, email: partner.email, name: partner.name, partner: partner) end end end diff --git a/spec/factories/partners/child.rb b/spec/factories/partners/child.rb deleted file mode 100644 index cf6a20204f..0000000000 --- a/spec/factories/partners/child.rb +++ /dev/null @@ -1,15 +0,0 @@ -FactoryBot.define do - factory :partners_child, class: Partners::Child do - association :family, factory: :partners_family - - active { true } - archived { false } - comments { 'Comments ' } - date_of_birth { Time.zone.today - 5.years } - first_name { Faker::Name.first_name } - last_name { Faker::Name.last_name } - gender { Faker::Gender.binary_type } - # TODO: change when closing #4199 - item_needed_diaperid { Item.all.sample&.id || create(:item, organization: family.partner.organization).id } - end -end diff --git a/spec/factories/partners/children.rb b/spec/factories/partners/children.rb new file mode 100644 index 0000000000..76329c7226 --- /dev/null +++ b/spec/factories/partners/children.rb @@ -0,0 +1,35 @@ +# == Schema Information +# +# Table name: children +# +# id :bigint not null, primary key +# active :boolean default(TRUE) +# archived :boolean +# child_lives_with :jsonb +# comments :text +# date_of_birth :date +# first_name :string +# gender :string +# health_insurance :jsonb +# item_needed_diaperid :integer +# last_name :string +# race :jsonb +# created_at :datetime not null +# updated_at :datetime not null +# agency_child_id :string +# family_id :bigint +# +FactoryBot.define do + factory :partners_child, class: Partners::Child do + association :family, factory: :partners_family + + active { true } + archived { false } + comments { "Comments " } + date_of_birth { Time.zone.today - 5.years } + first_name { Faker::Name.first_name } + last_name { Faker::Name.last_name } + gender { Faker::Gender.binary_type } + requested_item_ids { [create(:item, organization: family.partner.organization).id] } + end +end diff --git a/spec/factories/partners/families.rb b/spec/factories/partners/families.rb new file mode 100644 index 0000000000..a565f642a1 --- /dev/null +++ b/spec/factories/partners/families.rb @@ -0,0 +1,49 @@ +# == Schema Information +# +# Table name: families +# +# id :bigint not null, primary key +# archived :boolean default(FALSE) +# case_manager :string +# comments :text +# guardian_county :string +# guardian_employed :boolean +# guardian_employment_type :jsonb +# guardian_first_name :string +# guardian_health_insurance :jsonb +# guardian_last_name :string +# guardian_monthly_pay :decimal(, ) +# guardian_phone :string +# guardian_zip_code :string +# home_adult_count :integer +# home_child_count :integer +# home_young_child_count :integer +# military :boolean default(FALSE) +# sources_of_income :jsonb +# created_at :datetime not null +# updated_at :datetime not null +# old_partner_id :bigint +# partner_id :bigint +# +FactoryBot.define do + factory :partners_family, class: Partners::Family do + association :partner + + comments { Faker::Lorem.paragraph } + # Faker doesn't have county, community is same flavour, we don't use it, and it is not country. + guardian_county { Faker::Address.community } + guardian_employed { false } + guardian_employment_type { nil } + guardian_first_name { Faker::Name.first_name } + guardian_health_insurance { nil } + guardian_last_name { Faker::Name.last_name } + guardian_monthly_pay { rand(500.0..2000.0).round(2) } + guardian_phone { Faker::PhoneNumber.phone_number_with_country_code } + guardian_zip_code { Faker::Address.zip } + home_adult_count { rand(1..5) } + home_child_count { rand(0..5) } + home_young_child_count { rand(0..5) } + military { false } + archived { false } + end +end diff --git a/spec/factories/partners/family.rb b/spec/factories/partners/family.rb deleted file mode 100644 index 2b2df19662..0000000000 --- a/spec/factories/partners/family.rb +++ /dev/null @@ -1,22 +0,0 @@ -FactoryBot.define do - factory :partners_family, class: Partners::Family do - association :partner - - comments { Faker::Lorem.paragraph } - # Faker doesn't have county, community is same flavour, we don't use it, and it is not country. - guardian_county { Faker::Address.community } - guardian_employed { false } - guardian_employment_type { nil } - guardian_first_name { Faker::Name.first_name } - guardian_health_insurance { nil } - guardian_last_name { Faker::Name.last_name } - guardian_monthly_pay { rand(500.0..2000.0).round(2) } - guardian_phone { Faker::PhoneNumber.phone_number_with_country_code } - guardian_zip_code { Faker::Address.zip } - home_adult_count { rand(1..5) } - home_child_count { rand(0..5) } - home_young_child_count { rand(0..5) } - military { false } - archived { false } - end -end diff --git a/spec/factories/partners/item_request.rb b/spec/factories/partners/item_request.rb deleted file mode 100644 index 9c5ebbe393..0000000000 --- a/spec/factories/partners/item_request.rb +++ /dev/null @@ -1,9 +0,0 @@ -FactoryBot.define do - factory :item_request, class: Partners::ItemRequest do - item - request { build(:request, organization: item.organization) } - quantity { 5 } - sequence(:name) { |n| "Item Request #{n}" } - sequence(:partner_key) { |n| "partner_key#{n}" } - end -end diff --git a/spec/factories/partners/item_requests.rb b/spec/factories/partners/item_requests.rb new file mode 100644 index 0000000000..df61db4d2e --- /dev/null +++ b/spec/factories/partners/item_requests.rb @@ -0,0 +1,24 @@ +# == Schema Information +# +# Table name: item_requests +# +# id :bigint not null, primary key +# name :string +# partner_key :string +# quantity :string +# request_unit :string +# created_at :datetime not null +# updated_at :datetime not null +# item_id :integer +# old_partner_request_id :integer +# partner_request_id :bigint +# +FactoryBot.define do + factory :item_request, class: Partners::ItemRequest do + item + request { build(:request, organization: item.organization) } + quantity { 5 } + sequence(:name) { |n| "Item Request #{n}" } + sequence(:partner_key) { |n| "partner_key#{n}" } + end +end diff --git a/spec/factories/partners/profile.rb b/spec/factories/partners/profile.rb deleted file mode 100644 index 292100cb50..0000000000 --- a/spec/factories/partners/profile.rb +++ /dev/null @@ -1,9 +0,0 @@ -FactoryBot.define do - factory :partner_profile, class: Partners::Profile do - partner { Partner.first || create(:partner) } - essentials_bank_id { Organization.try(:first).id || create(:organization).id } - website { "http://some-site.org" } - primary_contact_email { Faker::Internet.email } - primary_contact_name { Faker::Name.name } - end -end diff --git a/spec/factories/partners/profiles.rb b/spec/factories/partners/profiles.rb new file mode 100644 index 0000000000..7b2e3f3500 --- /dev/null +++ b/spec/factories/partners/profiles.rb @@ -0,0 +1,90 @@ +# == Schema Information +# +# Table name: partner_profiles +# +# id :bigint not null, primary key +# above_1_2_times_fpl :integer +# address1 :string +# address2 :string +# agency_mission :text +# agency_type :string +# application_data :text +# at_fpl_or_below :integer +# case_management :boolean +# city :string +# client_capacity :string +# currently_provide_diapers :boolean +# describe_storage_space :text +# distribution_times :string +# distributor_type :string +# enable_child_based_requests :boolean default(TRUE), not null +# enable_individual_requests :boolean default(TRUE), not null +# enable_quantity_based_requests :boolean default(TRUE), not null +# essentials_budget :string +# essentials_funding_source :string +# essentials_use :string +# evidence_based :boolean +# executive_director_email :string +# executive_director_name :string +# executive_director_phone :string +# facebook :string +# form_990 :boolean +# founded :integer +# greater_2_times_fpl :integer +# income_requirement_desc :boolean +# income_verification :boolean +# instagram :string +# more_docs_required :string +# name :string +# new_client_times :string +# no_social_media_presence :boolean +# other_agency_type :string +# partner_status :string default("pending") +# pick_up_email :string +# pick_up_name :string +# pick_up_phone :string +# population_american_indian :integer +# population_asian :integer +# population_black :integer +# population_hispanic :integer +# population_island :integer +# population_multi_racial :integer +# population_other :integer +# population_white :integer +# poverty_unknown :integer +# primary_contact_email :string +# primary_contact_mobile :string +# primary_contact_name :string +# primary_contact_phone :string +# program_address1 :string +# program_address2 :string +# program_age :string +# program_city :string +# program_description :text +# program_name :string +# program_state :string +# program_zip_code :integer +# receives_essentials_from_other :string +# sources_of_diapers :string +# sources_of_funding :string +# state :string +# status_in_diaper_base :string +# storage_space :boolean +# twitter :string +# website :string +# zip_code :string +# zips_served :string +# created_at :datetime not null +# updated_at :datetime not null +# essentials_bank_id :bigint +# partner_id :integer +# +FactoryBot.define do + factory :partner_profile, class: Partners::Profile do + partner { Partner.first || create(:partner) } + essentials_bank_id { Organization.try(:first).id || create(:organization).id } + website { "http://some-site.org" } + primary_contact_email { Faker::Internet.email } + primary_contact_name { Faker::Name.name } + end +end diff --git a/spec/factories/partners/user.rb b/spec/factories/partners/user.rb deleted file mode 100644 index 26cd34591c..0000000000 --- a/spec/factories/partners/user.rb +++ /dev/null @@ -1,16 +0,0 @@ -FactoryBot.define do - factory :partners_user, class: ::User do - name { "Partner User" } - sequence(:email) { |n| "partner_user_#{n}@example.com" } - password { "password!" } - password_confirmation { "password!" } - invitation_sent_at { Time.current - 1.day } - last_sign_in_at { Time.current } - transient do - partner { Partner.first || create(:partner) } - end - after(:create) do |instance, evaluator| - instance.add_role(Role::PARTNER, evaluator.partner) - end - end -end diff --git a/spec/factories/product_drive.rb b/spec/factories/product_drive.rb deleted file mode 100644 index fe89886e48..0000000000 --- a/spec/factories/product_drive.rb +++ /dev/null @@ -1,9 +0,0 @@ -FactoryBot.define do - factory :product_drive do - name { "Test Drive" } - start_date { Time.current } - end_date { Time.current } - virtual { [true, false].sample } - organization { Organization.try(:first) || create(:organization) } - end -end diff --git a/spec/factories/product_drives.rb b/spec/factories/product_drives.rb new file mode 100644 index 0000000000..95b16c65d1 --- /dev/null +++ b/spec/factories/product_drives.rb @@ -0,0 +1,22 @@ +# == Schema Information +# +# Table name: product_drives +# +# id :bigint not null, primary key +# end_date :date +# name :string +# start_date :date +# virtual :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# organization_id :bigint +# +FactoryBot.define do + factory :product_drive do + name { "Test Drive" } + start_date { Time.current } + end_date { Time.current } + virtual { [true, false].sample } + organization { Organization.try(:first) || create(:organization) } + end +end diff --git a/spec/factories/requests.rb b/spec/factories/requests.rb index e8a28128d8..b0ddad8e1d 100644 --- a/spec/factories/requests.rb +++ b/spec/factories/requests.rb @@ -27,30 +27,49 @@ def random_request_items organization { Organization.try(:first) || create(:organization, :with_items) } request_items { random_request_items } comments { "Urgent" } - partner_user { ::User.partner_users.first || create(:partners_user) } - end + partner_user { ::User.partner_users.first || create(:partner_user) } - trait :started do - status { 'started' } - end + # For compatibility we can take in a list of request_items and turn it into a + # list of item_requests + trait :with_item_requests do + after(:build) do |request| + if request.item_requests.empty? + request.request_items.each do |request_item| + item = Item.find(request_item['item_id']) + request.item_requests << Partners::ItemRequest.new( + item_id: item.id, + quantity: request_item['quantity'], + name: item.name, + partner_key: item.partner_key, + request_unit: request_item["request_unit"] + ) + end + end + end + end - trait :fulfilled do - status { 'fulfilled' } - end + trait :started do + status { 'started' } + end - trait :pending do - status { 'pending' } - end + trait :fulfilled do + status { 'fulfilled' } + end + + trait :pending do + status { 'pending' } + end - trait :with_varied_quantities do - request_items { - # get 10 unique item ids - keys = Item.active.pluck(:id).sample(10) + trait :with_varied_quantities do + request_items { + # get 10 unique item ids + keys = Item.active.pluck(:id).sample(10) - # This *could* pass in error -- if the plucking order happens to match the end order. + # This *could* pass in error -- if the plucking order happens to match the end order. - item_quantities = [50, 150, 75, 125, 200, 3, 15, 88, 46, 22] - keys.map.with_index { |k, i| { "item_id" => k, "quantity" => item_quantities[i]} } - } + item_quantities = [50, 150, 75, 125, 200, 3, 15, 88, 46, 22] + keys.map.with_index { |k, i| { "item_id" => k, "quantity" => item_quantities[i]} } + } + end end end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 3f2c869181..2438dcfcec 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -51,6 +51,22 @@ end end + factory :partner_user do + name { "Partner User" } + sequence(:email) { |n| "partner_user_#{n}@example.com" } + password { "password!" } + password_confirmation { "password!" } + invitation_sent_at { Time.current - 1.day } + last_sign_in_at { Time.current } + organization { nil } + transient do + partner { Partner.first || create(:partner) } + end + after(:create) do |instance, evaluator| + instance.add_role(Role::PARTNER, evaluator.partner) + end + end + factory :organization_admin do name { "Very Organized Admin" } after(:create) do |user, evaluator| @@ -77,6 +93,10 @@ end end + trait :no_roles do + organization { nil } + end + trait :deactivated do discarded_at { Time.zone.now } end diff --git a/spec/factories/users_roles.rb b/spec/factories/users_roles.rb new file mode 100644 index 0000000000..4a507b56c2 --- /dev/null +++ b/spec/factories/users_roles.rb @@ -0,0 +1,14 @@ +# == Schema Information +# +# Table name: users_roles +# +# id :bigint not null, primary key +# role_id :bigint +# user_id :bigint +# +FactoryBot.define do + factory :users_role do + user + role + end +end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index be9be7e407..0648985cad 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe ApplicationHelper, type: :helper do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/helpers/product_drive_helper_spec.rb b/spec/helpers/product_drive_helper_spec.rb index 79dea65f55..2ab0950c2a 100644 --- a/spec/helpers/product_drive_helper_spec.rb +++ b/spec/helpers/product_drive_helper_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe ProductDriveHelper, type: :helper do describe '#is_virtual' do context 'when the product drive was held virtually' do diff --git a/spec/helpers/purchases_helper_spec.rb b/spec/helpers/purchases_helper_spec.rb index 05da4bcc05..631a7b03ed 100644 --- a/spec/helpers/purchases_helper_spec.rb +++ b/spec/helpers/purchases_helper_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe PurchasesHelper, type: :helper do describe "#new_purchase_default_location" do helper do diff --git a/spec/helpers/ui_helper_spec.rb b/spec/helpers/ui_helper_spec.rb index 3051ffe791..1ada45de70 100644 --- a/spec/helpers/ui_helper_spec.rb +++ b/spec/helpers/ui_helper_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe UiHelper, type: :helper do describe 'optional_data_text' do subject { helper.optional_data_text(field) } diff --git a/spec/jobs/historical_data_cache_job_spec.rb b/spec/jobs/historical_data_cache_job_spec.rb index 3c96462285..a5d432d609 100644 --- a/spec/jobs/historical_data_cache_job_spec.rb +++ b/spec/jobs/historical_data_cache_job_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe HistoricalDataCacheJob, type: :job do include ActiveJob::TestHelper diff --git a/spec/jobs/reminder_deadline_job_spec.rb b/spec/jobs/reminder_deadline_job_spec.rb index 2cb029a6af..4d76376d41 100644 --- a/spec/jobs/reminder_deadline_job_spec.rb +++ b/spec/jobs/reminder_deadline_job_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe ReminderDeadlineJob, type: :job do describe '#perform' do subject { -> { described_class.perform_now } } diff --git a/spec/models/account_request_spec.rb b/spec/models/account_request_spec.rb index 539f61ff58..50570a8665 100644 --- a/spec/models/account_request_spec.rb +++ b/spec/models/account_request_spec.rb @@ -15,7 +15,6 @@ # updated_at :datetime not null # ndbn_member_id :bigint # -require 'rails_helper' RSpec.describe AccountRequest, type: :model do let(:account_request) { create(:account_request) } diff --git a/spec/models/audit_spec.rb b/spec/models/audit_spec.rb index 54f1155180..882b41b22b 100644 --- a/spec/models/audit_spec.rb +++ b/spec/models/audit_spec.rb @@ -12,8 +12,6 @@ # user_id :bigint # -require 'rails_helper' - RSpec.describe Audit, type: :model do let(:organization) { create(:organization) } diff --git a/spec/models/base_item_spec.rb b/spec/models/base_item_spec.rb index f8e3356816..65708b24c8 100644 --- a/spec/models/base_item_spec.rb +++ b/spec/models/base_item_spec.rb @@ -13,8 +13,6 @@ # updated_at :datetime not null # -require "rails_helper" - RSpec.describe BaseItem, type: :model do let(:organization) { create(:organization) } diff --git a/spec/models/broadcast_announcement_spec.rb b/spec/models/broadcast_announcement_spec.rb index 36dae39714..e07294e2b5 100644 --- a/spec/models/broadcast_announcement_spec.rb +++ b/spec/models/broadcast_announcement_spec.rb @@ -11,7 +11,6 @@ # organization_id :bigint # user_id :bigint not null # -require "rails_helper" RSpec.describe BroadcastAnnouncement, type: :model do it { should belong_to(:organization).optional } diff --git a/spec/models/concerns/deadlinable_spec.rb b/spec/models/concerns/deadlinable_spec.rb index 9f8b235c22..4034b5bc5f 100644 --- a/spec/models/concerns/deadlinable_spec.rb +++ b/spec/models/concerns/deadlinable_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe Deadlinable, type: :model do let(:dummy_class) do Class.new do diff --git a/spec/models/county_spec.rb b/spec/models/county_spec.rb index 8275ea629d..ae19646b0d 100644 --- a/spec/models/county_spec.rb +++ b/spec/models/county_spec.rb @@ -9,7 +9,6 @@ # created_at :datetime not null # updated_at :datetime not null # -require "rails_helper" RSpec.describe County, type: :model do it { should have_many(:served_areas) } diff --git a/spec/models/item_category_spec.rb b/spec/models/item_category_spec.rb index 461ee5bc46..74bf71f222 100644 --- a/spec/models/item_category_spec.rb +++ b/spec/models/item_category_spec.rb @@ -9,7 +9,6 @@ # updated_at :datetime not null # organization_id :integer not null # -require 'rails_helper' RSpec.describe ItemCategory, type: :model do describe 'validations' do diff --git a/spec/models/kit_allocation_spec.rb b/spec/models/kit_allocation_spec.rb index 3ba8f089e2..24ace8a884 100644 --- a/spec/models/kit_allocation_spec.rb +++ b/spec/models/kit_allocation_spec.rb @@ -10,7 +10,6 @@ # organization_id :bigint not null # storage_location_id :bigint not null # -require "rails_helper" RSpec.describe KitAllocation, type: :model do describe "versioning" do diff --git a/spec/models/kit_spec.rb b/spec/models/kit_spec.rb index 0963e04785..989fb83ce2 100644 --- a/spec/models/kit_spec.rb +++ b/spec/models/kit_spec.rb @@ -11,7 +11,6 @@ # updated_at :datetime not null # organization_id :integer not null # -require 'rails_helper' RSpec.describe Kit, type: :model do let(:organization) { create(:organization) } diff --git a/spec/models/manufacturer_spec.rb b/spec/models/manufacturer_spec.rb index ee77e0ce9e..d1aba71341 100644 --- a/spec/models/manufacturer_spec.rb +++ b/spec/models/manufacturer_spec.rb @@ -9,8 +9,6 @@ # organization_id :bigint # -require "rails_helper" - RSpec.describe Manufacturer, type: :model do context "Validations" do subject { build(:manufacturer) } diff --git a/spec/models/ndbn_member_spec.rb b/spec/models/ndbn_member_spec.rb index 30a54ccaf7..ee0a7ef183 100644 --- a/spec/models/ndbn_member_spec.rb +++ b/spec/models/ndbn_member_spec.rb @@ -7,7 +7,6 @@ # updated_at :datetime not null # ndbn_member_id :bigint not null, primary key # -require "rails_helper" RSpec.describe NDBNMember, type: :model do describe "validations" do diff --git a/spec/models/partner_spec.rb b/spec/models/partner_spec.rb index 4db7acd523..c16921d568 100644 --- a/spec/models/partner_spec.rb +++ b/spec/models/partner_spec.rb @@ -161,7 +161,7 @@ context 'when it has a profile and users' do it 'should return false' do create(:partner_profile, partner_id: partner.id) - create(:partners_user, email: partner.email, name: partner.name, partner: partner) + create(:partner_user, email: partner.email, name: partner.name, partner: partner) expect(partner.reload).not_to be_deletable end end diff --git a/spec/models/partners/authorized_family_member_spec.rb b/spec/models/partners/authorized_family_member_spec.rb index 1832cc820e..8da9bbcb49 100644 --- a/spec/models/partners/authorized_family_member_spec.rb +++ b/spec/models/partners/authorized_family_member_spec.rb @@ -12,7 +12,6 @@ # updated_at :datetime not null # family_id :bigint # -require "rails_helper" RSpec.describe Partners::AuthorizedFamilyMember, type: :model do describe 'associations' do diff --git a/spec/models/partners/child_item_request_spec.rb b/spec/models/partners/child_item_request_spec.rb index f5599a1372..bfaf162274 100644 --- a/spec/models/partners/child_item_request_spec.rb +++ b/spec/models/partners/child_item_request_spec.rb @@ -12,7 +12,6 @@ # child_id :bigint # item_request_id :bigint # -require 'rails_helper' RSpec.describe Partners::ChildItemRequest, type: :model do describe 'associations' do diff --git a/spec/models/partners/child_spec.rb b/spec/models/partners/child_spec.rb index 3e58946bb1..7934f19a8e 100644 --- a/spec/models/partners/child_spec.rb +++ b/spec/models/partners/child_spec.rb @@ -19,12 +19,12 @@ # agency_child_id :string # family_id :bigint # -require "rails_helper" RSpec.describe Partners::Child, type: :model do describe 'associations' do it { should belong_to(:family) } it { should have_many(:child_item_requests).dependent(:destroy) } + it { should have_and_belong_to_many(:requested_items).class_name('Item') } end describe "#display_name" do diff --git a/spec/models/partners/family_request_spec.rb b/spec/models/partners/family_request_spec.rb index 6bdbcfa53b..fa34242727 100644 --- a/spec/models/partners/family_request_spec.rb +++ b/spec/models/partners/family_request_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe Partners::FamilyRequest do describe "Partners::FamilyRequest.new_with_attrs" do it "creates a new FamilyRequest with attributes" do diff --git a/spec/models/partners/family_spec.rb b/spec/models/partners/family_spec.rb index 60e036662b..f039189178 100644 --- a/spec/models/partners/family_spec.rb +++ b/spec/models/partners/family_spec.rb @@ -26,8 +26,6 @@ # partner_id :bigint # -require "rails_helper" - RSpec.describe Partners::Family, type: :model do describe "associations" do it { should belong_to(:partner) } diff --git a/spec/models/partners/item_request_spec.rb b/spec/models/partners/item_request_spec.rb index f4effcf340..1c128d3e68 100644 --- a/spec/models/partners/item_request_spec.rb +++ b/spec/models/partners/item_request_spec.rb @@ -13,7 +13,6 @@ # old_partner_request_id :integer # partner_request_id :bigint # -require "rails_helper" RSpec.describe Partners::ItemRequest, type: :model do let(:organization) { create(:organization) } diff --git a/spec/models/partners/profile_spec.rb b/spec/models/partners/profile_spec.rb index a82a3d1f17..0ecc60220a 100644 --- a/spec/models/partners/profile_spec.rb +++ b/spec/models/partners/profile_spec.rb @@ -79,7 +79,6 @@ # essentials_bank_id :bigint # partner_id :integer # -require "rails_helper" RSpec.describe Partners::Profile, type: :model do describe "associations" do diff --git a/spec/models/partners/served_area_spec.rb b/spec/models/partners/served_area_spec.rb index ad65d38066..949d99ed80 100644 --- a/spec/models/partners/served_area_spec.rb +++ b/spec/models/partners/served_area_spec.rb @@ -9,7 +9,6 @@ # county_id :bigint not null # partner_profile_id :bigint not null # -require "rails_helper" RSpec.describe Partners::ServedArea, type: :model do it { should belong_to(:partner_profile) } diff --git a/spec/models/product_drive_participant_spec.rb b/spec/models/product_drive_participant_spec.rb index d2c5ae419c..cde7776d19 100644 --- a/spec/models/product_drive_participant_spec.rb +++ b/spec/models/product_drive_participant_spec.rb @@ -16,8 +16,6 @@ # organization_id :integer # -require "rails_helper" - RSpec.describe ProductDriveParticipant, type: :model do it_behaves_like "provideable" @@ -27,6 +25,10 @@ expect(build(:product_drive_participant, phone: nil)).to be_valid expect(build(:product_drive_participant, email: nil)).to be_valid end + it "is invalid if the comment field has more than 500 characters" do + long_comment = "a" * 501 + expect(build(:product_drive_participant, comment: long_comment)).not_to be_valid + end end context "Methods" do diff --git a/spec/models/product_drive_spec.rb b/spec/models/product_drive_spec.rb index 5ae45b64c1..eacd48bd3f 100644 --- a/spec/models/product_drive_spec.rb +++ b/spec/models/product_drive_spec.rb @@ -12,8 +12,6 @@ # organization_id :bigint # -require "rails_helper" - RSpec.describe ProductDrive, type: :model do let(:organization) { create(:organization) } let(:product_drive) { create(:product_drive, organization: organization) } diff --git a/spec/models/question_spec.rb b/spec/models/question_spec.rb index 183ca22163..7793984ec7 100644 --- a/spec/models/question_spec.rb +++ b/spec/models/question_spec.rb @@ -9,7 +9,6 @@ # created_at :datetime not null # updated_at :datetime not null # -require "rails_helper" RSpec.describe Question, type: :model do describe "scope for_partners" do diff --git a/spec/models/role_spec.rb b/spec/models/role_spec.rb index 6028c95a00..17c9b14f51 100644 --- a/spec/models/role_spec.rb +++ b/spec/models/role_spec.rb @@ -10,7 +10,6 @@ # old_resource_id :bigint # resource_id :bigint # -require "rails_helper" RSpec.describe Role, type: :model do describe "Validations" do diff --git a/spec/models/unit_spec.rb b/spec/models/unit_spec.rb new file mode 100644 index 0000000000..733c719920 --- /dev/null +++ b/spec/models/unit_spec.rb @@ -0,0 +1,25 @@ +# == Schema Information +# +# Table name: units +# +# id :bigint not null, primary key +# name :string not null +# created_at :datetime not null +# updated_at :datetime not null +# organization_id :bigint +# + +RSpec.describe Unit, type: :model do + let!(:organization) { create(:organization) } + let!(:unit_1) { create(:unit, name: "WolfPack", organization: organization) } + + describe "Validations" do + it "validates uniqueness of name in context of organization" do + expect { described_class.create!(name: "WolfPack", organization: organization) }.to raise_exception(ActiveRecord::RecordInvalid).with_message("Validation failed: Name has already been taken") + end + end + + describe "Associations" do + it { should belong_to(:organization) } + end +end diff --git a/spec/models/vendor_spec.rb b/spec/models/vendor_spec.rb index a32015a269..98a172340f 100644 --- a/spec/models/vendor_spec.rb +++ b/spec/models/vendor_spec.rb @@ -16,8 +16,6 @@ # organization_id :integer # -require "rails_helper" - RSpec.describe Vendor, type: :model do it_behaves_like "provideable" diff --git a/spec/pdfs/donation_pdf_spec.rb b/spec/pdfs/donation_pdf_spec.rb new file mode 100644 index 0000000000..21a8ee2174 --- /dev/null +++ b/spec/pdfs/donation_pdf_spec.rb @@ -0,0 +1,68 @@ +describe DonationPdf do + let(:donation_site) { create(:donation_site, name: "Site X", address: "1500 Remount Road, Front Royal, VA 22630", email: "test@example.com") } + let(:organization) { create(:organization) } + let(:donation) do + create(:donation, organization: organization, donation_site: donation_site, source: Donation::SOURCES[:donation_site], + comment: "A donation comment") + end + let(:item1) { FactoryBot.create(:item, name: "Item 1", package_size: 50, value_in_cents: 100) } + let(:item2) { FactoryBot.create(:item, name: "Item 2", value_in_cents: 200) } + let(:item3) { FactoryBot.create(:item, name: "Item 3", value_in_cents: 300) } + let(:item4) { FactoryBot.create(:item, name: "Item 4", package_size: 25, value_in_cents: 400) } + + let(:org_hiding_packages_and_values) do + FactoryBot.create(:organization, name: DEFAULT_TEST_ORGANIZATION_NAME, + hide_value_columns_on_receipt: true, hide_package_column_on_receipt: true) + end + let(:org_hiding_packages) { FactoryBot.create(:organization, name: DEFAULT_TEST_ORGANIZATION_NAME, hide_package_column_on_receipt: true) } + let(:org_hiding_values) { FactoryBot.create(:organization, name: DEFAULT_TEST_ORGANIZATION_NAME, hide_value_columns_on_receipt: true) } + + before(:each) do + create(:line_item, itemizable: donation, item: item1, quantity: 50) + create(:line_item, itemizable: donation, item: item2, quantity: 100) + end + + specify "#donation_data" do + results = described_class.new(organization, donation).donation_data + expect(results).to eq([ + ["Items Received", "Value/item", "In-Kind Value", "Quantity"], + ["Item 1", "$1.00", "$50.00", 50], + ["Item 2", "$2.00", "$200.00", 100], + ["", "", "", ""], + ["Total Items Received", "", "$250.00", 150] + ]) + end + + context "with donation data" do + it "hides value and package columns when true on organization" do + pdf = described_class.new(org_hiding_packages_and_values, donation) + data = pdf.donation_data + pdf.hide_columns(data) + expect(data).to eq([ + ["Items Received", "Quantity"], + ["Item 1", 50], + ["Item 2", 100], + ["", ""], + ["Total Items Received", 150] + ]) + end + end + + context "render pdf" do + it "renders correctly" do + pdf = described_class.new(organization, donation) + pdf_test = PDF::Reader.new(StringIO.new(pdf.compute_and_render)) + expect(pdf_test.page(1).text).to include(donation_site.name) + expect(pdf_test.page(1).text).to include(donation_site.address) + expect(pdf_test.page(1).text).to include(donation_site.email) + if donation.comment + expect(pdf_test.page(1).text).to include(donation.comment) + end + expect(pdf_test.page(1).text).to include("Money Raised In Dollars: $0.00") + expect(pdf_test.page(1).text).to include("Items Received") + expect(pdf_test.page(1).text).to match(/Item 1\s+\$1\.00\s+\$50\.00\s+50/) + expect(pdf_test.page(1).text).to match(/Item 2\s+\$2\.00\s+\$200\.00\s+100/) + expect(pdf_test.page(1).text).to include("Total Items Received") + end + end +end diff --git a/spec/requests/account_requests_spec.rb b/spec/requests/account_requests_spec.rb index f9804e664a..d2d48e093a 100644 --- a/spec/requests/account_requests_spec.rb +++ b/spec/requests/account_requests_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe "/account_requests", type: :request do describe "GET #new" do it "renders a successful response" do diff --git a/spec/requests/adjustments_requests_spec.rb b/spec/requests/adjustments_requests_spec.rb index 8bbc96ccfe..14d857c509 100644 --- a/spec/requests/adjustments_requests_spec.rb +++ b/spec/requests/adjustments_requests_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "Adjustments", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/admin/account_requests_requests_spec.rb b/spec/requests/admin/account_requests_requests_spec.rb index b796fa07c6..ee59621f0c 100644 --- a/spec/requests/admin/account_requests_requests_spec.rb +++ b/spec/requests/admin/account_requests_requests_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe 'Admin::AccountRequestsController', type: :request do let(:organization) { create(:organization) } diff --git a/spec/requests/admin/barcode_items_requests_spec.rb b/spec/requests/admin/barcode_items_requests_spec.rb index b2ebb9a91f..06c1c8ec67 100644 --- a/spec/requests/admin/barcode_items_requests_spec.rb +++ b/spec/requests/admin/barcode_items_requests_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe 'Admin::BarcodeItemsController', type: :request do let(:organization) { create(:organization) } diff --git a/spec/requests/admin/base_items_requests_spec.rb b/spec/requests/admin/base_items_requests_spec.rb index 6fd868f338..ee04d33c1e 100644 --- a/spec/requests/admin/base_items_requests_spec.rb +++ b/spec/requests/admin/base_items_requests_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe "Admin::BaseItems", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/admin/broadcast_announcements_spec.rb b/spec/requests/admin/broadcast_announcements_spec.rb index c19e92b661..d082b2653d 100644 --- a/spec/requests/admin/broadcast_announcements_spec.rb +++ b/spec/requests/admin/broadcast_announcements_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "BroadcastAnnouncements", type: :request do let(:organization) { create(:organization) } let(:user) { create(:super_admin, organization: organization) } diff --git a/spec/requests/admin/ndbn_members_spec.rb b/spec/requests/admin/ndbn_members_spec.rb index 647a1f401a..0ba07aa168 100644 --- a/spec/requests/admin/ndbn_members_spec.rb +++ b/spec/requests/admin/ndbn_members_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "NDBNMembers", type: :request do let(:user) { create(:super_admin) } diff --git a/spec/requests/admin/partners_requests_spec.rb b/spec/requests/admin/partners_requests_spec.rb index 89c849b695..13a973ee14 100644 --- a/spec/requests/admin/partners_requests_spec.rb +++ b/spec/requests/admin/partners_requests_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe "Admin::Partners", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/admin/questions_spec.rb b/spec/requests/admin/questions_spec.rb index 1e5f3ead44..ac97faaa60 100644 --- a/spec/requests/admin/questions_spec.rb +++ b/spec/requests/admin/questions_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "Admin::Questions", type: :request do context "while signed in as a super admin" do before do diff --git a/spec/requests/admin/users_requests_spec.rb b/spec/requests/admin/users_requests_spec.rb index b2253abbef..760935ad17 100644 --- a/spec/requests/admin/users_requests_spec.rb +++ b/spec/requests/admin/users_requests_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe "Admin::UsersController", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/audits_requests_spec.rb b/spec/requests/audits_requests_spec.rb index 4e5b36d3f6..80d21de719 100644 --- a/spec/requests/audits_requests_spec.rb +++ b/spec/requests/audits_requests_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe "Audits", type: :request do let(:organization) { create(:organization) } let(:organization_admin) { create(:organization_admin, organization: organization) } diff --git a/spec/requests/broadcast_announcements_spec.rb b/spec/requests/broadcast_announcements_spec.rb index 77be7c8d58..9ed0a3e746 100644 --- a/spec/requests/broadcast_announcements_spec.rb +++ b/spec/requests/broadcast_announcements_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "BroadcastAnnouncements", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/dashboard_requests_spec.rb b/spec/requests/dashboard_requests_spec.rb index 4b482ee000..3517d6e4e8 100644 --- a/spec/requests/dashboard_requests_spec.rb +++ b/spec/requests/dashboard_requests_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe "Dashboard", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/distributions_by_county_spec.rb b/spec/requests/distributions_by_county_spec.rb index d72bed8403..72cf0d429e 100644 --- a/spec/requests/distributions_by_county_spec.rb +++ b/spec/requests/distributions_by_county_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "DistributionsByCounties", type: :request do include_examples "distribution_by_county" diff --git a/spec/requests/distributions_requests_spec.rb b/spec/requests/distributions_requests_spec.rb index e3b87997f4..b4adc8fb97 100644 --- a/spec/requests/distributions_requests_spec.rb +++ b/spec/requests/distributions_requests_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe "Distributions", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } @@ -415,6 +413,27 @@ get edit_distribution_path(id: distribution.id) expect(response.body).not_to include("Youโ€™ve had an audit since this distribution was started.") end + + # Bug fix #4537 + context "when distribution sets storage location total inventory to zero" do + let(:item1) { create(:item, name: "Item 1", organization: organization) } + let(:test_storage_name) { "Test Storage" } + let(:storage_location) { create(:storage_location, name: test_storage_name, organization: organization) } + before(:each) do + quantity = 20 + TestInventory.create_inventory(organization, { + storage_location.id => { + item1.id => quantity + } + }) + @distribution_all = create(:distribution, :with_items, item: item1, item_quantity: quantity, storage_location: storage_location, organization: organization) + DistributionCreateService.new(@distribution_all).call + end + it "allows you to select the original storage location for the distribution" do + get edit_distribution_path(id: @distribution_all.id) + expect(response.body).to include("") + end + end end end diff --git a/spec/requests/donation_sites_requests_spec.rb b/spec/requests/donation_sites_requests_spec.rb index c049dcbe20..b5a0c76cd5 100644 --- a/spec/requests/donation_sites_requests_spec.rb +++ b/spec/requests/donation_sites_requests_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "DonationSites", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/donations_requests_spec.rb b/spec/requests/donations_requests_spec.rb index 0876b4a189..963a5244c6 100644 --- a/spec/requests/donations_requests_spec.rb +++ b/spec/requests/donations_requests_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "Donations", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } @@ -28,6 +26,11 @@ expect(subject.body).to include("Source") expect(subject.body).to include("Details") end + it "shows a print button" do + page = Nokogiri::HTML(subject.body) + pdf = page.at_css("a[href*='print.pdf']") + expect(pdf.text).to include("Print") + end context "when given a product drive" do let(:product_drive) { create(:product_drive, name: "Drive Name") } @@ -83,10 +86,27 @@ end end + describe "GET #print" do + let(:item) { create(:item) } + let!(:donation) { create(:donation, :with_items, item: item) } + + it "returns http success" do + get print_donation_path(id: donation.id) + expect(response).to be_successful + end + end + describe "GET #show" do let(:item) { create(:item) } let!(:donation) { create(:donation, :with_items, item: item) } + it "shows a print button" do + get donation_path(id: donation.id) + page = Nokogiri::HTML(response.body) + pdf = page.at_css("a[href*='#{print_donation_path(id: donation.id)}']") + expect(pdf.text).to include("Print") + end + it "shows an enabled edit button" do get donation_path(id: donation.id) page = Nokogiri::HTML(response.body) diff --git a/spec/requests/events_requests_spec.rb b/spec/requests/events_requests_spec.rb index 730120450e..8ed1f96d71 100644 --- a/spec/requests/events_requests_spec.rb +++ b/spec/requests/events_requests_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "Events", type: :request do let(:organization) { create(:organization) } let(:user) { create(:organization_admin, organization: organization) } diff --git a/spec/requests/item_categories_requests_spec.rb b/spec/requests/item_categories_requests_spec.rb index 1c4f96df52..24f4b69fbb 100644 --- a/spec/requests/item_categories_requests_spec.rb +++ b/spec/requests/item_categories_requests_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "ItemCategories", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/items_requests_spec.rb b/spec/requests/items_requests_spec.rb index 73425005fa..e2270a9c78 100644 --- a/spec/requests/items_requests_spec.rb +++ b/spec/requests/items_requests_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "Items", type: :request do let(:organization) { create(:organization, short_name: "my_org") } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/kit_requests_spec.rb b/spec/requests/kit_requests_spec.rb index a749da27eb..35284743bb 100644 --- a/spec/requests/kit_requests_spec.rb +++ b/spec/requests/kit_requests_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "/kits", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/organization_requests_spec.rb b/spec/requests/organization_requests_spec.rb index 60df080987..3c93b169be 100644 --- a/spec/requests/organization_requests_spec.rb +++ b/spec/requests/organization_requests_spec.rb @@ -1,9 +1,11 @@ -require "rails_helper" - RSpec.describe "Organizations", type: :request do let(:organization) { create(:organization) } - let(:user) { create(:user, organization: organization) } - let(:organization_admin) { create(:organization_admin, organization: organization) } + let!(:user) { create(:user, organization: organization) } + let!(:organization_admin) { create(:organization_admin, organization: organization) } + let!(:admin_user) { create(:organization_admin, organization: organization, name: "ADMIN USER") } + let!(:unit) { create(:unit, name: "WolfPack", organization: organization) } + let!(:store) { create(:storage_location, organization: organization) } + let!(:ndbn_member) { create(:ndbn_member, ndbn_member_id: "50000", account_name: "Best Place") } context "While signed in as a normal user" do before do @@ -22,6 +24,47 @@ url: organization.url ) end + + it "can view organization details", :aggregate_failures do + html = Nokogiri::HTML(response.body) + expect(html.text).to include(organization.name) + expect(html.css("a").text).to include("Home") + expect(html.css("a").to_s).to include(dashboard_path) + expect(html.text).to include("Organization Info") + expect(html.text).to include("Contact Info") + expect(html.text).to include("Default email text") + expect(html.text).to include("Users") + expect(html.text).to include("Short Name") + expect(html.text).to include("URL") + expect(html.text).to include("Partner Profile Sections") + expect(html.text).to include("Custom Partner Invitation Message") + expect(html.text).to include("Child Based Requests?") + expect(html.text).to include("Individual Requests?") + expect(html.text).to include("Quantity Based Requests?") + expect(html.text).to include("Show Year-to-date values on distribution printout?") + expect(html.text).to include("Logo") + expect(html.text).to include("Use One step Partner invite and approve process?") + end + + context "when enable_packs flipper is on" do + it "displays organization's custom units" do + Flipper.enable(:enable_packs) + get organization_path + expect(response.body).to include "Wolf Pack" + end + end + + context "when enable_packs flipper is off" do + it "does not display organization's custom units" do + Flipper.disable(:enable_packs) + get organization_path + expect(response.body).to_not include "Wolf Pack" + end + end + + it "cannot see 'Demote to User' button for admins" do + expect(response.body).to_not include "Demote to User" + end end describe "GET #edit" do @@ -45,6 +88,59 @@ sign_in(organization_admin) end + describe "GET #show" do + before { get organization_path } + + it "can view organization details", :aggregate_failures do + html = Nokogiri::HTML(response.body) + expect(html.text).to include(organization.name) + expect(html.css("a").text).to include("Home") + expect(html.css("a").to_s).to include(dashboard_path) + expect(html.text).to include("Organization Info") + expect(html.text).to include("Contact Info") + expect(html.text).to include("Default email text") + expect(html.text).to include("Users") + expect(html.text).to include("Short Name") + expect(html.text).to include("URL") + expect(html.text).to include("Partner Profile Sections") + expect(html.text).to include("Custom Partner Invitation Message") + expect(html.text).to include("Child Based Requests?") + expect(html.text).to include("Individual Requests?") + expect(html.text).to include("Quantity Based Requests?") + expect(html.text).to include("Show Year-to-date values on distribution printout?") + expect(html.text).to include("Logo") + expect(html.text).to include("Use One step Partner invite and approve process?") + end + + context "when enable_packs flipper is on" do + it "displays organization's custom units" do + Flipper.enable(:enable_packs) + get organization_path + expect(response.body).to include "Wolf Pack" + end + end + + context "when enable_packs flipper is off" do + it "does not display organization's custom units" do + Flipper.disable(:enable_packs) + get organization_path + expect(response.body).to_not include "Wolf Pack" + end + end + + it "can see 'Demote to User' button for admins" do + create(:organization_admin, organization: organization, name: "ADMIN USER") + get organization_path + expect(response.body).to include "Demote to User" + end + + it "can re-invite a user to an organization after 7 days" do + create(:user, name: "Ye Olde Invited User", invitation_sent_at: Time.current - 7.days) + get organization_path + expect(response.body).to include("Re-send invitation") + end + end + describe "GET #edit" do before { get edit_organization_path } @@ -58,6 +154,24 @@ url: organization.url ) end + + context "when enable_packs flipper is on" do + it "should display custom units and units form" do + Flipper.enable(:enable_packs) + get edit_organization_path + expect(response.body).to include("Custom request units used (please use singular form -- e.g. pack, not packs)") + expect(response.body).to include "WolfPack" + end + end + + context "when enable_packs flipper is off" do + it "should not display custom units and units form" do + Flipper.disable(:enable_packs) + get edit_organization_path + expect(response.body).to_not include("Custom request units used (please use singular form -- e.g. pack, not packs)") + expect(response.body).to_not include "WolfPack" + end + end end describe "PATCH #update" do @@ -74,16 +188,120 @@ end context "when organization can not be updated" do - let(:invalid_organization) { create(:organization, name: "Original Name") } - let(:invalid_params) { { organization: { name: nil } } } - - subject { patch "/manage", params: invalid_params } + let(:update_param) { { organization: { name: nil } } } it "renders edit template with an error message" do - expect(subject).to render_template("edit") + expect(subject).to render_template(:edit) expect(flash[:error]).to be_present end end + + context "when the organization URL is updated" do + let(:update_param) { { organization: { url: "http://www.diaperbase.com" } } } + it "updates the organization's URL" do + subject + expect(response).to redirect_to(organization_path) + follow_redirect! + expect(response.body).to include("pdated") + end + end + + context "updates reminder and deadline days" do + let(:update_param) { { organization: { reminder_day: 12, deadline_day: 16 } } } + it "works" do + subject + expect(response).to redirect_to(organization_path) + follow_redirect! + expect(response.body).to include("Updated") + end + end + + context "updates repackage essentials setting" do + let(:update_param) { { organization: { repackage_essentials: true } } } + it "works" do + subject + expect(response).to redirect_to(organization_path) + follow_redirect! + expect(response.body).to include("Yes") + end + end + + context "can select if the org distributes essentials monthly" do + let(:update_param) { { organization: { distribute_monthly: true } } } + it "works" do + subject + expect(response).to redirect_to(organization_path) + follow_redirect! + expect(response.body).to include("Yes") + end + end + + context 'can select if the org shows year-to-date values on the distribution printout' do + let(:update_param) { { organization: { ytd_on_distribution_printout: false } } } + it "works" do + subject + expect(response).to redirect_to(organization_path) + follow_redirect! + expect(response.body).to include("No") + end + end + + context 'can set a default storage location on the organization' do + let(:update_param) { { organization: { default_storage_location: store.id } } } + it "works" do + subject + expect(response).to redirect_to(organization_path) + follow_redirect! + expect(response.body).to include(store.name) + end + end + + context 'can set the NDBN Member ID' do + let(:update_param) { { organization: { ndbn_member_id: ndbn_member.id } } } + it "works" do + subject + expect(response).to redirect_to(organization_path) + follow_redirect! + expect(response.body).to include(ndbn_member.full_name) + end + end + + context 'can select and deselect Required Partner Fields' do + let(:update_param) { { organization: { partner_form_fields: ['media_information'] } } } + it "works" do + subject + expect(response).to redirect_to(organization_path) + follow_redirect! + expect(response.body).to include('Media Information') + expect(organization.reload.partner_form_fields).to eq(['media_information']) + + patch "/manage", params: { organization: { partner_form_fields: [] } } + expect(response).to redirect_to(organization_path) + follow_redirect! + expect(response.body).to_not include('Media Information') + expect(organization.reload.partner_form_fields).to eq([]) + end + end + + context "can disable if the org does NOT use single step invite and approve partner process" do + let(:update_param) { { organization: { one_step_partner_invite: false } } } + it "works" do + subject + expect(response).to redirect_to(organization_path) + follow_redirect! + expect(response.body).to include("No") + end + end + + context "can enable if the org uses single step invite and approve partner process" do + let(:update_param) { { organization: { one_step_partner_invite: true } } } + it "works" do + subject + expect(response).to redirect_to(organization_path) + follow_redirect! + expect(response.body).to include("Yes") + end + end end describe "POST #promote_to_org_admin" do @@ -97,9 +315,6 @@ end describe "POST #demote_to_user" do - let(:admin_user) do - create(:organization_admin, organization: organization, name: "ADMIN USER") - end subject { post demote_to_user_organization_path(user_id: admin_user.id) } it "runs correctly" do @@ -109,28 +324,28 @@ end end - describe "PUT #deactivate_user" do - subject { put deactivate_user_organization_path(user_id: user.id) } + describe "POST #remove_user" do + subject { post remove_user_organization_path(user_id: user.id) } - it "redirect after update" do - subject - expect(response).to redirect_to(organization_path) - end - it "deactivates the user" do - expect { subject }.to change { user.reload.discarded_at }.to be_present + context "when user is org user" do + it "redirects after update" do + subject + expect(response).to redirect_to(organization_path) + end + + it "removes the org user role" do + expect { subject }.to change { user.has_role?(Role::ORG_USER, organization) }.from(true).to(false) + end end - end - describe "PUT #reactivate_user" do - subject { put reactivate_user_organization_path(user_id: user.id) } - before { user.discard! } + context "when user is not an org user" do + let(:user) { create(:user, organization: create(:organization)) } - it "redirect after update" do - subject - expect(response).to redirect_to(organization_path) - end - it "reactivates the user" do - expect { subject }.to change { user.reload.discarded_at }.to be_nil + it 'raises an error' do + subject + + expect(response).to be_not_found + end end end diff --git a/spec/requests/partner_users_requests_spec.rb b/spec/requests/partner_users_requests_spec.rb index 1cdfb9cdfb..3fd3a5d71f 100644 --- a/spec/requests/partner_users_requests_spec.rb +++ b/spec/requests/partner_users_requests_spec.rb @@ -1,7 +1,5 @@ # spec/requests/partner_users_controller_spec.rb -require "rails_helper" - RSpec.describe PartnerUsersController, type: :request do let!(:partner) { create(:partner) } # Assuming you have a factory for creating partners let!(:user) { create(:user) } # Assuming you have a factory for creating users diff --git a/spec/requests/partners/children_requests_spec.rb b/spec/requests/partners/children_requests_spec.rb index ca42e7386a..8547c91a30 100644 --- a/spec/requests/partners/children_requests_spec.rb +++ b/spec/requests/partners/children_requests_spec.rb @@ -1,9 +1,9 @@ -require "rails_helper" - RSpec.describe "/partners/children", type: :request do let(:partner_user) { partner.primary_user } let(:partner) { create(:partner) } - let(:family) { create(:partners_family, partner: partner) } + let(:family) { create(:partners_family, partner: partner, guardian_first_name: "First Name", guardian_last_name: "Last Name") } + let(:item1) { create(:item, organization: partner.organization, name: "Item A") } + let(:item2) { create(:item, organization: partner.organization, name: "Item B") } let!(:child1) do create(:partners_child, first_name: "John", @@ -15,7 +15,7 @@ agency_child_id: "Agency McAgence", health_insurance: "Private insurance", comments: "Some comment", - item_needed_diaperid: nil, + requested_item_ids: nil, active: true, archived: false, family: family) @@ -31,9 +31,9 @@ agency_child_id: "Agency McAgence", health_insurance: "Private insurance", comments: "Some comment", - item_needed_diaperid: nil, active: true, archived: false, + requested_item_ids: [item1.id, item2.id], family: family) end @@ -51,9 +51,9 @@ headers = {"Accept" => "text/csv", "Content-Type" => "text/csv"} get partners_children_path, headers: headers csv = <<~CSV - id,first_name,last_name,date_of_birth,gender,child_lives_with,race,agency_child_id,health_insurance,comments,created_at,updated_at,family_id,item_needed_diaperid,active,archived - #{child1.id},John,Smith,2019-01-01,Male,"mother,grandfather",Other,Agency McAgence,Private insurance,Some comment,#{child1.created_at},#{child1.updated_at},#{family.id},"",true,false - #{child2.id},Jane,Smith,2018-01-01,Female,father,Hispanic,Agency McAgence,Private insurance,Some comment,#{child2.created_at},#{child2.updated_at},#{family.id},"",true,false + id,first_name,last_name,date_of_birth,gender,child_lives_with,race,agency_child_id,health_insurance,comments,created_at,updated_at,guardian_last_name,guardian_first_name,requested_items,active,archived + #{child1.id},John,Smith,2019-01-01,Male,"mother,grandfather",Other,Agency McAgence,Private insurance,Some comment,#{child1.created_at},#{child1.updated_at},Last Name,First Name,"",true,false + #{child2.id},Jane,Smith,2018-01-01,Female,father,Hispanic,Agency McAgence,Private insurance,Some comment,#{child2.created_at},#{child2.updated_at},Last Name,First Name,"Item A, Item B",true,false CSV expect(response.body).to eq(csv) end diff --git a/spec/requests/partners/dashboard_requests_spec.rb b/spec/requests/partners/dashboard_requests_spec.rb index 85a9d1cf2d..761f379abb 100644 --- a/spec/requests/partners/dashboard_requests_spec.rb +++ b/spec/requests/partners/dashboard_requests_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "/partners/dashboard", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/partners/distributions_spec.rb b/spec/requests/partners/distributions_spec.rb index 21c35f7905..a5d3d0afff 100644 --- a/spec/requests/partners/distributions_spec.rb +++ b/spec/requests/partners/distributions_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "/partners/distributions", type: :request do let(:partner) { create(:partner) } let(:partner_user) { partner.primary_user } diff --git a/spec/requests/partners/family_requests_controller_spec.rb b/spec/requests/partners/family_requests_controller_spec.rb index 04b17f75f2..eb7b01ecd2 100644 --- a/spec/requests/partners/family_requests_controller_spec.rb +++ b/spec/requests/partners/family_requests_controller_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe Partners::FamilyRequestsController, type: :request do let(:partner) { create(:partner) } let(:params) do @@ -34,7 +32,7 @@ # Set one child as deactivated and the other as active but # without a item_needed_diaperid children[0].update(active: false) - children[1].update(item_needed_diaperid: nil) + children[1].update(requested_item_ids: []) end subject { post partners_family_requests_path, params: params } diff --git a/spec/requests/partners/family_requests_spec.rb b/spec/requests/partners/family_requests_spec.rb index ce6db4b0fb..1545dc8b73 100644 --- a/spec/requests/partners/family_requests_spec.rb +++ b/spec/requests/partners/family_requests_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "/partners/family", type: :request do let(:partner_user) { partner.primary_user } let(:partner) { create(:partner) } diff --git a/spec/requests/partners/individuals_requests_controller_spec.rb b/spec/requests/partners/individuals_requests_controller_spec.rb index e795a8d326..e9630f0787 100644 --- a/spec/requests/partners/individuals_requests_controller_spec.rb +++ b/spec/requests/partners/individuals_requests_controller_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe Partners::IndividualsRequestsController, type: :request do let(:organization) { create(:organization, :with_items) } let(:partner) { create(:partner, status: :approved, organization: organization) } diff --git a/spec/requests/partners/profiles_requests_county_spec.rb b/spec/requests/partners/profiles_requests_county_spec.rb index 1d7793ad25..fa5bbf6841 100644 --- a/spec/requests/partners/profiles_requests_county_spec.rb +++ b/spec/requests/partners/profiles_requests_county_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "/partners/profiles", type: :request do describe "basic" do let(:organization) { create(:organization, name: "Favourite Bank", partner_form_fields: []) } diff --git a/spec/requests/partners/profiles_requests_spec.rb b/spec/requests/partners/profiles_requests_spec.rb index d33db298bc..b57af08dd2 100644 --- a/spec/requests/partners/profiles_requests_spec.rb +++ b/spec/requests/partners/profiles_requests_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "/partners/profiles", type: :request do let(:partner) { create(:partner, name: "Partnerrific") } let(:partner_user) { partner.primary_user } diff --git a/spec/requests/partners/requests_spec.rb b/spec/requests/partners/requests_spec.rb index 03d611d3fa..2230dbee18 100644 --- a/spec/requests/partners/requests_spec.rb +++ b/spec/requests/partners/requests_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe "/partners/requests", type: :request do let(:organization) { create(:organization) } let(:partner) { create(:partner, organization: organization) } @@ -89,6 +87,38 @@ get partners_request_path(other_request) expect(response.code).to eq("404") end + + it 'should show the units if they are provided and enabled' do + item1 = create(:item, name: "First item") + item2 = create(:item, name: "Second item") + item3 = create(:item, name: "Third item") + create(:item_unit, item: item1, name: "flat") + create(:item_unit, item: item2, name: "flat") + create(:item_unit, item: item3, name: "flat") + request = create( + :request, + :with_item_requests, + partner_id: partner.id, + partner_user_id: partner_user.id, + request_items: [ + {item_id: item1.id, quantity: '125'}, + {item_id: item2.id, quantity: '559', request_unit: 'flat'}, + {item_id: item3.id, quantity: '1', request_unit: 'flat'} + ] + ) + + Flipper.enable(:enable_packs) + get partners_request_path(request) + expect(response.body).to match(/125\s+of\s+First item/m) + expect(response.body).to match(/559\s+flats\s+of\s+Second item/m) + expect(response.body).to match(/1\s+flat\s+of\s+Third item/m) + + Flipper.disable(:enable_packs) + get partners_request_path(request) + expect(response.body).to match(/125\s+of\s+First item/m) + expect(response.body).to match(/559\s+of\s+Second item/m) + expect(response.body).to match(/1\s+of\s+Third item/m) + end end describe "POST #create" do diff --git a/spec/requests/partners/user_requests_spec.rb b/spec/requests/partners/user_requests_spec.rb index 60447e3821..f25d835ef1 100644 --- a/spec/requests/partners/user_requests_spec.rb +++ b/spec/requests/partners/user_requests_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "/partners/users", type: :request do let(:partner) { create(:partner) } let(:partner_user) { partner.primary_user } @@ -7,6 +5,7 @@ before do sign_in(partner_user) end + describe "GET #edit" do it "successfully loads the page" do get edit_partners_user_path(partner_user) @@ -28,4 +27,59 @@ expect(partner_user.name).to eq "New name" end end + + describe "POST #create" do + let(:params) do + {user: {name: "New User", email: "new_partner_email@example.com"}} + end + + it "creates a new user" do + post partners_users_path, params: params + + aggregate_failures do + expect(response.request.flash[:success]).to eq "You have invited New User to join your organization!" + expect(response).to redirect_to(partners_users_path) + end + end + + context "when the user already exists" do + let(:organization) { create(:organization) } + let(:org_user) { create(:user, name: "Existing User", organization: organization) } + + let(:params) do + {user: {name: org_user.name, email: org_user.email}} + end + + it "succeeds and adds additional role to user" do + post partners_users_path, params: params + + aggregate_failures do + expect(response.request.flash[:success]).to eq "You have invited Existing User to join your organization!" + expect(response).to redirect_to(partners_users_path) + expect(org_user.has_role?(Role::PARTNER, partner)).to be true + expect(org_user.has_role?(Role::ORG_USER, organization)).to be true + end + end + + context "when org user had role removed" do + before do + sign_in create(:organization_admin, organization: organization) + post remove_user_organization_path(user_id: org_user.id) + sign_in(partner_user) + end + + it "adds role to user" do + expect(org_user.has_role?(Role::ORG_USER, organization)).to be false + + post partners_users_path, params: params + + aggregate_failures do + expect(response.request.flash[:success]).to eq "You have invited Existing User to join your organization!" + expect(response).to redirect_to(partners_users_path) + expect(org_user.has_role?(Role::PARTNER, partner)).to be true + end + end + end + end + end end diff --git a/spec/requests/product_drive_participants_requests_spec.rb b/spec/requests/product_drive_participants_requests_spec.rb index 5682c4ea4d..b55e84b7a7 100644 --- a/spec/requests/product_drive_participants_requests_spec.rb +++ b/spec/requests/product_drive_participants_requests_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe "ProductDriveParticipants", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } @@ -97,9 +95,12 @@ end describe "GET #show" do - it "returns http success" do - get product_drive_participant_path(id: create(:product_drive_participant, organization: organization)) + it "returns http success and displays comments" do + comment = "Test comment about product drive participant." + @participant = create(:product_drive_participant, organization: organization, comment: comment) + get product_drive_participant_path(id: @participant) expect(response).to be_successful + expect(response.body).to include(comment) end end diff --git a/spec/requests/product_drives_requests_spec.rb b/spec/requests/product_drives_requests_spec.rb index a4223aaa46..fe6f4476ea 100644 --- a/spec/requests/product_drives_requests_spec.rb +++ b/spec/requests/product_drives_requests_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe "ProductDrives", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/purchases_requests_spec.rb b/spec/requests/purchases_requests_spec.rb index 7a41ab6f44..abf7f1342a 100644 --- a/spec/requests/purchases_requests_spec.rb +++ b/spec/requests/purchases_requests_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "Purchases", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/reports/activity_graph_spec.rb b/spec/requests/reports/activity_graph_spec.rb index 0a68ac5337..900c027737 100644 --- a/spec/requests/reports/activity_graph_spec.rb +++ b/spec/requests/reports/activity_graph_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "Reports::ActivityGraph", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/reports/annual_reports_requests_spec.rb b/spec/requests/reports/annual_reports_requests_spec.rb index 08a799dba6..c6229428b5 100644 --- a/spec/requests/reports/annual_reports_requests_spec.rb +++ b/spec/requests/reports/annual_reports_requests_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe "Annual Reports", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/reports/distributions_summary_requests_spec.rb b/spec/requests/reports/distributions_summary_requests_spec.rb index 6188f07b7a..6fcc04e0bd 100644 --- a/spec/requests/reports/distributions_summary_requests_spec.rb +++ b/spec/requests/reports/distributions_summary_requests_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "Distributions", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/reports/donations_summary_spec.rb b/spec/requests/reports/donations_summary_spec.rb index ab6c08cb28..7d16c5b37c 100644 --- a/spec/requests/reports/donations_summary_spec.rb +++ b/spec/requests/reports/donations_summary_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "Reports::DonationsSummary", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/reports/itemized_distributions_spec.rb b/spec/requests/reports/itemized_distributions_spec.rb index 25f0fc855c..b0e5a5814c 100644 --- a/spec/requests/reports/itemized_distributions_spec.rb +++ b/spec/requests/reports/itemized_distributions_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "Reports::ItemizedDistributions", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/reports/itemized_donations_spec.rb b/spec/requests/reports/itemized_donations_spec.rb index 7be38d749d..449cf073de 100644 --- a/spec/requests/reports/itemized_donations_spec.rb +++ b/spec/requests/reports/itemized_donations_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "Reports::ItemizedDonations", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/reports/manufacturer_donations_summary_spec.rb b/spec/requests/reports/manufacturer_donations_summary_spec.rb index 75ea4e70ce..bd3285c751 100644 --- a/spec/requests/reports/manufacturer_donations_summary_spec.rb +++ b/spec/requests/reports/manufacturer_donations_summary_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "Reports::ManufacturerDonationsSummary", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/reports/product_drives_summary_spec.rb b/spec/requests/reports/product_drives_summary_spec.rb index bdae706fc0..52d31f32e1 100644 --- a/spec/requests/reports/product_drives_summary_spec.rb +++ b/spec/requests/reports/product_drives_summary_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "Reports::ProductDrivesSummary", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/reports/purchases_summary_requests_spec.rb b/spec/requests/reports/purchases_summary_requests_spec.rb index 5205bbeea1..3f9a47d2c4 100644 --- a/spec/requests/reports/purchases_summary_requests_spec.rb +++ b/spec/requests/reports/purchases_summary_requests_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "Purchases", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/requests_requests_spec.rb b/spec/requests/requests_requests_spec.rb index 162ad9adde..cc891607a6 100644 --- a/spec/requests/requests_requests_spec.rb +++ b/spec/requests/requests_requests_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe 'Requests', type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } @@ -76,6 +74,42 @@ expect(response.body).not_to include('Default storage location inventory') end end + + context 'When packs are enabled' do + before { Flipper.enable(:enable_packs) } + let(:item) { create(:item, name: "Item", organization: organization) } + let(:request) { create(:request, organization: organization) } + + it 'shows a units column and custom unit if any item has custom units' do + create(:item_unit, item: item, name: "Pack") + create(:item_request, request: request, request_unit: "Pack", item: item) + + get request_path(request) + + expect(response.body).to include('Units (if applicable)') + expect(response.body).to include('Packs') + end + + it 'does not show a units column or any unit if no items have custom units' do + create(:item_unit, item: item, name: "Pack") + create(:item_request, request: request, request_unit: nil, item: item) + + get request_path(request) + + expect(response.body).to_not include('Units (if applicable)') + expect(response.body).to_not include('Packs') + end + end + + context 'When packs are not enabled' do + let(:request) { create(:request, organization: organization) } + + it 'does not show a units column' do + get request_path(request) + + expect(response.body).not_to include('Units (if applicable)') + end + end end describe 'POST #start' do diff --git a/spec/requests/sessions_requests_spec.rb b/spec/requests/sessions_requests_spec.rb index 3a3028866d..b3688c29a2 100644 --- a/spec/requests/sessions_requests_spec.rb +++ b/spec/requests/sessions_requests_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "Sessions", type: :request, order: :defined do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/static_requests_spec.rb b/spec/requests/static_requests_spec.rb index eef97776ee..1d6e534904 100644 --- a/spec/requests/static_requests_spec.rb +++ b/spec/requests/static_requests_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "Static", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/requests/storage_locations_requests_spec.rb b/spec/requests/storage_locations_requests_spec.rb index fbde891162..ded6d9943d 100644 --- a/spec/requests/storage_locations_requests_spec.rb +++ b/spec/requests/storage_locations_requests_spec.rb @@ -235,20 +235,77 @@ let(:inventory_item) { storage_location.inventory_items.first } context "with a version found" do - it "should show the version specified" do - travel 1.day do - inventory_item.update!(quantity: 100) + context "with events_read on" do + before(:each) { allow(Event).to receive(:read_events?).and_return(true) } + context "before active events" do + it "should show the version specified" do + travel 1.day do + inventory_item.update!(quantity: 100) + end + travel 1.week do + inventory_item.update!(quantity: 300) + end + travel 8.days do + SnapshotEvent.delete_all + SnapshotEvent.publish(organization) + end + travel 2.weeks do + get storage_location_path(storage_location, format: response_format, + version_date: 9.days.ago.to_date.to_fs(:db)) + expect(response).to be_successful + expect(response.body).to include("Smithsonian") + expect(response.body).to include("Test Item") + expect(response.body).to include("100") + end + end end - travel 1.week do - inventory_item.update!(quantity: 300) + + context "with active events" do + it 'should show the right version' do + travel 1.day do + TestInventory.create_inventory(organization, { + storage_location.id => { + item.id => 100, + item2.id => 0 + } + }) + end + travel 1.week do + TestInventory.create_inventory(organization, { + storage_location.id => { + item.id => 300, + item2.id => 0 + } + }) + end + travel 2.weeks do + get storage_location_path(storage_location, format: response_format, + version_date: 9.days.ago.to_date.to_fs(:db)) + expect(response).to be_successful + expect(response.body).to include("Smithsonian") + expect(response.body).to include("Test Item") + expect(response.body).to include("100") + end + end end - travel 2.weeks do - get storage_location_path(storage_location, format: response_format, - version_date: 9.days.ago.to_date.to_fs(:db)) - expect(response).to be_successful - expect(response.body).to include("Smithsonian") - expect(response.body).to include("Test Item") - expect(response.body).to include("100") + end + context "with events_read off" do + before(:each) { allow(Event).to receive(:read_events?).and_return(false) } + it "should show the version specified" do + travel 1.day do + inventory_item.update!(quantity: 100) + end + travel 1.week do + inventory_item.update!(quantity: 300) + end + travel 2.weeks do + get storage_location_path(storage_location, format: response_format, + version_date: 9.days.ago.to_date.to_fs(:db)) + expect(response).to be_successful + expect(response.body).to include("Smithsonian") + expect(response.body).to include("Test Item") + expect(response.body).to include("100") + end end end end @@ -338,6 +395,12 @@ def item_to_h(view_item) expect(response.parsed_body).to eq(items_at_storage_location) expect(response.parsed_body).to eq(inventory_items_at_storage_location) end + + it "returns items sorted alphabetically by item name" do + get inventory_storage_location_path(storage_location, format: :json) + sorted_items = inventory_items_at_storage_location.sort_by { |item| item['item_name'].downcase } + expect(response.parsed_body).to eq(sorted_items) + end end context "when also including inactive items" do diff --git a/spec/requests/users/omniauth_callbacks_requests_spec.rb b/spec/requests/users/omniauth_callbacks_requests_spec.rb index 27058fbde5..5a2922a73f 100644 --- a/spec/requests/users/omniauth_callbacks_requests_spec.rb +++ b/spec/requests/users/omniauth_callbacks_requests_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "Users - Omniauth Callbacks", type: :request do before do Rails.application.env_config["devise.mapping"] = Devise.mappings[:user] diff --git a/spec/requests/users_requests_spec.rb b/spec/requests/users_requests_spec.rb index 08b3d2ed3b..cf5bd3aeed 100644 --- a/spec/requests/users_requests_spec.rb +++ b/spec/requests/users_requests_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe "Users", type: :request do let(:organization) { create(:organization) } let(:user) { create(:user, organization: organization) } diff --git a/spec/routing/account_requests_routing_spec.rb b/spec/routing/account_requests_routing_spec.rb index e34c04c94e..4d06795366 100644 --- a/spec/routing/account_requests_routing_spec.rb +++ b/spec/routing/account_requests_routing_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe AccountRequestsController, type: :routing do describe "routing" do it "routes to #new" do diff --git a/spec/services/distribution_destroy_service_spec.rb b/spec/services/distribution_destroy_service_spec.rb index 677f901519..e765bc9a91 100644 --- a/spec/services/distribution_destroy_service_spec.rb +++ b/spec/services/distribution_destroy_service_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe DistributionDestroyService do describe '#call' do subject { described_class.new(distribution_id).call } diff --git a/spec/services/donation_destroy_service_spec.rb b/spec/services/donation_destroy_service_spec.rb index 33381eabfe..68a1251ee2 100644 --- a/spec/services/donation_destroy_service_spec.rb +++ b/spec/services/donation_destroy_service_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe DonationDestroyService do describe '#call' do subject { described_class.new(organization_id: organization_id, donation_id: donation_id) } diff --git a/spec/services/exports/export_report_csv_service_spec.rb b/spec/services/exports/export_report_csv_service_spec.rb index 66f76cabfa..1d90c1e07f 100644 --- a/spec/services/exports/export_report_csv_service_spec.rb +++ b/spec/services/exports/export_report_csv_service_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe Exports::ExportReportCSVService do describe ".generate_csv_data" do it "creates CSV data including headers" do diff --git a/spec/services/exports/export_request_service_spec.rb b/spec/services/exports/export_request_service_spec.rb index c6a1eb38e3..cca32a18ce 100644 --- a/spec/services/exports/export_request_service_spec.rb +++ b/spec/services/exports/export_request_service_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe Exports::ExportRequestService do let(:org) { create(:organization) } diff --git a/spec/services/historical_trend_service_spec.rb b/spec/services/historical_trend_service_spec.rb index cfff9f9cef..5b94b00fb4 100644 --- a/spec/services/historical_trend_service_spec.rb +++ b/spec/services/historical_trend_service_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe HistoricalTrendService, type: :service do let(:organization) { create(:organization) } let(:type) { "Donation" } diff --git a/spec/services/kit_create_service_spec.rb b/spec/services/kit_create_service_spec.rb index 31a9ea0f57..9a2aaaadb6 100644 --- a/spec/services/kit_create_service_spec.rb +++ b/spec/services/kit_create_service_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe KitCreateService do describe '#call' do subject { described_class.new(**args).call } diff --git a/spec/services/organization_update_service_spec.rb b/spec/services/organization_update_service_spec.rb index 3ed51f340c..def193ac96 100644 --- a/spec/services/organization_update_service_spec.rb +++ b/spec/services/organization_update_service_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe OrganizationUpdateService do let(:organization) { create(:organization) } @@ -11,6 +9,14 @@ expect(organization.errors.none?).to eq(true) expect(organization.reload.name).to eq("A brand NEW NEW name") end + + it "Should set request_units on the organization" do + Flipper.enable(:enable_packs) + params = {request_unit_names: ["newpack"]} + described_class.update(organization, params) + expect(organization.errors.none?).to eq(true) + expect(organization.reload.request_units.pluck(:name)).to match_array(["newpack"]) + end end context "when object is invalid" do diff --git a/spec/services/partner_approval_service_spec.rb b/spec/services/partner_approval_service_spec.rb index c961fd3c57..5142e8b795 100644 --- a/spec/services/partner_approval_service_spec.rb +++ b/spec/services/partner_approval_service_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe PartnerApprovalService do describe '#call' do subject { described_class.new(partner: partner).call } diff --git a/spec/services/partner_create_service_spec.rb b/spec/services/partner_create_service_spec.rb index 1de1daf4c4..e69ca00231 100644 --- a/spec/services/partner_create_service_spec.rb +++ b/spec/services/partner_create_service_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe PartnerCreateService do describe '#call' do subject { described_class.new(organization: organization, partner_attrs: partner_attrs).call } diff --git a/spec/services/partner_fetch_requestable_items_service_spec.rb b/spec/services/partner_fetch_requestable_items_service_spec.rb index 6da10e4170..bca5f2fd09 100644 --- a/spec/services/partner_fetch_requestable_items_service_spec.rb +++ b/spec/services/partner_fetch_requestable_items_service_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe PartnerFetchRequestableItemsService do describe '#call' do subject { described_class.new(partner_id: partner.id).call } diff --git a/spec/services/partner_invite_service_spec.rb b/spec/services/partner_invite_service_spec.rb index 9651fb9ba5..41f12472d1 100644 --- a/spec/services/partner_invite_service_spec.rb +++ b/spec/services/partner_invite_service_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe PartnerInviteService do subject { described_class.new(partner: partner).call } let(:partner) { create(:partner) } diff --git a/spec/services/partner_profile_update_service_spec.rb b/spec/services/partner_profile_update_service_spec.rb index 0597a66c92..a0dccd7668 100644 --- a/spec/services/partner_profile_update_service_spec.rb +++ b/spec/services/partner_profile_update_service_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe PartnerProfileUpdateService do let(:county_1) { create(:county, name: "county1", region: "region1") } let(:county_2) { create(:county, name: "county2", region: "region2") } diff --git a/spec/services/partner_request_recertification_service_spec.rb b/spec/services/partner_request_recertification_service_spec.rb index 524ddf885f..8b2802d2b2 100644 --- a/spec/services/partner_request_recertification_service_spec.rb +++ b/spec/services/partner_request_recertification_service_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe PartnerRequestRecertificationService do describe '#call' do subject { described_class.new(partner: partner).call } diff --git a/spec/services/partners/family_request_create_service_spec.rb b/spec/services/partners/family_request_create_service_spec.rb index a7b5ca2275..a51e5ff9a7 100644 --- a/spec/services/partners/family_request_create_service_spec.rb +++ b/spec/services/partners/family_request_create_service_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe Partners::FamilyRequestCreateService do describe '#call' do subject { described_class.new(**args).call } diff --git a/spec/services/partners/fetch_partners_to_remind_now_service_spec.rb b/spec/services/partners/fetch_partners_to_remind_now_service_spec.rb index 9c2a44d3ab..b4ca3d8eff 100644 --- a/spec/services/partners/fetch_partners_to_remind_now_service_spec.rb +++ b/spec/services/partners/fetch_partners_to_remind_now_service_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe Partners::FetchPartnersToRemindNowService do describe ".fetch" do subject { described_class.new.fetch } diff --git a/spec/services/partners/request_approval_service_spec.rb b/spec/services/partners/request_approval_service_spec.rb index c1061a7007..151d27d84d 100644 --- a/spec/services/partners/request_approval_service_spec.rb +++ b/spec/services/partners/request_approval_service_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe Partners::RequestApprovalService do describe '#call' do subject { described_class.new(partner: partner).call } diff --git a/spec/services/partners/request_create_service_spec.rb b/spec/services/partners/request_create_service_spec.rb index c3ab8f4fa2..80425cf60b 100644 --- a/spec/services/partners/request_create_service_spec.rb +++ b/spec/services/partners/request_create_service_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe Partners::RequestCreateService do describe '#call' do subject { described_class.new(**args).call } diff --git a/spec/services/partners/update_family_spec.rb b/spec/services/partners/update_family_spec.rb index 75cc9395e4..fbd11c007b 100644 --- a/spec/services/partners/update_family_spec.rb +++ b/spec/services/partners/update_family_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe Partners::UpdateFamily do describe "#call" do subject { described_class.archive(family) } diff --git a/spec/services/profile_update_service_spec.rb b/spec/services/profile_update_service_spec.rb index adff4901fa..ceac4cae4f 100644 --- a/spec/services/profile_update_service_spec.rb +++ b/spec/services/profile_update_service_spec.rb @@ -1,5 +1,3 @@ -require "rails_helper" - RSpec.describe ProfileUpdateService do describe "#update" do let(:partner) { create(:partner, name: "Partnerrific") } diff --git a/spec/services/reports/summary_report_service_spec.rb b/spec/services/reports/summary_report_service_spec.rb index e0c404d3f9..7e07c03a7a 100644 --- a/spec/services/reports/summary_report_service_spec.rb +++ b/spec/services/reports/summary_report_service_spec.rb @@ -12,7 +12,7 @@ expect(report.report).to eq({ entries: { "% difference in yearly donations" => "0%", "% difference in total money donated" => "0%", - "% difference in diaper donations" => "0%" }, + "% difference in disposable diaper donations" => "0%" }, name: "Year End Summary" }) end @@ -47,7 +47,7 @@ expect(report.report).to eq({ entries: { "% difference in yearly donations" => "+200%", "% difference in total money donated" => "+800%", - "% difference in diaper donations" => "+200%" }, + "% difference in disposable diaper donations" => "+200%" }, name: "Year End Summary" }) end @@ -79,7 +79,7 @@ expect(report.report).to eq({ entries: { "% difference in yearly donations" => "-67%", "% difference in total money donated" => "0%", - "% difference in diaper donations" => "-67%" }, + "% difference in disposable diaper donations" => "-67%" }, name: "Year End Summary" }) end diff --git a/spec/services/service_object_errors_mixin_spec.rb b/spec/services/service_object_errors_mixin_spec.rb index 68406c5e97..457f81f5cb 100644 --- a/spec/services/service_object_errors_mixin_spec.rb +++ b/spec/services/service_object_errors_mixin_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'rails_helper' - RSpec.describe ServiceObjectErrorsMixin do describe 'self.included' do before do diff --git a/spec/services/text_interpolator_service_spec.rb b/spec/services/text_interpolator_service_spec.rb index 97d91ca361..fb2cfb47d1 100644 --- a/spec/services/text_interpolator_service_spec.rb +++ b/spec/services/text_interpolator_service_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe TextInterpolatorService, type: :service do describe '#call' do subject { described_class.new(text, interpolations).call } diff --git a/spec/system/dashboard_system_spec.rb b/spec/system/dashboard_system_spec.rb index 6e7da20e8a..52886a780f 100644 --- a/spec/system/dashboard_system_spec.rb +++ b/spec/system/dashboard_system_spec.rb @@ -16,25 +16,25 @@ # rubocop:disable Layout/ExtraSpacing - # When dashboard loads, ensure that we are on step 1 (Partner Agencies) + # When dashboard loads, ensure that we are on step 1 (Storage Locations) expect(org_dashboard_page).to have_getting_started_guide - expect(org_dashboard_page).to have_add_partner_call_to_action - expect(org_dashboard_page).not_to have_add_storage_location_call_to_action + expect(org_dashboard_page).to have_add_storage_location_call_to_action + expect(org_dashboard_page).not_to have_add_partner_call_to_action expect(org_dashboard_page).not_to have_add_donation_site_call_to_action expect(org_dashboard_page).not_to have_add_inventory_call_to_action - # After we create a partner, ensure that we are on step 2 (Storage Locations) - create(:partner, organization: organization) + # After we create a storage location, ensure that we are on step 2 (Partner Agency) + create(:storage_location, organization: organization) org_dashboard_page.visit expect(org_dashboard_page).to have_getting_started_guide - expect(org_dashboard_page).not_to have_add_partner_call_to_action - expect(org_dashboard_page).to have_add_storage_location_call_to_action + expect(org_dashboard_page).to have_add_partner_call_to_action + expect(org_dashboard_page).not_to have_add_storage_location_call_to_action expect(org_dashboard_page).not_to have_add_donation_site_call_to_action expect(org_dashboard_page).not_to have_add_inventory_call_to_action - # After we create a storage location, ensure that we are on step 3 (Donation Site) - create(:storage_location, organization: organization) + # After we create a partner agency, ensure that we are on step 3 (Donation Site) + create(:partner, organization: organization) org_dashboard_page.visit expect(org_dashboard_page).to have_getting_started_guide diff --git a/spec/system/distribution_system_spec.rb b/spec/system/distribution_system_spec.rb index e4fbc78a52..956a2a3111 100644 --- a/spec/system/distribution_system_spec.rb +++ b/spec/system/distribution_system_spec.rb @@ -304,6 +304,10 @@ click_button "Save", match: :first expect(page).to have_css('.alert.error', text: /storage location/i) + + # 4438- Bug Fix + select storage_location.name, from: "From storage location" + expect(page).not_to have_css('#__add_line_item.disabled') end context "With an existing distribution" do diff --git a/spec/system/donation_system_spec.rb b/spec/system/donation_system_spec.rb index 2ab891fee7..1fbeafd373 100644 --- a/spec/system/donation_system_spec.rb +++ b/spec/system/donation_system_spec.rb @@ -265,7 +265,8 @@ fill_in "product_drive_participant_business_name", with: "businesstest" fill_in "product_drive_participant_contact_name", with: "test" fill_in "product_drive_participant_email", with: "123@mail.ru" - click_on "diaper-drive-participant-submit" + fill_in "product_drive_participant_comment", with: "test comment" + click_on "product-drive-participant-submit" select "businesstest", from: "donation_product_drive_participant_id" end diff --git a/spec/system/log_in_system_spec.rb b/spec/system/log_in_system_spec.rb index f15c5041be..5a666cd82d 100644 --- a/spec/system/log_in_system_spec.rb +++ b/spec/system/log_in_system_spec.rb @@ -10,9 +10,23 @@ end end + describe "User with no roles" do + before do + create(:user, :no_roles, email: "no_role_user@example.com") + end + + it "should not allow the user to log in" do + visit "/users/sign_in" + fill_in "user_email", with: "no_role_user@example.com" + fill_in "user_password", with: DEFAULT_USER_PASSWORD + find('input[name="commit"]').click + expect(page).to have_content("You need to sign in before continuing.") + end + end + describe "Deactivated user" do before do - create(:user, :deactivated, email: "deactivated@exmaple.com") + create(:user, :deactivated, email: "deactivated@example.com") end it "should not allow the user to log in" do diff --git a/spec/system/organization_system_spec.rb b/spec/system/organization_system_spec.rb index 6099954340..6316da2567 100644 --- a/spec/system/organization_system_spec.rb +++ b/spec/system/organization_system_spec.rb @@ -5,139 +5,11 @@ include ActionView::RecordIdentifier - context "while signed in as a normal user" do - before do - sign_in(user) - end - - it "can see summary details about the organization as a user" do - visit organization_path - end - - it "cannot see 'Make user' button for admins" do - visit organization_path - expect(page.find(".table.border")).to have_no_content "Make User" - end - end - context "while signed in as an organization admin" do - let!(:store) { create(:storage_location, organization: organization) } - let!(:ndbn_member) { create(:ndbn_member, ndbn_member_id: "50000", account_name: "Best Place") } before do sign_in(organization_admin) end - describe "Viewing the organization" do - it "can view organization details", :aggregate_failures do - organization.update!(one_step_partner_invite: true) - - visit organization_path - - expect(page.find("h1")).to have_text(organization.name) - expect(page).to have_link("Home", href: dashboard_path) - - expect(page).to have_content("Organization Info") - expect(page).to have_content("Contact Info") - expect(page).to have_content("Default email text") - expect(page).to have_content("Users") - expect(page).to have_content("Short Name") - expect(page).to have_content("URL") - expect(page).to have_content("Partner Profile Sections") - expect(page).to have_content("Custom Partner Invitation Message") - expect(page).to have_content("Child Based Requests?") - expect(page).to have_content("Individual Requests?") - expect(page).to have_content("Quantity Based Requests?") - expect(page).to have_content("Show Year-to-date values on distribution printout?") - expect(page).to have_content("Logo") - expect(page).to have_content("Use One step Partner invite and approve process?") - end - end - - describe "Editing the organization" do - before do - visit edit_organization_path - end - - it "is prompted with placeholder text and a more helpful error message to ensure correct URL format as a user" do - fill_in "Url", with: "www.diaperbase.com" - click_on "Save" - - fill_in "Url", with: "http://www.diaperbase.com" - click_on "Save" - expect(page.find(".alert")).to have_content "pdated" - end - - it "can set a reminder and a deadline day" do - fill_in "organization_reminder_day", with: 12 - fill_in "organization_deadline_day", with: 16 - click_on "Save" - expect(page.find(".alert")).to have_content "Updated" - end - - it 'can select if the org repackages essentials' do - choose('organization[repackage_essentials]', option: true) - - click_on "Save" - expect(page).to have_content("Yes") - end - - it 'can select if the org distributes essentials monthly' do - choose('organization[distribute_monthly]', option: true) - - click_on "Save" - expect(page).to have_content("Yes") - end - - it 'can select if the org shows year-to-date values on the distribution printout' do - choose('organization[ytd_on_distribution_printout]', option: false) - - click_on "Save" - expect(page).to have_content("No") - end - - it 'can set a default storage location on the organization' do - select(store.name, from: 'Default Storage Location') - - click_on "Save" - expect(page).to have_content(store.name) - end - - it 'can set the NDBN Member ID' do - select(ndbn_member.full_name) - - click_on "Save" - expect(page).to have_content(ndbn_member.full_name) - end - - it 'can select and deselect Required Partner Fields' do - # select first option in from Required Partner Fields - select('Media Information', from: 'organization_partner_form_fields', visible: false) - click_on "Save" - expect(page).to have_content('Media Information') - expect(organization.reload.partner_form_fields).to eq(['media_information']) - # deselect previously chosen Required Partner Field - click_on "Edit" - unselect('Media Information', from: 'organization_partner_form_fields', visible: false) - click_on "Save" - expect(page).to_not have_content('Media Information') - expect(organization.reload.partner_form_fields).to eq([]) - end - - it "can disable if the org does NOT use single step invite and approve partner process" do - choose("organization[one_step_partner_invite]", option: false) - - click_on "Save" - expect(page).to have_content("No") - end - - it "can enable if the org uses single step invite and approve partner process" do - choose("organization[one_step_partner_invite]", option: true) - - click_on "Save" - expect(page).to have_content("Yes") - end - end - it "can add a new user to an organization" do allow(User).to receive(:invite!).and_return(true) visit organization_path @@ -149,40 +21,16 @@ expect(page).to have_content("invited to organization") end - it "can re-invite a user to an organization after 7 days" do - create(:user, name: "Ye Olde Invited User", invitation_sent_at: Time.current - 7.days) - visit organization_path - expect(page).to have_xpath("//i[@alt='Re-send invitation']") - end - - it "can see 'Make user' button for admins" do - create(:organization_admin) - visit organization_path - expect(page.find(".table.border")).to have_content "Make User" - end - - it "can deactivate a user in the organization" do - user = create(:user, name: "User to be deactivated") - visit organization_path - accept_confirm do - click_button dom_id(user, "dropdownMenu") - click_link dom_id(user) - end - - expect(page).to have_content("User has been deactivated") - expect(user.reload.discarded_at).to be_present - end - - it "can re-activate a user in the organization" do - user = create(:user, :deactivated) + it "can remove a user from the organization" do + user = create(:user, name: "User to be deactivated", organization: organization) visit organization_path accept_confirm do click_button dom_id(user, "dropdownMenu") - click_link dom_id(user) + click_link "Remove User" end - expect(page).to have_content("User has been reactivated") - expect(user.reload.discarded_at).to be_nil + expect(page).to have_content("User has been removed!") + expect(user.has_role?(Role::ORG_USER)).to be false end end end diff --git a/spec/system/partners/children_system_spec.rb b/spec/system/partners/children_system_spec.rb new file mode 100644 index 0000000000..25b2531138 --- /dev/null +++ b/spec/system/partners/children_system_spec.rb @@ -0,0 +1,36 @@ +RSpec.describe "Creating a parner child", type: :system, js: true do + let(:organization) { create(:organization) } + let(:partner) { FactoryBot.create(:partner, organization: organization) } + let(:partner_user) { partner.primary_user } + let(:family) { create(:partners_family, guardian_first_name: "Main", guardian_last_name: "Family", partner: partner) } + + before do + partner.update(status: :approved) + login_as(partner_user) + create(:item, name: "Item 1", organization: organization) + create(:item, name: "Item 2", organization: organization) + end + + describe "creating a child for a family" do + it "creates a child with correct info" do + visit new_partners_child_path(family_id: family.id) + fill_in "First Name", with: "Child First Name" + fill_in "Last Name", with: "Child Last Name" + select "Other", from: "Race" + fill_in "Agency Child ID", with: "01234" + fill_in "Comments", with: "Some Comment" + + select2(page, "requestable-items-container", "Item 2") + select2(page, "requestable-items-container", "Item 1") + + click_button "Create Child" + + expect(page).to have_text("Child was successfully created.") + expect(page).to have_text("Child First Name") + expect(page).to have_text("Child Last Name") + expect(page).to have_text("01234") + expect(page).to have_text("Some Comment") + expect(page).to have_text("Item 1, Item 2") + end + end +end diff --git a/spec/system/partners/family_requests_system_spec.rb b/spec/system/partners/family_requests_system_spec.rb index e1030f284a..08b416506c 100644 --- a/spec/system/partners/family_requests_system_spec.rb +++ b/spec/system/partners/family_requests_system_spec.rb @@ -1,8 +1,8 @@ RSpec.describe "Family requests", type: :system, js: true do let(:partner) { FactoryBot.create(:partner) } let(:partner_user) { partner.primary_user } - let(:family) { create(:partners_family, guardian_last_name: "Morales", partner: partner) } - let(:other_family) { create(:partners_family, partner: partner) } + let(:family) { create(:partners_family, guardian_first_name: "Main", guardian_last_name: "Family", partner: partner) } + let(:other_family) { create(:partners_family, partner: partner, guardian_first_name: "Other", guardian_last_name: "Family") } before do partner.update(status: :approved) @@ -10,26 +10,56 @@ end describe "for children with different items, from different families" do - let(:item_id) { Item.all.sample.id } - let!(:children) do - [ - create(:partners_child, family: family), - create(:partners_child, family: family, item_needed_diaperid: item_id), - create(:partners_child, family: family, item_needed_diaperid: item_id), - create(:partners_child, family: other_family, item_needed_diaperid: item_id), - create(:partners_child, family: other_family) - ] + let(:item1) { create(:item, name: "Item 1") } + let(:item2) { create(:item, name: "Item 2") } + let(:item3) { create(:item, name: "Item 3") } + + before do + create(:partners_child, family: family, first_name: "Main", last_name: "No Items", requested_item_ids: nil) + create(:partners_child, family: family, first_name: "Main", last_name: "Items1", requested_item_ids: [item1.id, item2.id]) + create(:partners_child, family: family, first_name: "Main", last_name: "Items2", requested_item_ids: [item2.id, item3.id]) + create(:partners_child, first_name: "Other", last_name: "Items", family: other_family, requested_item_ids: [item1.id, item2.id]) + create(:partners_child, first_name: "Other", last_name: "No Items", family: other_family, requested_item_ids: nil) end scenario "it creates family requests" do visit partners_requests_path find('a[aria-label="Create a request for a child or family"]').click + + within("table tbody tr", text: "Main Items1") do |row| + expect(row).to have_css("td", text: "Main Family") + expect(row).to have_css("td", text: "Main Items1") + expect(row).to have_css("td", text: "Item 1, Item 2") + end + + within("table tbody tr", text: "Main Items2") do |row| + expect(row).to have_css("td", text: "Main Family") + expect(row).to have_css("td", text: "Main Items2") + expect(row).to have_css("td", text: "Item 2, Item 3") + end + + within("table tbody tr", text: "Main No Items") do |row| + expect(row).to have_css("td", text: "Main Family") + expect(row).to have_css("td", text: "Main No Items") + expect(row).to have_css("td", text: "N/A") + end + + within("table tbody tr", text: "Other Items") do |row| + expect(row).to have_css("td", text: "Other Family") + expect(row).to have_css("td", text: "Other Items") + expect(row).to have_css("td", text: "Item 1, Item 2") + end + + within("table tbody tr", text: "Other No Items") do |row| + expect(row).to have_css("td", text: "Other Family") + expect(row).to have_css("td", text: "Other No Items") + expect(row).to have_css("td", text: "N/A") + end + find('input[type="submit"]').click expect(page).to have_text("Request Details") click_link "Your Previous Requests" expect(page).to have_text("Request History") - expect(Partners::ChildItemRequest.pluck(:child_id)).to match_array(children.pluck(:id)) - expect(Partners::ItemRequest.pluck(:item_id)).to match_array(children.pluck(:item_needed_diaperid).uniq) end end @@ -53,7 +83,7 @@ visit partners_requests_path find('a[aria-label="Create a request for a child or family"]').click expect(page).to have_css("table tbody tr", count: 3) - fill_in "Search By Guardian Name", with: "Morales" + fill_in "Search By Guardian Name", with: "Main Family" expect(page).to have_text("Zeno") expect(page).to have_text("Arthur") expect(page).to_not have_text("Louis") diff --git a/spec/system/product_drive_participant_system_spec.rb b/spec/system/product_drive_participant_system_spec.rb index 92e245ad7e..09f6a2ae23 100644 --- a/spec/system/product_drive_participant_system_spec.rb +++ b/spec/system/product_drive_participant_system_spec.rb @@ -25,19 +25,19 @@ expect(page.find(:xpath, "//table/tbody/tr[3]/td[1]")).to have_content(@third.business_name) end - context "When the s have donations associated with them already" do + context "When the participants have donations associated with them already" do before(:each) do create(:donation, :with_items, created_at: 1.day.ago, item_quantity: 10, source: Donation::SOURCES[:product_drive], product_drive: product_drive, product_drive_participant: product_drive_participant) create(:donation, :with_items, created_at: 1.week.ago, item_quantity: 15, source: Donation::SOURCES[:product_drive], product_drive: product_drive, product_drive_participant: product_drive_participant) end - it "shows existing Participants in the #index with some summary stats" do + it "shows existing participants in the #index with some summary stats" do visit subject expect(page).to have_xpath("//table/tbody/tr/td", text: product_drive_participant.business_name) expect(page).to have_xpath("//table/tbody/tr/td", text: "25") end - it "allows single Participants to show semi-detailed stats about donations from that product drive" do + it "allows single participants to show semi-detailed stats about donations from that product drive" do visit product_drive_participant_path(product_drive_participant) expect(page).to have_xpath("//table/tbody/tr", count: 3) end @@ -47,12 +47,13 @@ context "when creating new product drive participants" do subject { new_product_drive_participant_path } - it "allows a user to create a new product drive instance" do + it "allows a user to create a new product drive participant" do visit subject product_drive_participant_traits = attributes_for(:product_drive_participant) fill_in "Contact Name", with: product_drive_participant_traits[:contact_name] fill_in "Business Name", with: product_drive_participant_traits[:business_name] fill_in "Phone", with: product_drive_participant_traits[:phone] + fill_in "Comment", with: product_drive_participant_traits[:comment] expect do click_button "Save" @@ -61,7 +62,7 @@ expect(page.find(".alert")).to have_content "added" end - it "does not allow a user to add a new product drive instance with empty attributes" do + it "does not allow a user to add a new product drive participant with empty attributes" do visit subject click_button "Save" @@ -72,16 +73,21 @@ context "when editing an existing product drive participant" do subject { edit_product_drive_participant_path(product_drive_participant.id) } - it "allows a user to update the contact info for a product drive participant" do + it "allows a user to update the contact info and comments for a product drive participant" do new_email = "foo@bar.com" + new_comment = "test comment" visit subject fill_in "Phone", with: "" fill_in "E-mail", with: new_email + fill_in "Comment", with: new_comment click_button "Save" expect(page.find(".alert")).to have_content "updated" expect(page).to have_content(product_drive_participant.contact_name) expect(page).to have_content(new_email) + + visit product_drive_participant_path(product_drive_participant) + expect(page).to have_content(new_comment) end it "does not allow a user to update a product drive participant with empty attributes" do diff --git a/spec/system/sign_in_system_spec.rb b/spec/system/sign_in_system_spec.rb index f76b9ed210..f6f64b9c95 100644 --- a/spec/system/sign_in_system_spec.rb +++ b/spec/system/sign_in_system_spec.rb @@ -31,7 +31,7 @@ context 'when a partner user logs in' do it 'redirects to the partner page' do partner = create(:partner) - partner_user = create(:partners_user, partner: partner) + partner_user = create(:partner_user, partner: partner) fill_in "Email", with: partner_user.email fill_in "user_password", with: partner_user.password click_button "Log in" diff --git a/spec/system/transfer_system_spec.rb b/spec/system/transfer_system_spec.rb index e1a78a95ae..42069c5881 100644 --- a/spec/system/transfer_system_spec.rb +++ b/spec/system/transfer_system_spec.rb @@ -107,6 +107,14 @@ def create_transfer(amount, from_name, to_name) expect(page).to have_no_text 'Inactive R Us' end + # 4438 - Bug Fix + it "add item button should be activated when from storage location is selected after rendering again on failure" do + from_storage_location = create(:storage_location, :with_items, item: item, name: "From me", organization: organization) + create_transfer('', '', '') + select from_storage_location.name, from: "From storage location" + expect(page).not_to have_css("#__add_line_item.disabled") + end + context "when there's insufficient inventory at the origin to cover the move" do let!(:from_storage_location) { create(:storage_location, :with_items, item: item, item_quantity: 10, name: "From me", organization: organization) } let!(:to_storage_location) { create(:storage_location, :with_items, name: "To me", organization: organization) }