mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-01 21:47:16 +00:00
165 lines
4.8 KiB
Ruby
165 lines
4.8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Reporting
|
|
class ReportRowsBuilder
|
|
attr_reader :report
|
|
|
|
def initialize(report)
|
|
@report = report
|
|
end
|
|
|
|
# Structured data by groups. This tree is used to render
|
|
# the grouped rows including group header and group summary_row if needed
|
|
def grouped_data
|
|
@grouped_data ||= build_tree(computed_data, report.formatted_rules)
|
|
rescue NotImplementedError
|
|
nil
|
|
end
|
|
|
|
# Array of rows, each row being an OpenStruct with the computed data
|
|
# Exple [
|
|
# { producer: "Freddy Shop", shop: true },
|
|
# { producer: "Mary Farm", shop: false },
|
|
# ]
|
|
def rows
|
|
@rows ||= extract_rows(grouped_data, [])
|
|
end
|
|
|
|
# Array of rows, each row being a simple array with the data
|
|
# Exple [
|
|
# ["Freddy Shop", true],
|
|
# ["Mary Farm", false],
|
|
# ]
|
|
def table_rows
|
|
@table_rows ||= rows.map(&:to_h).map(&:values)
|
|
end
|
|
|
|
private
|
|
|
|
def computed_data
|
|
@computed_data ||= report.query_result.map { |item|
|
|
row = build_row(item)
|
|
OpenStruct.new(item: item, full_row: row, row: slice_row_fields(row))
|
|
}
|
|
end
|
|
|
|
def extract_rows(data, result)
|
|
data.each do |group_or_row|
|
|
if group_or_row[:is_group].present?
|
|
# Header Row
|
|
if group_or_row[:header].present? && report.display_header_row?
|
|
result << OpenStruct.new(header: group_or_row[:header])
|
|
end
|
|
# Normal Row
|
|
extract_rows(group_or_row[:data], result)
|
|
# Summary Row
|
|
if group_or_row[:summary_row].present? && report.display_summary_row?
|
|
result << group_or_row[:summary_row]
|
|
end
|
|
else
|
|
result << group_or_row.row
|
|
end
|
|
end
|
|
result
|
|
end
|
|
|
|
def build_tree(datas, remaining_rules)
|
|
return datas if remaining_rules.empty?
|
|
|
|
rules = remaining_rules.clone
|
|
group_and_sort(rules.delete_at(0), rules, datas)
|
|
end
|
|
|
|
def group_and_sort(rule, remaining_rules, datas)
|
|
result = []
|
|
groups = group_data_with_rule(datas, rule)
|
|
sorted_groups = sort_groups_with_rule(groups, rule)
|
|
|
|
sorted_groups.each do |group_value, group_datas|
|
|
result << {
|
|
is_group: true,
|
|
header: build_header(rule, group_value, group_datas),
|
|
header_class: rule[:header_class],
|
|
summary_row: build_summary_row(rule, group_value, group_datas),
|
|
summary_row_class: rule[:summary_row_class],
|
|
data: build_tree(group_datas, remaining_rules)
|
|
}
|
|
end
|
|
result
|
|
end
|
|
|
|
def group_data_with_rule(datas, rule)
|
|
datas.group_by { |data|
|
|
if rule[:group_by].is_a?(Symbol)
|
|
data.full_row[rule[:group_by]]
|
|
else
|
|
rule[:group_by].call(data.item, data.full_row)
|
|
end
|
|
}
|
|
end
|
|
|
|
def sort_groups_with_rule(groups, rule)
|
|
groups.sort_by do |group_key, _items|
|
|
# By default sort with the group_key if no sort_by rule is present
|
|
if rule[:sort_by].present?
|
|
rule[:sort_by].call(group_key)
|
|
else
|
|
# downcase for better comparaison
|
|
group_key.is_a?(String) ? group_key.downcase : group_key.to_s
|
|
end
|
|
end.to_h
|
|
end
|
|
|
|
def build_header(rule, group_value, group_datas)
|
|
return if rule[:header].blank?
|
|
|
|
rule[:header].call(group_value, group_datas.map(&:item), group_datas.map(&:full_row))
|
|
end
|
|
|
|
def build_summary_row(rule, group_value, datas)
|
|
return if rule[:summary_row].blank?
|
|
|
|
proc_args = [group_value, datas.map(&:item), datas.map(&:full_row)]
|
|
row = rule[:summary_row].call(*proc_args)
|
|
row = slice_row_fields(OpenStruct.new(row.reverse_merge!(blank_row)))
|
|
add_summary_row_label(row, rule, proc_args)
|
|
end
|
|
|
|
def add_summary_row_label(row, rule, proc_args)
|
|
previous_key = nil
|
|
label = rule[:summary_row_label]
|
|
label = label.call(*proc_args) if label.respond_to?(:call)
|
|
# Adds Total before first non empty column
|
|
row.each_pair do |key, value|
|
|
if value.present? && previous_key.present? && row[previous_key].blank?
|
|
row[previous_key] = label and break
|
|
end
|
|
|
|
previous_key = key
|
|
end
|
|
row
|
|
end
|
|
|
|
def blank_row
|
|
report.columns.transform_values { |_v| "" }
|
|
end
|
|
|
|
def slice_row_fields(row)
|
|
OpenStruct.new(row.to_h.reject { |k, _v| k.in?(report.fields_to_hide) })
|
|
end
|
|
|
|
# Compute the query result item into a result row
|
|
# We use OpenStruct to it's easier to access the properties
|
|
# i.e. row.my_field, rows.sum(&:quantity)
|
|
def build_row(item)
|
|
OpenStruct.new(report.columns.transform_values do |column_constructor|
|
|
if column_constructor.is_a?(Symbol)
|
|
report.__send__(column_constructor, item)
|
|
else
|
|
column_constructor.call(item)
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
end
|