Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions app/models/adjustment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,13 @@ def split_difference
[increasing_adjustment, decreasing_adjustment]
end

def self.csv_export_headers
["Created", "Organization", "Storage Location", "Comment", "Changes"]
end
def self.generate_csv(adjustments)
return nil if adjustments.empty?

def csv_export_attributes
[
created_at.strftime("%F"),
organization.name,
storage_location.name,
comment,
line_items.count
]
Exports::ExportAdjustmentsCSVService.new(
adjustments: adjustments,
organization: adjustments.first.organization
).generate_csv
end

private
Expand Down
52 changes: 52 additions & 0 deletions app/services/exports/export_adjustments_csv_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
module Exports
class ExportAdjustmentsCSVService
def initialize(adjustments:, organization:)
Comment thread
cyang-el marked this conversation as resolved.
Outdated
@adjustments = adjustments
@organization = organization
end

def generate_csv
CSV.generate(headers: true) do |csv|
generate_csv_data.each { |row| csv << row }
end
end

def generate_csv_data
[headers] + @adjustments.map { |adjustment| build_row(adjustment) }
end

private

def headers
[
"Created date", "Storage Area",
"Comment", "# of changes"
] + item_names
end

def item_names
@organization.items.order(:name).pluck(:name).uniq
end

def build_row(adjustment)
row = [
adjustment.created_at.strftime("%F"),
adjustment.storage_location.name,
adjustment.comment,
adjustment.line_items.count { |item| !item.quantity.eql?(0) }
]

item_quantities = Hash.new(0)

adjustment.line_items.each do |line_item|
item_quantities[line_item.item.name] += line_item.quantity
end

item_names.each do |item_name|
row << item_quantities[item_name]
end

row
end
end
end
7 changes: 4 additions & 3 deletions docs/user_guide/bank/exports.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ The exports available include (in alphabetical order):
Click "Inventory", then "Inventory Adjustments" in the left-hand menu. Then click "Export Adjustments",

### Contents of adjustment export
Creation date, Organization, Storage Area, Comment, # of changes.

[! NOTE] We have improving the adjustments export to include the changes made in each adjustment on our todo list. We'll also remove the organization as redundant information. Please reach out if this is a priority for you.
- Creation date
- Storage Area, Comment
- # of changes
- the quantity of each of your organization's Items in the adjustments

## Annual Survey
### Navigating to export annual survey
Expand Down
84 changes: 84 additions & 0 deletions spec/controllers/adjustments_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
RSpec.describe AdjustmentsController, type: :controller do
Comment thread
cyang-el marked this conversation as resolved.
Outdated
let(:organization) { create(:organization) }
let(:user) { create(:user, organization: organization) }
let(:storage_location) { create(:storage_location, organization: organization) }
let(:item1) { create(:item, name: "Item One", organization: organization) }
let(:item2) { create(:item, name: "Item Two", organization: organization) }

let!(:adjustment1) do
adj = create(:adjustment,
organization: organization,
storage_location: storage_location,
comment: "First adjustment",
created_at: 1.day.ago
)
adj.line_items << build(:line_item, quantity: 10, item: item1, itemizable: adj)
adj.line_items << build(:line_item, quantity: 5, item: item2, itemizable: adj)
adj
end

let!(:adjustment2) do
adj = create(:adjustment,
organization: organization,
storage_location: storage_location,
comment: "Second adjustment",
created_at: 5.days.ago
)
adj.line_items << build(:line_item, quantity: -5, item: item1, itemizable: adj)
adj
end

before do
sign_in(user)
end

describe "GET #index" do
context "with CSV format" do
it "returns a CSV file" do
get :index, format: :csv
expect(response).to be_successful
expect(response.header['Content-Type']).to include 'text/csv'
end

it "includes appropriate headers for adjustments" do
get :index, format: :csv
expect(response.body).to include("Created date")
Comment thread
cyang-el marked this conversation as resolved.
Outdated
expect(response.body).to include("Storage Area")
expect(response.body).to include("Comment")
expect(response.body).to include("# of changes")
expect(response.body).to include(item1.name)
expect(response.body).to include(item2.name)
end

it "includes data from the adjustments" do
get :index, format: :csv
parsed_csv = CSV.parse(response.body, headers: true)

expect(parsed_csv.count).to eq(2)

expect(parsed_csv[0]["Comment"]).to eq(adjustment1.comment)
expect(parsed_csv[1]["Comment"]).to eq(adjustment2.comment)

expect(parsed_csv[0][item1.name]).to eq("10")
expect(parsed_csv[0][item2.name]).to eq("5")
expect(parsed_csv[0]["# of changes"]).to eq("2")

expect(parsed_csv[1][item1.name]).to eq("-5")
expect(parsed_csv[1]["# of changes"]).to eq("1")
end

context "when filtering by date" do
it "returns adjustments filtered by date range" do
start_date = 3.days.ago.to_fs(:date_picker)
end_date = Time.zone.today.to_fs(:date_picker)

get :index, params: { filters: { date_range: "#{start_date} - #{end_date}" } }, format: :csv

parsed_csv = CSV.parse(response.body, headers: true)
expect(parsed_csv.count).to eq(1)
expect(assigns(:adjustments)).to include(adjustment1)
end
end
end
end
end
180 changes: 180 additions & 0 deletions spec/services/exports/export_adjustments_csv_service_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
RSpec.describe Exports::ExportAdjustmentsCSVService, :wip do
# Create organization after items to ensure proper associations
let!(:item1) { create(:item, name: 'item1') }
let!(:item2) { create(:item, name: 'item2') }
let!(:item3) { create(:item, name: 'item3') }
let!(:item4) { create(:item, name: 'item4') }
let!(:item5) { create(:item, :inactive, name: 'item5') }

# Now create organization and associate items with it
let(:organization) do
org = create(:organization)
[item1, item2, item3, item4, item5].each do |item|
item.update!(organization_id: org.id)
end
org
end

let(:sorted_item_names) do
[item1, item2, item3, item4, item5].map(&:name).sort
end

let(:expected_headers) do
[
"Created date", "Storage Area",
"Comment", "# of changes"
] + sorted_item_names
end

let(:storage_location) { create(:storage_location, organization: organization) }
let(:user) { create(:user, organization: organization) }

describe "#generate_csv_data" do
subject { described_class.new(adjustments: adjustments, organization: organization).generate_csv_data }

context "with multiple adjustments and items" do
let(:adjustments) do
[
# 1st adjustment with 2 items
create(:adjustment,
user_id: user.id,
storage_location: storage_location,
organization: organization,
comment: "adjustment 1",
line_items_attributes: [
{item_id: item1.id, quantity: 10},
{item_id: item2.id, quantity: -5}
]),

# 2nd adjustment with 1 item
create(:adjustment,
user_id: user.id,
storage_location: storage_location,
organization: organization,
comment: "adjustment 2",
line_items_attributes: [
{item_id: item3.id, quantity: 3}
]),

# 3rd adjustment with the :with_items trait
create(:adjustment, :with_items,
user_id: user.id,
storage_location: storage_location,
organization: organization,
comment: "adjustment 3",
item: item1,
item_quantity: 7)
]
end

it "should have the expected headers" do
expect(subject[0]).to eq(expected_headers)
end

it "should include the adjustment data in the rows" do
# Check 1st adjustment
expect(subject[1]).to include(
Comment thread
cyang-el marked this conversation as resolved.
Outdated
adjustments[0].created_at.strftime("%F"),
storage_location.name,
"adjustment 1",
2 # Number of items changed
)

# Check 2nd adjustment
expect(subject[2]).to include(
adjustments[1].created_at.strftime("%F"),
storage_location.name,
"adjustment 2",
1 # Number of items changed
)

# Check 3rd adjustment
expect(subject[3]).to include(
adjustments[2].created_at.strftime("%F"),
storage_location.name,
"adjustment 3",
1 # Number of items changed
)
end

it "should include the correct item quantities" do
# Get indexes of item quantity columns
item1_idx = expected_headers.index(item1.name)
item2_idx = expected_headers.index(item2.name)
item3_idx = expected_headers.index(item3.name)
item4_idx = expected_headers.index(item4.name)
item5_idx = expected_headers.index(item5.name)

# Check 1st adjustment
expect(subject[1][item1_idx]).to eq(10)
expect(subject[1][item2_idx]).to eq(-5)
expect(subject[1][item3_idx]).to eq(0)
expect(subject[1][item4_idx]).to eq(0)
expect(subject[1][item5_idx]).to eq(0)

# Check 2nd adjustment
expect(subject[2][item1_idx]).to eq(0)
expect(subject[2][item2_idx]).to eq(0)
expect(subject[2][item3_idx]).to eq(3)
expect(subject[2][item4_idx]).to eq(0)
expect(subject[2][item5_idx]).to eq(0)

# Check 3rd adjustment
expect(subject[3][item1_idx]).to eq(7)
expect(subject[3][item2_idx]).to eq(0)
expect(subject[3][item3_idx]).to eq(0)
expect(subject[3][item4_idx]).to eq(0)
expect(subject[3][item5_idx]).to eq(0)
end

it "should correctly sum up the number of changes" do
idx = expected_headers.index("# of changes")
expect(subject[1][idx]).to eq(2)
expect(subject[2][idx]).to eq(1)
expect(subject[3][idx]).to eq(1)
end
end

context "when there are no adjustments" do
let(:adjustments) { [] }

it "returns only headers row" do
expect(subject.size).to eq(1)
expect(subject[0]).to eq(expected_headers)
end
end
end

describe "#generate_csv" do
subject { described_class.new(adjustments: adjustments, organization: organization).generate_csv }

let(:adjustments) do
[
create(:adjustment,
storage_location: storage_location,
organization: organization,
line_items_attributes: [
{item_id: item1.id, quantity: 111},
{item_id: item2.id, quantity: -7}
])
]
end

it "generates valid CSV data" do
expect(subject).to be_a(String)
parsed_csv = CSV.parse(subject, headers: true)
expect(parsed_csv.headers).to include("Created date",
"Storage Area",
"Comment",
"# of changes",
item1.name,
item2.name,
item3.name,
item4.name)

expect(parsed_csv.first["# of changes"]).to eq("2")
expect(parsed_csv.first[item1.name]).to eq("111")
expect(parsed_csv.first[item2.name]).to eq("-7")
end
end
end
Loading