Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

4052 disallow deleting transfers if audit #4057

Merged

Conversation

MooseCowBear
Copy link
Contributor

Resolves #4052

Description

This PR adds a method deletable? to the Transfer model to check if any audits have occurred 1. since the transfer was created at either the transfer's to location or its from location, and 2. that the audit involved items that were transferred. It updates the transfer_row partial to only display a delete button if there are no such audits.

It also adds a check that the transfer is deletable in the DestroyTransferService so that the controller wouldn't destroy a transfer that shouldn't be deleted. (If you decide you don't want this, I'm happy to remove it.)

The query for audits is kind of beastly. I couldn't think of a better way that would keep the db queries to a minimum.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

I added unit tests for correctness of the method and the change to the service, and a system test for the conditional delete button rendering.

Screenshots

Copy link
Collaborator

@dorner dorner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution! I had some suggestions.

@@ -56,6 +56,11 @@ def csv_export_attributes
]
end

def deletable?
item_ids = line_items.pluck(:item_id)
Audit.where(storage_location_id: [to_id, from_id]).where(created_at: created_at..).joins(:line_items).where(line_items: {item_id: item_ids}).empty?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we split this line up to make it more readable?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I'm not sure this belongs inside Transfer since it really isn't checking transfers at all. I think it'd make more sense to move this to the Audit class as a method, e.g. Audit.since?(self) and it can take an Itemizable as its parameter.

@@ -4,6 +4,8 @@ def initialize(transfer_id:)
end

def call
raise StandardError.new "Transfer cannot be deleted" unless transfer.deletable?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to explicitly call out StandardError:

raise "Transfer cannot be deleted" unless transfer.deletable?

But can you add more information here? "Transfer cannot be deleted since an audit was performed after it."

@@ -135,5 +135,21 @@ def create_transfer(amount, from_name, to_name)
end

it_behaves_like "Date Range Picker", Transfer

it "only displays delete button for deletable transfers" do
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this doesn't have any interaction, it can be a request spec (which uses a lot less resources).

@MooseCowBear
Copy link
Contributor Author

I'll get to work on the changes. One question @dorner, how do you feel about splitting up the query in Audit like:

def self.at(*location_ids)
  where(storage_location_id: location_ids)
end

def self.since?(itemizable)
  item_ids = itemizable.line_items.pluck(:item_id)
  where(created_at: itemizable.created_at..)
    .joins(:line_items)
    .where(line_items: {item_id: item_ids})
    .empty?
end

And then transfer's deletable method could be:

def deletable?
  Audit.at(to_id, from_id).since?(self)
end

Since I believe only transfers have both a to and from location?

@dorner
Copy link
Collaborator

dorner commented Jan 25, 2024

I don't think we need to split it up since we only have one use case. But you can pass in a list of location IDs as an array.

@@ -4,6 +4,8 @@ def initialize(transfer_id:)
end

def call
raise "Transfer cannot be deleted because an audit was performed after it" unless transfer.deletable?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... I'm torn here because the name of the method is generic ("can this be deleted") but the error assumes there is only one possible reason the audit couldn't be deleted. I think it's OK to just have this service call Audit.since? directly rather than deletable?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean that the transfer couldn't be deleted, yes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dorner Originally, I was thinking that a delete button would only appear if the transfer met the criteria for deletability and that the view might look cleaner with a deletable? method, but since that's not the case there doesn't seem to be a reason for it.

@@ -5,7 +5,9 @@
<td><%= transfer_row.comment || "none" %></td>
<td><%= transfer_row.line_items.total %></td>
<td>
<%= delete_button_to transfer_path(transfer_row.id), { confirm: "Are you sure you want to permanently remove this transfer?" } %>
<% if transfer_row.deletable? %>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general we don't do validations before we perform the action. @cielf would it be OK to only have this raise an error and not try to change the view beforehand? I think this is an edge case and I don't want to overthink this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's definitely an edge case. I'm ok with that -- we need to be pretty clear on why they can't in the error, though.

@@ -5,7 +5,9 @@
<td><%= transfer_row.comment || "none" %></td>
<td><%= transfer_row.line_items.total %></td>
<td>
<%= delete_button_to transfer_path(transfer_row.id), { confirm: "Are you sure you want to permanently remove this transfer?" } %>
<% if transfer_row.deletable? %>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general we don't do validations before we perform the action. @cielf would it be OK to only have this raise an error and not try to change the view beforehand? I think this is an edge case and I don't want to overthink this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's definitely an edge case. I'm ok with that -- we need to be pretty clear on why they can't in the error, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cielf Is "Transfer cannot be deleted because an audit was performed after it" a clear enough message? Or maybe something more specific like "This transfer cannot be deleted because an audit involving its items has already been performed"? I can't tell if being more specific makes the error message clearer.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MooseCowBear I don't want to spend too much time on the wording because this is a bit of an edge case. How about "We can't delete this transfer because its items were audited since you made the transfer."

@dorner
Copy link
Collaborator

dorner commented Feb 7, 2024

Looks good - thanks!

@dorner dorner merged commit d5e0341 into rubyforgood:main Feb 7, 2024
11 checks passed
Copy link
Contributor

@MooseCowBear: Your PR 4052 disallow deleting transfers if audit is part of today's Human Essentials production release: 2024.02.11.
Thank you very much for your contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Disallow deleting transfers if there has been an audit in either location since the transfer was made
3 participants