-
-
Notifications
You must be signed in to change notification settings - Fork 527
/
Copy pathexport_distributions_csv_service.rb
155 lines (133 loc) · 5.06 KB
/
export_distributions_csv_service.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
module Exports
class ExportDistributionsCSVService
include DistributionHelper
def initialize(distributions:, organization:, filters: [])
# Currently, the @distributions are already loaded by the controllers that are delegating exporting
# to this service object; this is happening within the same request/response cycle, so it's already
# in memory, so we can pass that collection in directly. Should this be moved to a background / async
# job, we will need to pass in a collection of IDs instead.
# Also, adding in a `filters` parameter to make the filters that have been used available to this
# service object.
@distributions = distributions
@filters = filters
@organization = organization
end
def generate_csv
csv_data = generate_csv_data
CSV.generate(headers: true) do |csv|
csv_data.each { |row| csv << row }
end
end
def generate_csv_data
csv_data = []
csv_data << headers
distributions.each do |distribution|
csv_data << build_row_data(distribution)
end
csv_data
end
private
attr_reader :distributions
def headers
# Build the headers in the correct order
base_headers + item_headers
end
# Returns a Hash of keys to indexes so that obtaining the index
# doesn't require a linear scan.
def headers_with_indexes
@headers_with_indexes ||= headers.each_with_index.to_h
end
# This method keeps the base headers associated with the lambdas
# for extracting the values for the base columns from the given
# distribution.
#
# Doing so (as opposed to expressing them in distinct methods) makes
# it less likely that a future edit will inadvertently modify the
# order of the headers in a way that isn't also reflected in the
# values for the these base columns.
#
# Reminder: Since Ruby 1.9, Hashes are ordered based on insertion
# (or on the order of the literal).
def base_table
{
"Partner" => ->(distribution) {
distribution.partner.name
},
"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
},
base_item_header_col_name => ->(distribution) {
# filter the line items by item id (for selected item filter) to
# get the number of items
if @filters[:by_item_id].present?
distribution.line_items.where(item_id: @filters[:by_item_id].to_i).total
else
distribution.line_items.total
end
},
"Total Value" => ->(distribution) {
Money.from_cents(distribution.line_items.total_value)
},
"Delivery Method" => ->(distribution) {
distribution.delivery_method
},
"Shipping Cost" => ->(distribution) {
distribution_shipping_cost(distribution.shipping_cost)
},
"Status" => ->(distribution) {
distribution.state
},
"Agency Representative" => ->(distribution) {
distribution.agency_rep
},
"Comments" => ->(distribution) {
distribution.comment
}
}
end
# if filtered based on an item, change the column accordingly
def base_item_header_col_name
@filters[:by_item_id].present? ? "Total Number of #{filtered_item.name}" : "Total Items"
end
def filtered_item
@filtered_item ||= Item.find(@filters[:by_item_id].to_i)
end
def base_headers
base_table.keys
end
def item_headers
return @item_headers if @item_headers
@item_headers = @organization.items.select("DISTINCT ON (LOWER(name)) items.name").order("LOWER(name) ASC").map(&:name)
@item_headers = @item_headers.flat_map { |h| [h, "#{h} In-Kind Value"] } if @organization.include_in_kind_values_in_exported_files
@item_headers
end
def build_row_data(distribution)
row = base_table.values.map { |closure| closure.call(distribution) }
row += make_item_quantity_and_value_slots
distribution.line_items.each do |line_item|
item_name = line_item.item.name
item_column_idx = headers_with_indexes[item_name]
next unless item_column_idx
row[item_column_idx] += line_item.quantity
row[item_column_idx + 1] += Money.new(line_item.value_per_line_item) if @organization.include_in_kind_values_in_exported_files
end
convert_to_dollar(row)
end
def make_item_quantity_and_value_slots
slots = Array.new(item_headers.size, 0)
slots = slots.map.with_index { |value, index| index.odd? ? Money.new(0) : value } if @organization.include_in_kind_values_in_exported_files
slots
end
def convert_to_dollar(row)
row.map do |column|
column.is_a?(Money) ? column.to_f : column
end
end
end
end