mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-03-04 02:31:33 +00:00
Replace data loading with new Reports::QueryInterface
This commit is contained in:
@@ -44,7 +44,7 @@ module Admin
|
||||
def load_form_options
|
||||
return unless form_options_required?
|
||||
|
||||
form_options = Reports::FrontendData.new(spree_current_user)
|
||||
form_options = Reporting::FrontendData.new(spree_current_user)
|
||||
|
||||
@distributors = form_options.distributors.to_a
|
||||
@suppliers = form_options.suppliers.to_a
|
||||
|
||||
@@ -5,7 +5,7 @@ module Api
|
||||
class ReportsController < Api::V0::BaseController
|
||||
include ReportsActions
|
||||
|
||||
rescue_from Reports::Errors::Base, with: :render_error
|
||||
rescue_from ::Reporting::Errors::Base, with: :render_error
|
||||
|
||||
before_action :validate_report, :authorize_report, :validate_query
|
||||
|
||||
@@ -18,7 +18,7 @@ module Api
|
||||
private
|
||||
|
||||
def render_report
|
||||
render json: @report.as_hashes
|
||||
render json: @report.as_json
|
||||
end
|
||||
|
||||
def render_error(error)
|
||||
@@ -26,12 +26,12 @@ module Api
|
||||
end
|
||||
|
||||
def validate_report
|
||||
raise Reports::Errors::NoReportType if report_type.blank?
|
||||
raise Reports::Errors::ReportNotFound if report_class.blank?
|
||||
raise ::Reporting::Errors::NoReportType if report_type.blank?
|
||||
raise ::Reporting::Errors::ReportNotFound if report_class.blank?
|
||||
end
|
||||
|
||||
def validate_query
|
||||
raise Reports::Errors::MissingQueryParams if ransack_params.blank?
|
||||
raise ::Reporting::Errors::MissingQueryParams if ransack_params.blank?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,7 +16,7 @@ module ReportsActions
|
||||
end
|
||||
|
||||
def report_loader
|
||||
@report_loader ||= Reports::ReportLoader.new(report_type, report_subtype)
|
||||
@report_loader ||= ::Reporting::ReportLoader.new(report_type, report_subtype)
|
||||
end
|
||||
|
||||
def report_type
|
||||
|
||||
@@ -198,7 +198,7 @@ module Spree
|
||||
end
|
||||
|
||||
def load_associated_data
|
||||
form_options = Reports::FrontendData.new(spree_current_user)
|
||||
form_options = Reporting::FrontendData.new(spree_current_user)
|
||||
|
||||
@distributors = form_options.distributors
|
||||
@suppliers = form_options.suppliers
|
||||
|
||||
@@ -10,6 +10,6 @@ module ReportsHelper
|
||||
end
|
||||
|
||||
def report_subtypes(report)
|
||||
Reports::ReportLoader.new(report).report_subtypes
|
||||
Reporting::ReportLoader.new(report).report_subtypes
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,6 +5,9 @@ class ApplicationRecord < ActiveRecord::Base
|
||||
include Spree::Core::Permalinks
|
||||
include Spree::Preferences::Preferable
|
||||
include Searchable
|
||||
include ArelHelpers::ArelTable
|
||||
include ArelHelpers::Aliases
|
||||
include ArelHelpers::JoinAssociation
|
||||
|
||||
self.abstract_class = true
|
||||
end
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
%br
|
||||
= select_tag :report_format, options_for_select({t('.on_screen') => '', t('.csv_spreadsheet') => 'csv', t('.excel_spreadsheet') => 'xlsx', t('.openoffice_spreadsheet') => 'ods'})
|
||||
|
||||
.inline-checkbox
|
||||
= check_box_tag "options[exclude_summaries]", true, params[:options].andand[:exclude_summaries]
|
||||
= label_tag t(".hide_summary_rows")
|
||||
-#.inline-checkbox
|
||||
-# = check_box_tag "options[exclude_summaries]", true, params[:options].andand[:exclude_summaries]
|
||||
-# = label_tag t(".hide_summary_rows")
|
||||
|
||||
@@ -153,9 +153,9 @@ module Openfoodnetwork
|
||||
)
|
||||
|
||||
initializer "ofn.reports" do |_app|
|
||||
module ::Reports; end
|
||||
module ::Reporting; end
|
||||
loader = Zeitwerk::Loader.new
|
||||
loader.push_dir("#{Rails.root}/lib/reports", namespace: ::Reports)
|
||||
loader.push_dir("#{Rails.root}/lib/reporting", namespace: ::Reporting)
|
||||
loader.setup
|
||||
loader.eager_load
|
||||
end
|
||||
|
||||
@@ -1213,6 +1213,7 @@ en:
|
||||
variant: "Variant"
|
||||
quantity: "Quantity"
|
||||
is_temperature_controlled: "TempControlled?"
|
||||
temp_controlled: "TempControlled?"
|
||||
rendering_options:
|
||||
generate_report: "Generate report:"
|
||||
on_screen: "On screen"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Reports
|
||||
module Reporting
|
||||
module Errors
|
||||
class Base < StandardError
|
||||
def i18n_error_scope
|
||||
@@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Reports
|
||||
module Reporting
|
||||
class FrontendData
|
||||
def initialize(current_user)
|
||||
@current_user = current_user
|
||||
52
lib/reporting/queries/joins.rb
Normal file
52
lib/reporting/queries/joins.rb
Normal file
@@ -0,0 +1,52 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Reporting
|
||||
module Queries
|
||||
module Joins
|
||||
def joins_order
|
||||
reflect query.join(association(Spree::LineItem, :order))
|
||||
end
|
||||
|
||||
def joins_order_distributor
|
||||
reflect query.join(association(Spree::Order, :distributor, distributor_alias))
|
||||
end
|
||||
|
||||
def joins_variant
|
||||
reflect query.join(association(Spree::LineItem, :variant))
|
||||
end
|
||||
|
||||
def joins_variant_product
|
||||
reflect query.join(association(Spree::Variant, :product))
|
||||
end
|
||||
|
||||
def joins_product_supplier
|
||||
reflect query.join(association(Spree::Product, :supplier, supplier_alias))
|
||||
end
|
||||
|
||||
def joins_product_shipping_category
|
||||
reflect query.join(association(Spree::Product, :shipping_category))
|
||||
end
|
||||
|
||||
def joins_order_and_distributor
|
||||
reflect query.
|
||||
join(association(Spree::LineItem, :order)).
|
||||
join(association(Spree::Order, :distributor, distributor_alias))
|
||||
end
|
||||
|
||||
def joins_order_customer
|
||||
reflect query.join(association(Spree::Order, :customer))
|
||||
end
|
||||
|
||||
def joins_order_bill_address
|
||||
reflect query.join(association(Spree::Order, :bill_address, bill_address_alias))
|
||||
end
|
||||
|
||||
def join_line_item_option_values
|
||||
reflect query.
|
||||
join(association(Spree::LineItem, :option_values)).
|
||||
join(association(Spree::OptionValuesLineItem, :option_value)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
88
lib/reporting/queries/query_builder.rb
Normal file
88
lib/reporting/queries/query_builder.rb
Normal file
@@ -0,0 +1,88 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Reporting
|
||||
module Queries
|
||||
class QueryBuilder < QueryInterface
|
||||
include Joins
|
||||
include Tables
|
||||
|
||||
attr_reader :grouping_fields
|
||||
|
||||
def initialize(model, grouping_fields = [])
|
||||
@grouping_fields = instance_exec(&grouping_fields)
|
||||
|
||||
super model.arel_table
|
||||
end
|
||||
|
||||
def selecting(lambda)
|
||||
fields = instance_exec(&lambda).map{ |key, value| value.public_send(:as, key.to_s) }
|
||||
|
||||
reflect query.project(*fields)
|
||||
end
|
||||
|
||||
def scoped_to_orders(orders_relation)
|
||||
reflect query.where(
|
||||
line_item_table[:order_id].in(Arel.sql(orders_relation.to_sql))
|
||||
)
|
||||
end
|
||||
|
||||
def with_managed_orders(orders_relation)
|
||||
reflect query.
|
||||
outer_join(managed_orders_alias).
|
||||
on(
|
||||
managed_orders_alias[:id].eq(line_item_table[:order_id]).
|
||||
and(managed_orders_alias[:distributor_id].in(Arel.sql(orders_relation.to_sql)))
|
||||
)
|
||||
end
|
||||
|
||||
def grouped_in_sets(group_sets)
|
||||
reflect query.group(*instance_exec(&group_sets))
|
||||
end
|
||||
|
||||
def ordered_by(ordering_fields)
|
||||
reflect query.order(*instance_exec(&ordering_fields))
|
||||
end
|
||||
|
||||
def masked(field, message = nil, mask_rule = nil)
|
||||
Case.new.
|
||||
when(mask_rule || default_mask_rule).
|
||||
then(field).
|
||||
else(quoted(message || I18n.t("hidden_field", scope: i18n_scope)))
|
||||
end
|
||||
|
||||
def distinct_results(fields = nil)
|
||||
return reflect query.distinct if fields.blank?
|
||||
|
||||
reflect query.distinct_on(fields)
|
||||
end
|
||||
|
||||
def variant_full_name
|
||||
display_name = variant_table[:display_name]
|
||||
display_as = variant_table[:display_as]
|
||||
options_text = option_value_table[:presentation]
|
||||
|
||||
unit_to_display = coalesce(nullify_empty_strings(display_as), options_text)
|
||||
combined_description = sql_concat(display_name, raw("' ('"), unit_to_display, raw("')'"))
|
||||
|
||||
Case.new.
|
||||
when(nullify_empty_strings(display_name).eq(nil)).then(unit_to_display).
|
||||
when(nullify_empty_strings(unit_to_display).not_eq(nil)).then(combined_description).
|
||||
else(display_name)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_mask_rule
|
||||
line_item_table[:order_id].in(raw("#{managed_orders_alias.name}.id"))
|
||||
end
|
||||
|
||||
def summary_row_title
|
||||
I18n.t("total_items", scope: i18n_scope)
|
||||
end
|
||||
|
||||
def i18n_scope
|
||||
"admin.reports"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
125
lib/reporting/queries/query_interface.rb
Normal file
125
lib/reporting/queries/query_interface.rb
Normal file
@@ -0,0 +1,125 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "arel-helpers"
|
||||
|
||||
module Reporting
|
||||
module Queries
|
||||
class QueryInterface < ::ArelHelpers::QueryBuilder
|
||||
include Arel::Nodes
|
||||
|
||||
def coalesce(field, default = 0)
|
||||
NamedFunction.new("COALESCE", [field, default])
|
||||
end
|
||||
|
||||
def sum_values(field, default = 0)
|
||||
NamedFunction.new("SUM", [coalesce(field, default)])
|
||||
end
|
||||
|
||||
def sum_grouped(field, default = 0)
|
||||
Case.new(sql_grouping(grouping_fields)).when(0).then(field.maximum).else(field.sum)
|
||||
end
|
||||
|
||||
def sum_new(field, default = 0)
|
||||
Case.new(sql_grouping(grouping_fields)).when(0).then(field.maximum).else(sum_values(field))
|
||||
end
|
||||
|
||||
def round(field, places: 2)
|
||||
NamedFunction.new("ROUND", [field, places])
|
||||
end
|
||||
|
||||
def association(base_class, association, alias_node = nil, join_type = InnerJoin)
|
||||
options = alias_node.present? ? { aliases: [alias_node] } : {}
|
||||
|
||||
Arel.sql(base_class.join_association(association, join_type, options).first.to_sql)
|
||||
end
|
||||
|
||||
def arel_join(join)
|
||||
Arel.sql(join.first.to_sql)
|
||||
end
|
||||
|
||||
def join_source(join_association)
|
||||
join_association[0].right
|
||||
end
|
||||
|
||||
def default_value(field)
|
||||
field.maximum
|
||||
end
|
||||
|
||||
def default_blank(field)
|
||||
Case.new(sql_grouping(grouping_fields)).when(0).then(field.maximum).else(empty_string)
|
||||
end
|
||||
|
||||
def default_string(field, string)
|
||||
Case.new(sql_grouping(grouping_fields)).when(0).then(field.maximum).else(quoted(string))
|
||||
end
|
||||
|
||||
def default_summary(field)
|
||||
Case.new(sql_grouping(grouping_fields)).when(0).then(field.maximum).else(empty_string)
|
||||
end
|
||||
|
||||
def boolean_blank(field, true_string = I18n.t(:yes), false_string = I18n.t(:no))
|
||||
Case.new(sql_grouping(grouping_fields)).when(0).
|
||||
then(pretty_boolean(field, true_string, false_string).maximum).
|
||||
else(empty_string)
|
||||
end
|
||||
|
||||
def pretty_boolean(field, true_string, false_string)
|
||||
Case.new(field).when(true).
|
||||
then(Arel.sql("'#{true_string}'")).
|
||||
else(Arel.sql("'#{false_string}'"))
|
||||
end
|
||||
|
||||
def cast(field, type)
|
||||
NamedFunction.new("CAST", [field.as(type)])
|
||||
end
|
||||
|
||||
def null_if(field, nullif)
|
||||
NamedFunction.new("NULLIF", [field, nullif])
|
||||
end
|
||||
|
||||
def parenthesise(args)
|
||||
Grouping.new(args)
|
||||
end
|
||||
|
||||
def nullify_empty_strings(field)
|
||||
null_if(field, empty_string)
|
||||
end
|
||||
|
||||
def empty_string
|
||||
raw("''")
|
||||
end
|
||||
|
||||
def sql_concat(*args)
|
||||
NamedFunction.new("CONCAT", args)
|
||||
end
|
||||
|
||||
def raw(string)
|
||||
SqlLiteral.new(string)
|
||||
end
|
||||
|
||||
def quoted(string)
|
||||
Quoted.new(string)
|
||||
end
|
||||
|
||||
def sql_grouping(groupings = grouping_fields)
|
||||
NamedFunction.new("GROUPING", [groupings])
|
||||
end
|
||||
|
||||
def grouping_sets(groupings)
|
||||
GroupingSet.new(groupings)
|
||||
end
|
||||
|
||||
def sql_case(expression)
|
||||
Case.new(expression)
|
||||
end
|
||||
|
||||
def rollup(groupings)
|
||||
RollUp.new(groupings)
|
||||
end
|
||||
|
||||
def raw_result
|
||||
ActiveRecord::Base.connection.exec_query(query.to_sql)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
51
lib/reporting/queries/tables.rb
Normal file
51
lib/reporting/queries/tables.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Reporting
|
||||
module Queries
|
||||
module Tables
|
||||
def order_table
|
||||
Spree::Order.arel_table
|
||||
end
|
||||
|
||||
def line_item_table
|
||||
Spree::LineItem.arel_table
|
||||
end
|
||||
|
||||
def product_table
|
||||
Spree::Product.arel_table
|
||||
end
|
||||
|
||||
def variant_table
|
||||
Spree::Variant.arel_table
|
||||
end
|
||||
|
||||
def customer_table
|
||||
::Customer.arel_table
|
||||
end
|
||||
|
||||
def distributor_alias
|
||||
Enterprise.arel_table.alias(:order_distributor)
|
||||
end
|
||||
|
||||
def supplier_alias
|
||||
Enterprise.arel_table.alias(:product_supplier)
|
||||
end
|
||||
|
||||
def bill_address_alias
|
||||
Spree::Address.arel_table.alias(:bill_address)
|
||||
end
|
||||
|
||||
def managed_orders_alias
|
||||
Spree::Order.arel_table.alias(:managed_orders)
|
||||
end
|
||||
|
||||
def option_value_table
|
||||
Spree::OptionValue.arel_table
|
||||
end
|
||||
|
||||
def shipping_category_table
|
||||
Spree::ShippingCategory.arel_table
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Reports
|
||||
module Reporting
|
||||
class ReportLoader
|
||||
delegate :report_subtypes, to: :base_class
|
||||
|
||||
@@ -12,7 +12,7 @@ module Reports
|
||||
def report_class
|
||||
"#{report_module}::#{report_subtype_class}".constantize
|
||||
rescue NameError
|
||||
raise Reports::Errors::ReportNotFound
|
||||
raise Reporting::Errors::ReportNotFound
|
||||
end
|
||||
|
||||
def default_report_subtype
|
||||
@@ -24,7 +24,7 @@ module Reports
|
||||
attr_reader :report_type, :report_subtype
|
||||
|
||||
def report_module
|
||||
"Reports::#{report_type.camelize}"
|
||||
"Reporting::Reports::#{report_type.camelize}"
|
||||
end
|
||||
|
||||
def report_subtype_class
|
||||
@@ -34,7 +34,7 @@ module Reports
|
||||
def base_class
|
||||
"#{report_module}::Base".constantize
|
||||
rescue NameError
|
||||
raise Reports::Errors::ReportNotFound
|
||||
raise Reporting::Errors::ReportNotFound
|
||||
end
|
||||
end
|
||||
end
|
||||
39
lib/reporting/report_renderer.rb
Normal file
39
lib/reporting/report_renderer.rb
Normal file
@@ -0,0 +1,39 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spreadsheet_architect'
|
||||
|
||||
module Reporting
|
||||
class ReportRenderer
|
||||
def initialize(report)
|
||||
@report = report
|
||||
end
|
||||
|
||||
def table_headers
|
||||
@report.report_data.columns
|
||||
end
|
||||
|
||||
def table_rows
|
||||
@report.report_data.rows
|
||||
end
|
||||
|
||||
def as_json
|
||||
@report.report_data.as_json
|
||||
end
|
||||
|
||||
def as_arrays
|
||||
@as_arrays ||= [table_headers] + table_rows
|
||||
end
|
||||
|
||||
def to_csv
|
||||
SpreadsheetArchitect.to_csv(headers: table_headers, data: table_rows)
|
||||
end
|
||||
|
||||
def to_ods
|
||||
SpreadsheetArchitect.to_ods(headers: table_headers, data: table_rows)
|
||||
end
|
||||
|
||||
def to_xlsx
|
||||
SpreadsheetArchitect.to_xlsx(headers: table_headers, data: table_rows)
|
||||
end
|
||||
end
|
||||
end
|
||||
52
lib/reporting/report_template.rb
Normal file
52
lib/reporting/report_template.rb
Normal file
@@ -0,0 +1,52 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Reporting
|
||||
class ReportTemplate
|
||||
delegate :as_json, :as_arrays, :table_headers, :table_rows,
|
||||
:to_csv, :to_xlsx, :to_ods, :to_json, to: :renderer
|
||||
|
||||
attr_reader :options
|
||||
|
||||
SUBTYPES = []
|
||||
|
||||
def self.report_subtypes
|
||||
self::SUBTYPES
|
||||
end
|
||||
|
||||
def initialize(current_user, ransack_params, options = {})
|
||||
@current_user = current_user
|
||||
@ransack_params = ransack_params.with_indifferent_access
|
||||
@options = ( options || {} ).with_indifferent_access
|
||||
end
|
||||
|
||||
def report_data
|
||||
@report_data ||= report_query.raw_result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :current_user, :ransack_params
|
||||
|
||||
def renderer
|
||||
@renderer ||= ReportRenderer.new(self)
|
||||
end
|
||||
|
||||
def scoped_orders_relation
|
||||
visible_orders_relation.ransack(ransack_params).result
|
||||
end
|
||||
|
||||
def visible_orders_relation
|
||||
::Permissions::Order.new(current_user).
|
||||
visible_orders.complete.not_state(:canceled).
|
||||
select(:id).distinct
|
||||
end
|
||||
|
||||
def managed_orders_relation
|
||||
::Enterprise.managed_by(current_user).select(:id).distinct
|
||||
end
|
||||
|
||||
def i18n_scope
|
||||
"admin.reports"
|
||||
end
|
||||
end
|
||||
end
|
||||
41
lib/reporting/reports/packing/base.rb
Normal file
41
lib/reporting/reports/packing/base.rb
Normal file
@@ -0,0 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Reporting
|
||||
module Reports
|
||||
module Packing
|
||||
class Base < ReportTemplate
|
||||
SUBTYPES = ["customer", "supplier"]
|
||||
|
||||
def primary_model
|
||||
Spree::LineItem
|
||||
end
|
||||
|
||||
def report_query
|
||||
Queries::QueryBuilder.new(primary_model, grouping_fields).
|
||||
scoped_to_orders(scoped_orders_relation).
|
||||
with_managed_orders(managed_orders_relation).
|
||||
joins_order_and_distributor.
|
||||
joins_order_customer.
|
||||
joins_order_bill_address.
|
||||
joins_variant.
|
||||
joins_variant_product.
|
||||
joins_product_supplier.
|
||||
joins_product_shipping_category.
|
||||
join_line_item_option_values.
|
||||
selecting(select_fields).
|
||||
grouped_in_sets(group_sets).
|
||||
ordered_by(ordering_fields)
|
||||
end
|
||||
|
||||
def grouping_fields
|
||||
lambda do
|
||||
[
|
||||
order_table[:id],
|
||||
line_item_table[:id]
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
49
lib/reporting/reports/packing/customer.rb
Normal file
49
lib/reporting/reports/packing/customer.rb
Normal file
@@ -0,0 +1,49 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Reporting
|
||||
module Reports
|
||||
module Packing
|
||||
class Customer < Base
|
||||
def select_fields
|
||||
lambda do
|
||||
{
|
||||
hub: default_blank(distributor_alias[:name]),
|
||||
customer_code: default_blank(masked(customer_table[:code])),
|
||||
first_name: default_blank(masked(bill_address_alias[:firstname])),
|
||||
last_name: default_blank(masked(bill_address_alias[:lastname])),
|
||||
supplier: default_blank(supplier_alias[:name]),
|
||||
product: default_string(product_table[:name], summary_row_title),
|
||||
variant: default_blank(variant_full_name),
|
||||
quantity: sum_values(line_item_table[:quantity]),
|
||||
temp_controlled: boolean_blank(shipping_category_table[:temperature_controlled]),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def ordering_fields
|
||||
lambda do
|
||||
[
|
||||
distributor_alias[:name],
|
||||
bill_address_alias[:lastname],
|
||||
order_table[:id],
|
||||
sql_grouping(grouping_fields),
|
||||
Arel.sql("supplier"),
|
||||
Arel.sql("product"),
|
||||
Arel.sql("variant"),
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def group_sets
|
||||
lambda do
|
||||
[
|
||||
distributor_alias[:name],
|
||||
bill_address_alias[:lastname],
|
||||
grouping_sets([parenthesise(order_table[:id]), parenthesise(grouping_fields)])
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
48
lib/reporting/reports/packing/supplier.rb
Normal file
48
lib/reporting/reports/packing/supplier.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Reporting
|
||||
module Reports
|
||||
module Packing
|
||||
class Supplier < Base
|
||||
def select_fields
|
||||
lambda do
|
||||
{
|
||||
hub: default_blank(distributor_alias[:name]),
|
||||
supplier: default_blank(supplier_alias[:name]),
|
||||
customer_code: default_blank(customer_table[:code]),
|
||||
first_name: default_blank(bill_address_alias[:firstname]),
|
||||
last_name: default_blank(bill_address_alias[:lastname]),
|
||||
product: default_string(product_table[:name], summary_row_title),
|
||||
variant: default_blank(variant_full_name),
|
||||
quantity: sum_values(line_item_table[:quantity]),
|
||||
temp_controlled: boolean_blank(shipping_category_table[:temperature_controlled]),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def group_sets
|
||||
lambda do
|
||||
[
|
||||
distributor_alias[:name],
|
||||
supplier_alias[:name],
|
||||
grouping_sets([parenthesise(supplier_alias[:name]), parenthesise(grouping_fields)])
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def ordering_fields
|
||||
lambda do
|
||||
[
|
||||
distributor_alias[:name],
|
||||
supplier_alias[:name],
|
||||
sql_grouping(grouping_fields),
|
||||
Arel.sql("product"),
|
||||
Arel.sql("variant"),
|
||||
Arel.sql("supplier"),
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,17 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Reports
|
||||
module Packing
|
||||
class Base < ReportTemplate
|
||||
SUBTYPES = ["customer", "supplier"]
|
||||
|
||||
|
||||
|
||||
private
|
||||
|
||||
def i18n_scope
|
||||
"admin.reports"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,9 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Reports
|
||||
module Packing
|
||||
class Customer < Base
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,9 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Reports
|
||||
module Packing
|
||||
class Supplier < Base
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,67 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spreadsheet_architect'
|
||||
|
||||
module Reports
|
||||
class ReportRenderer
|
||||
def initialize(report)
|
||||
@report = report
|
||||
end
|
||||
|
||||
def table_headers
|
||||
as_arrays.first
|
||||
end
|
||||
|
||||
def table_rows
|
||||
as_arrays.drop(1)
|
||||
end
|
||||
|
||||
def as_hashes
|
||||
report_rows
|
||||
end
|
||||
|
||||
def as_arrays
|
||||
@as_arrays ||= rows_as_arrays
|
||||
end
|
||||
|
||||
def to_csv
|
||||
SpreadsheetArchitect.to_csv(headers: table_headers, data: table_rows)
|
||||
end
|
||||
|
||||
def to_ods
|
||||
SpreadsheetArchitect.to_ods(headers: table_headers, data: table_rows)
|
||||
end
|
||||
|
||||
def to_xlsx
|
||||
SpreadsheetArchitect.to_xlsx(headers: table_headers, data: table_rows)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def report_rows
|
||||
@report.report_rows
|
||||
end
|
||||
|
||||
def rows_as_arrays
|
||||
report_array = [header_row]
|
||||
|
||||
report_rows.each do |row|
|
||||
report_array << row_or_summary(row)
|
||||
end
|
||||
|
||||
report_array
|
||||
end
|
||||
|
||||
def header_row
|
||||
report_rows.first.keys - [:summary_row_title]
|
||||
end
|
||||
|
||||
def row_or_summary(row)
|
||||
summary_row_title = row.delete :summary_row_title
|
||||
row_values = row.values
|
||||
row_values[0] = summary_row_title if summary_row_title
|
||||
|
||||
row_values
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,42 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Reports
|
||||
class ReportTemplate
|
||||
delegate :as_hashes, :as_arrays, :table_headers, :table_rows,
|
||||
:to_csv, :to_xlsx, :to_ods, :to_json, to: :report_renderer
|
||||
|
||||
attr_reader :options
|
||||
attr_accessor :report_rows
|
||||
|
||||
SUBTYPES = []
|
||||
|
||||
def self.report_subtypes
|
||||
self::SUBTYPES
|
||||
end
|
||||
|
||||
def initialize(current_user, ransack_params, options = {})
|
||||
@current_user = current_user
|
||||
@ransack_params = ransack_params.with_indifferent_access
|
||||
@options = ( options || {} ).with_indifferent_access
|
||||
@report_rows = []
|
||||
|
||||
build_report
|
||||
end
|
||||
|
||||
def headers
|
||||
report_rows.first&.keys || []
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :current_user, :ransack_params
|
||||
|
||||
def build_report
|
||||
# TODO
|
||||
end
|
||||
|
||||
def report_renderer
|
||||
@report_renderer ||= ReportRenderer.new(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -59,7 +59,7 @@ describe "Packing Reports" do
|
||||
|
||||
it "shows line items supplied by my producers, with names hidden" do
|
||||
expect(subject.collection).to eq([line_item2])
|
||||
expect(subject.as_hashes.first[:first_name]).to eq(
|
||||
expect(subject.as_json.first[:first_name]).to eq(
|
||||
I18n.t('admin.reports.hidden_field')
|
||||
)
|
||||
end
|
||||
|
||||
@@ -2,17 +2,19 @@
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
module Reports
|
||||
module Bananas
|
||||
class Base; end
|
||||
class Green; end
|
||||
class Yellow; end
|
||||
module Reporting
|
||||
module Reports
|
||||
module Bananas
|
||||
class Base; end
|
||||
class Green; end
|
||||
class Yellow; end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe Reports::ReportLoader do
|
||||
let(:service) { Reports::ReportLoader.new(*arguments) }
|
||||
let(:report_base_class) { Reports::Bananas::Base }
|
||||
describe Reporting::ReportLoader do
|
||||
let(:service) { Reporting::ReportLoader.new(*arguments) }
|
||||
let(:report_base_class) { Reporting::Reports::Bananas::Base }
|
||||
let(:report_subtypes) { ["green", "yellow"] }
|
||||
|
||||
before do
|
||||
@@ -24,7 +26,7 @@ describe Reports::ReportLoader do
|
||||
let(:arguments) { ["bananas", "yellow"] }
|
||||
|
||||
it "returns a report class when given type and subtype" do
|
||||
expect(service.report_class).to eq Reports::Bananas::Yellow
|
||||
expect(service.report_class).to eq Reporting::Reports::Bananas::Yellow
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,7 +35,7 @@ describe Reports::ReportLoader do
|
||||
let(:arguments) { ["bananas"] }
|
||||
|
||||
it "returns first listed report type" do
|
||||
expect(service.report_class).to eq Reports::Bananas::Green
|
||||
expect(service.report_class).to eq Reporting::Reports::Bananas::Green
|
||||
end
|
||||
end
|
||||
|
||||
@@ -42,7 +44,7 @@ describe Reports::ReportLoader do
|
||||
let(:report_subtypes) { [] }
|
||||
|
||||
it "returns base class" do
|
||||
expect(service.report_class).to eq Reports::Bananas::Base
|
||||
expect(service.report_class).to eq Reporting::Reports::Bananas::Base
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,7 +53,7 @@ describe Reports::ReportLoader do
|
||||
let(:report_subtypes) { [] }
|
||||
|
||||
it "raises an error" do
|
||||
expect{ service.report_class }.to raise_error(Reports::Errors::ReportNotFound)
|
||||
expect{ service.report_class }.to raise_error(Reporting::Errors::ReportNotFound)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -80,7 +82,7 @@ describe Reports::ReportLoader do
|
||||
let(:report_subtypes) { [] }
|
||||
|
||||
it "raises an error" do
|
||||
expect{ service.report_class }.to raise_error(Reports::Errors::ReportNotFound)
|
||||
expect{ service.report_class }.to raise_error(Reporting::Errors::ReportNotFound)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,9 +27,9 @@ describe Reports::ReportRenderer do
|
||||
end
|
||||
end
|
||||
|
||||
describe "#as_hashes" do
|
||||
describe "#as_json" do
|
||||
it "returns the report's data as hashes" do
|
||||
expect(service.as_hashes).to eq report_rows
|
||||
expect(service.as_json).to eq report_rows
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user