Skip to content

Add an option to control whether "in-kind value" of each item should be added in export of donations and distributions#5122

Merged
dorner merged 15 commits into
rubyforgood:mainfrom
nozomirin:4858-add-an-option-to-include-in-kind-values-in-export-of-donations-and-distributions-new
May 15, 2025
Merged

Add an option to control whether "in-kind value" of each item should be added in export of donations and distributions#5122
dorner merged 15 commits into
rubyforgood:mainfrom
nozomirin:4858-add-an-option-to-include-in-kind-values-in-export-of-donations-and-distributions-new

Conversation

@nozomirin
Copy link
Copy Markdown
Contributor

@nozomirin nozomirin commented Mar 27, 2025

Resolves #4858

Sorry for leaving it untouched for a long time. Since it is far behind the main branch, I made a new PR.
the original one is #4897.

Description

  1. add include_in_kind_values_in_exported_files column in Organizations table.
  2. add radio buttons in view/organization/edit.html.erb.
  3. make some changes to add in-kind value for each item in exported donation and distribution files when the Include in-kind value in donation and distribution exports? is set to yes.
  4. add explanations in docs/user_guide/bank/exports.md

Note: I changed the "In-Kind Value" header to "In-Kind Total" in the exported donation file to avoid ambiguity. However, I used "Header + In-Kind Value" for the in-kind value of items, so perhaps the change is unnecessary. If there’s anything incorrect or unclear, please let me know.

Type of change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

How Has This Been Tested?

bundle exec rspec spec/services/exports/export_distributions_csv_service_spec.rb 
bundle exec rspec spec/services/exports/export_donations_csv_service_spec.rb

Screenshots

…be added in export of donations and distributions
@cielf cielf requested review from cielf and dorner March 27, 2025 13:37
cielf
cielf previously requested changes Mar 27, 2025
Copy link
Copy Markdown
Collaborator

@cielf cielf left a comment

Choose a reason for hiding this comment

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

Hey @nozomirin It still works well!

I can see, though, that we're going to have a conflict with the work in #5116, where we are reorganizing the organizations page..

With that in place, I would want the new field here to be in its own section, "Exports" on those screens. (refer to #5116 for what weight the heading should be)

Ideally, I would want it that section just above the "Annual Survey" section.

This is a change that is needed before merge, but might be easiest to do once #5116 is merged into main (which I anticipate this week).

I don't see any reason that it would hold up our technical re-review, so over to @dorner or @awwaiid.

@cielf cielf requested a review from awwaiid March 27, 2025 13:52
@nozomirin
Copy link
Copy Markdown
Contributor Author

@cielf OK, I will make some adjustments after #5116 is merged.

@nozomirin
Copy link
Copy Markdown
Contributor Author

There were no errors when I ran bundle exec rspec on my local machine. It seems the issue came from the first merge from main.

@nozomirin
Copy link
Copy Markdown
Contributor Author

@cielf Please check whether it fits your expectations.

@cielf
Copy link
Copy Markdown
Collaborator

cielf commented Apr 5, 2025

Yup -- I saw some things that are out of scope, and will be addressed in a separate issue, but your changes LGTM on manual testing. Over to @dorner for technical input

@cielf
Copy link
Copy Markdown
Collaborator

cielf commented Apr 5, 2025

@nozomirin One very small change to the docs --- please change "Configuring exports" to "Exports" - so it matches what the bank user will be seeing. Thanks

@cielf
Copy link
Copy Markdown
Collaborator

cielf commented Apr 6, 2025

LGTM.

class AddIncludeInKindValuesInExportedFilesToOrganizations < ActiveRecord::Migration[8.0]
def change
add_column :organizations, :include_in_kind_values_in_exported_files, :boolean
add_check_constraint :organizations, "include_in_kind_values_in_exported_files IS NOT NULL", name: "include_in_kind_values_in_exported_files_not_null", validate: false
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This seems a very awkward way of just saying null: false...?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Adding the NOT NULL constraint directly will lock the table. This method helps avoid that. I don't know how big the table will be, so I think it's better to avoid it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Organizations is a tiny table. You can lock it.

@distribution_totals = DistributionTotalsService.call(Distribution.where(organization:).class_filter(filters))

@export_line_item_adder = Exports::ExportLineItemColumnsAdder.new
@export_line_item_adder.register(Exports::ExportInKindTotalValue) if @organization.include_in_kind_values_in_exported_files
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This seems like an overly complex way of adding a column to an export. You're basically turning a single if statement into a factory pattern. Can you explain why you did it this way?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If you have other attributes that need to be added to the exported CSV, just create another class and register it here. There's no need to modify any other code. Since both distribution and donation share the same export functionality, I extracted it to avoid duplication.

Copy link
Copy Markdown
Collaborator

@dorner dorner Apr 16, 2025

Choose a reason for hiding this comment

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

This is a case of unnecessary DRY along with a big helping of YAGNI. As far as we know there are no additional attributes that will need this, and in general the idea that "attributes in a CSV" is a generic feature is probably not a helpful construct. I'd much prefer a simpler approach even if it means a little bit of duplicated code.

@registry = []
end

def headers(item_headers)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

In general this code is written in an unusual manner... I'd need to see a bunch more comments here since I can't really understand what some of it is supposed to be doing. What does "registry" contain? What kind of "row" does get_row return? What additional_columns do we need and how does that relate to the registry...?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The registry contains classes that implement Exports::ExportColumnsBase. Each element in the registry adds a column after each "line_item" column.
get_row returns a row like this:
|item A|col1|col2|item B|col1|col2|...
These col1, col2, etc., are the additional_columns.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The explanation has to be in comments, not in the PR.

But in general, if you have to explain what the code is doing via comments, it might be doing things in a way that isn't straightforward.

distribution.created_at.strftime("%m/%d/%Y"),
distribution.issued_at.strftime("%m/%d/%Y"),
distribution.storage_location.name,
distribution.line_items.where(item_id: item_id).total,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Using calculated values in specs make it not only hard to follow but also a lot flakier. I've had many instances where a value passed because it happened to match the expected value, but was actually reading the wrong source. Can you explicitly create the things you're trying to test and expect the hardcoded values from those things?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok, I will make some changes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It seems really annoying to do that with FactoryBot...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Please check whether it fits your expectations.

"$#{expected_dist[:shipping_cost].to_f}",
expected_dist[:state],
expected_dist[:agency_rep],
expected_dist[:comment],
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is there a reason you can't just generate a flat CSV string and compare the results against it instead of going row by row?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This comment still stands - see e.g.

it "generates csv with items that are also inactive" do

@nozomirin
Copy link
Copy Markdown
Contributor Author

If you have time, please review this. @dorner

let(:duplicate_item) { create(:item, name: "Dupe Item", value_in_cents: 300, organization: organization) }

let(:items_lists) do
let(:expected_distributions) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is there a reason you're not just using FactoryBot.build / FactoryBot.create here?

This feels a lot like a Go table test instead of a Rails test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Please check whether it fits your expectations.

"$#{expected_dist[:shipping_cost].to_f}",
expected_dist[:state],
expected_dist[:agency_rep],
expected_dist[:comment],
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This comment still stands - see e.g.

it "generates csv with items that are also inactive" do

@nozomirin nozomirin force-pushed the 4858-add-an-option-to-include-in-kind-values-in-export-of-donations-and-distributions-new branch from 60d1fee to 6dae33a Compare May 3, 2025 15:39
Copy link
Copy Markdown
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.

A bit more explicitness on the tests please!


it 'should match the expected content without in-kind value of each item for the csv' do
csv = <<~CSV
#{expected_headers.join(",")}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we make the entire value explicit please? We should know/specify the partner names and the storage location names. We should also know the headers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK, I just thought it would be really long.

expect(subject[idx + 1]).to eq(row)
csv = CSV.generate do |csv|
csv << expected_headers
csv << ["Product Drive", "2025-01-01", source_name(donations[0]), storage_location.name, 15, 2, 94.0, "It's a fine day for diapers.", 7, "70.00", 0, "0.00", 0, "0.00", 8, "24.00", 0, "0.00"]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Here especially - we're calculating values rather than using them directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The Faker gem generates names with commas, so I used CSV.generate to prevent test failures.
I’ve now replaced the comma in the name with a semicolon—please check if that's OK.

Copy link
Copy Markdown
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.

I'm good with this. In the future, if you need to compare/assert against any value, I'd recommend hardcoding that value instead of relying on Faker.

@dorner dorner merged commit 0acf065 into rubyforgood:main May 15, 2025
12 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

@nozomirin: Your PR Add an option to control whether "in-kind value" of each item should be added in export of donations and distributions is part of today's Human Essentials production release: 2025.05.18.
Thank you very much for your contribution!

GiovannyCordeiro pushed a commit to GiovannyCordeiro/human-essentials that referenced this pull request Jun 6, 2025
…be added in export of donations and distributions (rubyforgood#5122)

* Add an option to control whether "in-kind value" of each item should be added in export of donations and distributions

* Change 'Configuring exports' to 'Exports' in getting_started_customization.md

* Fix: Explicitly define test data and address money conversion problems.

* fix: linter errors.

* Fix: simplify the include_in_kind_values_in_exported_files column migration.

* Fix: simplify the way to add In-Kind Total Value in csv file.

* Fix: change from asserting each line in the CSV to asserting the entire CSV at once.

* Fix: use <<~CSV...CSV to replace array based csv construction.

* Fix: solve test error when the donation_site name contains comma.

* Fix: make export csv header more explicit in distribution and donation tests.
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.

Add an option to include in-kind values in export of donations and distributions

3 participants