diff --git a/app/controllers/transfers_controller.rb b/app/controllers/transfers_controller.rb index e227e16990..e94949a272 100644 --- a/app/controllers/transfers_controller.rb +++ b/app/controllers/transfers_controller.rb @@ -11,8 +11,8 @@ def index .during(helpers.selected_range) @selected_from = filter_params[:from_location] @selected_to = filter_params[:to_location] - @from_storage_locations = Transfer.storage_locations_transferred_from_in(current_organization) - @to_storage_locations = Transfer.storage_locations_transferred_to_in(current_organization) + @from_storage_locations = StorageLocation.with_transfers_from(current_organization) + @to_storage_locations = StorageLocation.with_transfers_to(current_organization) respond_to do |format| format.html format.csv { send_data Transfer.generate_csv(@transfers), filename: "Transfers-#{Time.zone.today}.csv" } diff --git a/app/helpers/date_range_helper.rb b/app/helpers/date_range_helper.rb index 8d3db3a7a5..cc73af80d8 100644 --- a/app/helpers/date_range_helper.rb +++ b/app/helpers/date_range_helper.rb @@ -18,6 +18,10 @@ def date_range_label "this month" when "last month" "last month" + when "last 12 months" + "last 12 months" + when "prior year" + "prior year" else selected_range_described end diff --git a/app/javascript/application.js b/app/javascript/application.js index c94704d50d..bb8df687e1 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -99,7 +99,9 @@ $(document).ready(function(){ 'This Month': [today.startOf('month').toJSDate(), today.endOf('month').toJSDate()], 'Last Month': [today.minus({'months': 1}).startOf('month').toJSDate(), today.minus({'month': 1}).endOf('month').toJSDate()], - 'This Year': [today.startOf('year').toJSDate(), today.endOf('year').toJSDate()] + 'Last 12 Months': [today.minus({'months': 12}).plus({'days': 1}).toJSDate(), today.toJSDate()], + 'Prior Year': [today.startOf('year').minus({'years': 1}).toJSDate(), today.minus({'year': 1}).endOf('year').toJSDate()], + 'This Year': [today.startOf('year').toJSDate(), today.endOf('year').toJSDate()], } } }); diff --git a/app/models/storage_location.rb b/app/models/storage_location.rb index 6ad65c7fa9..94f2a0a23e 100644 --- a/app/models/storage_location.rb +++ b/app/models/storage_location.rb @@ -34,11 +34,11 @@ class StorageLocation < ApplicationRecord has_many :distributions, dependent: :destroy has_many :transfers_from, class_name: "Transfer", inverse_of: :from, - foreign_key: :id, + foreign_key: :from_id, dependent: :destroy has_many :transfers_to, class_name: "Transfer", inverse_of: :to, - foreign_key: :id, + foreign_key: :to_id, dependent: :destroy has_many :kit_allocations, dependent: :destroy @@ -55,6 +55,12 @@ class StorageLocation < ApplicationRecord scope :alphabetized, -> { order(:name) } scope :for_csv_export, ->(organization, *) { where(organization: organization) } scope :active_locations, -> { where(discarded_at: nil) } + scope :with_transfers_to, ->(organization) { + joins(:transfers_to).where(organization_id: organization.id).distinct.order(:name) + } + scope :with_transfers_from, ->(organization) { + joins(:transfers_from).where(organization_id: organization.id).distinct.order(:name) + } # @param organization [Organization] # @param inventory [View::Inventory] diff --git a/app/models/transfer.rb b/app/models/transfer.rb index 3945ac776f..4237f5fef0 100644 --- a/app/models/transfer.rb +++ b/app/models/transfer.rb @@ -29,14 +29,6 @@ class Transfer < ApplicationRecord } scope :during, ->(range) { where(created_at: range) } - def self.storage_locations_transferred_to_in(organization) - includes(:to).where(organization_id: organization.id).distinct(:to_id).collect(&:to).uniq.sort_by(&:name) - end - - def self.storage_locations_transferred_from_in(organization) - includes(:from).where(organization_id: organization.id).distinct(:from_id).collect(&:from).uniq.sort_by(&:name) - end - validates :from, :to, :organization, presence: true validate :storage_locations_belong_to_organization validate :storage_locations_must_be_different diff --git a/app/views/partners/profiles/_actions.html.erb b/app/views/partners/profiles/_actions.html.erb new file mode 100644 index 0000000000..05ef380746 --- /dev/null +++ b/app/views/partners/profiles/_actions.html.erb @@ -0,0 +1,31 @@ +<%# locals: (partner:) %> + +
+
+
+
+
+ +
+
+
+
+
diff --git a/app/views/partners/profiles/show.html.erb b/app/views/partners/profiles/show.html.erb index 225bf4009e..c9e50d1ad9 100644 --- a/app/views/partners/profiles/show.html.erb +++ b/app/views/partners/profiles/show.html.erb @@ -22,6 +22,8 @@ +<%= render 'actions', partner: current_partner %> +
@@ -42,37 +44,4 @@
-
-
-
- -
- -
- -
- -
- -
- -
-
+<%= render 'actions', partner: current_partner %> diff --git a/app/views/users/mailer/invitation_instructions.html.erb b/app/views/users/mailer/invitation_instructions.html.erb index 7d959b4882..87c7f64cc8 100644 --- a/app/views/users/mailer/invitation_instructions.html.erb +++ b/app/views/users/mailer/invitation_instructions.html.erb @@ -374,7 +374,6 @@ <% if @resource.invitation_due_at %>

<%= t("devise.mailer.invitation_instructions.accept_until", due_date: l(@resource.invitation_due_at, format: :'devise.mailer.invitation_instructions.accept_until_format')) %>

<% end %> -

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 <%= link_to "here", new_user_password_url %> 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.

diff --git a/app/views/users/mailer/reset_password_instructions.html.erb b/app/views/users/mailer/reset_password_instructions.html.erb index 6656a60751..1352deb8fa 100644 --- a/app/views/users/mailer/reset_password_instructions.html.erb +++ b/app/views/users/mailer/reset_password_instructions.html.erb @@ -6,7 +6,7 @@

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

-

For security reasons these invitations expire. This invitation will expire in 8 hours or if a new password reset is triggered.

+

For security reasons these invitations expire. This invitation will expire in 6 hours or if a new password reset is triggered.

If your invitation has an expired message, go <%= link_to "here", new_user_password_url %> and enter your email address to reset your password.

If you didn't request this, please ignore this email.

Your password won't change until you access the link above and create a new one.

diff --git a/clock.rb b/clock.rb index fcf3e803b4..b19137dd1c 100644 --- a/clock.rb +++ b/clock.rb @@ -35,6 +35,6 @@ module Clockwork end every(1.day, "Send reminder emails", at: "12:00", if: lambda { |_| Rails.env.production? }) do - ReminderDeadlineJob.perform_now + ReminderDeadlineJob.perform_later end end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 3951cd3f08..f18cde16ff 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -120,7 +120,7 @@ # The period the generated invitation token is valid, after # this period, the invited resource won't be able to accept the invitation. # When invite_for is 0 (the default), the invitation won't expire. - # config.invite_for = 2.weeks + config.invite_for = 2.weeks # Number of invitations users can send. # - If invitation_limit is nil, there is no limit for invitations, users can diff --git a/config/locales/devise_invitable.en.yml b/config/locales/devise_invitable.en.yml index 7d5fc7c09c..ba47068d82 100644 --- a/config/locales/devise_invitable.en.yml +++ b/config/locales/devise_invitable.en.yml @@ -21,7 +21,7 @@ en: hello: "Hello %{email}" someone_invited_you: "Someone has invited you to %{url}, you can accept it through the link below." accept: "Accept invitation" - accept_until: "This invitation will be due in %{due_date}." + accept_until: "This invitation will expire at %{due_date} GMT or if a new password reset is triggered." ignore: "If you don't want to accept the invitation, please ignore this email.
\nYour account won't be created until you access the link above and set your password." time: formats: diff --git a/spec/mailers/custom_devise_mailer_spec.rb b/spec/mailers/custom_devise_mailer_spec.rb index 842c9d7541..4943481cd8 100644 --- a/spec/mailers/custom_devise_mailer_spec.rb +++ b/spec/mailers/custom_devise_mailer_spec.rb @@ -34,15 +34,21 @@ end context "when user is invited" do - let(:user) { create(:user) } + let(:invitation_sent_at) { Time.zone.now } + let(:user) { create(:user, invitation_sent_at: invitation_sent_at) } it "invites to user" do expect(mail.subject).to eq("Your Human Essentials App Account Approval") expect(mail.html_part.body).to include("Your request has been approved and you're invited to become an user of the Human Essentials inventory management system!") end - it "has invite expiration message" do - expect(mail.html_part.body).to include("For security reasons these invitations expire. This invitation will expire in 8 hours or if a new password reset is triggered.") + it "has invite expiration message and reset instructions" do + expect(mail.html_part.body).to include("This invitation will expire at #{user.invitation_due_at.strftime("%B %d, %Y %I:%M %p")} GMT or if a new password reset is triggered.") + end + + it "has reset instructions" do + expect(mail.html_part.body).to match(%r{

If your invitation has an expired message, go here and enter your email address to reset your password.

}) + expect(mail.html_part.body).to include("Feel free to ignore this email if you are not interested or if you feel it was sent by mistake.") end end end diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 236ecd1147..a01a01971e 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -22,7 +22,7 @@ let(:mail) { ActionMailer::Base.deliveries.last } it "sends an email with instructions" do - expect(mail.body.encoded).to include("For security reasons these invitations expire. This invitation will expire in 8 hours or if a new password reset is triggered.") + expect(mail.body.encoded).to include("For security reasons these invitations expire. This invitation will expire in 6 hours or if a new password reset is triggered.") end end end diff --git a/spec/models/storage_location_spec.rb b/spec/models/storage_location_spec.rb index 332a1eed0b..5ebdb20ba2 100644 --- a/spec/models/storage_location_spec.rb +++ b/spec/models/storage_location_spec.rb @@ -52,6 +52,32 @@ expect(results.length).to eq(1) expect(results.first.discarded_at).to be_nil end + + it "->with_transfers_to yields storage locations with transfers to an organization" do + storage_location1 = create(:storage_location, name: "loc1", organization: organization) + storage_location2 = create(:storage_location, name: "loc2", organization: organization) + storage_location3 = create(:storage_location, name: "loc3", organization: organization) + storage_location4 = create(:storage_location, name: "loc4", organization: create(:organization)) + storage_location5 = create(:storage_location, name: "loc5", organization: storage_location4.organization) + create(:transfer, from: storage_location3, to: storage_location1, organization: organization) + create(:transfer, from: storage_location3, to: storage_location2, organization: organization) + create(:transfer, from: storage_location5, to: storage_location4, organization: storage_location4.organization) + + expect(StorageLocation.with_transfers_to(organization).to_a).to match_array([storage_location1, storage_location2]) + end + + it "->with_transfers_from yields storage locations with transfers from an organization" do + storage_location1 = create(:storage_location, name: "loc1", organization: organization) + storage_location2 = create(:storage_location, name: "loc2", organization: organization) + storage_location3 = create(:storage_location, name: "loc3", organization: organization) + storage_location4 = create(:storage_location, name: "loc4", organization: create(:organization)) + storage_location5 = create(:storage_location, name: "loc5", organization: storage_location4.organization) + create(:transfer, from: storage_location3, to: storage_location1, organization: organization) + create(:transfer, from: storage_location3, to: storage_location2, organization: organization) + create(:transfer, from: storage_location5, to: storage_location4, organization: storage_location4.organization) + + expect(StorageLocation.with_transfers_from(organization).to_a).to match_array([storage_location3]) + end end context "Methods >" do diff --git a/spec/models/transfer_spec.rb b/spec/models/transfer_spec.rb index 2ca192938d..e3f9d17151 100644 --- a/spec/models/transfer_spec.rb +++ b/spec/models/transfer_spec.rb @@ -69,21 +69,6 @@ end end - context "Methods >" do - it "`self.storage_locations_transferred_to` and `..._from` constrains appropriately" do - storage_location1 = create(:storage_location, name: "loc1", organization: organization) - storage_location2 = create(:storage_location, name: "loc2", organization: organization) - storage_location3 = create(:storage_location, name: "loc3", organization: organization) - storage_location4 = create(:storage_location, name: "loc4", organization: create(:organization)) - storage_location5 = create(:storage_location, name: "loc5", organization: storage_location4.organization) - create(:transfer, from: storage_location3, to: storage_location1, organization: organization) - create(:transfer, from: storage_location3, to: storage_location2, organization: organization) - create(:transfer, from: storage_location5, to: storage_location4, organization: storage_location4.organization) - expect(Transfer.storage_locations_transferred_to_in(organization).to_a).to match_array([storage_location1, storage_location2]) - expect(Transfer.storage_locations_transferred_from_in(organization).to_a).to match_array([storage_location3]) - end - end - describe "versioning" do it { is_expected.to be_versioned } end diff --git a/spec/system/distributions_by_county_system_spec.rb b/spec/system/distributions_by_county_system_spec.rb index a9ca7c8de9..82006de4a3 100644 --- a/spec/system/distributions_by_county_system_spec.rb +++ b/spec/system/distributions_by_county_system_spec.rb @@ -1,8 +1,8 @@ RSpec.feature "Distributions by County", type: :system do include_examples "distribution_by_county" - let(:year) { Time.current.year } - let(:issued_at_last_year) { Time.current.utc.change(year: year - 1).to_datetime } + let(:current_year) { Time.current.year } + let(:issued_at_last_year) { Time.current.utc.change(year: current_year - 1).to_datetime } before do sign_in(user) @@ -18,8 +18,9 @@ partner_1.profile.served_areas.each do |served_area| expect(page).to have_text(served_area.county.name) end - expect(page).to have_text("50", count: 4) - expect(page).to have_text("$525.00", count: 4) + + expect(page).to have_css("table tbody tr td", text: "50", exact_text: true, count: 4) + expect(page).to have_css("table tbody tr td", text: "$525.00", exact_text: true, count: 4) end it("works for this year") do @@ -31,8 +32,53 @@ partner_1.profile.served_areas.each do |served_area| expect(page).to have_text(served_area.county.name) end - expect(page).to have_text("25", count: 4) - expect(page).to have_text("$262.50", count: 4) + + expect(page).to have_css("table tbody tr td", text: "25", exact_text: true, count: 4) + expect(page).to have_css("table tbody tr td", text: "$262.50", exact_text: true, count: 4) + end + + it("works for prior year") do + # Should NOT return distribution issued before previous calendar year + last_day_of_two_years_ago = Time.current.utc.change(year: current_year - 2, month: 12, day: 31).to_datetime + create(:distribution, :with_items, item: item_1, organization: user.organization, partner: partner_1, issued_at: last_day_of_two_years_ago) + + # Should return distribution issued during previous calendar year + one_year_ago = issued_at_last_year + create(:distribution, :with_items, item: item_1, organization: user.organization, partner: partner_1, issued_at: one_year_ago) + + # Should NOT return distribution issued after previous calendar year + first_day_of_current_year = Time.current.utc.change(year: current_year, month: 1, day: 1).to_datetime + create(:distribution, :with_items, item: item_1, organization: user.organization, partner: partner_1, issued_at: first_day_of_current_year) + + visit_distribution_by_county_with_specified_date_range("Prior Year") + + partner_1.profile.served_areas.each do |served_area| + expect(page).to have_text(served_area.county.name) + end + expect(page).to have_css("table tbody tr td", text: "25", exact_text: true, count: 4) + expect(page).to have_css("table tbody tr td", text: "$262.50", exact_text: true, count: 4) + end + + it("works for last 12 months") do + # Should NOT return disitribution issued before 12 months ago + one_year_and_one_day_ago = 1.year.ago.prev_day.to_datetime + create(:distribution, :with_items, item: item_1, organization: user.organization, partner: partner_1, issued_at: one_year_and_one_day_ago) + + # Should return distribution issued during previous 12 months + today = issued_at_present + create(:distribution, :with_items, item: item_1, organization: user.organization, partner: partner_1, issued_at: today) + + # Should NOT return distribution issued in the future + tomorrow = 1.day.from_now.to_datetime + create(:distribution, :with_items, item: item_1, organization: user.organization, partner: partner_1, issued_at: tomorrow) + + visit_distribution_by_county_with_specified_date_range("Last 12 Months") + + partner_1.profile.served_areas.each do |served_area| + expect(page).to have_text(served_area.county.name) + end + expect(page).to have_css("table tbody tr td", text: "25", exact_text: true, count: 4) + expect(page).to have_css("table tbody tr td", text: "$262.50", exact_text: true, count: 4) end end diff --git a/spec/system/partners/approval_process_spec.rb b/spec/system/partners/approval_process_spec.rb index 26d69cbcd4..24b78341f2 100644 --- a/spec/system/partners/approval_process_spec.rb +++ b/spec/system/partners/approval_process_spec.rb @@ -28,7 +28,7 @@ before do click_on 'My Profile' assert page.has_content? 'Uninvited' - click_on 'Update Information' + all('a', text: 'Update Information').last.click fill_in 'Other Agency Type', with: 'Lorem' @@ -41,7 +41,7 @@ click_on 'Update Information' assert page.has_content? 'Details were successfully updated.' - find_link(text: 'Submit for Approval').click + all('a', text: 'Submit for Approval').last.click assert page.has_content? 'You have submitted your details for approval.' assert page.has_content? 'Awaiting Review' end @@ -77,7 +77,7 @@ login_as(partner_user) visit partner_user_root_path click_on 'My Profile' - click_on 'Submit for Approval' + all('a', text: 'Submit for Approval').last.click end it "should render an error message", :aggregate_failures do