From 406309c577d7fe702bd79701d544cca38ab172da Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 5 Jun 2020 16:43:38 +0100 Subject: [PATCH] Move BulkCoop reports out of deprecated lib/ directory into OrderManagement engine. The BulkCoop reports are not generated the same way as the EnterpriseFeeSummary report is generated yet so that may need to be updated. --- .../spree/admin/reports_controller.rb | 8 +- .../spree/admin/reports/bulk_coop.html.haml | 19 -- .../reports/bulk_coop_controller.rb | 64 ++++ .../reports/bulk_coop/authorizer.rb | 16 + .../bulk_coop/bulk_coop_allocation_report.rb | 67 +++++ .../reports/bulk_coop/bulk_coop_report.rb | 275 ++++++++++++++++++ .../bulk_coop/bulk_coop_supplier_report.rb | 64 ++++ .../reports/bulk_coop/parameters.rb | 55 ++++ .../reports/bulk_coop/permissions.rb | 11 + .../bulk_coop/renderers/csv_renderer.rb | 30 ++ .../bulk_coop/renderers/html_renderer.rb | 22 ++ .../reports/bulk_coop/report_service.rb | 27 ++ .../enterprise_fee_summary/authorizer.rb | 13 - .../app/services/reports/authorizer.rb | 15 + .../reports/bulk_coop/_filters.html.haml | 31 ++ .../reports/bulk_coop/_report.html.haml | 20 ++ .../reports/bulk_coop/create.html.haml | 2 + .../reports/bulk_coop/new.html.haml | 1 + engines/order_management/config/routes.rb | 1 + lib/open_food_network/bulk_coop_report.rb | 266 ----------------- .../reports/bulk_coop_allocation_report.rb | 63 ---- .../reports/bulk_coop_supplier_report.rb | 60 ---- 22 files changed, 707 insertions(+), 423 deletions(-) delete mode 100644 app/views/spree/admin/reports/bulk_coop.html.haml create mode 100644 engines/order_management/app/controllers/order_management/reports/bulk_coop_controller.rb create mode 100644 engines/order_management/app/services/order_management/reports/bulk_coop/authorizer.rb create mode 100644 engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_allocation_report.rb create mode 100644 engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_report.rb create mode 100644 engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_supplier_report.rb create mode 100644 engines/order_management/app/services/order_management/reports/bulk_coop/parameters.rb create mode 100644 engines/order_management/app/services/order_management/reports/bulk_coop/permissions.rb create mode 100644 engines/order_management/app/services/order_management/reports/bulk_coop/renderers/csv_renderer.rb create mode 100644 engines/order_management/app/services/order_management/reports/bulk_coop/renderers/html_renderer.rb create mode 100644 engines/order_management/app/services/order_management/reports/bulk_coop/report_service.rb create mode 100644 engines/order_management/app/views/order_management/reports/bulk_coop/_filters.html.haml create mode 100644 engines/order_management/app/views/order_management/reports/bulk_coop/_report.html.haml create mode 100644 engines/order_management/app/views/order_management/reports/bulk_coop/create.html.haml create mode 100644 engines/order_management/app/views/order_management/reports/bulk_coop/new.html.haml delete mode 100644 lib/open_food_network/bulk_coop_report.rb delete mode 100644 lib/open_food_network/reports/bulk_coop_allocation_report.rb delete mode 100644 lib/open_food_network/reports/bulk_coop_supplier_report.rb diff --git a/app/controllers/spree/admin/reports_controller.rb b/app/controllers/spree/admin/reports_controller.rb index 6fd19ae734..ce3ec90c57 100644 --- a/app/controllers/spree/admin/reports_controller.rb +++ b/app/controllers/spree/admin/reports_controller.rb @@ -12,7 +12,6 @@ require 'open_food_network/order_cycle_management_report' require 'open_food_network/packing_report' require 'open_food_network/sales_tax_report' require 'open_food_network/xero_invoices_report' -require 'open_food_network/bulk_coop_report' require 'open_food_network/payments_report' require 'open_food_network/orders_and_fulfillments_report' @@ -21,6 +20,11 @@ module Spree class ReportsController < Spree::Admin::BaseController include Spree::ReportsHelper + ORDER_MANAGEMENT_ENGINE_REPORTS = [ + :bulk_coop, + :enterprise_fee_summary + ] + helper_method :render_content? before_filter :cache_search_state @@ -309,7 +313,7 @@ module Spree # List of reports that have been moved to the Order Management engine def report_in_order_management_engine?(report) - report == :enterprise_fee_summary + ORDER_MANAGEMENT_ENGINE_REPORTS.include?(report) end def timestamp diff --git a/app/views/spree/admin/reports/bulk_coop.html.haml b/app/views/spree/admin/reports/bulk_coop.html.haml deleted file mode 100644 index c644e8dd6e..0000000000 --- a/app/views/spree/admin/reports/bulk_coop.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -= form_for @report.search, :url => spree.bulk_coop_admin_reports_path do |f| - = render 'date_range_form', f: f - - .row - .four.columns.alpha - = label_tag nil, t(:report_distributor) - = f.collection_select(:distributor_id_eq, @distributors, :id, :name, {:include_blank => t(:all)}, {:class => "select2 fullwidth"}) - = label_tag nil, t(:report_type) - %br - = select_tag(:report_type, options_for_select([:bulk_coop_supplier_report, :bulk_coop_allocation, :bulk_coop_packing_sheets, :bulk_coop_customer_payments].map{ |e| [t(".#{e}"), e] }, @report_type)) - %br - %br - = check_box_tag :csv - = label_tag :csv, t(:report_customers_csv) - %br - %br - = button t(:search) - -= render "table", id: "listing_orders", msg_option: t(:search) diff --git a/engines/order_management/app/controllers/order_management/reports/bulk_coop_controller.rb b/engines/order_management/app/controllers/order_management/reports/bulk_coop_controller.rb new file mode 100644 index 0000000000..26397e40bf --- /dev/null +++ b/engines/order_management/app/controllers/order_management/reports/bulk_coop_controller.rb @@ -0,0 +1,64 @@ +module OrderManagement + module Reports + class BulkCoopController < Spree::Admin::BaseController + before_filter :load_report_parameters + before_filter :load_permissions + + def new; end + + def create + return respond_to_invalid_parameters unless @report_parameters.valid? + + @report_parameters.authorize!(@permissions) + + @report = report_klass::ReportService.new(@permissions, params[:report], spree_current_user) + renderer.render(self) + rescue ::Reports::Authorizer::ParameterNotAllowedError => e + flash[:error] = e.message + render_report_form + end + + private + + def respond_to_invalid_parameters + flash[:error] = I18n.t("invalid_filter_parameters", scope: i18n_scope) + render_report_form + end + + def i18n_scope + "order_management.reports.enterprise_fee_summary" + end + + def render_report_form + render action: :new + end + + def report_klass + OrderManagement::Reports::BulkCoop + end + + def load_report_parameters + @report_parameters = report_klass::Parameters.new(params[:report] || {}) + end + + def load_permissions + @permissions = report_klass::Permissions.new(spree_current_user) + end + + def report_renderer_klass + case params[:report_format] + when "csv" + report_klass::Renderers::CsvRenderer + when nil, "", "html" + report_klass::Renderers::HtmlRenderer + else + raise Reports::UnsupportedReportFormatException + end + end + + def renderer + @renderer ||= report_renderer_klass.new(@report) + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/bulk_coop/authorizer.rb b/engines/order_management/app/services/order_management/reports/bulk_coop/authorizer.rb new file mode 100644 index 0000000000..39562e6da2 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/bulk_coop/authorizer.rb @@ -0,0 +1,16 @@ +module OrderManagement + module Reports + module BulkCoop + class Authorizer < ::Reports::Authorizer + def self.parameter_not_allowed_error_message + i18n_scope = "order_management.reports.enterprise_fee_summary" + I18n.t("parameter_not_allowed_error", scope: i18n_scope) + end + + def authorize! + require_ids_allowed(parameters.distributor_ids, permissions.allowed_distributors) + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_allocation_report.rb b/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_allocation_report.rb new file mode 100644 index 0000000000..9513f51e9b --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_allocation_report.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module OrderManagement + module Reports + module BulkCoop + class BulkCoopAllocationReport + def header + [ + I18n.t(:report_header_customer), + I18n.t(:report_header_product), + I18n.t(:report_header_bulk_unit_size), + I18n.t(:report_header_variant), + I18n.t(:report_header_variant_value), + I18n.t(:report_header_variant_unit), + I18n.t(:report_header_weight), + I18n.t(:report_header_sum_total), + I18n.t(:report_header_total_available), + I18n.t(:report_header_unallocated), + I18n.t(:report_header_max_quantity_excess), + ] + end + + def rules + [ + { + group_by: proc { |line_item| line_item.product }, + sort_by: proc { |product| product.name }, + summary_columns: [ + :total_label, + :variant_product_name, + :variant_product_group_buy_unit_size_f, + :empty_cell, + :empty_cell, + :empty_cell, + :empty_cell, + :total_amount, + :total_available, + :remainder, + :max_quantity_excess + ] + }, + { + group_by: proc { |line_item| line_item.order }, + sort_by: proc { |order| order.to_s } + } + ] + end + + def columns + [ + :order_billing_address_name, + :product_name, + :product_group_buy_unit_size, + :full_name, + :option_value_value, + :option_value_unit, + :weight_from_unit_value, + :total_amount, + :empty_cell, + :empty_cell, + :empty_cell + ] + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_report.rb b/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_report.rb new file mode 100644 index 0000000000..3d776c31f5 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_report.rb @@ -0,0 +1,275 @@ +require "open_food_network/reports/line_items" + +module OrderManagement + module Reports + module BulkCoop + class BulkCoopReport + REPORT_TYPES = [ + :bulk_coop_supplier_report, + :bulk_coop_allocation, + :bulk_coop_packing_sheets, + :bulk_coop_customer_payments + ] + + attr_reader :params + def initialize(user, params = {}, render_table = false) + @params = params + @user = user + @render_table = render_table + + @supplier_report = BulkCoopSupplierReport.new + @allocation_report = BulkCoopAllocationReport.new + end + + def header + case params[:report_type] + when "bulk_coop_supplier_report" + @supplier_report.header + when "bulk_coop_allocation" + @allocation_report.header + when "bulk_coop_packing_sheets" + [I18n.t(:report_header_customer), + I18n.t(:report_header_product), + I18n.t(:report_header_variant), + I18n.t(:report_header_sum_total)] + when "bulk_coop_customer_payments" + [I18n.t(:report_header_customer), + I18n.t(:report_header_date_of_order), + I18n.t(:report_header_total_cost), + I18n.t(:report_header_amount_owing), + I18n.t(:report_header_amount_paid)] + else + [I18n.t(:report_header_supplier), + I18n.t(:report_header_product), + I18n.t(:report_header_product), + I18n.t(:report_header_bulk_unit_size), + I18n.t(:report_header_variant), + I18n.t(:report_header_weight), + I18n.t(:report_header_sum_total), + I18n.t(:report_header_sum_max_total), + I18n.t(:report_header_units_required), + I18n.t(:report_header_remainder)] + end + end + + def search + report_line_items.orders + end + + def table_items + return [] unless @render_table + report_line_items.list(line_item_includes) + end + + def rules + case params[:report_type] + when "bulk_coop_supplier_report" + @supplier_report.rules + when "bulk_coop_allocation" + @allocation_report.rules + when "bulk_coop_packing_sheets" + [{ group_by: proc { |li| li.product }, + sort_by: proc { |product| product.name } }, + { group_by: proc { |li| li.full_name }, + sort_by: proc { |full_name| full_name } }, + { group_by: proc { |li| li.order }, + sort_by: proc { |order| order.to_s } }] + when "bulk_coop_customer_payments" + [{ group_by: proc { |li| li.order }, + sort_by: proc { |order| order.completed_at } }] + else + [{ group_by: proc { |li| li.product.supplier }, + sort_by: proc { |supplier| supplier.name } }, + { group_by: proc { |li| li.product }, + sort_by: proc { |product| product.name }, + summary_columns: [proc { |lis| lis.first.product.supplier.name }, + proc { |lis| lis.first.product.name }, + proc { |lis| lis.first.product.group_buy_unit_size || 0.0 }, + proc { |_lis| "" }, + proc { |_lis| "" }, + proc { |lis| lis.sum { |li| li.quantity * (li.weight_from_unit_value || 0) } }, + proc { |lis| lis.sum { |li| (li.max_quantity || 0) * (li.weight_from_unit_value || 0) } }, + proc { |lis| ( (lis.first.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| [li.max_quantity || 0, li.quantity || 0].max * (li.weight_from_unit_value || 0) } / lis.first.product.group_buy_unit_size ) ).floor }, + proc { |lis| lis.sum { |li| [li.max_quantity || 0, li.quantity || 0].max * (li.weight_from_unit_value || 0) } - ( ( (lis.first.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| [li.max_quantity || 0, li.quantity || 0].max * (li.weight_from_unit_value || 0) } / lis.first.product.group_buy_unit_size ) ).floor * (lis.first.product.group_buy_unit_size || 0) ) }] }, + { group_by: proc { |li| li.full_name }, + sort_by: proc { |full_name| full_name } }] + end + end + + def columns + case params[:report_type] + when "bulk_coop_supplier_report" + @supplier_report.columns + when "bulk_coop_allocation" + @allocation_report.columns + when "bulk_coop_packing_sheets" + [ + :order_billing_address_name, + :product_name, + :full_name, + :total_quantity + ] + when "bulk_coop_customer_payments" + [ + :order_billing_address_name, + :order_completed_at, + :customer_payments_total_cost, + :customer_payments_amount_owed, + :customer_payments_amount_paid + ] + else + [ + :product_supplier_name, + :product_name, + :product_group_buy_unit_size, + :full_name, + :weight_from_unit_value, + :total_quantity, + :total_max_quantity, + :empty_cell, + :empty_cell + ] + end + end + + private + + def line_item_includes + [{ order: [:bill_address], + variant: [{ option_values: :option_type }, { product: :supplier }] }] + end + + def order_permissions + return @order_permissions unless @order_permissions.nil? + @order_permissions = ::Permissions::Order.new(@user, @params[:q]) + end + + def report_line_items + @report_line_items ||= OpenFoodNetwork::Reports::LineItems.new(order_permissions, @params) + end + + def customer_payments_total_cost(line_items) + line_items.map(&:order).uniq.sum(&:total) + end + + def customer_payments_amount_owed(line_items) + line_items.map(&:order).uniq.sum(&:outstanding_balance) + end + + def customer_payments_amount_paid(line_items) + line_items.map(&:order).uniq.sum(&:payment_total) + end + + def empty_cell(*) + "" + end + + def full_name(line_items) + line_items.first.full_name + end + + def group_buy_unit_size(line_items) + (line_items.first.variant.product.group_buy_unit_size || 0.0) / + (line_items.first.product.variant_unit_scale || 1) + end + + def max_quantity_excess(line_items) + max_quantity_amount(line_items) - total_amount(line_items) + end + + def max_quantity_amount(line_items) + line_items.sum do |line_item| + max_quantity = [line_item.max_quantity || 0, line_item.quantity || 0].max + max_quantity * scaled_unit_value(line_item.variant) + end + end + + def option_value_value(line_items) + OpenFoodNetwork::OptionValueNamer.new(line_items.first).value + end + + def option_value_unit(line_items) + OpenFoodNetwork::OptionValueNamer.new(line_items.first).unit + end + + def order_billing_address_name(line_items) + billing_address = line_items.first.order.bill_address + billing_address.firstname + " " + billing_address.lastname + end + + def order_completed_at(line_items) + line_items.first.order.completed_at.to_s + end + + def product_group_buy_unit_size(line_items) + line_items.first.product.group_buy_unit_size || 0.0 + end + + def product_name(line_items) + line_items.first.product.name + end + + def product_supplier_name(line_items) + line_items.first.product.supplier.name + end + + def remainder(line_items) + remainder = total_available(line_items) - total_amount(line_items) + remainder >= 0 ? remainder : '' + end + + def scaled_final_weight_volume(line_item) + (line_item.final_weight_volume || 0) / (line_item.product.variant_unit_scale || 1) + end + + def scaled_unit_value(variant) + (variant.unit_value || 0) / (variant.product.variant_unit_scale || 1) + end + + def total_amount(line_items) + line_items.sum { |li| scaled_final_weight_volume(li) } + end + + def total_available(line_items) + units_required(line_items) * group_buy_unit_size(line_items) + end + + def total_max_quantity(line_items) + line_items.sum { |line_item| line_item.max_quantity || 0 } + end + + def total_quantity(line_items) + line_items.sum(&:quantity) + end + + def total_label(*) + I18n.t('admin.reports.total') + end + + def units_required(line_items) + if group_buy_unit_size(line_items).zero? + 0 + else + ( total_amount(line_items) / group_buy_unit_size(line_items) ).ceil + end + end + + def variant_product_group_buy_unit_size_f(line_items) + group_buy_unit_size(line_items) + end + + def variant_product_name(line_items) + line_items.first.variant.product.name + end + + def variant_product_supplier_name(line_items) + line_items.first.variant.product.supplier.name + end + + def weight_from_unit_value(line_items) + line_items.first.weight_from_unit_value || 0 + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_supplier_report.rb b/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_supplier_report.rb new file mode 100644 index 0000000000..076a0a63c9 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_supplier_report.rb @@ -0,0 +1,64 @@ +module OrderManagement + module Reports + module BulkCoop + class BulkCoopSupplierReport + def header + [ + I18n.t(:report_header_supplier), + I18n.t(:report_header_product), + I18n.t(:report_header_bulk_unit_size), + I18n.t(:report_header_variant), + I18n.t(:report_header_variant_value), + I18n.t(:report_header_variant_unit), + I18n.t(:report_header_weight), + I18n.t(:report_header_sum_total), + I18n.t(:report_header_units_required), + I18n.t(:report_header_unallocated), + I18n.t(:report_header_max_quantity_excess), + ] + end + + def rules + [ + { group_by: proc { |line_item| line_item.product.supplier }, + sort_by: proc { |supplier| supplier.name } }, + { group_by: proc { |line_item| line_item.product }, + sort_by: proc { |product| product.name }, + summary_columns: [ + :variant_product_supplier_name, + :variant_product_name, + :variant_product_group_buy_unit_size_f, + :empty_cell, + :empty_cell, + :empty_cell, + :empty_cell, + :total_amount, + :units_required, + :remainder, + :max_quantity_excess + ] + }, + { group_by: proc { |line_item| line_item.full_name }, + sort_by: proc { |full_name| full_name } } + ] + end + + def columns + [ + :variant_product_supplier_name, + :variant_product_name, + :variant_product_group_buy_unit_size_f, + :full_name, + :option_value_value, + :option_value_unit, + :weight_from_unit_value, + :total_amount, + :empty_cell, + :empty_cell, + :empty_cell + ] + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/bulk_coop/parameters.rb b/engines/order_management/app/services/order_management/reports/bulk_coop/parameters.rb new file mode 100644 index 0000000000..16ed31a744 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/bulk_coop/parameters.rb @@ -0,0 +1,55 @@ +module OrderManagement + module Reports + module BulkCoop + class Parameters < ::Reports::Parameters::Base + extend ActiveModel::Naming + extend ActiveModel::Translation + include ActiveModel::Validations + + attr_accessor :start_at, :end_at, :distributor_ids, :report_type + + before_validation :cleanup_arrays + + validates :start_at, :end_at, date_time_string: true + validates :distributor_ids, integer_array: true + validates_inclusion_of :report_type, in: OrderManagement::Reports::BulkCoop::BulkCoopReport::REPORT_TYPES.map(&:to_s) + + validate :require_valid_datetime_range + + def self.date_end_before_start_error_message + i18n_scope = "order_management.reports.enterprise_fee_summary" + I18n.t("date_end_before_start_error", scope: i18n_scope) + end + + def initialize(attributes = {}) + self.distributor_ids = [] + + super(attributes) + end + + def authorize!(permissions) + authorizer = Authorizer.new(self, permissions) + authorizer.authorize! + end + + protected + + def require_valid_datetime_range + return if start_at.blank? || end_at.blank? + + error_message = self.class.date_end_before_start_error_message + errors.add(:end_at, error_message) unless start_at < end_at + end + + # Remove the blank strings that Rails multiple selects add by default to + # make sure that blank lists are still submitted to the server as arrays + # instead of nil. + # + # https://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-select + def cleanup_arrays + distributor_ids.reject!(&:blank?) + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/bulk_coop/permissions.rb b/engines/order_management/app/services/order_management/reports/bulk_coop/permissions.rb new file mode 100644 index 0000000000..23eb0aaca0 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/bulk_coop/permissions.rb @@ -0,0 +1,11 @@ +module OrderManagement + module Reports + module BulkCoop + class Permissions < ::Reports::Permissions + def allowed_distributors + @allowed_distributors ||= Enterprise.is_distributor.managed_by(user) + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/bulk_coop/renderers/csv_renderer.rb b/engines/order_management/app/services/order_management/reports/bulk_coop/renderers/csv_renderer.rb new file mode 100644 index 0000000000..ca19e0a873 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/bulk_coop/renderers/csv_renderer.rb @@ -0,0 +1,30 @@ +module OrderManagement + module Reports + module BulkCoop + module Renderers + class CsvRenderer < ::Reports::Renderers::Base + def render(context) + context.send_data(generate, filename: filename) + end + + def generate + CSV.generate do |csv| + csv << report_data.header + + report_data.list.each do |data| + csv << data + end + end + end + + private + + def filename + timestamp = Time.zone.now.strftime("%Y%m%d") + "#{report_data.parameters[:report_type]}_#{timestamp}.csv" + end + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/bulk_coop/renderers/html_renderer.rb b/engines/order_management/app/services/order_management/reports/bulk_coop/renderers/html_renderer.rb new file mode 100644 index 0000000000..edbda863e1 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/bulk_coop/renderers/html_renderer.rb @@ -0,0 +1,22 @@ +module OrderManagement + module Reports + module BulkCoop + module Renderers + class HtmlRenderer < ::Reports::Renderers::Base + def render(context) + context.instance_variable_set :@renderer, self + context.render(action: :create, renderer: self) + end + + def header + report_data.header + end + + def data_rows + report_data.list + end + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/bulk_coop/report_service.rb b/engines/order_management/app/services/order_management/reports/bulk_coop/report_service.rb new file mode 100644 index 0000000000..c74e40934e --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/bulk_coop/report_service.rb @@ -0,0 +1,27 @@ +require 'open_food_network/order_grouper' + +module OrderManagement + module Reports + module BulkCoop + class ReportService + attr_accessor :permissions, :parameters, :user + + def initialize(permissions, parameters, user) + @permissions = permissions + @parameters = parameters + @user = user + @report = BulkCoopReport.new(user, parameters, true) + end + + def header + @report.header + end + + def list + order_grouper = OpenFoodNetwork::OrderGrouper.new @report.rules, @report.columns, @report + order_grouper.table(@report.table_items) + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/authorizer.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/authorizer.rb index e129905dc6..51b1393630 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/authorizer.rb +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/authorizer.rb @@ -2,11 +2,6 @@ module OrderManagement module Reports module EnterpriseFeeSummary class Authorizer < ::Reports::Authorizer - def self.parameter_not_allowed_error_message - i18n_scope = "order_management.reports.enterprise_fee_summary" - I18n.t("parameter_not_allowed_error", scope: i18n_scope) - end - def authorize! authorize_by_distribution! authorize_by_fee! @@ -25,14 +20,6 @@ module OrderManagement require_ids_allowed(parameters.shipping_method_ids, permissions.allowed_shipping_methods) require_ids_allowed(parameters.payment_method_ids, permissions.allowed_payment_methods) end - - def require_ids_allowed(array, allowed_objects) - error_klass = ::Reports::Authorizer::ParameterNotAllowedError - error_message = self.class.parameter_not_allowed_error_message - ids_allowed = (array - allowed_objects.map(&:id).map(&:to_s)).blank? - - raise error_klass, error_message unless ids_allowed - end end end end diff --git a/engines/order_management/app/services/reports/authorizer.rb b/engines/order_management/app/services/reports/authorizer.rb index 279952249a..5871e7e395 100644 --- a/engines/order_management/app/services/reports/authorizer.rb +++ b/engines/order_management/app/services/reports/authorizer.rb @@ -8,5 +8,20 @@ module Reports @parameters = parameters @permissions = permissions end + + def self.parameter_not_allowed_error_message + i18n_scope = "order_management.reports.shared" + I18n.t("parameter_not_allowed_error", scope: i18n_scope) + end + + private + + def require_ids_allowed(array, allowed_objects) + error_klass = ::Reports::Authorizer::ParameterNotAllowedError + error_message = self.class.parameter_not_allowed_error_message + ids_allowed = (array - allowed_objects.map(&:id).map(&:to_s)).blank? + + raise error_klass, error_message unless ids_allowed + end end end diff --git a/engines/order_management/app/views/order_management/reports/bulk_coop/_filters.html.haml b/engines/order_management/app/views/order_management/reports/bulk_coop/_filters.html.haml new file mode 100644 index 0000000000..012d910070 --- /dev/null +++ b/engines/order_management/app/views/order_management/reports/bulk_coop/_filters.html.haml @@ -0,0 +1,31 @@ += form_for @report_parameters, as: :report, url: main_app.order_management_reports_bulk_coop_path, method: :post do |f| + .row.date-range-filter + .sixteen.columns.alpha + = label_tag nil, t(".date_range") + %br + + = f.label :start_at, class: "inline" + = f.text_field :start_at, class: "datetimepicker datepicker-from" + + %span.range-divider + %i.icon-arrow-right + + = f.text_field :end_at, class: "datetimepicker datepicker-to" + = f.label :end_at, class: "inline" + + .row + .sixteen.columns.alpha + = f.label :distributor_ids + = f.collection_select(:distributor_ids, @permissions.allowed_distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + + .row + .sixteen.columns.alpha + = f.label :report_type + = f.collection_select(:report_type, OrderManagement::Reports::BulkCoop::BulkCoopReport::REPORT_TYPES.map { |report_type| [t(".#{report_type}"), report_type] }, :last, :first, {}, {class: "select2 fullwidth", multiple: false}) + + .row + .sixteen.columns.alpha + = check_box_tag :report_format, "csv", false, id: "report_format_csv" + = label_tag :report_format_csv, t(".report_format_csv") + + = button t(".generate_report") diff --git a/engines/order_management/app/views/order_management/reports/bulk_coop/_report.html.haml b/engines/order_management/app/views/order_management/reports/bulk_coop/_report.html.haml new file mode 100644 index 0000000000..c4e2e5fc76 --- /dev/null +++ b/engines/order_management/app/views/order_management/reports/bulk_coop/_report.html.haml @@ -0,0 +1,20 @@ +- if @report.present? + %table#bulk_coop_report.report__table + %thead + %tr + - @renderer.header.each do |heading| + %th= heading + + %tbody + - @renderer.data_rows.each do |row| + %tr + - row.each do |cell_value| + %td= cell_value + + - if @renderer.data_rows.empty? + %tr + %td{colspan: @renderer.header.length}= t('.none') +- else + %p.report__message + = t(".select_and_search") + diff --git a/engines/order_management/app/views/order_management/reports/bulk_coop/create.html.haml b/engines/order_management/app/views/order_management/reports/bulk_coop/create.html.haml new file mode 100644 index 0000000000..6d8f0c79ab --- /dev/null +++ b/engines/order_management/app/views/order_management/reports/bulk_coop/create.html.haml @@ -0,0 +1,2 @@ += render "filters" += render "report" diff --git a/engines/order_management/app/views/order_management/reports/bulk_coop/new.html.haml b/engines/order_management/app/views/order_management/reports/bulk_coop/new.html.haml new file mode 100644 index 0000000000..790853ca1f --- /dev/null +++ b/engines/order_management/app/views/order_management/reports/bulk_coop/new.html.haml @@ -0,0 +1 @@ += render "filters" diff --git a/engines/order_management/config/routes.rb b/engines/order_management/config/routes.rb index 79706c635a..405219634b 100644 --- a/engines/order_management/config/routes.rb +++ b/engines/order_management/config/routes.rb @@ -1,6 +1,7 @@ Openfoodnetwork::Application.routes.prepend do namespace :order_management do namespace :reports do + resource :bulk_coop, only: [:new, :create], controller: :bulk_coop resource :enterprise_fee_summary, only: [:new, :create] end end diff --git a/lib/open_food_network/bulk_coop_report.rb b/lib/open_food_network/bulk_coop_report.rb deleted file mode 100644 index 9eb8cf07c6..0000000000 --- a/lib/open_food_network/bulk_coop_report.rb +++ /dev/null @@ -1,266 +0,0 @@ -require 'open_food_network/reports/bulk_coop_supplier_report' -require 'open_food_network/reports/bulk_coop_allocation_report' -require "open_food_network/reports/line_items" - -module OpenFoodNetwork - class BulkCoopReport - attr_reader :params - def initialize(user, params = {}, render_table = false) - @params = params - @user = user - @render_table = render_table - - @supplier_report = Reports::BulkCoopSupplierReport.new - @allocation_report = Reports::BulkCoopAllocationReport.new - end - - def header - case params[:report_type] - when "bulk_coop_supplier_report" - @supplier_report.header - when "bulk_coop_allocation" - @allocation_report.header - when "bulk_coop_packing_sheets" - [I18n.t(:report_header_customer), - I18n.t(:report_header_product), - I18n.t(:report_header_variant), - I18n.t(:report_header_sum_total)] - when "bulk_coop_customer_payments" - [I18n.t(:report_header_customer), - I18n.t(:report_header_date_of_order), - I18n.t(:report_header_total_cost), - I18n.t(:report_header_amount_owing), - I18n.t(:report_header_amount_paid)] - else - [I18n.t(:report_header_supplier), - I18n.t(:report_header_product), - I18n.t(:report_header_product), - I18n.t(:report_header_bulk_unit_size), - I18n.t(:report_header_variant), - I18n.t(:report_header_weight), - I18n.t(:report_header_sum_total), - I18n.t(:report_header_sum_max_total), - I18n.t(:report_header_units_required), - I18n.t(:report_header_remainder)] - end - end - - def search - report_line_items.orders - end - - def table_items - return [] unless @render_table - report_line_items.list(line_item_includes) - end - - def rules - case params[:report_type] - when "bulk_coop_supplier_report" - @supplier_report.rules - when "bulk_coop_allocation" - @allocation_report.rules - when "bulk_coop_packing_sheets" - [{ group_by: proc { |li| li.product }, - sort_by: proc { |product| product.name } }, - { group_by: proc { |li| li.full_name }, - sort_by: proc { |full_name| full_name } }, - { group_by: proc { |li| li.order }, - sort_by: proc { |order| order.to_s } }] - when "bulk_coop_customer_payments" - [{ group_by: proc { |li| li.order }, - sort_by: proc { |order| order.completed_at } }] - else - [{ group_by: proc { |li| li.product.supplier }, - sort_by: proc { |supplier| supplier.name } }, - { group_by: proc { |li| li.product }, - sort_by: proc { |product| product.name }, - summary_columns: [proc { |lis| lis.first.product.supplier.name }, - proc { |lis| lis.first.product.name }, - proc { |lis| lis.first.product.group_buy_unit_size || 0.0 }, - proc { |_lis| "" }, - proc { |_lis| "" }, - proc { |lis| lis.sum { |li| li.quantity * (li.weight_from_unit_value || 0) } }, - proc { |lis| lis.sum { |li| (li.max_quantity || 0) * (li.weight_from_unit_value || 0) } }, - proc { |lis| ( (lis.first.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| [li.max_quantity || 0, li.quantity || 0].max * (li.weight_from_unit_value || 0) } / lis.first.product.group_buy_unit_size ) ).floor }, - proc { |lis| lis.sum { |li| [li.max_quantity || 0, li.quantity || 0].max * (li.weight_from_unit_value || 0) } - ( ( (lis.first.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| [li.max_quantity || 0, li.quantity || 0].max * (li.weight_from_unit_value || 0) } / lis.first.product.group_buy_unit_size ) ).floor * (lis.first.product.group_buy_unit_size || 0) ) }] }, - { group_by: proc { |li| li.full_name }, - sort_by: proc { |full_name| full_name } }] - end - end - - def columns - case params[:report_type] - when "bulk_coop_supplier_report" - @supplier_report.columns - when "bulk_coop_allocation" - @allocation_report.columns - when "bulk_coop_packing_sheets" - [ - :order_billing_address_name, - :product_name, - :full_name, - :total_quantity - ] - when "bulk_coop_customer_payments" - [ - :order_billing_address_name, - :order_completed_at, - :customer_payments_total_cost, - :customer_payments_amount_owed, - :customer_payments_amount_paid - ] - else - [ - :product_supplier_name, - :product_name, - :product_group_buy_unit_size, - :full_name, - :weight_from_unit_value, - :total_quantity, - :total_max_quantity, - :empty_cell, - :empty_cell - ] - end - end - - private - - def line_item_includes - [{ order: [:bill_address], - variant: [{ option_values: :option_type }, { product: :supplier }] }] - end - - def order_permissions - return @order_permissions unless @order_permissions.nil? - @order_permissions = ::Permissions::Order.new(@user, @params[:q]) - end - - def report_line_items - @report_line_items ||= Reports::LineItems.new(order_permissions, @params) - end - - def customer_payments_total_cost(line_items) - line_items.map(&:order).uniq.sum(&:total) - end - - def customer_payments_amount_owed(line_items) - line_items.map(&:order).uniq.sum(&:outstanding_balance) - end - - def customer_payments_amount_paid(line_items) - line_items.map(&:order).uniq.sum(&:payment_total) - end - - def empty_cell(*) - "" - end - - def full_name(line_items) - line_items.first.full_name - end - - def group_buy_unit_size(line_items) - (line_items.first.variant.product.group_buy_unit_size || 0.0) / - (line_items.first.product.variant_unit_scale || 1) - end - - def max_quantity_excess(line_items) - max_quantity_amount(line_items) - total_amount(line_items) - end - - def max_quantity_amount(line_items) - line_items.sum do |line_item| - max_quantity = [line_item.max_quantity || 0, line_item.quantity || 0].max - max_quantity * scaled_unit_value(line_item.variant) - end - end - - def option_value_value(line_items) - OpenFoodNetwork::OptionValueNamer.new(line_items.first).value - end - - def option_value_unit(line_items) - OpenFoodNetwork::OptionValueNamer.new(line_items.first).unit - end - - def order_billing_address_name(line_items) - billing_address = line_items.first.order.bill_address - billing_address.firstname + " " + billing_address.lastname - end - - def order_completed_at(line_items) - line_items.first.order.completed_at.to_s - end - - def product_group_buy_unit_size(line_items) - line_items.first.product.group_buy_unit_size || 0.0 - end - - def product_name(line_items) - line_items.first.product.name - end - - def product_supplier_name(line_items) - line_items.first.product.supplier.name - end - - def remainder(line_items) - remainder = total_available(line_items) - total_amount(line_items) - remainder >= 0 ? remainder : '' - end - - def scaled_final_weight_volume(line_item) - (line_item.final_weight_volume || 0) / (line_item.product.variant_unit_scale || 1) - end - - def scaled_unit_value(variant) - (variant.unit_value || 0) / (variant.product.variant_unit_scale || 1) - end - - def total_amount(line_items) - line_items.sum { |li| scaled_final_weight_volume(li) } - end - - def total_available(line_items) - units_required(line_items) * group_buy_unit_size(line_items) - end - - def total_max_quantity(line_items) - line_items.sum { |line_item| line_item.max_quantity || 0 } - end - - def total_quantity(line_items) - line_items.sum(&:quantity) - end - - def total_label(*) - I18n.t('admin.reports.total') - end - - def units_required(line_items) - if group_buy_unit_size(line_items).zero? - 0 - else - ( total_amount(line_items) / group_buy_unit_size(line_items) ).ceil - end - end - - def variant_product_group_buy_unit_size_f(line_items) - group_buy_unit_size(line_items) - end - - def variant_product_name(line_items) - line_items.first.variant.product.name - end - - def variant_product_supplier_name(line_items) - line_items.first.variant.product.supplier.name - end - - def weight_from_unit_value(line_items) - line_items.first.weight_from_unit_value || 0 - end - end -end diff --git a/lib/open_food_network/reports/bulk_coop_allocation_report.rb b/lib/open_food_network/reports/bulk_coop_allocation_report.rb deleted file mode 100644 index bcc068c0c5..0000000000 --- a/lib/open_food_network/reports/bulk_coop_allocation_report.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -module OpenFoodNetwork::Reports - class BulkCoopAllocationReport - def header - [ - I18n.t(:report_header_customer), - I18n.t(:report_header_product), - I18n.t(:report_header_bulk_unit_size), - I18n.t(:report_header_variant), - I18n.t(:report_header_variant_value), - I18n.t(:report_header_variant_unit), - I18n.t(:report_header_weight), - I18n.t(:report_header_sum_total), - I18n.t(:report_header_total_available), - I18n.t(:report_header_unallocated), - I18n.t(:report_header_max_quantity_excess), - ] - end - - def rules - [ - { - group_by: proc { |line_item| line_item.product }, - sort_by: proc { |product| product.name }, - summary_columns: [ - :total_label, - :variant_product_name, - :variant_product_group_buy_unit_size_f, - :empty_cell, - :empty_cell, - :empty_cell, - :empty_cell, - :total_amount, - :total_available, - :remainder, - :max_quantity_excess - ] - }, - { - group_by: proc { |line_item| line_item.order }, - sort_by: proc { |order| order.to_s } - } - ] - end - - def columns - [ - :order_billing_address_name, - :product_name, - :product_group_buy_unit_size, - :full_name, - :option_value_value, - :option_value_unit, - :weight_from_unit_value, - :total_amount, - :empty_cell, - :empty_cell, - :empty_cell - ] - end - end -end diff --git a/lib/open_food_network/reports/bulk_coop_supplier_report.rb b/lib/open_food_network/reports/bulk_coop_supplier_report.rb deleted file mode 100644 index 0c1c86e3b2..0000000000 --- a/lib/open_food_network/reports/bulk_coop_supplier_report.rb +++ /dev/null @@ -1,60 +0,0 @@ -module OpenFoodNetwork::Reports - class BulkCoopSupplierReport - def header - [ - I18n.t(:report_header_supplier), - I18n.t(:report_header_product), - I18n.t(:report_header_bulk_unit_size), - I18n.t(:report_header_variant), - I18n.t(:report_header_variant_value), - I18n.t(:report_header_variant_unit), - I18n.t(:report_header_weight), - I18n.t(:report_header_sum_total), - I18n.t(:report_header_units_required), - I18n.t(:report_header_unallocated), - I18n.t(:report_header_max_quantity_excess), - ] - end - - def rules - [ - { group_by: proc { |line_item| line_item.product.supplier }, - sort_by: proc { |supplier| supplier.name } }, - { group_by: proc { |line_item| line_item.product }, - sort_by: proc { |product| product.name }, - summary_columns: [ - :variant_product_supplier_name, - :variant_product_name, - :variant_product_group_buy_unit_size_f, - :empty_cell, - :empty_cell, - :empty_cell, - :empty_cell, - :total_amount, - :units_required, - :remainder, - :max_quantity_excess - ] - }, - { group_by: proc { |line_item| line_item.full_name }, - sort_by: proc { |full_name| full_name } } - ] - end - - def columns - [ - :variant_product_supplier_name, - :variant_product_name, - :variant_product_group_buy_unit_size_f, - :full_name, - :option_value_value, - :option_value_unit, - :weight_from_unit_value, - :total_amount, - :empty_cell, - :empty_cell, - :empty_cell - ] - end - end -end