diff --git a/.devcontainer/.env.codespaces b/.devcontainer/.env.codespaces new file mode 100644 index 0000000000..fbbb31c4b7 --- /dev/null +++ b/.devcontainer/.env.codespaces @@ -0,0 +1,14 @@ +# This is the .env that is used by the codespaces environment. +# See the .devcontainer/devcontainer.json file for how this is configured. +PG_USERNAME=postgres +PG_PASSWORD=postgres +DELAYED_JOB_USERNAME='admin' +DELAYED_JOB_PASSWORD='password' +FLIPPER_USERNAME="admin" +FLIPPER_PASSWORD="password" +RECAPTCHA_SITE_KEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI +RECAPTCHA_PRIVATE_KEY=6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe + +# [OPTIONAL] - Used to fetch copies of production DB +AZURE_STORAGE_ACCOUNT_NAME= +AZURE_STORAGE_ACCESS_KEY= diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000000..396a22d452 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,5 @@ +# Installs Ruby 3.2.2. When human-essentials moves to a newer version of ruby, +# 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 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..87c12cd44b --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,20 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/postgres +{ + "dockerComposeFile": "docker-compose.yml", + "forwardPorts": [3000, 5432], + "workspaceFolder": "/workspaces/human-essentials", + "service": "app", + "customizations": { + "vscode": { + "extensions": ["Shopify.ruby-extensions-pack"], + "settings": { + "rubyLsp.rubyVersionManager": { + "identifier": "rvm" + } + } + } + }, + + "postCreateCommand": ".devcontainer/post-create.sh" + } diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000000..b04277eb83 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,32 @@ +version: '3.8' + +services: + app: + build: + context: .. + dockerfile: .devcontainer/Dockerfile + + volumes: + - ../..:/workspaces:cached + + # Overrides default command so things don't shut down after the process ends. + command: sleep infinity + + # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. + network_mode: service:postgres + + postgres: + image: postgres:latest + restart: always + volumes: + - postgres-data:/var/lib/postgresql/data + environment: + POSTGRES_USER: postgres + POSTGRES_DB: postgres + POSTGRES_PASSWORD: postgres + # Note that these ports are ignored by the devcontainer. + # Instead, the ports are specified in .devcontainer/devcontainer.json. + # ports: + # - "5432:5432" +volumes: + postgres-data: diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100755 index 0000000000..69debb7c8b --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,14 @@ +RUBY_VERSION="$(cat .ruby-version | tr -d '\n')" + +# copy the file only if it doesn't already exist +cp -n .devcontainer/.env.codespaces .env + +# 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. +if [ "$RUBY_VERSION" != "3.2.2" ]; then + rvm install $RUBY_VERSION + rvm use $RUBY_VERSION + echo "Ruby $RUBY_VERSION installed" +fi + +bin/setup diff --git a/.github/workflows/brakeman.yml b/.github/workflows/brakeman.yml index b1f3d03c88..c564ab3ad6 100644 --- a/.github/workflows/brakeman.yml +++ b/.github/workflows/brakeman.yml @@ -2,11 +2,11 @@ name: brakeman on: push: - branches: - - main + paths-ignore: + - 'doc/**' + - '*.md' + - 'bin/*' pull_request: - branches: - - main paths-ignore: - 'doc/**' - '*.md' diff --git a/.github/workflows/factory-bot-lint.yml b/.github/workflows/factory-bot-lint.yml index f31604b2c4..21811ff1ab 100644 --- a/.github/workflows/factory-bot-lint.yml +++ b/.github/workflows/factory-bot-lint.yml @@ -2,11 +2,11 @@ name: factory bot lint on: push: - branches: - - main + paths-ignore: + - 'doc/**' + - '*.md' + - 'bin/*' pull_request: - branches: - - main paths-ignore: - 'doc/**' - '*.md' diff --git a/.github/workflows/plantuml.yml b/.github/workflows/plantuml.yml index 95f1ea6eb1..848b6c55f2 100644 --- a/.github/workflows/plantuml.yml +++ b/.github/workflows/plantuml.yml @@ -1,5 +1,12 @@ name: Generate PlantUML on: push + paths: + - 'doc/**' + - '*.md' + pull_request: + - 'doc/**' + - '*.md' + jobs: generate_plantuml: runs-on: ubuntu-latest diff --git a/.github/workflows/rspec-events.yml b/.github/workflows/rspec-events.yml index 0dadf9c726..4d490859a7 100644 --- a/.github/workflows/rspec-events.yml +++ b/.github/workflows/rspec-events.yml @@ -2,11 +2,11 @@ name: rspec-events on: push: - branches: - - main + paths-ignore: + - "doc/**" + - "*.md" + - "bin/*" pull_request: - branches: - - main paths-ignore: - "doc/**" - "*.md" @@ -87,4 +87,6 @@ jobs: uses: actions/upload-artifact@v4 with: name: failed-browser-tests - path: tmp/screenshots + path: | + tmp/screenshots + tmp/capybara diff --git a/.github/workflows/rspec-system-events.yml b/.github/workflows/rspec-system-events.yml index 23e31f1fb0..a8ac952e31 100644 --- a/.github/workflows/rspec-system-events.yml +++ b/.github/workflows/rspec-system-events.yml @@ -2,11 +2,11 @@ name: rspec-system-events on: push: - branches: - - main + paths-ignore: + - "doc/**" + - "*.md" + - "bin/*" pull_request: - branches: - - main paths-ignore: - "doc/**" - "*.md" diff --git a/.github/workflows/rspec-system.yml b/.github/workflows/rspec-system.yml index d9b7c68e76..2402b60d87 100644 --- a/.github/workflows/rspec-system.yml +++ b/.github/workflows/rspec-system.yml @@ -2,11 +2,11 @@ name: rspec-system on: push: - branches: - - main + paths-ignore: + - "doc/**" + - "*.md" + - "bin/*" pull_request: - branches: - - main paths-ignore: - "doc/**" - "*.md" diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index a4bd758938..177272d45d 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -2,11 +2,11 @@ name: rspec on: push: - branches: - - main + paths-ignore: + - "doc/**" + - "*.md" + - "bin/*" pull_request: - branches: - - main paths-ignore: - "doc/**" - "*.md" diff --git a/.github/workflows/ruby_lint.yml b/.github/workflows/ruby_lint.yml index f85057735a..16d3548d95 100644 --- a/.github/workflows/ruby_lint.yml +++ b/.github/workflows/ruby_lint.yml @@ -2,15 +2,11 @@ name: rubocop lint on: push: - branches: - - main paths-ignore: - "doc/**" - "*.md" - "bin/*" pull_request: - branches: - - main paths-ignore: - "doc/**" - "*.md" diff --git a/Gemfile b/Gemfile index 2c85eae330..94746d4be3 100644 --- a/Gemfile +++ b/Gemfile @@ -103,7 +103,7 @@ 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.9.0" +gem "mini_racer", "~> 0.12.0" gem "nokogiri", ">= 1.10.4" gem "image_processing" gem "sprockets", "~> 4.2.1" @@ -152,9 +152,9 @@ group :development, :test do gem "rubocop" # Rails add-on for static analysis. gem 'rubocop-performance' - gem "rubocop-rails", "~> 2.24.1" + gem "rubocop-rails", "~> 2.25.0" # Default rules for Rubocop. - gem "standard", "~> 1.35" + gem "standard", "~> 1.36" # Erb linter. gem "erb_lint" end diff --git a/Gemfile.lock b/Gemfile.lock index 817a0eee4a..ed4f64f9c5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -132,12 +132,13 @@ GEM coderay (1.1.3) concurrent-ruby (1.2.3) connection_pool (2.4.1) - coverband (6.1.0) + coverband (6.1.2) redis (>= 3.0) crack (1.0.0) bigdecimal rexml crass (1.0.6) + csv (3.3.0) cuprite (0.15) capybara (~> 3.0) ferrum (~> 0.14.0) @@ -170,9 +171,9 @@ GEM discard (1.3.0) activerecord (>= 4.2, < 8) docile (1.4.0) - dotenv (3.1.0) - dotenv-rails (3.1.0) - dotenv (= 3.1.0) + dotenv (3.1.2) + dotenv-rails (3.1.2) + dotenv (= 3.1.2) railties (>= 6.1) dry-core (1.0.1) concurrent-ruby (~> 1.0) @@ -255,7 +256,9 @@ GEM sanitize (< 7) foreman (0.88.1) formatador (1.1.0) - geocoder (1.8.2) + geocoder (1.8.3) + base64 (>= 0.1.0) + csv (>= 3.0.0) globalid (1.2.1) activesupport (>= 6.1) groupdate (6.4.0) @@ -276,10 +279,11 @@ GEM rspec (>= 2.99.0, < 4.0) hashdiff (1.1.0) hashie (5.0.0) - httparty (0.21.0) + httparty (0.22.0) + csv mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) - i18n (1.14.4) + i18n (1.14.5) concurrent-ruby (~> 1.0) icalendar (2.10.1) ice_cube (~> 0.16) @@ -292,10 +296,10 @@ GEM actionpack (>= 6.0.0) activesupport (>= 6.0.0) railties (>= 6.0.0) - jbuilder (2.11.5) + jbuilder (2.12.0) actionview (>= 5.0.0) activesupport (>= 5.0.0) - json (2.7.1) + json (2.7.2) jwt (2.8.1) base64 kaminari (1.2.2) @@ -310,7 +314,7 @@ GEM activerecord kaminari-core (= 1.2.2) kaminari-core (1.2.2) - knapsack_pro (7.0.1) + knapsack_pro (7.2.0) rake language_server-protocol (3.17.0.3) launchy (3.0.0) @@ -318,9 +322,9 @@ GEM childprocess (~> 5.0) letter_opener (1.10.0) launchy (>= 2.2, < 4) - libv8-node (18.19.0.0-arm64-darwin) - libv8-node (18.19.0.0-x86_64-darwin) - libv8-node (18.19.0.0-x86_64-linux) + 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) @@ -346,12 +350,12 @@ GEM net-smtp marcel (1.0.2) matrix (0.4.2) - method_source (1.0.0) + method_source (1.1.0) mini_magick (4.11.0) mini_mime (1.1.5) - mini_racer (0.9.0) - libv8-node (~> 18.19.0.0) - minitest (5.22.3) + mini_racer (0.12.0) + libv8-node (~> 21.7.2.0) + minitest (5.23.0) monetize (1.12.0) money (~> 6.12) money (6.16.0) @@ -379,11 +383,11 @@ GEM net-protocol newrelic_rpm (9.9.0) nio4r (2.7.0) - nokogiri (1.16.4-arm64-darwin) + nokogiri (1.16.5-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.4-x86_64-darwin) + nokogiri (1.16.5-x86_64-darwin) racc (~> 1.4) - nokogiri (1.16.4-x86_64-linux) + nokogiri (1.16.5-x86_64-linux) racc (~> 1.4) notiffany (0.1.3) nenv (~> 0.1) @@ -407,7 +411,7 @@ GEM omniauth-oauth2 (1.8.0) oauth2 (>= 1.4, < 3) omniauth (~> 2.0) - omniauth-rails_csrf_protection (1.0.1) + omniauth-rails_csrf_protection (1.0.2) actionpack (>= 4.2) omniauth (~> 2.0) orderly (0.1.1) @@ -418,7 +422,7 @@ GEM activerecord (>= 6.1) request_store (~> 1.4) parallel (1.24.0) - parser (3.3.0.5) + parser (3.3.1.0) ast (~> 2.4.1) racc pdf-core (0.9.0) @@ -507,13 +511,14 @@ GEM redis-client (>= 0.22.0) redis-client (0.22.1) connection_pool - regexp_parser (2.9.0) + regexp_parser (2.9.2) request_store (1.5.1) rack (>= 1.4) responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) - rexml (3.2.6) + rexml (3.2.8) + strscan (>= 3.0.9) rolify (6.0.1) rouge (4.1.2) rspec (3.13.0) @@ -537,7 +542,7 @@ GEM rspec-mocks (~> 3.13) rspec-support (~> 3.13) rspec-support (3.13.1) - rubocop (1.62.1) + rubocop (1.63.5) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -548,12 +553,12 @@ GEM rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.31.2) - parser (>= 3.3.0.4) - rubocop-performance (1.20.2) + rubocop-ast (1.31.3) + parser (>= 3.3.1.0) + rubocop-performance (1.21.0) rubocop (>= 1.48.1, < 2.0) - rubocop-ast (>= 1.30.0, < 2.0) - rubocop-rails (2.24.1) + rubocop-ast (>= 1.31.1, < 2.0) + rubocop-rails (2.25.0) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) @@ -606,22 +611,23 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - standard (1.35.1) + standard (1.36.0) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) - rubocop (~> 1.62.0) + rubocop (~> 1.63.0) standard-custom (~> 1.0.0) - standard-performance (~> 1.3) + standard-performance (~> 1.4) standard-custom (1.0.2) lint_roller (~> 1.0) rubocop (~> 1.50) - standard-performance (1.3.1) + standard-performance (1.4.0) lint_roller (~> 1.1) - rubocop-performance (~> 1.20.2) + rubocop-performance (~> 1.21.0) stimulus-rails (1.3.3) railties (>= 6.0.0) strong_migrations (1.8.0) activerecord (>= 5.2) + strscan (3.1.0) terser (1.2.2) execjs (>= 0.3.0, < 3) thor (1.3.1) @@ -715,7 +721,7 @@ DEPENDENCIES lograge magic_test matrix - mini_racer (~> 0.9.0) + mini_racer (~> 0.12.0) money-rails newrelic_rpm nokogiri (>= 1.10.4) @@ -740,13 +746,13 @@ DEPENDENCIES rspec-rails (~> 6.1.2) rubocop rubocop-performance - rubocop-rails (~> 2.24.1) + rubocop-rails (~> 2.25.0) sass-rails shoulda-matchers (~> 6.2) simple_form simplecov sprockets (~> 4.2.1) - standard (~> 1.35) + standard (~> 1.36) stimulus-rails strong_migrations (= 1.8.0) terser @@ -755,4 +761,4 @@ DEPENDENCIES webmock (~> 3.23) BUNDLED WITH - 2.5.7 + 2.5.10 diff --git a/README.md b/README.md index 19dc1c3636..8153066137 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Thanks for checking us out! If you're new here, here are some things you should ### Join us on slack 💬 You can sign up [here](https://join.slack.com/t/rubyforgood/shared_invite/zt-21pyz2ab8-H6JgQfGGI0Ab6MfNOZRIQA) and find us in #human-essentials. Many helpful members are available to answer your questions. Just ask, and someone will be there to help you! -## Getting Started 🛠️ +## Getting Started (Local Environment) 🛠️ 1. Install Ruby - Install the version specified in [`.ruby-version`](.ruby-version). @@ -124,6 +124,15 @@ You can sign up [here](https://join.slack.com/t/rubyforgood/shared_invite/zt-21p ``` +## Getting Started (Codespaces - 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 +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 +4. Login as a sample user with these default credentials (which also work for [staging](https://staging.humanessentials.app/)): + ## Troubleshooting 👷🏼‍♀️ Please let us know by opening up an issue! We have many new contributors come through and it is likely what you experienced will happen to them as well. @@ -411,4 +420,3 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! - diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index bf33711572..e80333a9ee 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -37,31 +37,10 @@ def current_role return nil unless current_user @role = Role.find_by(id: session[:current_role]) || UsersRole.current_role_for(current_user) - session[:current_role] = @role&.id @role end - def organization_url_options(options = {}) - options.merge(organization_name: current_organization.to_param) - end - helper_method :organization_url_options - - # override Rails' default_url_options to ensure organization_name is added to - # each URL generated - def default_url_options(options = {}) - # Early return if the request is not authenticated and no - # current_user is defined - return options if current_user.blank? || current_role.blank? || current_role.name == Role::SUPER_ADMIN.to_s - - if current_organization.present? && !options.key?(:organization_name) - options[:organization_name] = current_organization.to_param - elsif current_role.name == Role::ORG_ADMIN.to_s - options[:organization_name] = current_user.organization.to_param - end - options - end - def dashboard_path_from_current_role return root_path if current_role.blank? @@ -70,7 +49,7 @@ def dashboard_path_from_current_role elsif current_role.name == Role::PARTNER.to_s partners_dashboard_path elsif current_user.organization - dashboard_path(current_user.organization) + dashboard_path else "/403" end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 45454f4ba9..c1af5332b2 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -3,29 +3,12 @@ class DashboardController < ApplicationController respond_to :html, :js def index - setup_date_range_picker - - @donations = current_organization.donations.during(helpers.selected_range) - @recent_donations = @donations.recent - @purchases = current_organization.purchases.during(helpers.selected_range) - @recent_purchases = @purchases.recent.includes(:vendor) - - distributions = current_organization.distributions.includes(:partner).during(helpers.selected_range) - @recent_distributions = distributions.recent - - @itemized_donation_data = DonationItemizedBreakdownService.new(organization: current_organization, donation_ids: @donations.pluck(:id)).fetch - @itemized_distribution_data = DistributionItemizedBreakdownService.new(organization: current_organization, distribution_ids: distributions.pluck(:id)).fetch - - @total_inventory = current_organization.total_inventory - @org_stats = OrganizationStats.new(current_organization) + @total_inventory = current_organization.total_inventory + @partners_awaiting_review = current_organization.partners.awaiting_review + @outstanding_requests = current_organization.ordered_requests.where(status: %i[pending started]).order(:created_at) - # calling .recent on recent donations by manufacturers will only count the last 3 donations - # which may not make sense when calculating total count using a date range - @recent_donations_from_manufacturers = current_organization.donations.during(helpers.selected_range).by_source(:manufacturer) - @top_manufacturers = current_organization.manufacturers.by_donation_count - - @distribution_data = helpers.received_distributed_data(helpers.selected_range) + @low_inventory_report = LowInventoryQuery.call(current_organization) # passing nil here filters the announcements that didn't come from an organization @broadcast_announcements = BroadcastAnnouncement.filter_announcements(nil) diff --git a/app/controllers/item_categories_controller.rb b/app/controllers/item_categories_controller.rb index a131458ce1..3d291e76ab 100644 --- a/app/controllers/item_categories_controller.rb +++ b/app/controllers/item_categories_controller.rb @@ -8,7 +8,7 @@ def create @item_category.assign_attributes(item_category_params) if @item_category.save - redirect_to items_path(organization: current_organization) + redirect_to items_path else render :new end diff --git a/app/controllers/organizations_controller.rb b/app/controllers/organizations_controller.rb index 006a318a4b..8196e0fd4e 100644 --- a/app/controllers/organizations_controller.rb +++ b/app/controllers/organizations_controller.rb @@ -16,7 +16,7 @@ def update @organization = current_organization if OrganizationUpdateService.update(@organization, organization_params) - redirect_to organization_path(@organization), notice: "Updated your organization!" + redirect_to organization_path, notice: "Updated your organization!" else flash[:error] = @organization.errors.full_messages.join("\n") render :edit @@ -48,7 +48,7 @@ def promote_to_org_admin resource_id: current_organization.id) redirect_to user_update_redirect_path, notice: "User has been promoted!" rescue => e - redirect_back(fallback_location: organization_path(current_organization), alert: e.message) + redirect_back(fallback_location: organization_path, alert: e.message) end end @@ -61,7 +61,7 @@ def demote_to_user resource_id: current_organization.id) redirect_to user_update_redirect_path, notice: notice rescue => e - redirect_back(fallback_location: organization_path(current_organization), alert: e.message) + redirect_back(fallback_location: organization_path, alert: e.message) end end @@ -98,6 +98,7 @@ def organization_params :ndbn_member_id, :enable_child_based_requests, :enable_individual_requests, :enable_quantity_based_requests, :ytd_on_distribution_printout, :one_step_partner_invite, + :hide_value_columns_on_receipt, :hide_package_column_on_receipt, partner_form_fields: [] ) end diff --git a/app/controllers/partners/family_requests_controller.rb b/app/controllers/partners/family_requests_controller.rb index ff91b4b9bc..efa0f45eb3 100644 --- a/app/controllers/partners/family_requests_controller.rb +++ b/app/controllers/partners/family_requests_controller.rb @@ -29,8 +29,8 @@ def create 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 - create_service = Partners::FamilyRequestCreateService.new( + request_type: "child", partner_user_id: current_user.id, family_requests_attributes: family_requests_attributes, for_families: true diff --git a/app/controllers/partners/individuals_requests_controller.rb b/app/controllers/partners/individuals_requests_controller.rb index dff8560914..015d88035e 100644 --- a/app/controllers/partners/individuals_requests_controller.rb +++ b/app/controllers/partners/individuals_requests_controller.rb @@ -10,6 +10,7 @@ def new def create create_service = Partners::FamilyRequestCreateService.new( + request_type: "individual", partner_user_id: current_user.id, comments: individuals_request_params[:comments], family_requests_attributes: individuals_request_params[:items_attributes]&.values diff --git a/app/controllers/partners/requests_controller.rb b/app/controllers/partners/requests_controller.rb index eec3c8cf4a..0763d00e3f 100644 --- a/app/controllers/partners/requests_controller.rb +++ b/app/controllers/partners/requests_controller.rb @@ -20,6 +20,7 @@ def show def create create_service = Partners::RequestCreateService.new( + request_type: "quantity", partner_user_id: current_user.id, comments: partner_request_params[:comments], item_requests_attributes: partner_request_params[:item_requests_attributes]&.values || [] diff --git a/app/controllers/purchases_controller.rb b/app/controllers/purchases_controller.rb index 849cc61973..32e288c6b6 100644 --- a/app/controllers/purchases_controller.rb +++ b/app/controllers/purchases_controller.rb @@ -5,7 +5,7 @@ class PurchasesController < ApplicationController def index setup_date_range_picker @purchases = current_organization.purchases - .includes(:line_items, :storage_location) + .includes(:storage_location, :vendor, line_items: [:item]) .order(created_at: :desc) .class_filter(filter_params) .during(helpers.selected_range) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb new file mode 100644 index 0000000000..7c177789f9 --- /dev/null +++ b/app/controllers/reports_controller.rb @@ -0,0 +1,66 @@ +class ReportsController < ApplicationController + before_action :setup_date_range_picker + + def donations_summary + @donations = current_organization.donations.during(helpers.selected_range) + @recent_donations = @donations.recent + end + + def manufacturer_donations_summary + @recent_donations_from_manufacturers = current_organization.donations.during(helpers.selected_range).by_source(:manufacturer) + @top_manufacturers = current_organization.manufacturers.by_donation_count + @donations = current_organization.donations.during(helpers.selected_range) + @recent_donations = @donations.recent + end + + def purchases_summary + @purchases = current_organization.purchases.during(helpers.selected_range) + @recent_purchases = @purchases.recent.includes(:vendor) + end + + def product_drives_summary + @donations = current_organization.donations.during(helpers.selected_range) + @recent_donations = @donations.recent + end + + def itemized_donations + @donations = current_organization.donations.during(helpers.selected_range) + @itemized_donation_data = DonationItemizedBreakdownService.new(organization: current_organization, donation_ids: @donations.pluck(:id)).fetch + end + + def itemized_distributions + distributions = current_organization.distributions.includes(:partner).during(helpers.selected_range) + @itemized_distribution_data = DistributionItemizedBreakdownService.new(organization: current_organization, distribution_ids: distributions.pluck(:id)).fetch + end + + def distributions_summary + distributions = current_organization.distributions.includes(:partner).during(helpers.selected_range) + @recent_distributions = distributions.recent + end + + def activity_graph + @distribution_data = received_distributed_data(helpers.selected_range) + end + + private + + def total_purchased_unformatted(range = selected_range) + LineItem.active.where(itemizable: current_organization.purchases.during(range)).sum(:quantity) + end + + def total_distributed_unformatted(range = selected_range) + LineItem.active.where(itemizable: current_organization.distributions.during(range)).sum(:quantity) + end + + def total_received_donations_unformatted(range = selected_range) + LineItem.active.where(itemizable: current_organization.donations.during(range)).sum(:quantity) + end + + def received_distributed_data(range = selected_range) + { + "Received donations" => total_received_donations_unformatted(range), + "Purchased" => total_purchased_unformatted(range), + "Distributed" => total_distributed_unformatted(range) + } + end +end diff --git a/app/controllers/requests/cancelation_controller.rb b/app/controllers/requests/cancelation_controller.rb index 1602e728ce..b32311c029 100644 --- a/app/controllers/requests/cancelation_controller.rb +++ b/app/controllers/requests/cancelation_controller.rb @@ -15,7 +15,7 @@ def create else errors = svc.errors.full_messages.join(", ") flash[:error] = "Request #{params[:request_id]} could not be removed because #{errors}" - redirect_to new_request_cancelation_path(organization: @organization, request_id: params[:request_id]) + redirect_to new_request_cancelation_path(request_id: params[:request_id]) end end diff --git a/app/controllers/requests_controller.rb b/app/controllers/requests_controller.rb index d44f022e3d..1593b68bd7 100644 --- a/app/controllers/requests_controller.rb +++ b/app/controllers/requests_controller.rb @@ -13,10 +13,12 @@ def index @calculate_product_totals = RequestsTotalItemsService.new(requests: @requests).calculate @items = current_organization.items.alphabetized @partners = current_organization.partners.order(:name) - @statuses = Request.statuses.transform_keys(&:humanize) + @selected_partner = filter_params[:by_partner] @partner_users = User.where(id: @paginated_requests.pluck(:partner_user_id)) + @request_types = Request.request_types.transform_keys(&:humanize) + @selected_request_type = filter_params[:by_request_type] @selected_request_item = filter_params[:by_request_item_id] - @selected_partner = filter_params[:by_partner] + @statuses = Request.statuses.transform_keys(&:humanize) @selected_status = filter_params[:by_status] respond_to do |format| @@ -57,6 +59,6 @@ def load_items def filter_params return {} unless params.key?(:filters) - params.require(:filters).permit(:by_request_item_id, :by_partner, :by_status) + params.require(:filters).permit(:by_request_item_id, :by_partner, :by_status, :by_request_type) end end diff --git a/app/controllers/storage_locations_controller.rb b/app/controllers/storage_locations_controller.rb index 4cafad3bf3..d2a622c42b 100644 --- a/app/controllers/storage_locations_controller.rb +++ b/app/controllers/storage_locations_controller.rb @@ -99,6 +99,9 @@ def import_inventory flash[:notice] = "Inventory imported successfully!" redirect_back(fallback_location: storage_locations_path) end + rescue Errors::InventoryAlreadyHasItems => e + flash[:error] = e.message + redirect_back(fallback_location: storage_locations_path(organization_id: current_organization)) end def update diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index 8fa5c2c80c..a17d196660 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -17,7 +17,8 @@ def new # POST /resource/sign_in def create super - session[:current_role] = UsersRole.current_role_for(current_user)&.id + session[:current_role] ||= UsersRole.current_role_for(current_user)&.id + UsersRole.set_last_role_for(current_user, @role) end # DELETE /resource/sign_out diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 96959c6ba0..72cb6ed949 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -17,6 +17,7 @@ def switch_to_role end @role = role + UsersRole.set_last_role_for(current_user, role) session[:current_role] = params[:role_id] redirect_to dashboard_path_from_current_role end diff --git a/app/events/event_differ.rb b/app/events/event_differ.rb deleted file mode 100644 index 338d0f3c47..0000000000 --- a/app/events/event_differ.rb +++ /dev/null @@ -1,104 +0,0 @@ -module Types - include Dry.Types() -end - -module EventDiffer - # Used to indicate that a storage location exists in one source but not the other. - class LocationDiff < Dry::Struct - attribute :storage_location_id, Types::Integer - attribute :database, Types::Bool - attribute :aggregate, Types::Bool - - # @param options [Object] - # @return [Hash] - def as_json(options = nil) - super.merge(type: "location") - end - end - - # Used to indicate that the quantity of an item in one source doesn't match the other. - class ItemDiff < Dry::Struct - attribute :storage_location_id, Types::Integer - attribute :item_id, Types::Integer - attribute :database, Types::Integer - attribute :aggregate, Types::Integer - - # @param options [Object] - # @return [Hash] - def as_json(options = nil) - super.merge(type: "item") - end - end - - class << self - # @param locations [Array] - # @param inventory [EventTypes::Inventory] - # @return [Array] - def check_location_ids(locations, inventory) - db_ids = locations.map(&:id) - inventory_ids = inventory.storage_locations.keys - diffs = [] - (db_ids - inventory_ids).each do |id| - diffs.push(LocationDiff.new(storage_location_id: id, database: true, aggregate: false)) - end - (inventory_ids - db_ids).each do |id| - diffs.push(LocationDiff.new(storage_location_id: id, database: false, aggregate: true)) - end - diffs - end - - # @param inventory_loc [EventTypes::EventStorageLocation] - # @param db_loc [StorageLocation] - # @return [Array] - def check_items(inventory_loc, db_loc) - diffs = [] - diffs += check_item_ids(inventory_loc, db_loc) - db_loc.inventory_items.each do |db_item| - inventory_item = inventory_loc.items[db_item.item_id] - next if inventory_item.nil? - - if inventory_item.quantity != db_item.quantity - diffs.push(ItemDiff.new(item_id: db_item.item_id, - storage_location_id: db_loc.id, - database: db_item.quantity, - aggregate: inventory_item.quantity)) - end - end - diffs - end - - # @param inventory_loc [EventTypes::EventStorageLocation] - # @param db_loc [StorageLocation] - # @return [Array] - def check_item_ids(inventory_loc, db_loc) - inventory_ids = inventory_loc.items.keys - db_ids = db_loc.inventory_items.map(&:item_id) - diffs = [] - (db_ids - inventory_ids).each do |id| - item = db_loc.inventory_items.find { |f| f.item_id == id } - diffs.push(ItemDiff.new(item_id: id, storage_location_id: db_loc.id, database: item&.quantity, aggregate: 0)) - end - (inventory_ids - db_ids).each do |id| - item = inventory_loc.items[id] - diffs.push(ItemDiff.new(item_id: id, storage_location_id: db_loc.id, database: 0, aggregate: item.quantity)) - end - diffs - end - - # @param inventory [EventTypes::Inventory] - # @return [Array] - def check_difference(inventory) - diffs = [] - org = Organization.find(inventory.organization_id) - locations = org.storage_locations.to_a - diffs += check_location_ids(locations, inventory) - locations.each do |db_loc| - inventory_loc = inventory.storage_locations[db_loc.id] - next if inventory_loc.nil? - - diffs += check_items(inventory_loc, db_loc) - end - diffs - end - end -end diff --git a/app/events/inventory_aggregate.rb b/app/events/inventory_aggregate.rb index 278e7f22c6..2bc6204cb6 100644 --- a/app/events/inventory_aggregate.rb +++ b/app/events/inventory_aggregate.rb @@ -32,6 +32,11 @@ def inventory_for(organization_id, event_time: nil, validate: false) event_hash = {} events.group_by(&:group_id).each do |_, event_batch| last_grouped_event = event_batch.max_by(&:updated_at) + # don't do grouping for UpdateExistingEvents + if event_batch.any? { |e| e.is_a?(UpdateExistingEvent) } + handle(last_grouped_event, inventory, validate: validate) + next + end previous_event = event_hash[last_grouped_event.eventable] event_hash[last_grouped_event.eventable] = last_grouped_event handle(last_grouped_event, inventory, validate: validate, previous_event: previous_event) @@ -104,7 +109,8 @@ def handle_audit_event(payload, inventory) # diff previous event on DonationEvent, DistributionEvent, AdjustmentEvent, PurchaseEvent, TransferEvent, DistributionDestroyEvent, DonationDestroyEvent, - PurchaseDestroyEvent, TransferDestroyEvent do |event, inventory, validate: false, previous_event: nil| + PurchaseDestroyEvent, TransferDestroyEvent, + UpdateExistingEvent do |event, inventory, validate: false, previous_event: nil| handle_inventory_event(event.data, inventory, validate: validate, previous_event: previous_event) rescue InventoryError => e e.event = event diff --git a/app/events/update_existing_event.rb b/app/events/update_existing_event.rb new file mode 100644 index 0000000000..cdeb1430c3 --- /dev/null +++ b/app/events/update_existing_event.rb @@ -0,0 +1,58 @@ +class UpdateExistingEvent < Event + class << self + # @param line_items [Array] + # @param storage_location [StorageLocation] + # @param direction [Symbol] + # @return [Hash] + def item_quantities(line_items, storage_location, direction) + line_items.to_h do |line_item| + opts = (direction == :from) ? + {from: storage_location.id} : + {to: storage_location.id} + [line_item.item_id, EventTypes::EventLineItem.from_line_item(line_item, **opts)] + end + end + + # @param previous [Hash] + # @param current [Hash] + # @return [Array] + def diff(previous, current) + previous.each do |id, event_item| + previous[id] = if current[id] + event_item.new(quantity: current[id].quantity - event_item.quantity) + else + event_item.new(quantity: -event_item.quantity) + end + end + all_items = previous.values + (current.keys - previous.keys).each do |id| + all_items.push(current[id]) # it's been added + end + all_items + end + + # @param itemizable [Itemizable] + # @return [Symbol] + def direction(itemizable) + itemizable.is_a?(Distribution) ? :from : :to + end + + # @param itemizable [Itemizable] + def publish(itemizable, previous_line_items) + dir = direction(itemizable) + previous_items = item_quantities(previous_line_items, itemizable.storage_location, dir) + current_items = item_quantities(itemizable.line_items, itemizable.storage_location, dir) + diff_items = diff(previous_items, current_items) + + create( + eventable: itemizable, + group_id: "existing-#{itemizable.id}-#{SecureRandom.hex}", + organization_id: itemizable.organization_id, + event_time: Time.zone.now, + data: EventTypes::InventoryPayload.new( + items: diff_items + ) + ) + end + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index cb0a15ca7f..6f7075f680 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -14,12 +14,12 @@ def default_title_content end end - def active_class(name) - name.include?(controller_path) ? "active" : controller_path + def active_class(controller_action_names) + (controller_action_names.include?(params[:controller]) || controller_action_names.include?("#{params[:controller]}/#{params[:action]}")) ? 'active' : '' end - def menu_open?(name) - name.include?(controller_path) ? 'menu-open' : '' + def menu_open?(controller_action_names) + (controller_action_names.include?(params[:controller]) || controller_action_names.include?("#{params[:controller]}/#{params[:action]}")) ? 'menu-open' : '' end def can_administrate? diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb index 0fd66cff3b..4a81cb3f11 100644 --- a/app/helpers/dashboard_helper.rb +++ b/app/helpers/dashboard_helper.rb @@ -1,37 +1,9 @@ # Encapsulates methods used on the Dashboard that need some business logic module DashboardHelper - def received_distributed_data(range = selected_range) - { - "Received donations" => total_received_donations_unformatted(range), - "Purchased" => total_purchased_unformatted(range), - "Distributed" => total_distributed_unformatted(range) - } - end - def total_on_hand(total = nil) number_with_delimiter(total || "-1") end - def total_received_money_donations(range = selected_range) - current_organization.donations.during(range).sum { |d| d.money_raised || 0 } - end - - def total_received_money_donations_from_product_drives(range: selected_range) - current_organization.donations.by_source(:product_drive).during(range).sum { |d| d.money_raised || 0 } - end - - def total_received_donations(range = selected_range) - number_with_delimiter total_received_donations_unformatted(range) - end - - def total_received_from_product_drives(range = selected_range) - number_with_delimiter total_received_from_product_drives_unformatted(range) - end - - def total_purchased(range = selected_range) - number_with_delimiter total_purchased_unformatted(range) - end - def total_distributed(range = selected_range) number_with_delimiter total_distributed_unformatted(range) end @@ -46,18 +18,6 @@ def recently_added_user_display_text(user) private - def total_received_donations_unformatted(range = selected_range) - LineItem.active.where(itemizable: current_organization.donations.during(range)).sum(:quantity) - end - - def total_received_from_product_drives_unformatted(range = selected_range) - LineItem.active.where(itemizable: current_organization.donations.by_source(:product_drive).during(range)).sum(:quantity) - end - - def total_purchased_unformatted(range = selected_range) - LineItem.active.where(itemizable: current_organization.purchases.during(range)).sum(:quantity) - end - def total_distributed_unformatted(range = selected_range) LineItem.active.where(itemizable: current_organization.distributions.during(range)).sum(:quantity) end diff --git a/app/helpers/distribution_helper.rb b/app/helpers/distribution_helper.rb index 53b9d8801f..c1fc027ee0 100644 --- a/app/helpers/distribution_helper.rb +++ b/app/helpers/distribution_helper.rb @@ -16,7 +16,7 @@ def pickup_date def hashed_calendar_path crypt = ActiveSupport::MessageEncryptor.new(Rails.application.secret_key_base[0..31]) - distributions_calendar_url(hash: crypt.encrypt_and_sign(current_organization.id)) + calendar_distributions_url(hash: crypt.encrypt_and_sign(current_organization.id)) end def quantity_by_item_id(distribution, item_id) diff --git a/app/helpers/donations_helper.rb b/app/helpers/donations_helper.rb new file mode 100644 index 0000000000..ed07d514ea --- /dev/null +++ b/app/helpers/donations_helper.rb @@ -0,0 +1,28 @@ +# Encapsulates business logic related to displaying Donations +module DonationsHelper + def total_received_donations(range = selected_range) + number_with_delimiter total_received_donations_unformatted(range) + end + + def total_received_money_donations(range = selected_range) + current_organization.donations.during(range).sum { |d| d.money_raised || 0 } + end + + def total_received_money_donations_from_product_drives(range: selected_range) + current_organization.donations.by_source(:product_drive).during(range).sum { |d| d.money_raised || 0 } + end + + def total_received_from_product_drives(range = selected_range) + number_with_delimiter total_received_from_product_drives_unformatted(range) + end + + private + + def total_received_donations_unformatted(range = selected_range) + LineItem.active.where(itemizable: current_organization.donations.during(range)).sum(:quantity) + end + + def total_received_from_product_drives_unformatted(range = selected_range) + LineItem.active.where(itemizable: current_organization.donations.by_source(:product_drive).during(range)).sum(:quantity) + end +end diff --git a/app/javascript/utils/barcode_items.js b/app/javascript/utils/barcode_items.js index 8474a047e7..eb75d686bc 100644 --- a/app/javascript/utils/barcode_items.js +++ b/app/javascript/utils/barcode_items.js @@ -12,7 +12,7 @@ $(document).ready(function() { */ function capture_entry(event) { if (event.which == '10' || event.which == '13') { - barcode_item_lookup(event.target.value, $(event.target).data("organization-id"),event.target); + barcode_item_lookup(event.target.value, event.target); event.preventDefault(); } } @@ -21,12 +21,11 @@ $(document).ready(function() { barcode_item_lookup @brief Invokes an ajax lookup of a provided barcode value @param value : the barcode - @param organization_id : passed in as a param. This constrains the lookup @param src : the DOM source, so we can callback to it. */ - function barcode_item_lookup(value, organization_id,src) { + function barcode_item_lookup(value, src) { // Hardcoding magic URLs isn't ideal but it works for now - $.getJSON("/" + organization_id + "/barcode_items/find.json?barcode_item[value]=" + value, {}, function(data) { + $.getJSON("/barcode_items/find.json?barcode_item[value]=" + value, {}, function(data) { // Preserve this for reference of where we came from. data['src'] = src; data['value'] = value; diff --git a/app/models/errors.rb b/app/models/errors.rb index ef9e3c3762..1d9cc89cad 100644 --- a/app/models/errors.rb +++ b/app/models/errors.rb @@ -54,4 +54,10 @@ def message "KitAllocation not found for given kit" end end + + class InventoryAlreadyHasItems < StandardError + def message + "Could not complete action: inventory already has items stored" + end + end end diff --git a/app/models/event.rb b/app/models/event.rb index 23b14cc19c..ca56ecfd79 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -92,16 +92,4 @@ def validate_inventory end raise e end - - after_create_commit do - inventory = InventoryAggregate.inventory_for(organization_id) - diffs = EventDiffer.check_difference(inventory) - if diffs.any? - InventoryDiscrepancy.create!( - event_id: id, - organization_id: organization_id, - diff: diffs - ) - end - end end diff --git a/app/models/inventory_discrepancy.rb b/app/models/inventory_discrepancy.rb deleted file mode 100644 index 338a324ae1..0000000000 --- a/app/models/inventory_discrepancy.rb +++ /dev/null @@ -1,15 +0,0 @@ -# == Schema Information -# -# Table name: inventory_discrepancies -# -# id :bigint not null, primary key -# diff :json -# created_at :datetime not null -# updated_at :datetime not null -# event_id :bigint not null -# organization_id :bigint not null -# -class InventoryDiscrepancy < ApplicationRecord - belongs_to :event - belongs_to :organization -end diff --git a/app/models/organization.rb b/app/models/organization.rb index b7f577d6e7..3ed09d962a 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -11,6 +11,8 @@ # 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 +# hide_package_column_on_receipt :boolean default(FALSE) +# hide_value_columns_on_receipt :boolean default(FALSE) # intake_location :integer # invitation_text :text # latitude :float diff --git a/app/models/partners/profile.rb b/app/models/partners/profile.rb index 296204397b..af6416a83c 100644 --- a/app/models/partners/profile.rb +++ b/app/models/partners/profile.rb @@ -114,7 +114,8 @@ class Profile < Base ] def client_share_total - served_areas.sum(&:client_share) + # client_share could be nil + served_areas.map(&:client_share).compact.sum end private diff --git a/app/models/request.rb b/app/models/request.rb index c6963aeeb3..56959a0a10 100644 --- a/app/models/request.rb +++ b/app/models/request.rb @@ -7,6 +7,7 @@ # discard_reason :text # discarded_at :datetime # request_items :jsonb +# request_type :string # status :integer default("pending") # created_at :datetime not null # updated_at :datetime not null @@ -31,6 +32,7 @@ class Request < ApplicationRecord has_many :child_item_requests, through: :item_requests enum status: { pending: 0, started: 1, fulfilled: 2, discarded: 3 }, _prefix: true + enum request_type: %w[quantity individual child].map { |v| [v, v] }.to_h validates :distribution_id, uniqueness: true, allow_nil: true before_save :sanitize_items_data @@ -42,6 +44,7 @@ class Request < ApplicationRecord scope :by_partner, ->(partner_id) { where(partner_id: partner_id) } # status scope to allow filtering by status scope :by_status, ->(status) { where(status: status) } + scope :by_request_type, ->(request_type) { where(request_type: request_type) } scope :during, ->(range) { where(created_at: range) } scope :for_csv_export, ->(organization, *) { where(organization: organization) @@ -57,6 +60,10 @@ def user_email partner_user_id ? User.find_by(id: partner_user_id).email : Partner.find_by(id: partner_id).email end + def request_type_label + request_type&.first&.capitalize + end + private def sanitize_items_data diff --git a/app/models/storage_location.rb b/app/models/storage_location.rb index 2d562ec894..53fcdc3d25 100644 --- a/app/models/storage_location.rb +++ b/app/models/storage_location.rb @@ -132,6 +132,9 @@ def self.import_csv(csv, organization) # @param loc [Integer] StorageLocation ID # @return [void] def self.import_inventory(filename, org, loc) + storage_location = StorageLocation.find(loc.to_i) + raise Errors::InventoryAlreadyHasItems unless storage_location.empty_inventory? + current_org = Organization.find(org) adjustment = current_org.adjustments.new(storage_location_id: loc.to_i, user_id: User.with_role(Role::ORG_ADMIN, current_org).first&.id, diff --git a/app/models/user.rb b/app/models/user.rb index df8194a3c9..52e16fcc63 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -30,6 +30,7 @@ # created_at :datetime not null # updated_at :datetime not null # invited_by_id :integer +# last_role_id :bigint # organization_id :integer # partner_id :bigint # @@ -42,6 +43,10 @@ class User < ApplicationRecord has_one :organization_role_join, class_name: "UsersRole", dependent: :destroy has_one :organization_role, through: :organization_role_join, class_name: "Role", source: :role + + belongs_to :last_role_join, class_name: "UsersRole", optional: true, inverse_of: :user, foreign_key: :last_role_id + has_one :last_role, through: :last_role_join, class_name: "Role", source: :role + accepts_nested_attributes_for :organization_role_join has_one :organization, through: :organization_role, source: :resource, source_type: "Organization" has_many :organizations, through: :roles, source: :resource, source_type: "Organization" diff --git a/app/models/users_role.rb b/app/models/users_role.rb index 4432d445b8..d89f05aacd 100644 --- a/app/models/users_role.rb +++ b/app/models/users_role.rb @@ -10,12 +10,18 @@ class UsersRole < ApplicationRecord has_paper_trail belongs_to :user belongs_to :role + has_one :last_user, class_name: "User", foreign_key: :last_role_id, inverse_of: :last_role, dependent: :nullify accepts_nested_attributes_for :user + validates :role, uniqueness: {scope: :user} + # @param user [User] # @return [Role,nil] def self.current_role_for(user) + return nil if user.nil? + return user.last_role if user.last_role + role_order = [Role::SUPER_ADMIN, Role::ORG_ADMIN, Role::ORG_USER, Role::PARTNER] role_order.each do |role| found_role = user&.roles&.find { |r| r.name.to_sym == role } @@ -24,4 +30,13 @@ def self.current_role_for(user) nil end + + # @param user [User] + # @param role [Role] + def self.set_last_role_for(user, role) + users_role = UsersRole.find_by(user: user, role: role) + return if users_role.nil? + + user.update(last_role_id: users_role.id) + end end diff --git a/app/pdfs/distribution_pdf.rb b/app/pdfs/distribution_pdf.rb index f51beff39b..cafc458064 100644 --- a/app/pdfs/distribution_pdf.rb +++ b/app/pdfs/distribution_pdf.rb @@ -81,6 +81,9 @@ def compute_and_render data = @distribution.request ? request_data : non_request_data has_request = @distribution.request.present? + hide_columns(data) + hidden_columns_length = column_names_to_hide.length + font_size 11 # Line item table table(data) do @@ -109,7 +112,7 @@ def compute_and_render row(-2).borders = [:top] row(-2).padding = [2, 0, 2, 0] - column(0).width = 190 + column(0).width = 190 + (hidden_columns_length * 60) # Quantity column column(1..-1).row(1..-3).borders = [:left] @@ -212,4 +215,21 @@ def non_request_data @distribution.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 = @distribution.request.present? ? "In-Kind Value Received" : "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.push("Packages") if @organization.hide_package_column_on_receipt + columns_to_hide + end end diff --git a/app/queries/low_inventory_query.rb b/app/queries/low_inventory_query.rb new file mode 100644 index 0000000000..500fa53a38 --- /dev/null +++ b/app/queries/low_inventory_query.rb @@ -0,0 +1,45 @@ +class LowInventoryQuery + def self.call(organization) + if Event.read_events?(organization) + inventory = View::Inventory.new(organization.id) + items = inventory.all_items + + low_inventory_items = [] + items.each do |item| + quantity = inventory.quantity_for(item_id: item.id) + if quantity < item.on_hand_minimum_quantity.to_i || quantity < item.on_hand_recommended_quantity.to_i + low_inventory_items.push(OpenStruct.new( + id: item.id, + name: item.name, + on_hand_minimum_quantity: item.on_hand_minimum_quantity, + on_hand_recommended_quantity: item.on_hand_recommended_quantity, + total_quantity: quantity + )) + end + end + + low_inventory_items.sort_by { |item| item[:name] } + + else + sql_query = <<-SQL + SELECT + items.id, + items.name, + items.on_hand_minimum_quantity, + items.on_hand_recommended_quantity, + sum(inventory_items.quantity) as total_quantity + FROM inventory_items + JOIN items ON items.id = inventory_items.item_id + JOIN storage_locations ON storage_locations.id = inventory_items.storage_location_id + WHERE storage_locations.organization_id = ? + GROUP BY items.id, items.name, items.on_hand_minimum_quantity, items.on_hand_recommended_quantity + HAVING sum(inventory_items.quantity) < items.on_hand_minimum_quantity + OR sum(inventory_items.quantity) < items.on_hand_recommended_quantity + ORDER BY items.name + SQL + + sanitized_sql = ActiveRecord::Base.send(:sanitize_sql_array, [sql_query, organization.id]) + ActiveRecord::Base.connection.execute(sanitized_sql).to_a + end + end +end diff --git a/app/services/exports/export_distributions_csv_service.rb b/app/services/exports/export_distributions_csv_service.rb index a0f6569409..dae6df149c 100644 --- a/app/services/exports/export_distributions_csv_service.rb +++ b/app/services/exports/export_distributions_csv_service.rb @@ -63,8 +63,11 @@ def base_table "Partner" => ->(distribution) { distribution.partner.name }, - "Date of Distribution" => ->(distribution) { - distribution.issued_at.strftime("%m/%d/%Y") + "Initial Allocation" => ->(distribution) { + distribution.created_at.strftime("%m/%d/%Y") + }, + "Scheduled for" => ->(distribution) { + (distribution.issued_at.presence || distribution.created_at).strftime("%m/%d/%Y") }, "Source Inventory" => ->(distribution) { distribution.storage_location.name diff --git a/app/services/exports/export_request_service.rb b/app/services/exports/export_request_service.rb index e7bfba43af..0fba4d1bd9 100644 --- a/app/services/exports/export_request_service.rb +++ b/app/services/exports/export_request_service.rb @@ -46,6 +46,9 @@ def base_table "Requestor" => ->(request) { request.partner.name }, + "Type" => ->(request) { + request.request_type&.humanize + }, "Status" => ->(request) { request.status.humanize } diff --git a/app/services/itemizable_update_service.rb b/app/services/itemizable_update_service.rb index 519817d00c..df1fe1a8a3 100644 --- a/app/services/itemizable_update_service.rb +++ b/app/services/itemizable_update_service.rb @@ -18,6 +18,12 @@ def self.call(itemizable:, type: :increase, params: {}, event_class: nil) apply_change_method = (type == :increase) ? :increase_inventory : :decrease_inventory undo_change_method = (type == :increase) ? :decrease_inventory : :increase_inventory + previous = nil + # TODO once event sourcing has been out for long enough, we can safely remove this + if Event.where(eventable: itemizable).none? || UpdateExistingEvent.where(eventable: itemizable).any? + previous = itemizable.line_items.map(&:dup) + end + line_item_attrs = Array.wrap(params[:line_items_attributes]&.values) line_item_attrs.each { |attr| attr.delete(:id) } @@ -27,7 +33,11 @@ def self.call(itemizable:, type: :increase, params: {}, event_class: nil) params: params, from_location: from_location, to_location: to_location) - event_class&.publish(itemizable) + if previous + UpdateExistingEvent.publish(itemizable, previous) + else + event_class&.publish(itemizable) + end end end diff --git a/app/services/partners/family_request_create_service.rb b/app/services/partners/family_request_create_service.rb index e181d3e2f7..b967d20003 100644 --- a/app/services/partners/family_request_create_service.rb +++ b/app/services/partners/family_request_create_service.rb @@ -8,7 +8,7 @@ class FamilyRequestCreateService attr_reader :partner_user_id, :comments, :family_requests_attributes, :partner_request - def initialize(partner_user_id:, family_requests_attributes:, comments: nil, for_families: false) + def initialize(request_type:, partner_user_id:, family_requests_attributes:, comments: nil, for_families: false) @partner_user_id = partner_user_id @comments = comments @family_requests_attributes = family_requests_attributes.presence || [] @@ -19,6 +19,7 @@ def call return self unless valid? request_create_svc = Partners::RequestCreateService.new( + request_type: request_type, partner_user_id: partner_user_id, comments: comments, for_families: @for_families, @@ -72,5 +73,13 @@ def convert_person_count_to_item_quantity(item_id:, person_count:) def included_items_by_id @included_items_by_id ||= Item.where(id: family_requests_attributes.pluck(:item_id)).index_by(&:id) end + + def request_type + if @for_families + "child" + else + "individual" + end + end end end diff --git a/app/services/partners/request_create_service.rb b/app/services/partners/request_create_service.rb index e5c4058083..a3709b5c4e 100644 --- a/app/services/partners/request_create_service.rb +++ b/app/services/partners/request_create_service.rb @@ -2,9 +2,10 @@ module Partners class RequestCreateService include ServiceObjectErrorsMixin - attr_reader :partner_request + attr_reader :partner_request, :request_type - def initialize(partner_user_id:, comments: nil, for_families: false, item_requests_attributes: [], additional_attrs: {}) + def initialize(request_type:, partner_user_id:, comments: nil, for_families: false, item_requests_attributes: [], additional_attrs: {}) + @request_type = request_type @partner_user_id = partner_user_id @comments = comments @for_families = for_families @@ -13,7 +14,9 @@ def initialize(partner_user_id:, comments: nil, for_families: false, item_reques end def call - @partner_request = ::Request.new(partner_id: partner.id, + @partner_request = ::Request.new( + request_type: request_type, + partner_id: partner.id, organization_id: organization_id, comments: comments, partner_user_id: partner_user_id) diff --git a/app/services/remove_role_service.rb b/app/services/remove_role_service.rb index cc99c66078..f36675a026 100644 --- a/app/services/remove_role_service.rb +++ b/app/services/remove_role_service.rb @@ -18,6 +18,7 @@ def self.call(user_id:, role_id: nil, resource_type: nil, resource_id: nil) end user_role.destroy + if user_role.role.name.to_sym == Role::ORG_USER # they can't be an admin if they're not a user admin_role = Role.find_by(resource_id: user_role.role.resource_id, name: Role::ORG_ADMIN) if admin_role diff --git a/app/views/adjustments/index.html.erb b/app/views/adjustments/index.html.erb index be068c3c45..a945dd93dd 100644 --- a/app/views/adjustments/index.html.erb +++ b/app/views/adjustments/index.html.erb @@ -54,7 +54,7 @@ text: "Export Adjustments" ) if @adjustments.any? %> - <%= new_button_to new_adjustment_path(organization_name: current_organization), {text: "New Adjustment"} %> + <%= new_button_to new_adjustment_path, {text: "New Adjustment"} %> <% end # form %> diff --git a/app/views/admin/base_items/show.html.erb b/app/views/admin/base_items/show.html.erb index f66b6cc80f..71657d4052 100644 --- a/app/views/admin/base_items/show.html.erb +++ b/app/views/admin/base_items/show.html.erb @@ -45,7 +45,7 @@ <% @items.each do |item| %> <%= item.name %> - <%= link_to item.organization.name, organization_path(item.organization) %> + <%= item.organization.name %> (id: <%= item.organization.id %>) <% end %> diff --git a/app/views/admin/users/_roles.html.erb b/app/views/admin/users/_roles.html.erb index 21cc9b024b..1d1d843da6 100644 --- a/app/views/admin/users/_roles.html.erb +++ b/app/views/admin/users/_roles.html.erb @@ -24,7 +24,7 @@ <% user.roles.each do |role| %> <%= role.title %> - <%= link_to role.resource.name, role.resource %> + <%= role.resource.name %> (id: <%= role.resource.id %>) <%= delete_button_to admin_user_remove_role_path(user, role_id: role.id), confirm: "Are you sure you want to remove this role?" %> diff --git a/app/views/audits/edit.html.erb b/app/views/audits/edit.html.erb index b0170caf95..dce1f20fe0 100644 --- a/app/views/audits/edit.html.erb +++ b/app/views/audits/edit.html.erb @@ -14,7 +14,7 @@ Home <% end %> - + diff --git a/app/views/audits/index.html.erb b/app/views/audits/index.html.erb index 55195842b4..9bb22d2683 100644 --- a/app/views/audits/index.html.erb +++ b/app/views/audits/index.html.erb @@ -47,7 +47,7 @@ <%= clear_filter_button %>
- <%= new_button_to new_audit_path(organization_name: current_organization), {text: "New Audit"} %> + <%= new_button_to new_audit_path, {text: "New Audit"} %>
<% end # form %> diff --git a/app/views/audits/new.html.erb b/app/views/audits/new.html.erb index fbe0a75338..25d72693a5 100644 --- a/app/views/audits/new.html.erb +++ b/app/views/audits/new.html.erb @@ -14,7 +14,7 @@ Home <% end %> - + diff --git a/app/views/audits/show.html.erb b/app/views/audits/show.html.erb index f5a04a1ec2..f5f0ad6b11 100644 --- a/app/views/audits/show.html.erb +++ b/app/views/audits/show.html.erb @@ -14,7 +14,7 @@ Home <% end %> - + diff --git a/app/views/barcode_items/_barcode_item_lookup.html.erb b/app/views/barcode_items/_barcode_item_lookup.html.erb index 27e81092d2..73171d7821 100644 --- a/app/views/barcode_items/_barcode_item_lookup.html.erb +++ b/app/views/barcode_items/_barcode_item_lookup.html.erb @@ -1,2 +1,2 @@ <%# Increment a counter on how many we've seen so that we can create a unique ID -- really only necessary for test hooks %> -" class="__barcode_item_lookup form-control" value="" placeholder="Barcode Entry" data-organization-id="<%= params[:organization_name] %>" autocomplete="on"> +" class="__barcode_item_lookup form-control" value="" placeholder="Barcode Entry" autocomplete="on"> diff --git a/app/views/barcode_items/edit.html.erb b/app/views/barcode_items/edit.html.erb index e378c6a845..e4f912e866 100644 --- a/app/views/barcode_items/edit.html.erb +++ b/app/views/barcode_items/edit.html.erb @@ -13,7 +13,7 @@ Home <% end %> - + diff --git a/app/views/barcode_items/index.html.erb b/app/views/barcode_items/index.html.erb index 42e95ce068..fa8d97d260 100644 --- a/app/views/barcode_items/index.html.erb +++ b/app/views/barcode_items/index.html.erb @@ -48,7 +48,7 @@ <%= download_button_to(barcode_items_path(format: :csv, filters: filter_params.merge(date_range: date_range_params)), {text: "Export Barcode Items", size: "md"}) if @barcode_items.any? %> <%= download_button_to(font_barcode_items_path, {text: "Download Barcode Font"}) %> - <%= new_button_to new_barcode_item_path(organization_name: current_organization), {text: "New Barcode"} %> + <%= new_button_to new_barcode_item_path, {text: "New Barcode"} %> <% end # form %> diff --git a/app/views/barcode_items/new.html.erb b/app/views/barcode_items/new.html.erb index 79d43de9bd..22c3fd19ee 100644 --- a/app/views/barcode_items/new.html.erb +++ b/app/views/barcode_items/new.html.erb @@ -14,7 +14,7 @@ Home <% end %> - + diff --git a/app/views/barcode_items/show.html.erb b/app/views/barcode_items/show.html.erb index af98ea1010..c8facef031 100644 --- a/app/views/barcode_items/show.html.erb +++ b/app/views/barcode_items/show.html.erb @@ -14,7 +14,7 @@ Home <% end %> - + diff --git a/app/views/broadcast_announcements/edit.html.erb b/app/views/broadcast_announcements/edit.html.erb index 3a2c59f28a..a670857b3a 100644 --- a/app/views/broadcast_announcements/edit.html.erb +++ b/app/views/broadcast_announcements/edit.html.erb @@ -14,7 +14,7 @@ Home <% end %> - + diff --git a/app/views/broadcast_announcements/index.html.erb b/app/views/broadcast_announcements/index.html.erb index 4114c6a691..f80732a3fd 100644 --- a/app/views/broadcast_announcements/index.html.erb +++ b/app/views/broadcast_announcements/index.html.erb @@ -14,7 +14,7 @@ Home <% end %> - + @@ -31,7 +31,7 @@
- <%= new_button_to new_broadcast_announcement_path(organization_name: current_organization), {text: "New Announcement"} %> + <%= new_button_to new_broadcast_announcement_path, {text: "New Announcement"} %>
diff --git a/app/views/broadcast_announcements/new.html.erb b/app/views/broadcast_announcements/new.html.erb index 538632cb6e..91ba199da3 100644 --- a/app/views/broadcast_announcements/new.html.erb +++ b/app/views/broadcast_announcements/new.html.erb @@ -14,7 +14,7 @@ Home <% end %> - + diff --git a/app/views/dashboard/_announcements.html.erb b/app/views/dashboard/_announcements.html.erb new file mode 100644 index 0000000000..85d14b6349 --- /dev/null +++ b/app/views/dashboard/_announcements.html.erb @@ -0,0 +1,32 @@ +<% if @broadcast_announcements.any? %> +
+
+

Announcements + from Human Essentials

+
+ +
+
+
+
    + <% @broadcast_announcements.each do |announcement| %> +
  • + <%= if announcement.created_at.strftime("%Y") == DateTime.now.strftime("%Y") + announcement.created_at.strftime("%B %d") + else + announcement.created_at.strftime("%B %d %Y") + end %> +
    + <%= announcement.message %> + <% unless announcement.link == '' %> +
    + more info + <% end %> +
  • + <% end %> +
+
+
+<% end %> diff --git a/app/views/dashboard/_getting_started_prompt.html.erb b/app/views/dashboard/_getting_started_prompt.html.erb index 726f5b60a7..b56f408dcf 100644 --- a/app/views/dashboard/_getting_started_prompt.html.erb +++ b/app/views/dashboard/_getting_started_prompt.html.erb @@ -7,123 +7,133 @@ <% current_step = criterias.find_index(false) %> <% if current_step.present? %> -
-
-
-

- Just Starting? - Here are some things you may need to set up: -

-
-
-
- <%= render partial: "getting_started_progress_stepper", locals: { - 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"] - } %> -
+ <%= render( + "shared/card", + id: "summary", + header: "Getting Started", + type: :plain, + ) do %> +
+
+
+
+

+ Just Starting? + Here are some things you may need to set up: +

+
-
-
-
-
- <% if partner_criteria_met %> - - <% else %> - - <% end %> -

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

-
+
+
+ <%= render partial: "getting_started_progress_stepper", locals: { + 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"] + } %> +
+ +
+
+
+
+ <% if partner_criteria_met %> + + <% else %> + + <% end %> +

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

+
-

- To start building your community in Human Essentials, import a list of your current partner agencies or add them individually. -

+

+ To start building your community in Human Essentials, import a list of your current partner agencies or add them individually. +

-
- <% partner_link_text = partner_criteria_met ? "Add More Partners" : "Add a Partner" %> - <%= new_button_to new_partner_path, { text: partner_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" } %> +
+
-
-
-
-
-
-
- <% if location_criteria_met %> - - <% else %> - - <% end %> -

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

-
+
+
+
+
+ <% if location_criteria_met %> + + <% else %> + + <% end %> +

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

+
-

- Add details for all Storage Locations you use for your inventory. -

+

+ Add details for all Storage Locations you use for your inventory. +

-
- <% 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" } %> +
+ <% 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" } %> +
+
-
-
-
-
-
-
- <% if donation_criteria_met %> - - <% else %> - - <% end %> -

<%= pluralize(org_stats.donation_sites_added, 'Donation Site') %> Added

-
+
+
+
+
+ <% if donation_criteria_met %> + + <% else %> + + <% end %> +

<%= pluralize(org_stats.donation_sites_added, 'Donation Site') %> Added

+
-

- Add any community sites (including your primary storage facility) that accept donations on your behalf. -

+

+ Add any community sites (including your primary storage facility) that accept donations on your behalf. +

-
- <% donation_link_text = donation_criteria_met ? "Add More Donation Sites" : "Add a Donation Site" %> - <%= new_button_to new_donation_site_path, { text: donation_link_text, size: "md" } %> +
+ <% donation_link_text = donation_criteria_met ? "Add More Donation Sites" : "Add a Donation Site" %> + <%= new_button_to new_donation_site_path, { text: donation_link_text, size: "md" } %> +
+
-
-
-
-
-
-
- <% if inventory_criteria_met %> - - <% else %> - - <% end %> -

<%= pluralize(org_stats.locations_with_inventory.length, 'Storage Location') %> with Inventory

-
+
+
+
+
+ <% if inventory_criteria_met %> + + <% else %> + + <% end %> +

<%= pluralize(org_stats.locations_with_inventory.length, 'Storage Location') %> with Inventory

+
-
-

- As an essentials bank, you might already have inventory on hand. These items might have come from previous donations/purchases. If you have multiple sources or want to add inventory to multiple storage locations, you can create multiple donations or purchases while specifying the correct date, source, and location. -

-
+
+

+ As an essentials bank, you might already have inventory on hand. These items might have come from previous donations/purchases. If you have multiple sources or want to add inventory to multiple storage locations, you can create multiple donations or purchases while specifying the correct date, source, and location. +

+
-
- <%= new_button_to new_donation_path, { text: "Add past donation", size: "md" } %> - <%= new_button_to new_purchase_path, { text: "Add past purchase", size: "md" } %> +
+ <%= new_button_to new_donation_path, { text: "Add past donation", size: "md" } %> + <%= new_button_to new_purchase_path, { text: "Add past purchase", size: "md" } %> +
+
-
+ <% end %> <% end %> diff --git a/app/views/dashboard/_itemized_distributions_partial.html.erb b/app/views/dashboard/_itemized_distributions_partial.html.erb deleted file mode 100644 index 07fed7e051..0000000000 --- a/app/views/dashboard/_itemized_distributions_partial.html.erb +++ /dev/null @@ -1,21 +0,0 @@ -
- - - - - - - - <%# Ordering from highest distributed to lowest %> - <% (local_assigns[:itemized_breakdown] || []).each do |item| %> - - - - - - - <% end %> - -
   ItemTotal DistributedTotal On Hand
   <%= item[:name] %><%= item[:distributed] %> - <%= item[:current_onhand] || "Unknown" %> -
diff --git a/app/views/dashboard/_itemized_donations_partial.html.erb b/app/views/dashboard/_itemized_donations_partial.html.erb deleted file mode 100644 index fec34aff19..0000000000 --- a/app/views/dashboard/_itemized_donations_partial.html.erb +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - <%# Ordering from highest distributed to lowest %> - <% (local_assigns[:itemized_breakdown] || []).each do |item| %> - - - - - - - <% end %> - -
   ItemTotal DonatedTotal On Hand
   <%= item[:name] %><%= item[:donated] %><%= item[:current_onhand] || "Unknown" %>
diff --git a/app/views/dashboard/_low_inventory_report.html.erb b/app/views/dashboard/_low_inventory_report.html.erb new file mode 100644 index 0000000000..9563eaed98 --- /dev/null +++ b/app/views/dashboard/_low_inventory_report.html.erb @@ -0,0 +1,37 @@ +<%= render( + "shared/card", + id: "low_inventory", + gradient: "info", + title: "Bank-wide Low inventory" +) do %> + <% if @low_inventory_report.count == 0 %> +

Inventory is at recommended levels (minimum and recommended levels can be set on each item)

+ <% else %> + + + + + + + + + + + <% @low_inventory_report.each do |inventory_item| %> + + + + + + + <% end %> + +
Item NameQuantityMinimum QuantityRecommended Quantity
<%= inventory_item["name"] %> + <% if inventory_item["total_quantity"] < inventory_item["on_hand_minimum_quantity"] %> + <%= inventory_item["total_quantity"] %> + <% else %> + <%= inventory_item["total_quantity"] %> + <% end %> + <%= inventory_item["on_hand_minimum_quantity"] %><%= inventory_item["on_hand_recommended_quantity"] %>
+ <% end %> +<% end %> diff --git a/app/views/dashboard/_outstanding_requests.html.erb b/app/views/dashboard/_outstanding_requests.html.erb new file mode 100644 index 0000000000..3ea3f23e01 --- /dev/null +++ b/app/views/dashboard/_outstanding_requests.html.erb @@ -0,0 +1,35 @@ +<%= + render( + "shared/card", + id: "outstanding", + gradient: "warning", + title: "Outstanding Requests", + footer: link_to("View all requests", requests_path), + footer_options: { class: "text-center" }, + ) do +%> + <% if @outstanding_requests.empty? %> + No outstanding requests! + <% else %> + + + + + + + + + + + <% @outstanding_requests.take(25).each do |item| %> + + + + + + + <% end %> + +
DatePartnerRequestorComments
<%= link_to item.created_at.strftime("%m/%d/%Y"), item %><%= item.partner.name %><%= item.partner_user&.formatted_email %><%= item.comments %>
+ <% end %> +<% end %> diff --git a/app/views/dashboard/_partner_approvals.html.erb b/app/views/dashboard/_partner_approvals.html.erb new file mode 100644 index 0000000000..7c82b41395 --- /dev/null +++ b/app/views/dashboard/_partner_approvals.html.erb @@ -0,0 +1,34 @@ +<%= render( + "shared/card", + id: "partner_approvals", + title: "Partner Approvals", + class: "wide-table", + type: @partners_awaiting_review.blank? ? :box : :table +) do %> + <% if @partners_awaiting_review.present? %> + + + + + + + + + + + <% @partners_awaiting_review.each do |partner| %> + + + + + + + + <% end %> +
Partner NamePrimary Contact NamePrimary Contact EmailReview RequestedAction
<%= partner.name %><%= partner.profile.primary_contact_name %><%= partner.profile.primary_contact_email %><%= partner.updated_at.strftime("%B %d %Y") %> + <%= view_button_to partner_path(partner) + "#partner-information", { text: "Review Application", icon: "check", type: "warning", class: 'badge' } %> +
+ <% else %> + No partners waiting for approval + <% end %> +<% end %> diff --git a/app/views/dashboard/_product_drive.html.erb b/app/views/dashboard/_product_drive.html.erb deleted file mode 100644 index 6bdb592bdb..0000000000 --- a/app/views/dashboard/_product_drive.html.erb +++ /dev/null @@ -1,5 +0,0 @@ -
- <%= link_to donation do %> - <%= number_with_delimiter donation.line_items.total %> from <%= donation.product_drive&.name || donation.product_drive_participant.business_name %> - <% end %> -
diff --git a/app/views/dashboard/index.html.erb b/app/views/dashboard/index.html.erb index 07988d141d..148f601ace 100644 --- a/app/views/dashboard/index.html.erb +++ b/app/views/dashboard/index.html.erb @@ -19,368 +19,21 @@
-
-
-
<%= current_organization.name %>
-
- - -
-
- -
-
- <%= render partial: "getting_started_prompt", locals: {org_stats: @org_stats} %> - -
- <%== display_logo_or_name %> -
- -
- -
- -
- -
- -
+ <%= render partial: "getting_started_prompt", locals: {org_stats: @org_stats} %>
+
-
- <% if @broadcast_announcements.any? %> -
-
-

Announcements - from Human Essentials

-
- -
-
-
-
    - <% @broadcast_announcements.each do |announcement| %> -
  • - <%= if announcement.created_at.strftime("%Y") == DateTime.now.strftime("%Y") - announcement.created_at.strftime("%B %d") - else - announcement.created_at.strftime("%B %d %Y") - end %> -
    - <%= announcement.message %> - <% unless announcement.link == '' %> -
    - more info - <% end %> -
  • - <% end %> -
-
-
- <% end %> - -
-
-

Distributions - <%= @selected_date_range %>

-
- -
-
-
-
-
- <%= new_button_to new_distribution_path, {text: "New Distribution"} %> - <%= print_button_to distributions_by_county_report_path(filters: { date_range: date_range_params }), {text: "Distributions by County", size: "md"} %> - -

- - <%= total_distributed %> - - items distributed <%= @selected_date_range_label %> -

-

(<%= future_distributed %> items scheduled for future distribution)

-
-

Recent distributions

- <%= render partial: "distribution", collection: @recent_distributions, as: :distribution %> -
-
-
-
- -
- -
-
-

Itemized Distributions - <%= @selected_date_range %>

-
- -
-
-
-
- <%= download_button_to(itemized_breakdown_distributions_path(format: :csv, filters: { date_range: date_range_params }), {text: "Export To CSV"}) %> -
- - <%= render partial: "itemized_distributions_partial", locals: { itemized_breakdown: @itemized_distribution_data } %> -
-
- -
-
-

Activity - <%= @selected_date_range %> -

-
- -
-
-
-
-
-
-
-
- <% - activity_chart_config = { - chart: { - type: "bar" - }, - title: "", - xAxis: { - categories: @distribution_data.keys, - title: { - text: nil - } - }, - yAxis: { - title: { - text: nil - } - }, - legend: { - enabled: false - }, - series: [ - { - data: @distribution_data.values - } - ] - }.to_json - %> -
-
-
- -
-
-
-
-
-
-
-
- -
-
-

Donations (All Sources) - <%= @selected_date_range %>

-
- -
-
-
-
-
- <%= new_button_to new_donation_path, {text: "New Donation"} %> -

- - <%= total_received_donations %> - - items - received <%= @selected_date_range_label %>

-

<%= dollar_presentation(total_received_money_donations) %> - raised <%= @selected_date_range_label %>

-
-

Recent Donations

- <%= render partial: "donation", collection: @recent_donations, as: :donation %> -
-
-
-
- -
- -
-
-

Product Drives <%= @selected_date_range %>

-
- -
-
-
-
-
-

- - <%= total_received_from_product_drives %> - items - received <%= @selected_date_range_label %>

-

- - <%= dollar_presentation( total_received_money_donations_from_product_drives) %> - raised <%= @selected_date_range_label %>

-
-

Recent Donations from Product Drives

- <%= render partial: "product_drive", collection: @recent_donations.by_source(:product_drive), as: :donation %> -
-
-
-
- -
- -
-
-

Manufacturer Donations <%= @selected_date_range %>

-
- -
-
-
-
-
-

- - <%= number_with_delimiter(@recent_donations_from_manufacturers.sum { |d| d.line_items.total }) %> - - items donated <%= @selected_date_range_label %> - by - - <%= pluralize(@recent_donations_from_manufacturers.group_by(&:manufacturer).count, 'Manufacturer') %> - -

-
-

Top Manufacturer Donations

- <%= render partial: "manufacturer", collection: @top_manufacturers, as: :manufacturer %> -
-
-
-
- -
- -
-
-

Purchases <%= @selected_date_range %>

-
- -
-
-
-
-
- <%= new_button_to new_purchase_path, {text: "New Purchase"} %> -

<%= dollar_presentation(@purchases.sum(&:amount_spent_in_cents)) %> - spent - <%= @selected_date_range_label %>

-
-

Recent purchases

- <%= render partial: "purchase", collection: @recent_purchases, as: :purchase %> -
-
-
-
- -
+
-
-
-

Itemized Donations - <%= @selected_date_range %>

-
-
-
-
- <%= render partial: "itemized_donations_partial", locals: { itemized_breakdown: @itemized_donation_data } %> -
-
+ <%= render partial: "announcements" %> + <%= render partial: "outstanding_requests" %> + <%= render partial: "partner_approvals" %> + <%= render partial: "low_inventory_report" %>
diff --git a/app/views/distributions/_distribution_row.html.erb b/app/views/distributions/_distribution_row.html.erb index 625ffc1838..71fc0736a9 100644 --- a/app/views/distributions/_distribution_row.html.erb +++ b/app/views/distributions/_distribution_row.html.erb @@ -1,7 +1,8 @@ > <%= distribution_row.id %> <%= distribution_row.partner.name %> - <%= (distribution_row.issued_at.presence || distribution_row.created_at).strftime("%m/%d/%Y") %> + <%= distribution_row.created_at.strftime("%m/%d/%Y") %> + <%= (distribution_row.issued_at.presence || distribution_row.created_at).strftime("%m/%d/%Y") %> <%= distribution_row.storage_location.name %> diff --git a/app/views/distributions/_distribution_total.html.erb b/app/views/distributions/_distribution_total.html.erb index 37fc41304e..669c4251ca 100644 --- a/app/views/distributions/_distribution_total.html.erb +++ b/app/views/distributions/_distribution_total.html.erb @@ -2,6 +2,8 @@ Total: + + <%= number_with_delimiter(@total_items_all_distributions, :delimiter => ',') %> (Total)
diff --git a/app/views/distributions/edit.html.erb b/app/views/distributions/edit.html.erb index 2ccbd55a32..a25558c548 100644 --- a/app/views/distributions/edit.html.erb +++ b/app/views/distributions/edit.html.erb @@ -14,7 +14,7 @@ Home <% end %> - +
diff --git a/app/views/distributions/index.html.erb b/app/views/distributions/index.html.erb index 9760ba36a1..6466784bd1 100644 --- a/app/views/distributions/index.html.erb +++ b/app/views/distributions/index.html.erb @@ -76,7 +76,7 @@ ) end %> - <%= new_button_to new_distribution_path(organization_name: current_organization), {text: "New Distribution"} %> + <%= new_button_to new_distribution_path, {text: "New Distribution"} %>
<% end # form %> @@ -99,6 +99,7 @@ ID Partner + Inital Allocation Date of Distribution Source Inventory diff --git a/app/views/distributions/new.html.erb b/app/views/distributions/new.html.erb index 38fa80e41a..b40249ae7f 100644 --- a/app/views/distributions/new.html.erb +++ b/app/views/distributions/new.html.erb @@ -14,7 +14,7 @@ Home <% end %> - +
diff --git a/app/views/distributions/pickup_day.html.erb b/app/views/distributions/pickup_day.html.erb index ac32ad4e04..71b3ee35b9 100644 --- a/app/views/distributions/pickup_day.html.erb +++ b/app/views/distributions/pickup_day.html.erb @@ -13,8 +13,8 @@ Home <% end %> - - + + diff --git a/app/views/distributions/schedule.html.erb b/app/views/distributions/schedule.html.erb index f1c6bfb0c8..02e5185eda 100644 --- a/app/views/distributions/schedule.html.erb +++ b/app/views/distributions/schedule.html.erb @@ -16,7 +16,7 @@ Home <% end %> - + diff --git a/app/views/distributions/show.html.erb b/app/views/distributions/show.html.erb index 168ea57273..da6776ff12 100644 --- a/app/views/distributions/show.html.erb +++ b/app/views/distributions/show.html.erb @@ -14,7 +14,7 @@ Home <% end %> - + diff --git a/app/views/donation_sites/edit.html.erb b/app/views/donation_sites/edit.html.erb index 3fc34cd148..bf7b6958e9 100644 --- a/app/views/donation_sites/edit.html.erb +++ b/app/views/donation_sites/edit.html.erb @@ -14,7 +14,7 @@ Home <% end %> - + diff --git a/app/views/donation_sites/index.html.erb b/app/views/donation_sites/index.html.erb index 68f2003feb..36fc7a016c 100644 --- a/app/views/donation_sites/index.html.erb +++ b/app/views/donation_sites/index.html.erb @@ -55,7 +55,7 @@
<%= modal_button_to("#csvImportModal", {icon: "upload", text: "Import Donation Sites", size: "md"}) if @donation_sites.empty? %> <%= download_button_to(donation_sites_path(format: :csv, filters: filter_params.merge(date_range: date_range_params)), {text: "Export Donation Sites", size: "md"}) if @donation_sites.any? %> - <%= new_button_to new_donation_site_path(organization_name: current_organization), {text: "New Donation Site"} %> + <%= new_button_to new_donation_site_path, {text: "New Donation Site"} %>
diff --git a/app/views/donation_sites/new.html.erb b/app/views/donation_sites/new.html.erb index 36f35fe018..769c860582 100644 --- a/app/views/donation_sites/new.html.erb +++ b/app/views/donation_sites/new.html.erb @@ -14,7 +14,7 @@ Home <% end %> - + diff --git a/app/views/donation_sites/show.html.erb b/app/views/donation_sites/show.html.erb index 12c8cb6c12..77c3abc569 100644 --- a/app/views/donation_sites/show.html.erb +++ b/app/views/donation_sites/show.html.erb @@ -14,7 +14,7 @@ Home <% end %> - + diff --git a/app/views/donations/edit.html.erb b/app/views/donations/edit.html.erb index c21dd45d22..b7718e256a 100644 --- a/app/views/donations/edit.html.erb +++ b/app/views/donations/edit.html.erb @@ -14,7 +14,7 @@ Home <% end %> - + diff --git a/app/views/donations/index.html.erb b/app/views/donations/index.html.erb index 4d4705d136..903c50de43 100644 --- a/app/views/donations/index.html.erb +++ b/app/views/donations/index.html.erb @@ -81,7 +81,7 @@ <%= clear_filter_button %> <%= download_button_to(donations_path(format: :csv, filters: filter_params.merge(date_range: date_range_params)), {text: "Export Donations", size: "md"}) if @donations.any? %> - <%= new_button_to new_donation_path(organization_name: current_organization), {text: "New Donation"} %> + <%= new_button_to new_donation_path, {text: "New Donation"} %> <% end %> diff --git a/app/views/donations/new.html.erb b/app/views/donations/new.html.erb index 2547abd406..8c0d065b25 100644 --- a/app/views/donations/new.html.erb +++ b/app/views/donations/new.html.erb @@ -14,7 +14,7 @@ Home <% end %> - + diff --git a/app/views/donations/show.html.erb b/app/views/donations/show.html.erb index 4a89e1ee25..8e45413ae2 100644 --- a/app/views/donations/show.html.erb +++ b/app/views/donations/show.html.erb @@ -14,7 +14,7 @@ Home <% end %> - + @@ -75,11 +75,16 @@ enabled: !@donation.has_inactive_item?, size: "md" } %> <%= new_button_to new_distribution_path(donation_id: @donation.id, storage_location_id: @donation.storage_location_id), { text: "Start a new Distribution" } %> - <%= delete_button_to donation_path(@donation), { size: "md", confirm: "Are you sure you want to permanently remove this donation?" } if current_user.has_role?(Role::ORG_ADMIN, current_organization) %> + <% if current_user.has_role?(Role::ORG_ADMIN, current_organization) %> + <%= delete_button_to donation_path(@donation), { + size: "md", + enabled: !@donation.has_inactive_item?, + confirm: "Are you sure you want to permanently remove this donation?" } %> + <% end %> <% if @donation.has_inactive_item? %>
- You can only correct donations where all the items are active. - If you need to make a correction, please make the following items active: <%= @donation.inactive_items.map(&:name).join(", ") %> + 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 %> diff --git a/app/views/events/_event_row.html.erb b/app/views/events/_event_row.html.erb index 59d4c4ff3f..bb11e99e91 100644 --- a/app/views/events/_event_row.html.erb +++ b/app/views/events/_event_row.html.erb @@ -11,7 +11,7 @@ <%= link_to event.eventable_id, event.eventable %> <%= link_to( - events_path(current_organization, eventable_type: event.eventable_type, eventable_id: event.eventable_id), + events_path(eventable_type: event.eventable_type, eventable_id: event.eventable_id), class: 'btn btn-md') do %> <% end %> diff --git a/app/views/items/_item_categories.html.erb b/app/views/items/_item_categories.html.erb index 87525ed450..12554cd444 100644 --- a/app/views/items/_item_categories.html.erb +++ b/app/views/items/_item_categories.html.erb @@ -1,7 +1,7 @@
- <%= new_button_to new_item_category_path(organization_name: current_organization), {text: "New Item Category"} %> + <%= new_button_to new_item_category_path, {text: "New Item Category"} %>
diff --git a/app/views/items/_item_list.html.erb b/app/views/items/_item_list.html.erb index 49cd464521..2a3aa9d43d 100644 --- a/app/views/items/_item_list.html.erb +++ b/app/views/items/_item_list.html.erb @@ -1,6 +1,6 @@
- <%= new_button_to new_item_path(organization_name: current_organization), {text: "New Item"} %> + <%= new_button_to new_item_path, {text: "New Item"} %>
diff --git a/app/views/items/_items_inventory.html.erb b/app/views/items/_items_inventory.html.erb index 9ec8ea5ddd..de82d10c5f 100644 --- a/app/views/items/_items_inventory.html.erb +++ b/app/views/items/_items_inventory.html.erb @@ -1,6 +1,6 @@
- <%= new_button_to new_item_path(organization_name: current_organization), {text: "New Item"} %> + <%= new_button_to new_item_path, {text: "New Item"} %>
diff --git a/app/views/items/_items_quantity_and_location.html.erb b/app/views/items/_items_quantity_and_location.html.erb index 0fcf6bb2b9..c2aee4fb1e 100644 --- a/app/views/items/_items_quantity_and_location.html.erb +++ b/app/views/items/_items_quantity_and_location.html.erb @@ -1,6 +1,6 @@
- <%= new_button_to new_item_path(organization_name: current_organization), {text: "New Item"} %> + <%= new_button_to new_item_path, {text: "New Item"} %>
diff --git a/app/views/items/_kits.html.erb b/app/views/items/_kits.html.erb index b9081e0630..1a422e9fe8 100644 --- a/app/views/items/_kits.html.erb +++ b/app/views/items/_kits.html.erb @@ -1,6 +1,6 @@
- <%= new_button_to new_kit_path(organization_name: current_organization), {text: "New Kit"} %> + <%= new_button_to new_kit_path, {text: "New Kit"} %>
<%= render partial: 'kits/table' %> diff --git a/app/views/items/edit.html.erb b/app/views/items/edit.html.erb index 63475f9a23..5bcc779c3a 100644 --- a/app/views/items/edit.html.erb +++ b/app/views/items/edit.html.erb @@ -13,7 +13,7 @@ Home <% end %> - +
diff --git a/app/views/items/new.html.erb b/app/views/items/new.html.erb index b22ba13ec4..5c968bc585 100644 --- a/app/views/items/new.html.erb +++ b/app/views/items/new.html.erb @@ -14,7 +14,7 @@ Home <% end %> - + diff --git a/app/views/items/show.html.erb b/app/views/items/show.html.erb index 9ceb0ed3de..5aff1ca504 100644 --- a/app/views/items/show.html.erb +++ b/app/views/items/show.html.erb @@ -14,7 +14,7 @@ Home <% end %> - + diff --git a/app/views/kits/_table.html.erb b/app/views/kits/_table.html.erb index 09b81b4042..7d8bc053c3 100644 --- a/app/views/kits/_table.html.erb +++ b/app/views/kits/_table.html.erb @@ -44,7 +44,7 @@ <% end %>
- <%= edit_button_to allocations_kit_path(organization_name: current_organization.id, id: kit.id), { text: "Modify Allocation" } %> + <%= edit_button_to allocations_kit_path(kit), { text: "Modify Allocation" } %>
diff --git a/app/views/kits/allocations.html.erb b/app/views/kits/allocations.html.erb index 72b3d1c0e6..f9e775771c 100644 --- a/app/views/kits/allocations.html.erb +++ b/app/views/kits/allocations.html.erb @@ -14,7 +14,7 @@ Home <% end %> - +
diff --git a/app/views/kits/index.html.erb b/app/views/kits/index.html.erb index dd1c4b5e44..5571d91794 100644 --- a/app/views/kits/index.html.erb +++ b/app/views/kits/index.html.erb @@ -44,7 +44,7 @@ <%= filter_button %> <%= clear_filter_button %> - <%= new_button_to new_kit_path(organization_name: current_organization), { text: "New Kit" } %> + <%= new_button_to new_kit_path, { text: "New Kit" } %> <% end # form %> diff --git a/app/views/layouts/_lte_admin_navbar.html.erb b/app/views/layouts/_lte_admin_navbar.html.erb index 63e7cc4c10..d134860a8e 100644 --- a/app/views/layouts/_lte_admin_navbar.html.erb +++ b/app/views/layouts/_lte_admin_navbar.html.erb @@ -16,7 +16,7 @@
    • - <%= fa_icon "cog" %> <%= navigation_link_to "Account Settings", edit_user_registration_path(organization_name: nil) %> + <%= fa_icon "cog" %> <%= navigation_link_to "Account Settings", edit_user_registration_path %>
  • @@ -26,7 +26,7 @@
    - <%= delete_button_to destroy_user_session_path(organization_name: nil), {text: "Log out", icon: "sign-out", no_confirm: true, size: "md"} %> + <%= delete_button_to destroy_user_session_path, {text: "Log out", icon: "sign-out", no_confirm: true, size: "md"} %>
    diff --git a/app/views/layouts/_lte_admin_sidebar.html.erb b/app/views/layouts/_lte_admin_sidebar.html.erb index 889502b3b3..8a78402327 100644 --- a/app/views/layouts/_lte_admin_sidebar.html.erb +++ b/app/views/layouts/_lte_admin_sidebar.html.erb @@ -1,14 +1,14 @@ - - - - - - @@ -63,7 +63,7 @@ <%= current_user.display_name %> diff --git a/app/views/layouts/_lte_sidebar.html.erb b/app/views/layouts/_lte_sidebar.html.erb index 0e3ff132bb..6fea6b23db 100644 --- a/app/views/layouts/_lte_sidebar.html.erb +++ b/app/views/layouts/_lte_sidebar.html.erb @@ -1,6 +1,6 @@