From 1ff98a1d8a6790bc939530f2b980da7679b8ace7 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Tue, 22 Mar 2022 10:31:32 +0000 Subject: [PATCH 01/54] Reports: Improve UI --- .../admin/reports/_date_range_form.html.haml | 16 +++--- .../reports/_rendering_options.html.haml | 8 +-- app/views/admin/reports/_table.html.haml | 3 -- app/views/admin/reports/packing.html.haml | 49 ++++++++++--------- .../reports/_customer_names_message.html.haml | 2 +- .../admin/reports/_date_range_form.html.haml | 14 +++--- .../spree/admin/reports/_table.html.haml | 3 -- .../reports/orders_and_distributors.html.haml | 3 +- .../reports/orders_and_fulfillment.html.haml | 3 +- .../css/admin/components/date-picker.scss | 1 + app/webpacker/css/admin/reports.scss | 5 ++ config/locales/en.yml | 20 ++++---- spec/system/admin/reports_spec.rb | 1 - 13 files changed, 67 insertions(+), 61 deletions(-) diff --git a/app/views/admin/reports/_date_range_form.html.haml b/app/views/admin/reports/_date_range_form.html.haml index fa585fe14f..134e7d967f 100644 --- a/app/views/admin/reports/_date_range_form.html.haml +++ b/app/views/admin/reports/_date_range_form.html.haml @@ -1,9 +1,9 @@ .row.date-range-filter - = label_tag nil, t(:date_range) - %br - = label_tag nil, t(:start), :class => 'inline' - = text_field_tag "q[order_completed_at_gt]", params.dig(:q, :order_completed_at_gt), :class => 'datetimepicker datepicker-from' - %span.range-divider - %i.icon-arrow-right - = text_field_tag "q[order_completed_at_lt]", params.dig(:q, :order_completed_at_lt), :class => 'datetimepicker datepicker-to' - = label_tag nil, t(:end), :class => 'inline' + .alpha.two.columns + = label_tag nil, t(:date_range) + .omega.fourteen.columns + = text_field_tag "q[order_completed_at_gt]", params.dig(:q, :order_completed_at_gt), :class => 'datetimepicker datepicker-from', :placeholder => t(:start) + %span.range-divider + %i.icon-arrow-right + = text_field_tag "q[order_completed_at_lt]", params.dig(:q, :order_completed_at_lt), :class => 'datetimepicker datepicker-to', :placeholder => t(:stop) + diff --git a/app/views/admin/reports/_rendering_options.html.haml b/app/views/admin/reports/_rendering_options.html.haml index 0967df8999..2fb1dc5e1e 100644 --- a/app/views/admin/reports/_rendering_options.html.haml +++ b/app/views/admin/reports/_rendering_options.html.haml @@ -1,8 +1,8 @@ .row.rendering-options - = label_tag :report_format, t(".generate_report") - %br - = select_tag :report_format, options_for_select({t('.on_screen') => '', t('.csv_spreadsheet') => 'csv', t('.excel_spreadsheet') => 'xlsx', t('.openoffice_spreadsheet') => 'ods'}) - + .alpha.two.columns + = label_tag :report_format, t(".generate_report") + .omega.fourteen.columns + = 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") diff --git a/app/views/admin/reports/_table.html.haml b/app/views/admin/reports/_table.html.haml index edbc2f1a8f..70b2c2e3aa 100644 --- a/app/views/admin/reports/_table.html.haml +++ b/app/views/admin/reports/_table.html.haml @@ -15,6 +15,3 @@ - if @report.table_rows.empty? %tr %td{colspan: @report.table_headers.count}= t(:none) -- else - %p.report__message - = t(".select_and_search", option: msg_option.upcase) diff --git a/app/views/admin/reports/packing.html.haml b/app/views/admin/reports/packing.html.haml index 48fe52194a..f79c737ee2 100644 --- a/app/views/admin/reports/packing.html.haml +++ b/app/views/admin/reports/packing.html.haml @@ -1,31 +1,36 @@ = form_tag main_app.admin_reports_path, report_type: 'packing' do - = render partial: 'date_range_form' + %fieldset.no-border-bottom + %legend{ align: 'center'}= t(:report_filters) + = render partial: 'date_range_form' - .row - .alpha.two.columns= label_tag nil, t(:report_hubs) - .omega.fourteen.columns - = collection_select("q", "order_distributor_id_in", @distributors, :id, :name, {selected: params.dig(:q, :order_distributor_id_in)}, {class: "select2 fullwidth", multiple: true}) + .row + .alpha.two.columns= label_tag nil, t(:report_hubs) + .omega.fourteen.columns + = collection_select("q", "order_distributor_id_in", @distributors, :id, :name, {selected: params.dig(:q, :order_distributor_id_in)}, {class: "select2 fullwidth", multiple: true}) - .row - .alpha.two.columns= label_tag nil, t(:report_producers) - .omega.fourteen.columns - = select_tag("q[supplier_id_in]", options_from_collection_for_select(@suppliers, :id, :name, params.dig(:q, :supplier_id_in)), {class: "select2 fullwidth", multiple: true}) + .row + .alpha.two.columns= label_tag nil, t(:report_producers) + .omega.fourteen.columns + = select_tag("q[supplier_id_in]", options_from_collection_for_select(@suppliers, :id, :name, params.dig(:q, :supplier_id_in)), {class: "select2 fullwidth", multiple: true}) - .row - .alpha.two.columns= label_tag nil, t(:report_customers_cycle) - .omega.fourteen.columns - = select_tag("q[order_cycle_id_in]", options_for_select(report_order_cycle_options(@order_cycles), params.dig(:q, :order_cycle_id_in)), {class: "select2 fullwidth", multiple: true}) + .row + .alpha.two.columns= label_tag nil, t(:report_customers_cycle) + .omega.fourteen.columns + = select_tag("q[order_cycle_id_in]", options_for_select(report_order_cycle_options(@order_cycles), params.dig(:q, :order_cycle_id_in)), {class: "select2 fullwidth", multiple: true}) - .row - .alpha.two.columns= label_tag nil, t(:report_type) - .omega.fourteen.columns - = select_tag(:report_subtype, options_for_select(@report_subtypes, @report_subtype)) + %fieldset + %legend{ align: 'center'}= t(:report_display_options) + .row + .alpha.two.columns= label_tag nil, t(:report_type) + .omega.fourteen.columns + = select_tag(:report_subtype, options_for_select(@report_subtypes, @report_subtype)) - = render partial: "rendering_options" + = render partial: "rendering_options" - .row - = button t(:search) + .actions.filter-actions + = button t(:go), "report__submit-btn" -= render partial: "spree/admin/reports/customer_names_message" +- if params[:q].present? + = render partial: "spree/admin/reports/customer_names_message" -= render "table", id: "listing_orders", msg_option: t(:search) += render "table", id: "listing_orders" diff --git a/app/views/spree/admin/reports/_customer_names_message.html.haml b/app/views/spree/admin/reports/_customer_names_message.html.haml index 191a3e1aa1..27f18cc93c 100644 --- a/app/views/spree/admin/reports/_customer_names_message.html.haml +++ b/app/views/spree/admin/reports/_customer_names_message.html.haml @@ -1,2 +1,2 @@ -%p.customer-names-tip +%p.report__message = t(".customer_names_tip") diff --git a/app/views/spree/admin/reports/_date_range_form.html.haml b/app/views/spree/admin/reports/_date_range_form.html.haml index 4d76b95d9f..28d1385c51 100644 --- a/app/views/spree/admin/reports/_date_range_form.html.haml +++ b/app/views/spree/admin/reports/_date_range_form.html.haml @@ -1,9 +1,7 @@ .row.date-range-filter - = label_tag nil, t(:date_range) - %br - = label_tag nil, t(:start), :class => 'inline' - = f.text_field :completed_at_gt, :class => 'datetimepicker datepicker-from' - %span.range-divider - %i.icon-arrow-right - = f.text_field :completed_at_lt, :class => 'datetimepicker datepicker-to' - = label_tag nil, t(:end), :class => 'inline' + .alpha.two.columns= label_tag nil, t(:date_range) + .omega.fourteen.columns + = f.text_field :completed_at_gt, :class => 'datetimepicker datepicker-from', :placeholder => t(:start) + %span.range-divider + %i.icon-arrow-right + = f.text_field :completed_at_lt, :class => 'datetimepicker datepicker-to', :placeholder => t(:stop) diff --git a/app/views/spree/admin/reports/_table.html.haml b/app/views/spree/admin/reports/_table.html.haml index d1927aaf55..e9347669b3 100644 --- a/app/views/spree/admin/reports/_table.html.haml +++ b/app/views/spree/admin/reports/_table.html.haml @@ -18,6 +18,3 @@ - if @table.empty? %tr %td{colspan: @header.count}= t(:none) -- else - %p.report__message - = t(".select_and_search", option: msg_option.upcase) diff --git a/app/views/spree/admin/reports/orders_and_distributors.html.haml b/app/views/spree/admin/reports/orders_and_distributors.html.haml index 4d99b181af..fae9b26d99 100644 --- a/app/views/spree/admin/reports/orders_and_distributors.html.haml +++ b/app/views/spree/admin/reports/orders_and_distributors.html.haml @@ -6,6 +6,7 @@ %br = button t(:search) -= render partial: "customer_names_message" +- if render_content? + = render partial: "customer_names_message" = render "table", id: "listing_orders", msg_option: t(:search) diff --git a/app/views/spree/admin/reports/orders_and_fulfillment.html.haml b/app/views/spree/admin/reports/orders_and_fulfillment.html.haml index 038c4d8a0d..1a880f2dd5 100644 --- a/app/views/spree/admin/reports/orders_and_fulfillment.html.haml +++ b/app/views/spree/admin/reports/orders_and_fulfillment.html.haml @@ -25,6 +25,7 @@ .row = button t(:search) -= render partial: "customer_names_message" +- if render_content? + = render partial: "customer_names_message" = render "table", id: "listing_orders", msg_option: t(:search) diff --git a/app/webpacker/css/admin/components/date-picker.scss b/app/webpacker/css/admin/components/date-picker.scss index 8511a73617..5296bd1f6a 100644 --- a/app/webpacker/css/admin/components/date-picker.scss +++ b/app/webpacker/css/admin/components/date-picker.scss @@ -3,6 +3,7 @@ .date-range-filter { .range-divider { padding: 0; + margin-left: 5px; } input.datepicker { width: 96px !important; diff --git a/app/webpacker/css/admin/reports.scss b/app/webpacker/css/admin/reports.scss index e8c36b6af6..16fee65a8d 100644 --- a/app/webpacker/css/admin/reports.scss +++ b/app/webpacker/css/admin/reports.scss @@ -27,3 +27,8 @@ float: left; } } + +.report__submit-btn { + margin: 0 !important; + width: 120px; +} diff --git a/config/locales/en.yml b/config/locales/en.yml index ebfff986f6..cae025c8ff 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1330,7 +1330,7 @@ en: is_temperature_controlled: "TempControlled?" temp_controlled: "TempControlled?" rendering_options: - generate_report: "Generate report:" + generate_report: "Generate report" on_screen: "On screen" csv_spreadsheet: "CSV Spreadsheet" excel_spreadsheet: "Excel Spreadsheet" @@ -2622,20 +2622,22 @@ See the %{link} to find out more about %{sitename}'s features and to start using report_customers_cycle: "Order Cycle" report_customers_type: "Report Type" report_customers_csv: "Download as csv" - report_producers: "Producers: " - report_type: "Report Type: " - report_hubs: "Hubs: " - report_payment: "Payment Methods: " - report_distributor: "Distributor: " + report_producers: "Producers" + report_type: "Report Type" + report_hubs: "Hubs" + report_payment: "Payment Methods" + report_distributor: "Distributor" report_payment_by: 'Payments By Type' report_itemised_payment: 'Itemised Payment Totals' report_payment_totals: 'Payment Totals' report_all: 'all' - report_order_cycle: "Order Cycle: " - report_enterprises: "Enterprises: " - report_users: "Users: " + report_order_cycle: "Order Cycle" + report_enterprises: "Enterprises" + report_users: "Users" report_tax_rates: Tax rates report_tax_types: Tax types + report_filters: Report Filters + report_display_options: Display Options report_header_order_cycle: Order Cycle report_header_user: User report_header_email: Email diff --git a/spec/system/admin/reports_spec.rb b/spec/system/admin/reports_spec.rb index b6c0c7717c..7e0b7e00d6 100644 --- a/spec/system/admin/reports_spec.rb +++ b/spec/system/admin/reports_spec.rb @@ -39,7 +39,6 @@ describe ' it "customers report" do click_link "Mailing List" expect(page).to have_select('report_type', selected: 'Mailing List') - expect(page).to have_content "click on GO" click_button "Go" rows = find("table#listing_customers").all("thead tr") From d53d38906a5f26fc24ddf047fcd2b1aeee8407ab Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 23 Mar 2022 09:01:29 +0000 Subject: [PATCH 02/54] Packing Report: Refactor view to be reusable by other reports For now this view is used only in the context of packing, but later we would move all existing rpeort to use same view --- app/controllers/admin/reports_controller.rb | 5 ++- .../reports/_rendering_options.html.haml | 13 +++++-- app/views/admin/reports/_table.html.haml | 2 +- .../admin/reports/filters/_packing.html.haml | 16 +++++++++ app/views/admin/reports/packing.html.haml | 36 ------------------- app/views/admin/reports/show.html.haml | 16 +++++++++ config/locales/en.yml | 2 +- 7 files changed, 48 insertions(+), 42 deletions(-) create mode 100644 app/views/admin/reports/filters/_packing.html.haml delete mode 100644 app/views/admin/reports/packing.html.haml create mode 100644 app/views/admin/reports/show.html.haml diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index 79e5176792..92bc138f31 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -30,7 +30,7 @@ module Admin assign_view_data load_form_options - render report_type + render "show" end def assign_view_data @@ -39,6 +39,9 @@ module Admin @report_subtypes = report_class.report_subtypes.map do |subtype| [t("packing.#{subtype}_report", scope: i18n_scope), subtype] end + if @report_type == "packing" + @report_message = I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") + end end def load_form_options diff --git a/app/views/admin/reports/_rendering_options.html.haml b/app/views/admin/reports/_rendering_options.html.haml index 2fb1dc5e1e..a137477034 100644 --- a/app/views/admin/reports/_rendering_options.html.haml +++ b/app/views/admin/reports/_rendering_options.html.haml @@ -1,8 +1,15 @@ +- if @report_subtypes.any? + .row + .alpha.two.columns= label_tag nil, t(:report_type) + .omega.fourteen.columns + = select_tag(:report_subtype, options_for_select(@report_subtypes, @report_subtype)) + .row.rendering-options .alpha.two.columns = label_tag :report_format, t(".generate_report") .omega.fourteen.columns = 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") diff --git a/app/views/admin/reports/_table.html.haml b/app/views/admin/reports/_table.html.haml index 70b2c2e3aa..df47bdd881 100644 --- a/app/views/admin/reports/_table.html.haml +++ b/app/views/admin/reports/_table.html.haml @@ -1,5 +1,5 @@ - if params[:q].present? - %table.report__table{id: id} + %table.report__table %thead %tr - @report.table_headers.each do |heading| diff --git a/app/views/admin/reports/filters/_packing.html.haml b/app/views/admin/reports/filters/_packing.html.haml new file mode 100644 index 0000000000..c7f54750ee --- /dev/null +++ b/app/views/admin/reports/filters/_packing.html.haml @@ -0,0 +1,16 @@ += render partial: 'admin/reports/date_range_form' + +.row + .alpha.two.columns= label_tag nil, t(:report_hubs) + .omega.fourteen.columns + = collection_select("q", "order_distributor_id_in", @distributors, :id, :name, {selected: params.dig(:q, :order_distributor_id_in)}, {class: "select2 fullwidth", multiple: true}) + +.row + .alpha.two.columns= label_tag nil, t(:report_producers) + .omega.fourteen.columns + = select_tag("q[supplier_id_in]", options_from_collection_for_select(@suppliers, :id, :name, params.dig(:q, :supplier_id_in)), {class: "select2 fullwidth", multiple: true}) + +.row + .alpha.two.columns= label_tag nil, t(:report_customers_cycle) + .omega.fourteen.columns + = select_tag("q[order_cycle_id_in]", options_for_select(report_order_cycle_options(@order_cycles), params.dig(:q, :order_cycle_id_in)), {class: "select2 fullwidth", multiple: true}) \ No newline at end of file diff --git a/app/views/admin/reports/packing.html.haml b/app/views/admin/reports/packing.html.haml deleted file mode 100644 index f79c737ee2..0000000000 --- a/app/views/admin/reports/packing.html.haml +++ /dev/null @@ -1,36 +0,0 @@ -= form_tag main_app.admin_reports_path, report_type: 'packing' do - %fieldset.no-border-bottom - %legend{ align: 'center'}= t(:report_filters) - = render partial: 'date_range_form' - - .row - .alpha.two.columns= label_tag nil, t(:report_hubs) - .omega.fourteen.columns - = collection_select("q", "order_distributor_id_in", @distributors, :id, :name, {selected: params.dig(:q, :order_distributor_id_in)}, {class: "select2 fullwidth", multiple: true}) - - .row - .alpha.two.columns= label_tag nil, t(:report_producers) - .omega.fourteen.columns - = select_tag("q[supplier_id_in]", options_from_collection_for_select(@suppliers, :id, :name, params.dig(:q, :supplier_id_in)), {class: "select2 fullwidth", multiple: true}) - - .row - .alpha.two.columns= label_tag nil, t(:report_customers_cycle) - .omega.fourteen.columns - = select_tag("q[order_cycle_id_in]", options_for_select(report_order_cycle_options(@order_cycles), params.dig(:q, :order_cycle_id_in)), {class: "select2 fullwidth", multiple: true}) - - %fieldset - %legend{ align: 'center'}= t(:report_display_options) - .row - .alpha.two.columns= label_tag nil, t(:report_type) - .omega.fourteen.columns - = select_tag(:report_subtype, options_for_select(@report_subtypes, @report_subtype)) - - = render partial: "rendering_options" - - .actions.filter-actions - = button t(:go), "report__submit-btn" - -- if params[:q].present? - = render partial: "spree/admin/reports/customer_names_message" - -= render "table", id: "listing_orders" diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml new file mode 100644 index 0000000000..eb159cde4b --- /dev/null +++ b/app/views/admin/reports/show.html.haml @@ -0,0 +1,16 @@ += form_tag main_app.admin_reports_path, report_type: @report_type do + %fieldset.no-border-bottom + %legend{ align: 'center'}= t(:report_filters) + = render partial: "admin/reports/filters/#{@report_type}" + + %fieldset + %legend{ align: 'center'}= t(:report_render_options) + = render partial: "rendering_options" + + .actions.filter-actions + = button t(:go), "report__submit-btn" + +- if @report_message.present? + %p.report__message= @report_message + += render "table" diff --git a/config/locales/en.yml b/config/locales/en.yml index cae025c8ff..c0dbb1412f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2637,7 +2637,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using report_tax_rates: Tax rates report_tax_types: Tax types report_filters: Report Filters - report_display_options: Display Options + report_render_options: Render Options report_header_order_cycle: Order Cycle report_header_user: User report_header_email: Email From 213c0dd060e970a1c30c1515c2edcd3fe3f44891 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Tue, 22 Mar 2022 16:14:44 +0000 Subject: [PATCH 03/54] Packing Report: add price and reorder columns Adds price column last_name column first because it's used for ordering --- app/controllers/admin/reports_controller.rb | 6 ++-- app/controllers/concerns/reports_actions.rb | 4 --- .../reports/_rendering_options.html.haml | 7 +++- app/views/admin/reports/_table.html.haml | 33 ++++++++++--------- .../admin/reports/filters/_packing.html.haml | 0 app/views/admin/reports/show.html.haml | 3 +- app/views/layouts/pdf.html.haml | 32 ++++++++++++++++++ config/locales/en.yml | 2 ++ lib/reporting/queries/query_builder.rb | 2 +- lib/reporting/report_renderer.rb | 10 ++++++ lib/reporting/report_template.rb | 2 +- lib/reporting/reports/packing/customer.rb | 3 +- lib/reporting/reports/packing/supplier.rb | 5 ++- .../api/v0/reports/packing_report_spec.rb | 5 ++- 14 files changed, 83 insertions(+), 31 deletions(-) mode change 100644 => 100755 app/views/admin/reports/filters/_packing.html.haml create mode 100644 app/views/layouts/pdf.html.haml diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index 92bc138f31..31393a79a6 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -12,7 +12,7 @@ module Admin @report = report_class.new(spree_current_user, ransack_params, report_options) - if export_spreadsheet? + if report_format.present? export_report else render_report @@ -22,14 +22,12 @@ module Admin private def export_report - render report_format.to_sym => @report.public_send("to_#{report_format}"), - :filename => report_filename + send_data @report.public_send("to_#{report_format}"), filename: report_filename end def render_report assign_view_data load_form_options - render "show" end diff --git a/app/controllers/concerns/reports_actions.rb b/app/controllers/concerns/reports_actions.rb index 7ff6e0c461..8a4cd10afa 100644 --- a/app/controllers/concerns/reports_actions.rb +++ b/app/controllers/concerns/reports_actions.rb @@ -39,10 +39,6 @@ module ReportsActions params[:report_format] end - def export_spreadsheet? - ['xlsx', 'ods', 'csv'].include?(report_format) - end - def form_options_required? [:packing, :customers, :products_and_inventory, :order_cycle_management]. include? report_type.to_sym diff --git a/app/views/admin/reports/_rendering_options.html.haml b/app/views/admin/reports/_rendering_options.html.haml index a137477034..fa7b387bc0 100644 --- a/app/views/admin/reports/_rendering_options.html.haml +++ b/app/views/admin/reports/_rendering_options.html.haml @@ -8,7 +8,12 @@ .alpha.two.columns = label_tag :report_format, t(".generate_report") .omega.fourteen.columns - = select_tag :report_format, options_for_select({t('.on_screen') => '', t('.csv_spreadsheet') => 'csv', t('.excel_spreadsheet') => 'xlsx', t('.openoffice_spreadsheet') => 'ods'}) + = select_tag :report_format, options_for_select({ | + t('.on_screen') => '', | + t('.pdf') => 'pdf', | + 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] diff --git a/app/views/admin/reports/_table.html.haml b/app/views/admin/reports/_table.html.haml index df47bdd881..b5dd0586a5 100644 --- a/app/views/admin/reports/_table.html.haml +++ b/app/views/admin/reports/_table.html.haml @@ -1,17 +1,18 @@ -- if params[:q].present? - %table.report__table - %thead - %tr - - @report.table_headers.each do |heading| - %th - = t("admin.reports.table.headings.#{heading}") - %tbody - - @report.table_rows.each do |row| - - if row - %tr - - row.each do |cell| - %td - = cell - - if @report.table_rows.empty? +- report ||= @report + +%table.report__table + %thead + %tr + - report.table_headers.each do |heading| + %th + = t("admin.reports.table.headings.#{heading}") + %tbody + - report.table_rows.each do |row| + - if row %tr - %td{colspan: @report.table_headers.count}= t(:none) + - row.each do |cell| + %td + = cell + - if report.table_rows.empty? + %tr + %td{colspan: report.table_headers.count}= t(:none) diff --git a/app/views/admin/reports/filters/_packing.html.haml b/app/views/admin/reports/filters/_packing.html.haml old mode 100644 new mode 100755 diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index eb159cde4b..4108139d13 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -13,4 +13,5 @@ - if @report_message.present? %p.report__message= @report_message -= render "table" +- if params[:q].present? + = render "table" diff --git a/app/views/layouts/pdf.html.haml b/app/views/layouts/pdf.html.haml new file mode 100644 index 0000000000..f1cf20a008 --- /dev/null +++ b/app/views/layouts/pdf.html.haml @@ -0,0 +1,32 @@ +!!! +%html + %head + %meta{:content => "charset=UTF-8"} + -# Using wicked_pdf_stylesheet_pack_tag with a new pdf pack was not working when using + -# WickedPdf.new.pdf_from_string cause the css file reference was not absolute + -# So I ended up putting inline css here, so it's included for sure in the PDF + :css + body { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif; + } + table { + width: 100%; + border-collapse: collapse; + } + th { + text-align: left; + } + th, td { + padding: 5px; + } + thead { + background-color: #f6f6f6; + border-bottom: 1px solid grey; + } + + %body + = yield diff --git a/config/locales/en.yml b/config/locales/en.yml index c0dbb1412f..beeb972b88 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1329,9 +1329,11 @@ en: quantity: "Quantity" is_temperature_controlled: "TempControlled?" temp_controlled: "TempControlled?" + price: "Price" rendering_options: generate_report: "Generate report" on_screen: "On screen" + pdf: PDF csv_spreadsheet: "CSV Spreadsheet" excel_spreadsheet: "Excel Spreadsheet" openoffice_spreadsheet: "OpenOffice Spreadsheet" diff --git a/lib/reporting/queries/query_builder.rb b/lib/reporting/queries/query_builder.rb index ef3080aa31..e146699567 100644 --- a/lib/reporting/queries/query_builder.rb +++ b/lib/reporting/queries/query_builder.rb @@ -84,7 +84,7 @@ module Reporting end def summary_row_title - I18n.t("total_items", scope: i18n_scope) + I18n.t("total", scope: i18n_scope) end def i18n_scope diff --git a/lib/reporting/report_renderer.rb b/lib/reporting/report_renderer.rb index b9027ae60d..16b78d91de 100644 --- a/lib/reporting/report_renderer.rb +++ b/lib/reporting/report_renderer.rb @@ -35,5 +35,15 @@ module Reporting def to_xlsx SpreadsheetArchitect.to_xlsx(headers: table_headers, data: table_rows) end + + def to_pdf + WickedPdf.new.pdf_from_string( + ActionController::Base.new.render_to_string( + template: 'admin/reports/_table', + layout: 'pdf', + locals: { report: self } + ) + ) + end end end diff --git a/lib/reporting/report_template.rb b/lib/reporting/report_template.rb index e8de2cd322..3192a56ae4 100644 --- a/lib/reporting/report_template.rb +++ b/lib/reporting/report_template.rb @@ -3,7 +3,7 @@ module Reporting class ReportTemplate delegate :as_json, :as_arrays, :table_headers, :table_rows, - :to_csv, :to_xlsx, :to_ods, :to_json, to: :renderer + :to_csv, :to_xlsx, :to_ods, :to_pdf, :to_json, to: :renderer attr_reader :options diff --git a/lib/reporting/reports/packing/customer.rb b/lib/reporting/reports/packing/customer.rb index f12b83a3b5..5c3cfcae09 100644 --- a/lib/reporting/reports/packing/customer.rb +++ b/lib/reporting/reports/packing/customer.rb @@ -9,12 +9,13 @@ module Reporting { 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])), + first_name: default_blank(masked(bill_address_alias[:firstname])), 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]), + price: sum_values(line_item_table[:quantity] * line_item_table[:price]), temp_controlled: boolean_blank(shipping_category_table[:temperature_controlled]), } end diff --git a/lib/reporting/reports/packing/supplier.rb b/lib/reporting/reports/packing/supplier.rb index fa173afce8..fbeb96cb07 100644 --- a/lib/reporting/reports/packing/supplier.rb +++ b/lib/reporting/reports/packing/supplier.rb @@ -4,21 +4,24 @@ module Reporting module Reports module Packing class Supplier < Base + # rubocop:disable Metrics/AbcSize 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(masked(bill_address_alias[:firstname])), last_name: default_blank(masked(bill_address_alias[:lastname])), + first_name: default_blank(masked(bill_address_alias[:firstname])), product: default_string(product_table[:name], summary_row_title), variant: default_blank(variant_full_name), quantity: sum_values(line_item_table[:quantity]), + price: sum_values(line_item_table[:quantity] * line_item_table[:price]), temp_controlled: boolean_blank(shipping_category_table[:temperature_controlled]), } end end + # rubocop:enable Metrics/AbcSize def group_sets lambda do diff --git a/spec/controllers/api/v0/reports/packing_report_spec.rb b/spec/controllers/api/v0/reports/packing_report_spec.rb index 4becf70c79..92d14f112f 100644 --- a/spec/controllers/api/v0/reports/packing_report_spec.rb +++ b/spec/controllers/api/v0/reports/packing_report_spec.rb @@ -69,6 +69,7 @@ describe Api::V0::ReportsController, type: :controller do "product" => line_item.product.name, "variant" => line_item.full_name, "quantity" => line_item.quantity, + "price" => (line_item.quantity * line_item.price).to_s, "temp_controlled" => line_item.product.shipping_category&.temperature_controlled ? I18n.t(:yes) : I18n.t(:no) } @@ -84,6 +85,7 @@ describe Api::V0::ReportsController, type: :controller do "product" => line_item.product.name, "variant" => line_item.full_name, "quantity" => line_item.quantity, + "price" => (line_item.quantity * line_item.price).to_s, "temp_controlled" => line_item.product.shipping_category&.temperature_controlled ? I18n.t(:yes) : I18n.t(:no) } @@ -96,9 +98,10 @@ describe Api::V0::ReportsController, type: :controller do "first_name" => "", "last_name" => "", "supplier" => "", - "product" => I18n.t("total_items", scope: i18n_scope), + "product" => I18n.t("total", scope: i18n_scope), "variant" => "", "quantity" => order.line_items.sum(&:quantity), + "price" => order.line_items.sum(&:price).to_s, "temp_controlled" => "", } end From cbb6bd63d4c7d55bf1f780de88c0b7402351d96e Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 23 Mar 2022 09:13:11 +0000 Subject: [PATCH 04/54] Print: load css, hide navbar --- app/views/spree/admin/shared/_head.html.haml | 2 +- app/webpacker/css/admin/shared/layout.scss | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/views/spree/admin/shared/_head.html.haml b/app/views/spree/admin/shared/_head.html.haml index c90e5f0263..d4c5856fc3 100644 --- a/app/views/spree/admin/shared/_head.html.haml +++ b/app/views/spree/admin/shared/_head.html.haml @@ -11,7 +11,7 @@ %link{:href => "https://fonts.googleapis.com/css?family=Open+Sans:400italic,600italic,400,600&subset=latin,cyrillic,greek,vietnamese", :rel => "stylesheet", :type => "text/css"} -= stylesheet_pack_tag 'admin-styles' += stylesheet_pack_tag 'admin-styles', media: "screen, print" = render "layouts/bugsnag_js" = javascript_include_tag 'admin/all' diff --git a/app/webpacker/css/admin/shared/layout.scss b/app/webpacker/css/admin/shared/layout.scss index 7705d09caa..2e97bf7388 100644 --- a/app/webpacker/css/admin/shared/layout.scss +++ b/app/webpacker/css/admin/shared/layout.scss @@ -68,6 +68,11 @@ .hidden { display: none; } +@media print { + .print-hidden { + display: none; + } +} // Header //--------------------------------------------------- @@ -103,3 +108,9 @@ border-top: 1px solid $color-border; padding: 10px 0; } + +@media print { + header, nav { + display:none; + } +} \ No newline at end of file From 209b56ffd7b6ba455d8d949d94ad241d2b126b06 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 23 Mar 2022 09:13:28 +0000 Subject: [PATCH 05/54] Packing Report: Better print --- app/views/admin/reports/show.html.haml | 6 +++--- app/webpacker/css/admin/reports.scss | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index 4108139d13..714001f325 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -1,9 +1,9 @@ = form_tag main_app.admin_reports_path, report_type: @report_type do - %fieldset.no-border-bottom + %fieldset.no-border-bottom.print-hidden %legend{ align: 'center'}= t(:report_filters) = render partial: "admin/reports/filters/#{@report_type}" - %fieldset + %fieldset.print-hidden %legend{ align: 'center'}= t(:report_render_options) = render partial: "rendering_options" @@ -11,7 +11,7 @@ = button t(:go), "report__submit-btn" - if @report_message.present? - %p.report__message= @report_message + %p.report__message.print-hidden= @report_message - if params[:q].present? = render "table" diff --git a/app/webpacker/css/admin/reports.scss b/app/webpacker/css/admin/reports.scss index 16fee65a8d..2a6d3036d5 100644 --- a/app/webpacker/css/admin/reports.scss +++ b/app/webpacker/css/admin/reports.scss @@ -1,5 +1,8 @@ .report__table { margin-top: 2em; + @media print { + margin: 0; + } } .report__message { From 09247b21cd2897c41d5a585c3ca0f3e938218314 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Tue, 29 Mar 2022 17:48:54 +0200 Subject: [PATCH 06/54] Reports Refactor 1 Use code closer to the new packing report controller Handle nil @report_subtypes --- .rubocop_todo.yml | 10 ++-- .../spree/admin/reports_controller.rb | 58 ++++++++++--------- .../reports/_rendering_options.html.haml | 2 +- .../spree/admin/reports/_table.html.haml | 35 ++++++----- app/views/spree/admin/reports/show.html.haml | 17 ++++++ lib/reporting/report_renderer.rb | 4 +- .../admin/reports/packing_report_spec.rb | 10 ++-- spec/system/admin/reports_spec.rb | 20 +++---- 8 files changed, 87 insertions(+), 69 deletions(-) create mode 100644 app/views/spree/admin/reports/show.html.haml diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ac90405d33..ac1cfe8ee6 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -560,7 +560,7 @@ Metrics/CyclomaticComplexity: - 'lib/open_food_network/customers_report.rb' - 'lib/open_food_network/enterprise_issue_validator.rb' - 'lib/open_food_network/group_buy_report.rb' - - 'lib/open_food_network/orders_and_fulfillments_report/customer_totals_report.rb' + - 'lib/open_food_network/orders_and_fulfillment_report/customer_totals_report.rb' - 'lib/open_food_network/payments_report.rb' - 'lib/open_food_network/xero_invoices_report.rb' - 'lib/spree/core/controller_helpers/order.rb' @@ -716,7 +716,7 @@ Naming/VariableNumber: - 'app/controllers/spree/orders_controller.rb' - 'app/models/content_configuration.rb' - 'app/models/preference_sections/main_links_section.rb' - - 'lib/open_food_network/orders_and_fulfillments_report/customer_totals_report.rb' + - 'lib/open_food_network/orders_and_fulfillment_report/customer_totals_report.rb' - 'lib/spree/core/controller_helpers/common.rb' - 'spec/controllers/spree/admin/search_controller_spec.rb' - 'spec/factories/stock_location_factory.rb' @@ -1092,7 +1092,7 @@ Style/MissingRespondToMissing: # Offense count: 1 Style/MixinUsage: Exclude: - - 'lib/open_food_network/orders_and_fulfillments_report.rb' + - 'lib/open_food_network/orders_and_fulfillment_report.rb' # Offense count: 2 # Cop supports --auto-correct. @@ -1137,7 +1137,7 @@ Style/OptionalBooleanParameter: - 'lib/open_food_network/customers_report.rb' - 'lib/open_food_network/order_and_distributor_report.rb' - 'lib/open_food_network/order_cycle_management_report.rb' - - 'lib/open_food_network/orders_and_fulfillments_report.rb' + - 'lib/open_food_network/orders_and_fulfillment_report.rb' - 'lib/open_food_network/payments_report.rb' - 'lib/open_food_network/products_and_inventory_report_base.rb' - 'lib/open_food_network/users_and_enterprises_report.rb' @@ -1236,7 +1236,7 @@ Style/StringConcatenation: - 'app/services/embedded_page_service.rb' - 'app/services/products_renderer.rb' - 'engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_report.rb' - - 'lib/open_food_network/orders_and_fulfillments_report/customer_totals_report.rb' + - 'lib/open_food_network/orders_and_fulfillment_report/customer_totals_report.rb' - 'lib/spree/api/controller_setup.rb' - 'lib/spree/core/environment_extension.rb' - 'spec/lib/open_food_network/order_grouper_spec.rb' diff --git a/app/controllers/spree/admin/reports_controller.rb b/app/controllers/spree/admin/reports_controller.rb index de57236dbf..56e8a8e28d 100644 --- a/app/controllers/spree/admin/reports_controller.rb +++ b/app/controllers/spree/admin/reports_controller.rb @@ -14,12 +14,13 @@ require 'open_food_network/order_cycle_management_report' require 'open_food_network/sales_tax_report' require 'open_food_network/xero_invoices_report' require 'open_food_network/payments_report' -require 'open_food_network/orders_and_fulfillments_report' +require 'open_food_network/orders_and_fulfillment_report' module Spree module Admin class ReportsController < Spree::Admin::BaseController include Spree::ReportsHelper + include ReportsActions helper ::ReportsHelper ORDER_MANAGEMENT_ENGINE_REPORTS = [ @@ -32,7 +33,6 @@ module Spree before_action :cache_search_state # Fetches user's distributors, suppliers and order_cycles before_action :load_basic_data, only: [:customers, :products_and_inventory, :order_cycle_management] - before_action :load_associated_data, only: [:orders_and_fulfillment] respond_to :html @@ -101,21 +101,21 @@ module Spree end def orders_and_fulfillment - raw_params[:q] ||= orders_and_fulfillment_default_filters + now = Time.zone.now + raw_params[:q] ||= { + completed_at_gt: (now - 1.month).beginning_of_day, + completed_at_lt: (now + 1.day).beginning_of_day + } - @report_types = report_types[:orders_and_fulfillment] - @report_type = params[:report_type] + form_options = Reporting::FrontendData.new(spree_current_user) - @include_blank = I18n.t(:all) + @distributors = form_options.distributors + @suppliers = form_options.suppliers + @order_cycles = form_options.order_cycles - # -- Build Report with Order Grouper - @report = OpenFoodNetwork::OrdersAndFulfillmentsReport.new spree_current_user, - raw_params, - render_content? - @table = order_grouper_table - csv_file_name = "#{params[:report_type]}_#{timestamp}.csv" + @report_message = I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") - render_report(@report.header, @table, params[:csv], csv_file_name) + render_report2 end def products_and_inventory @@ -190,19 +190,27 @@ module Spree @searching end + def render_report2 + @report_subtypes = report_types[action_name.to_sym] + @report_subtype = params[:report_subtype] + klass = "OpenFoodNetwork::#{action_name.camelize}Report".constantize + @report = klass.new spree_current_user, raw_params, render_content? + + if report_format.present? + data = Reporting::ReportRenderer.new(@report).public_send("to_#{report_format}") + send_data data, filename: report_filename + else + @header = @report.table_headers + @table = @report.table_rows + + render "show" + end + end + def render_report(header, table, create_csv, csv_file_name) send_data csv_report(header, table), filename: csv_file_name if create_csv @header = header @table = table - # Rendering HTML is the default. - end - - def load_associated_data - form_options = Reporting::FrontendData.new(spree_current_user) - - @distributors = form_options.distributors - @suppliers = form_options.suppliers - @order_cycles = form_options.order_cycles end def csv_report(header, table) @@ -299,12 +307,6 @@ module Spree def timestamp Time.zone.now.strftime("%Y%m%d") end - - def orders_and_fulfillment_default_filters - now = Time.zone.now - { completed_at_gt: (now - 1.month).beginning_of_day, - completed_at_lt: (now + 1.day).beginning_of_day } - end end end end diff --git a/app/views/admin/reports/_rendering_options.html.haml b/app/views/admin/reports/_rendering_options.html.haml index fa7b387bc0..b6b20fd0cf 100644 --- a/app/views/admin/reports/_rendering_options.html.haml +++ b/app/views/admin/reports/_rendering_options.html.haml @@ -1,4 +1,4 @@ -- if @report_subtypes.any? +- if @report_subtypes.present? && @report_subtypes.count > 1 .row .alpha.two.columns= label_tag nil, t(:report_type) .omega.fourteen.columns diff --git a/app/views/spree/admin/reports/_table.html.haml b/app/views/spree/admin/reports/_table.html.haml index e9347669b3..81d7dd524f 100644 --- a/app/views/spree/admin/reports/_table.html.haml +++ b/app/views/spree/admin/reports/_table.html.haml @@ -1,20 +1,19 @@ - column_partials ||= {} -- if render_content? - %table.report__table{id: id} - %thead +%table.report__table + %thead + %tr + - @header.each do |heading| + %th= heading + %tbody + - @table.each do |row| %tr - - @header.each do |heading| - %th= heading - %tbody - - @table.each do |row| - %tr - - row.each_with_index do |cell_value, column_index| - %td - - partial = column_partials[column_index] - - if partial - = render partial, value: cell_value - - else - = cell_value - - if @table.empty? - %tr - %td{colspan: @header.count}= t(:none) + - row.each_with_index do |cell_value, column_index| + %td + - partial = column_partials[column_index] + - if partial + = render partial, value: cell_value + - else + = cell_value + - if @table.empty? + %tr + %td{colspan: @header.count}= t(:none) diff --git a/app/views/spree/admin/reports/show.html.haml b/app/views/spree/admin/reports/show.html.haml new file mode 100644 index 0000000000..4e7a640944 --- /dev/null +++ b/app/views/spree/admin/reports/show.html.haml @@ -0,0 +1,17 @@ += form_for @report.search, :url => url_for(only_path: false) do |f| + %fieldset.no-border-bottom.print-hidden + %legend{ align: 'center'}= t(:report_filters) + = render partial: "spree/admin/reports/filters/#{action_name}", locals: { f: f } + + %fieldset.print-hidden + %legend{ align: 'center'}= t(:report_render_options) + = render partial: "admin/reports/rendering_options" + + .actions.filter-actions + = button t(:go), "report__submit-btn" + +- if @report_message.present? + %p.report__message.print-hidden= @report_message + +- if render_content? + = render "table" diff --git a/lib/reporting/report_renderer.rb b/lib/reporting/report_renderer.rb index 16b78d91de..a4cfcc1d6d 100644 --- a/lib/reporting/report_renderer.rb +++ b/lib/reporting/report_renderer.rb @@ -9,11 +9,11 @@ module Reporting end def table_headers - @report.report_data.columns + @report.respond_to?(:report_data) ? @report.report_data.columns : @report.table_headers end def table_rows - @report.report_data.rows + @report.respond_to?(:report_data) ? @report.report_data.rows : @report.table_rows end def as_json diff --git a/spec/system/admin/reports/packing_report_spec.rb b/spec/system/admin/reports/packing_report_spec.rb index dcd5660641..421144b223 100644 --- a/spec/system/admin/reports/packing_report_spec.rb +++ b/spec/system/admin/reports/packing_report_spec.rb @@ -48,20 +48,20 @@ describe "Packing Reports", js: true do fill_in 'q_completed_at_lt', with: '2013-04-25 16:00:00' click_button 'Search' - rows = find("table#listing_orders").all("thead tr") + rows = find("table.report__table").all("thead tr") table = rows.map { |r| r.all("th").map { |c| c.text.strip } } expect(table).to eq([ ["Hub", "Code", "First Name", "Last Name", "Supplier", "Product", "Variant", "Quantity", "TempControlled?"].map(&:upcase) ]) - expect(page).to have_selector 'table#listing_orders tbody tr', count: 5 # Totals row per order + expect(page).to have_selector 'table.report__table tbody tr', count: 5 # Totals row per order end it "sorts alphabetically" do click_link "Pack By Customer" click_button 'Search' - rows = find("table#listing_orders").all("tr") + rows = find("table.report__table").all("tr") table = rows.map { |r| r.all("th,td").map { |c| c.text.strip }[3] } expect(table).to eq([ "LAST NAME", @@ -81,13 +81,13 @@ describe "Packing Reports", js: true do fill_in 'q_completed_at_lt', with: '2013-04-25 16:00:00' click_button 'Search' - rows = find("table#listing_orders").all("thead tr") + rows = find("table.report__table").all("thead tr") table = rows.map { |r| r.all("th").map { |c| c.text.strip } } expect(table).to eq([ ["Hub", "Supplier", "Code", "First Name", "Last Name", "Product", "Variant", "Quantity", "TempControlled?"].map(&:upcase) ]) - expect(all('table#listing_orders tbody tr').count).to eq(4) # Totals row per supplier + expect(all('table.report__table tbody tr').count).to eq(4) # Totals row per supplier end end end diff --git a/spec/system/admin/reports_spec.rb b/spec/system/admin/reports_spec.rb index 7e0b7e00d6..3c8b6f3122 100644 --- a/spec/system/admin/reports_spec.rb +++ b/spec/system/admin/reports_spec.rb @@ -38,10 +38,10 @@ describe ' it "customers report" do click_link "Mailing List" - expect(page).to have_select('report_type', selected: 'Mailing List') + expect(page).to have_select('report_subtype', selected: 'Mailing List') click_button "Go" - rows = find("table#listing_customers").all("thead tr") + rows = find("table.report__table").all("thead tr") table = rows.map { |r| r.all("th").map { |c| c.text.strip } } expect(table.sort).to eq([ ["Email", "First Name", "Last Name", "Suburb"].map(&:upcase) @@ -50,10 +50,10 @@ describe ' it "customers report" do click_link "Addresses" - expect(page).to have_select('report_type', selected: 'Addresses') + expect(page).to have_select('report_subtype', selected: 'Addresses') click_button "Go" - rows = find("table#listing_customers").all("thead tr") + rows = find("table.report__table").all("thead tr") table = rows.map { |r| r.all("th").map { |c| c.text.strip } } expect(table.sort).to eq([ ["First Name", "Last Name", "Billing Address", "Email", "Phone", "Hub", "Hub Address", @@ -70,7 +70,7 @@ describe ' it "payment method report" do click_link "Payment Methods Report" click_button "Search" - rows = find("table#listing_ocm_orders").all("thead tr") + rows = find("table.report__table").all("thead tr") table = rows.map { |r| r.all("th").map { |c| c.text.strip } } expect(table.sort).to eq([ ["First Name", "Last Name", "Hub", "Hub Code", "Email", "Phone", "Shipping Method", @@ -81,7 +81,7 @@ describe ' it "delivery report" do click_link "Delivery Report" click_button "Search" - rows = find("table#listing_ocm_orders").all("thead tr") + rows = find("table.report__table").all("thead tr") table = rows.map { |r| r.all("th").map { |c| c.text.strip } } expect(table.sort).to eq([ ["First Name", "Last Name", "Hub", "Hub Code", "Delivery Address", "Delivery Postcode", @@ -242,7 +242,7 @@ describe ' pick_datetime "#q_completed_at_gt", datetime_start pick_datetime "#q_completed_at_lt", datetime_end - select 'Order Cycle Customer Totals', from: 'report_type' + select 'Order Cycle Customer Totals', from: 'report_subtype' click_button 'Search' # Then I should see the rows for the first order but not the second expect(all('table#listing_orders tbody tr').count).to eq(4) # Two rows per order @@ -349,7 +349,7 @@ describe ' it "shows users and enterprises report" do click_button "Search" - rows = find("table#users_and_enterprises").all("tr") + rows = find("table.report__table").all("tr") table = rows.map { |r| r.all("th,td").map { |c| c.text.strip }[0..2] } expect(table.sort).to eq([ @@ -370,7 +370,7 @@ describe ' click_button "Search" - rows = find("table#users_and_enterprises").all("tr") + rows = find("table.report__table").all("tr") table = rows.map { |r| r.all("th,td").map { |c| c.text.strip }[0..2] } expect(table.sort).to eq([ @@ -555,7 +555,7 @@ describe ' private def xero_invoice_table - find("table#listing_invoices") + find("table.report__table") end def xero_invoice_header From e55462d18bf83fb5bd18bde60d524962768ab6d1 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Mon, 4 Apr 2022 22:02:18 +0200 Subject: [PATCH 07/54] Report Refactor 1: Orders & Fulfilment --- ...ders_and_fulfillment_description.html.haml | 5 ++- .../filters/_orders_and_fulfillment.html.haml | 14 +++++++++ .../reports/orders_and_fulfillment.html.haml | 31 ------------------- ...rt.rb => orders_and_fulfillment_report.rb} | 22 ++++++++----- .../customer_totals_report.rb | 4 +-- .../default_report.rb | 4 +-- .../distributor_totals_by_supplier_report.rb | 4 +-- .../supplier_totals_by_distributor_report.rb | 4 +-- .../supplier_totals_report.rb | 4 +-- .../customer_totals_report_spec.rb | 13 ++++---- ...tributor_totals_by_supplier_report_spec.rb | 10 +++--- ...plier_totals_by_distributor_report_spec.rb | 10 +++--- .../supplier_totals_report_spec.rb | 10 +++--- .../orders_and_fulfillments_report_spec.rb | 12 +++---- 14 files changed, 67 insertions(+), 80 deletions(-) create mode 100755 app/views/spree/admin/reports/filters/_orders_and_fulfillment.html.haml delete mode 100644 app/views/spree/admin/reports/orders_and_fulfillment.html.haml rename lib/open_food_network/{orders_and_fulfillments_report.rb => orders_and_fulfillment_report.rb} (78%) rename lib/open_food_network/{orders_and_fulfillments_report => orders_and_fulfillment_report}/customer_totals_report.rb (99%) rename lib/open_food_network/{orders_and_fulfillments_report => orders_and_fulfillment_report}/default_report.rb (96%) rename lib/open_food_network/{orders_and_fulfillments_report => orders_and_fulfillment_report}/distributor_totals_by_supplier_report.rb (97%) rename lib/open_food_network/{orders_and_fulfillments_report => orders_and_fulfillment_report}/supplier_totals_by_distributor_report.rb (97%) rename lib/open_food_network/{orders_and_fulfillments_report => orders_and_fulfillment_report}/supplier_totals_report.rb (96%) rename spec/lib/open_food_network/{orders_and_fulfillments_report => orders_and_fulfillment_report}/customer_totals_report_spec.rb (88%) rename spec/lib/open_food_network/{orders_and_fulfillments_report => orders_and_fulfillment_report}/distributor_totals_by_supplier_report_spec.rb (65%) rename spec/lib/open_food_network/{orders_and_fulfillments_report => orders_and_fulfillment_report}/supplier_totals_by_distributor_report_spec.rb (65%) rename spec/lib/open_food_network/{orders_and_fulfillments_report => orders_and_fulfillment_report}/supplier_totals_report_spec.rb (59%) diff --git a/app/views/spree/admin/reports/_orders_and_fulfillment_description.html.haml b/app/views/spree/admin/reports/_orders_and_fulfillment_description.html.haml index a813492cac..d17874cd38 100644 --- a/app/views/spree/admin/reports/_orders_and_fulfillment_description.html.haml +++ b/app/views/spree/admin/reports/_orders_and_fulfillment_description.html.haml @@ -1,5 +1,4 @@ %ul{style: "margin-left: 12pt"} - report_types.each do |report_type| - %li - = link_to report_type[0], "#{orders_and_fulfillment_admin_reports_url}?report_type=#{report_type[1]}" - \ No newline at end of file + %li + = link_to report_type[0], "#{orders_and_fulfillment_admin_reports_url}?report_subtype=#{report_type[1]}" diff --git a/app/views/spree/admin/reports/filters/_orders_and_fulfillment.html.haml b/app/views/spree/admin/reports/filters/_orders_and_fulfillment.html.haml new file mode 100755 index 0000000000..e43204e60d --- /dev/null +++ b/app/views/spree/admin/reports/filters/_orders_and_fulfillment.html.haml @@ -0,0 +1,14 @@ += render 'spree/admin/reports/date_range_form', f: f + +.row + .alpha.two.columns= label_tag nil, t(:report_hubs) + .omega.fourteen.columns= f.collection_select(:distributor_id_in, @distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + +.row + .alpha.two.columns= label_tag nil, t(:report_producers) + .omega.fourteen.columns= select_tag(:supplier_id_in, options_from_collection_for_select(@suppliers, :id, :name, params[:supplier_id_in]), {class: "select2 fullwidth", multiple: true}) + +.row + .alpha.two.columns= label_tag nil, t(:report_customers_cycle) + .omega.fourteen.columns + = f.select(:order_cycle_id_in, report_order_cycle_options(@order_cycles), {selected: params.dig(:q, :order_cycle_id_in)}, {class: "select2 fullwidth", multiple: true}) diff --git a/app/views/spree/admin/reports/orders_and_fulfillment.html.haml b/app/views/spree/admin/reports/orders_and_fulfillment.html.haml deleted file mode 100644 index 1a880f2dd5..0000000000 --- a/app/views/spree/admin/reports/orders_and_fulfillment.html.haml +++ /dev/null @@ -1,31 +0,0 @@ -= form_for @report.search, :url => spree.orders_and_fulfillment_admin_reports_path do |f| - = render 'date_range_form', f: f - - .row - .alpha.two.columns= label_tag nil, t(:report_hubs) - .omega.fourteen.columns= f.collection_select(:distributor_id_in, @distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) - - .row - .alpha.two.columns= label_tag nil, t(:report_producers) - .omega.fourteen.columns= select_tag(:supplier_id_in, options_from_collection_for_select(@suppliers, :id, :name, params[:supplier_id_in]), {class: "select2 fullwidth", multiple: true}) - - .row - .alpha.two.columns= label_tag nil, t(:report_customers_cycle) - .omega.fourteen.columns - = f.select(:order_cycle_id_in, report_order_cycle_options(@order_cycles), {selected: params.dig(:q, :order_cycle_id_in)}, {class: "select2 fullwidth", multiple: true}) - - .row - .alpha.two.columns= label_tag nil, t(:report_type) - .omega.fourteen.columns= select_tag(:report_type, options_for_select(@report_types, @report_type)) - - .row - = check_box_tag :csv - = label_tag :csv, t(:report_customers_csv) - - .row - = button t(:search) - -- if render_content? - = render partial: "customer_names_message" - -= render "table", id: "listing_orders", msg_option: t(:search) diff --git a/lib/open_food_network/orders_and_fulfillments_report.rb b/lib/open_food_network/orders_and_fulfillment_report.rb similarity index 78% rename from lib/open_food_network/orders_and_fulfillments_report.rb rename to lib/open_food_network/orders_and_fulfillment_report.rb index d6bd7487d6..ac62fa09d2 100644 --- a/lib/open_food_network/orders_and_fulfillments_report.rb +++ b/lib/open_food_network/orders_and_fulfillment_report.rb @@ -1,24 +1,25 @@ # frozen_string_literal: true require "open_food_network/reports/line_items" -require "open_food_network/orders_and_fulfillments_report/supplier_totals_report" -require "open_food_network/orders_and_fulfillments_report/supplier_totals_by_distributor_report" -require "open_food_network/orders_and_fulfillments_report/distributor_totals_by_supplier_report" -require "open_food_network/orders_and_fulfillments_report/customer_totals_report" -require 'open_food_network/orders_and_fulfillments_report/default_report' +require "open_food_network/orders_and_fulfillment_report/supplier_totals_report" +require "open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report" +require "open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report" +require "open_food_network/orders_and_fulfillment_report/customer_totals_report" +require 'open_food_network/orders_and_fulfillment_report/default_report' +require 'open_food_network/order_grouper' include Spree::ReportsHelper module OpenFoodNetwork - class OrdersAndFulfillmentsReport + class OrdersAndFulfillmentReport attr_reader :options, :report_type - delegate :header, :rules, :columns, to: :report + delegate :table_headers, :rules, :columns, to: :report def initialize(user, options = {}, render_table = false) @user = user @options = options - @report_type = options[:report_type] + @report_type = options[:report_subtype] @render_table = render_table @variant_scopers_by_distributor_id = {} end @@ -33,6 +34,11 @@ module OpenFoodNetwork report_line_items.list(report.line_item_includes) end + def table_rows + order_grouper = OpenFoodNetwork::OrderGrouper.new report.rules, report.columns, report + order_grouper.table(table_items) + end + def line_item_name proc { |line_item| line_item.variant.full_name } end diff --git a/lib/open_food_network/orders_and_fulfillments_report/customer_totals_report.rb b/lib/open_food_network/orders_and_fulfillment_report/customer_totals_report.rb similarity index 99% rename from lib/open_food_network/orders_and_fulfillments_report/customer_totals_report.rb rename to lib/open_food_network/orders_and_fulfillment_report/customer_totals_report.rb index f65ad3a16f..8ec81136a4 100644 --- a/lib/open_food_network/orders_and_fulfillments_report/customer_totals_report.rb +++ b/lib/open_food_network/orders_and_fulfillment_report/customer_totals_report.rb @@ -2,7 +2,7 @@ # rubocop:disable Metrics/ClassLength module OpenFoodNetwork - class OrdersAndFulfillmentsReport + class OrdersAndFulfillmentReport class CustomerTotalsReport REPORT_TYPE = "order_cycle_customer_totals" @@ -17,7 +17,7 @@ module OpenFoodNetwork end # rubocop:disable Metrics/AbcSize - def header + def table_headers [I18n.t(:report_header_hub), I18n.t(:report_header_customer), I18n.t(:report_header_email), I18n.t(:report_header_phone), I18n.t(:report_header_producer), I18n.t(:report_header_product), I18n.t(:report_header_variant), diff --git a/lib/open_food_network/orders_and_fulfillments_report/default_report.rb b/lib/open_food_network/orders_and_fulfillment_report/default_report.rb similarity index 96% rename from lib/open_food_network/orders_and_fulfillments_report/default_report.rb rename to lib/open_food_network/orders_and_fulfillment_report/default_report.rb index 3d10c9d7c4..2ec3c20a4e 100644 --- a/lib/open_food_network/orders_and_fulfillments_report/default_report.rb +++ b/lib/open_food_network/orders_and_fulfillment_report/default_report.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module OpenFoodNetwork - class OrdersAndFulfillmentsReport + class OrdersAndFulfillmentReport class DefaultReport delegate :line_item_name, :supplier_name, :product_name, :line_items_name, to: :context @@ -9,7 +9,7 @@ module OpenFoodNetwork @context = context end - def header + def table_headers [ I18n.t(:report_header_producer), I18n.t(:report_header_product), diff --git a/lib/open_food_network/orders_and_fulfillments_report/distributor_totals_by_supplier_report.rb b/lib/open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report.rb similarity index 97% rename from lib/open_food_network/orders_and_fulfillments_report/distributor_totals_by_supplier_report.rb rename to lib/open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report.rb index 5ce88bdab6..472aadf38d 100644 --- a/lib/open_food_network/orders_and_fulfillments_report/distributor_totals_by_supplier_report.rb +++ b/lib/open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module OpenFoodNetwork - class OrdersAndFulfillmentsReport + class OrdersAndFulfillmentReport class DistributorTotalsBySupplierReport REPORT_TYPE = "order_cycle_distributor_totals_by_supplier" @@ -11,7 +11,7 @@ module OpenFoodNetwork @context = context end - def header + def table_headers [I18n.t(:report_header_hub), I18n.t(:report_header_producer), I18n.t(:report_header_product), I18n.t(:report_header_variant), I18n.t(:report_header_quantity), I18n.t(:report_header_curr_cost_per_unit), diff --git a/lib/open_food_network/orders_and_fulfillments_report/supplier_totals_by_distributor_report.rb b/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report.rb similarity index 97% rename from lib/open_food_network/orders_and_fulfillments_report/supplier_totals_by_distributor_report.rb rename to lib/open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report.rb index 404fd2eeb3..8731c58c70 100644 --- a/lib/open_food_network/orders_and_fulfillments_report/supplier_totals_by_distributor_report.rb +++ b/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module OpenFoodNetwork - class OrdersAndFulfillmentsReport + class OrdersAndFulfillmentReport class SupplierTotalsByDistributorReport REPORT_TYPE = "order_cycle_supplier_totals_by_distributor" @@ -13,7 +13,7 @@ module OpenFoodNetwork @context = context end - def header + def table_headers [I18n.t(:report_header_producer), I18n.t(:report_header_product), I18n.t(:report_header_variant), I18n.t(:report_header_to_hub), I18n.t(:report_header_quantity), I18n.t(:report_header_curr_cost_per_unit), diff --git a/lib/open_food_network/orders_and_fulfillments_report/supplier_totals_report.rb b/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_report.rb similarity index 96% rename from lib/open_food_network/orders_and_fulfillments_report/supplier_totals_report.rb rename to lib/open_food_network/orders_and_fulfillment_report/supplier_totals_report.rb index 9fd5f7fcf9..8ee9decf89 100644 --- a/lib/open_food_network/orders_and_fulfillments_report/supplier_totals_report.rb +++ b/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_report.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module OpenFoodNetwork - class OrdersAndFulfillmentsReport + class OrdersAndFulfillmentReport class SupplierTotalsReport REPORT_TYPE = "order_cycle_supplier_totals" @@ -13,7 +13,7 @@ module OpenFoodNetwork @context = context end - def header + def table_headers [I18n.t(:report_header_producer), I18n.t(:report_header_product), I18n.t(:report_header_variant), I18n.t(:report_header_quantity), I18n.t(:report_header_total_units), I18n.t(:report_header_curr_cost_per_unit), diff --git a/spec/lib/open_food_network/orders_and_fulfillments_report/customer_totals_report_spec.rb b/spec/lib/open_food_network/orders_and_fulfillment_report/customer_totals_report_spec.rb similarity index 88% rename from spec/lib/open_food_network/orders_and_fulfillments_report/customer_totals_report_spec.rb rename to spec/lib/open_food_network/orders_and_fulfillment_report/customer_totals_report_spec.rb index 8f4a7f6a21..966cf6e05e 100644 --- a/spec/lib/open_food_network/orders_and_fulfillments_report/customer_totals_report_spec.rb +++ b/spec/lib/open_food_network/orders_and_fulfillment_report/customer_totals_report_spec.rb @@ -1,22 +1,21 @@ # frozen_string_literal: true require "spec_helper" -require 'open_food_network/orders_and_fulfillments_report' -require 'open_food_network/orders_and_fulfillments_report/customer_totals_report' -require 'open_food_network/order_grouper' +require 'open_food_network/orders_and_fulfillment_report' +require 'open_food_network/orders_and_fulfillment_report/customer_totals_report' -RSpec.describe OpenFoodNetwork::OrdersAndFulfillmentsReport::CustomerTotalsReport do +RSpec.describe OpenFoodNetwork::OrdersAndFulfillmentReport::CustomerTotalsReport do let!(:distributor) { create(:distributor_enterprise) } let!(:customer) { create(:customer, enterprise: distributor) } let(:current_user) { distributor.owner } let(:report) do - report_options = { report_type: described_class::REPORT_TYPE } - OpenFoodNetwork::OrdersAndFulfillmentsReport.new(current_user, report_options, true) + report_options = { report_subtype: described_class::REPORT_TYPE } + OpenFoodNetwork::OrdersAndFulfillmentReport.new(current_user, report_options, true) end let(:report_table) do - OpenFoodNetwork::OrderGrouper.new(report.rules, report.columns).table(report.table_items) + report.table_rows end context "viewing the report" do diff --git a/spec/lib/open_food_network/orders_and_fulfillments_report/distributor_totals_by_supplier_report_spec.rb b/spec/lib/open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report_spec.rb similarity index 65% rename from spec/lib/open_food_network/orders_and_fulfillments_report/distributor_totals_by_supplier_report_spec.rb rename to spec/lib/open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report_spec.rb index 7c5975eb7a..3da55893ad 100644 --- a/spec/lib/open_food_network/orders_and_fulfillments_report/distributor_totals_by_supplier_report_spec.rb +++ b/spec/lib/open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report_spec.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true require 'spec_helper' -require 'open_food_network/orders_and_fulfillments_report/distributor_totals_by_supplier_report' +require 'open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report' -RSpec.describe OpenFoodNetwork::OrdersAndFulfillmentsReport::DistributorTotalsBySupplierReport do +RSpec.describe OpenFoodNetwork::OrdersAndFulfillmentReport::DistributorTotalsBySupplierReport do let!(:distributor) { create(:distributor_enterprise) } let!(:order) do @@ -13,12 +13,12 @@ RSpec.describe OpenFoodNetwork::OrdersAndFulfillmentsReport::DistributorTotalsBy let(:current_user) { distributor.owner } let(:report) do - report_options = { report_type: described_class::REPORT_TYPE } - OpenFoodNetwork::OrdersAndFulfillmentsReport.new(current_user, report_options, true) + report_options = { report_subtype: described_class::REPORT_TYPE } + OpenFoodNetwork::OrdersAndFulfillmentReport.new(current_user, report_options, true) end let(:report_table) do - OpenFoodNetwork::OrderGrouper.new(report.rules, report.columns).table(report.table_items) + report.table_rows end it "generates the report" do diff --git a/spec/lib/open_food_network/orders_and_fulfillments_report/supplier_totals_by_distributor_report_spec.rb b/spec/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report_spec.rb similarity index 65% rename from spec/lib/open_food_network/orders_and_fulfillments_report/supplier_totals_by_distributor_report_spec.rb rename to spec/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report_spec.rb index 2ad62ba230..337f64ed45 100644 --- a/spec/lib/open_food_network/orders_and_fulfillments_report/supplier_totals_by_distributor_report_spec.rb +++ b/spec/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report_spec.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true require 'spec_helper' -require 'open_food_network/orders_and_fulfillments_report/supplier_totals_by_distributor_report' +require 'open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report' -RSpec.describe OpenFoodNetwork::OrdersAndFulfillmentsReport::SupplierTotalsByDistributorReport do +RSpec.describe OpenFoodNetwork::OrdersAndFulfillmentReport::SupplierTotalsByDistributorReport do let!(:distributor) { create(:distributor_enterprise) } let!(:order) do @@ -13,12 +13,12 @@ RSpec.describe OpenFoodNetwork::OrdersAndFulfillmentsReport::SupplierTotalsByDis let(:current_user) { distributor.owner } let(:report) do - report_options = { report_type: described_class::REPORT_TYPE } - OpenFoodNetwork::OrdersAndFulfillmentsReport.new(current_user, report_options, true) + report_options = { report_subtype: described_class::REPORT_TYPE } + OpenFoodNetwork::OrdersAndFulfillmentReport.new(current_user, report_options, true) end let(:report_table) do - OpenFoodNetwork::OrderGrouper.new(report.rules, report.columns).table(report.table_items) + report.table_rows end it "generates the report" do diff --git a/spec/lib/open_food_network/orders_and_fulfillments_report/supplier_totals_report_spec.rb b/spec/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_report_spec.rb similarity index 59% rename from spec/lib/open_food_network/orders_and_fulfillments_report/supplier_totals_report_spec.rb rename to spec/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_report_spec.rb index a3ec8baea9..f93c088866 100644 --- a/spec/lib/open_food_network/orders_and_fulfillments_report/supplier_totals_report_spec.rb +++ b/spec/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_report_spec.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true require 'spec_helper' -require 'open_food_network/orders_and_fulfillments_report/supplier_totals_report' +require 'open_food_network/orders_and_fulfillment_report/supplier_totals_report' -RSpec.describe OpenFoodNetwork::OrdersAndFulfillmentsReport::SupplierTotalsReport do +RSpec.describe OpenFoodNetwork::OrdersAndFulfillmentReport::SupplierTotalsReport do let!(:distributor) { create(:distributor_enterprise) } let!(:order) do @@ -13,12 +13,12 @@ RSpec.describe OpenFoodNetwork::OrdersAndFulfillmentsReport::SupplierTotalsRepor let(:current_user) { distributor.owner } let(:report) do - report_options = { report_type: described_class::REPORT_TYPE } - OpenFoodNetwork::OrdersAndFulfillmentsReport.new(current_user, report_options, true) + report_options = { report_subtype: described_class::REPORT_TYPE } + OpenFoodNetwork::OrdersAndFulfillmentReport.new(current_user, report_options, true) end let(:report_table) do - OpenFoodNetwork::OrderGrouper.new(report.rules, report.columns).table(report.table_items) + report.table_rows end it "generates the report" do diff --git a/spec/lib/open_food_network/orders_and_fulfillments_report_spec.rb b/spec/lib/open_food_network/orders_and_fulfillments_report_spec.rb index 51343ddcab..249ac1c61c 100644 --- a/spec/lib/open_food_network/orders_and_fulfillments_report_spec.rb +++ b/spec/lib/open_food_network/orders_and_fulfillments_report_spec.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true require 'spec_helper' -require 'open_food_network/orders_and_fulfillments_report' +require 'open_food_network/orders_and_fulfillment_report' require 'open_food_network/order_grouper' -describe OpenFoodNetwork::OrdersAndFulfillmentsReport do +describe OpenFoodNetwork::OrdersAndFulfillmentReport do include AuthenticationHelper let(:distributor) { create(:distributor_enterprise) } @@ -163,8 +163,8 @@ describe OpenFoodNetwork::OrdersAndFulfillmentsReport do ] report_types.each do |report_type| - report = described_class.new(admin_user, report_type: report_type) - expect(report.header.size).to eq(report.columns.size) + report = described_class.new(admin_user, report_subtype: report_type) + expect(report.table_headers.size).to eq(report.columns.size) end end end @@ -179,8 +179,8 @@ describe OpenFoodNetwork::OrdersAndFulfillmentsReport do end let(:items) { - report = described_class.new(admin_user, { report_type: "order_cycle_customer_totals" }, true) - OpenFoodNetwork::OrderGrouper.new(report.rules, report.columns).table(report.table_items) + described_class.new(admin_user, { report_subtype: "order_cycle_customer_totals" }, true) + .table_rows } before do From 1fee45035c82f12c6f720414abe2f2ddb1daeda9 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Thu, 24 Mar 2022 17:40:41 +0000 Subject: [PATCH 08/54] Report Refactor 1: Customers --- .../spree/admin/reports_controller.rb | 7 +--- .../reports/_customers_description.html.haml | 4 +-- .../spree/admin/reports/customers.html.haml | 32 ------------------- .../reports/filters/_customers.html.haml | 18 +++++++++++ app/views/spree/admin/reports/show.html.haml | 5 ++- lib/open_food_network/customers_report.rb | 6 ++-- .../spree/admin/reports_controller_spec.rb | 6 ++-- .../customers_report_spec.rb | 12 +++---- 8 files changed, 37 insertions(+), 53 deletions(-) delete mode 100644 app/views/spree/admin/reports/customers.html.haml create mode 100644 app/views/spree/admin/reports/filters/_customers.html.haml diff --git a/app/controllers/spree/admin/reports_controller.rb b/app/controllers/spree/admin/reports_controller.rb index 56e8a8e28d..06007377a3 100644 --- a/app/controllers/spree/admin/reports_controller.rb +++ b/app/controllers/spree/admin/reports_controller.rb @@ -46,11 +46,7 @@ module Spree end def customers - @report_types = report_types[:customers] - @report_type = params[:report_type] - @report = OpenFoodNetwork::CustomersReport.new spree_current_user, raw_params, - render_content? - render_report(@report.header, @report.table, params[:csv], "customers_#{timestamp}.csv") + render_report2 end def order_cycle_management @@ -195,7 +191,6 @@ module Spree @report_subtype = params[:report_subtype] klass = "OpenFoodNetwork::#{action_name.camelize}Report".constantize @report = klass.new spree_current_user, raw_params, render_content? - if report_format.present? data = Reporting::ReportRenderer.new(@report).public_send("to_#{report_format}") send_data data, filename: report_filename diff --git a/app/views/spree/admin/reports/_customers_description.html.haml b/app/views/spree/admin/reports/_customers_description.html.haml index 85f93a82c0..22d440725e 100644 --- a/app/views/spree/admin/reports/_customers_description.html.haml +++ b/app/views/spree/admin/reports/_customers_description.html.haml @@ -1,4 +1,4 @@ %ul{style: "margin-left: 12pt"} - report_types.each do |report_type| - %li - = link_to report_type[0], "#{customers_admin_reports_url}?report_type=#{report_type[1]}" + %li + = link_to report_type[0], "#{customers_admin_reports_url}?report_subtype=#{report_type[1]}" diff --git a/app/views/spree/admin/reports/customers.html.haml b/app/views/spree/admin/reports/customers.html.haml deleted file mode 100644 index 8f58149ae7..0000000000 --- a/app/views/spree/admin/reports/customers.html.haml +++ /dev/null @@ -1,32 +0,0 @@ -= form_tag spree.customers_admin_reports_url do |f| - %br - .row - .four.columns.alpha - = label_tag nil, t(:report_customers_distributor) - = select_tag(:distributor_id, - options_from_collection_for_select(@distributors, :id, :name, params[:distributor_id]), - {:include_blank => true, :class => "select2 fullwidth"}) - - .four.columns - = label_tag nil, t(:report_customers_supplier) - = select_tag(:supplier_id, - options_from_collection_for_select(@suppliers, :id, :name, params[:supplier_id]), - {:include_blank => true, :class => "select2 fullwidth"}) - - .six.columns - = label_tag nil, t(:report_customers_cycle) - = select_tag(:order_cycle_id, - options_for_select(report_order_cycle_options(@order_cycles), params[:order_cycle_id]), - {:include_blank => true, :class => "select2 fullwidth"}) - - = label_tag nil, t(:report_customers_type) - = select_tag(:report_type, options_for_select(@report_types, @report_type)) - - %br - %br - = check_box_tag :csv - = label_tag :csv, t(:report_customers_csv) - %br - = button t(:go) - -= render "table", id: "listing_customers", msg_option: t(:go) diff --git a/app/views/spree/admin/reports/filters/_customers.html.haml b/app/views/spree/admin/reports/filters/_customers.html.haml new file mode 100644 index 0000000000..83fb9f87b5 --- /dev/null +++ b/app/views/spree/admin/reports/filters/_customers.html.haml @@ -0,0 +1,18 @@ +.row + .alpha.two.columns= label_tag nil, t(:report_customers_distributor) + .omega.fourteen.columns + = select_tag(:distributor_id, + options_from_collection_for_select(@distributors, :id, :name, params[:distributor_id]), + {:include_blank => true, :class => "select2 fullwidth light"}) +.row + .alpha.two.columns= label_tag nil, t(:report_customers_supplier) + .omega.fourteen.columns + = select_tag(:supplier_id, + options_from_collection_for_select(@suppliers, :id, :name, params[:supplier_id]), + {:include_blank => true, :class => "select2 fullwidth light"}) +.row + .alpha.two.columns= label_tag nil, t(:report_customers_cycle) + .omega.fourteen.columns + = select_tag(:order_cycle_id, + options_for_select(report_order_cycle_options(@order_cycles), params[:order_cycle_id]), + {:include_blank => true, :class => "select2 fullwidth light"}) \ No newline at end of file diff --git a/app/views/spree/admin/reports/show.html.haml b/app/views/spree/admin/reports/show.html.haml index 4e7a640944..70e0424593 100644 --- a/app/views/spree/admin/reports/show.html.haml +++ b/app/views/spree/admin/reports/show.html.haml @@ -1,4 +1,7 @@ -= form_for @report.search, :url => url_for(only_path: false) do |f| +/ If the report object do not use ransack search, create a fake one just for the form_for +- ransack_search = @report.respond_to?(:search) ? @report.search : Ransack::Search.new(Spree::Order) += form_for ransack_search, :url => url_for(only_path: false) do |f| + %fieldset.no-border-bottom.print-hidden %legend{ align: 'center'}= t(:report_filters) = render partial: "spree/admin/reports/filters/#{action_name}", locals: { f: f } diff --git a/lib/open_food_network/customers_report.rb b/lib/open_food_network/customers_report.rb index 60a7863dea..c52edad648 100644 --- a/lib/open_food_network/customers_report.rb +++ b/lib/open_food_network/customers_report.rb @@ -10,7 +10,7 @@ module OpenFoodNetwork @compile_table = compile_table end - def header + def table_headers if is_mailing_list? [I18n.t(:report_header_email), I18n.t(:report_header_first_name), @@ -28,7 +28,7 @@ module OpenFoodNetwork end end - def table + def table_rows return [] unless @compile_table orders.map do |order| @@ -93,7 +93,7 @@ module OpenFoodNetwork private def is_mailing_list? - params[:report_type] == "mailing_list" + params[:report_subtype] == "mailing_list" end end end diff --git a/spec/controllers/spree/admin/reports_controller_spec.rb b/spec/controllers/spree/admin/reports_controller_spec.rb index 2f9edf9de7..23e967cd85 100644 --- a/spec/controllers/spree/admin/reports_controller_spec.rb +++ b/spec/controllers/spree/admin/reports_controller_spec.rb @@ -286,7 +286,7 @@ describe Spree::Admin::ReportsController, type: :controller do it "assigns report types" do spree_get :customers - expect(assigns(:report_types)).to eq(subject.report_types[:customers]) + expect(assigns(:report_subtypes)).to eq(subject.report_types[:customers]) end it "creates a CustomersReport" do @@ -295,8 +295,8 @@ describe Spree::Admin::ReportsController, type: :controller do "action" => "customers", "use_route" => "main_app", "report" => {} }, false) .and_return(report = double(:report)) - allow(report).to receive(:header).and_return [] - allow(report).to receive(:table).and_return [] + allow(report).to receive(:table_headers).and_return [] + allow(report).to receive(:table_rows).and_return [] spree_get :customers, test: "foo" expect(assigns(:report)).to eq(report) end diff --git a/spec/lib/open_food_network/customers_report_spec.rb b/spec/lib/open_food_network/customers_report_spec.rb index f1df21332f..ebe963c47a 100644 --- a/spec/lib/open_food_network/customers_report_spec.rb +++ b/spec/lib/open_food_network/customers_report_spec.rb @@ -15,11 +15,11 @@ module OpenFoodNetwork describe "mailing list report" do before do - allow(subject).to receive(:params).and_return(report_type: "mailing_list") + allow(subject).to receive(:params).and_return(report_subtype: "mailing_list") end it "returns headers for mailing_list" do - expect(subject.header).to eq(["Email", "First Name", "Last Name", "Suburb"]) + expect(subject.table_headers).to eq(["Email", "First Name", "Last Name", "Suburb"]) end it "builds a table from a list of variants" do @@ -29,7 +29,7 @@ module OpenFoodNetwork allow(order).to receive(:billing_address).and_return address allow(subject).to receive(:orders).and_return [order] - expect(subject.table).to eq([[ + expect(subject.table_rows).to eq([[ "test@test.com", "Firsty", "Lasty", "Suburbia" ]]) end @@ -37,11 +37,11 @@ module OpenFoodNetwork describe "addresses report" do before do - allow(subject).to receive(:params).and_return(report_type: "addresses") + allow(subject).to receive(:params).and_return(report_subtype: "addresses") end it "returns headers for addresses" do - expect(subject.header).to eq(["First Name", "Last Name", "Billing Address", "Email", + expect(subject.table_headers).to eq(["First Name", "Last Name", "Billing Address", "Email", "Phone", "Hub", "Hub Address", "Shipping Method"]) end @@ -52,7 +52,7 @@ module OpenFoodNetwork o.shipments << create(:shipment) allow(subject).to receive(:orders).and_return [o] - expect(subject.table).to eq([[ + expect(subject.table_rows).to eq([[ a.firstname, a.lastname, [a.address1, a.address2, a.city].join(" "), o.email, a.phone, d.name, From 01be6fb1f321f8cdb85659b75288b828ec884860 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Thu, 24 Mar 2022 17:40:36 +0000 Subject: [PATCH 09/54] Report Refactor 1: Order Cycle Management --- .../spree/admin/reports_controller.rb | 14 +------- ...der_cycle_management_description.html.haml | 2 +- .../filters/_order_cycle_management.html.haml | 18 +++++++++++ .../reports/order_cycle_management.html.haml | 32 ------------------- .../order_cycle_management_report.rb | 6 ++-- .../spree/admin/reports_controller_spec.rb | 2 +- .../order_cycle_management_report_spec.rb | 8 ++--- 7 files changed, 28 insertions(+), 54 deletions(-) create mode 100644 app/views/spree/admin/reports/filters/_order_cycle_management.html.haml delete mode 100644 app/views/spree/admin/reports/order_cycle_management.html.haml diff --git a/app/controllers/spree/admin/reports_controller.rb b/app/controllers/spree/admin/reports_controller.rb index 06007377a3..c12a37f942 100644 --- a/app/controllers/spree/admin/reports_controller.rb +++ b/app/controllers/spree/admin/reports_controller.rb @@ -50,19 +50,7 @@ module Spree end def order_cycle_management - raw_params[:q] ||= {} - - @report_types = report_types[:order_cycle_management] - @report_type = params[:report_type] - - # -- Build Report with Order Grouper - @report = OpenFoodNetwork::OrderCycleManagementReport.new spree_current_user, - raw_params, - render_content? - @table = @report.table_items - - render_report(@report.header, @table, params[:csv], - "order_cycle_management_#{timestamp}.csv") + render_report2 end def orders_and_distributors diff --git a/app/views/spree/admin/reports/_order_cycle_management_description.html.haml b/app/views/spree/admin/reports/_order_cycle_management_description.html.haml index 0a1ee7f69c..7c3b39a69e 100644 --- a/app/views/spree/admin/reports/_order_cycle_management_description.html.haml +++ b/app/views/spree/admin/reports/_order_cycle_management_description.html.haml @@ -1,4 +1,4 @@ %ul{style: "margin-left: 12pt"} - report_types.each do |report_type| %li - = link_to report_type[0], "#{order_cycle_management_admin_reports_url}?report_type=#{report_type[1]}" + = link_to report_type[0], "#{order_cycle_management_admin_reports_url}?report_subtype=#{report_type[1]}" diff --git a/app/views/spree/admin/reports/filters/_order_cycle_management.html.haml b/app/views/spree/admin/reports/filters/_order_cycle_management.html.haml new file mode 100644 index 0000000000..7de9126e14 --- /dev/null +++ b/app/views/spree/admin/reports/filters/_order_cycle_management.html.haml @@ -0,0 +1,18 @@ += render 'spree/admin/reports/date_range_form', f: f + +.row + .alpha.two.columns= label_tag nil, t(:report_hubs) + .omega.fourteen.columns= f.collection_select(:distributor_id_in, @distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + +.row + .alpha.two.columns= label_tag nil, t(:report_customers_cycle) + .omega.fourteen.columns + = f.select(:order_cycle_id_in, report_order_cycle_options(@order_cycles), {selected: params.dig(:q, :order_cycle_id_in)}, {class: "select2 fullwidth", multiple: true}) + +.row + .alpha.two.columns= label_tag nil, t(:report_payment) + .omega.fourteen.columns= select_tag(:payment_method_in, options_for_select(report_payment_method_options(@report.orders), params[:payment_method_in]), {class: "select2 fullwidth", multiple: true}) + +.row + .alpha.two.columns= label_tag nil, "#{t(:shipping_methods)}: " + .omega.fourteen.columns= select_tag(:shipping_method_in, options_for_select(report_shipping_method_options(@report.orders), params[:shipping_method_in]), {class: "select2 fullwidth", multiple: true}) diff --git a/app/views/spree/admin/reports/order_cycle_management.html.haml b/app/views/spree/admin/reports/order_cycle_management.html.haml deleted file mode 100644 index c91f2e6ce7..0000000000 --- a/app/views/spree/admin/reports/order_cycle_management.html.haml +++ /dev/null @@ -1,32 +0,0 @@ -= form_for @report.search, :url => spree.order_cycle_management_admin_reports_path do |f| - = render 'date_range_form', f: f - - .row - .alpha.two.columns= label_tag nil, t(:report_hubs) - .omega.fourteen.columns= f.collection_select(:distributor_id_in, @distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) - - .row - .alpha.two.columns= label_tag nil, t(:report_customers_cycle) - .omega.fourteen.columns - = f.select(:order_cycle_id_in, report_order_cycle_options(@order_cycles), {selected: params.dig(:q, :order_cycle_id_in)}, {class: "select2 fullwidth", multiple: true}) - - .row - .alpha.two.columns= label_tag nil, t(:report_payment) - .omega.fourteen.columns= select_tag(:payment_method_in, options_for_select(report_payment_method_options(@report.orders), params[:payment_method_in]), {class: "select2 fullwidth", multiple: true}) - - .row - .alpha.two.columns= label_tag nil, "#{t(:shipping_methods)}: " - .omega.fourteen.columns= select_tag(:shipping_method_in, options_for_select(report_shipping_method_options(@report.orders), params[:shipping_method_in]), {class: "select2 fullwidth", multiple: true}) - - .row - .alpha.two.columns= label_tag nil, "#{t(:report_type)}: " - .omega.fourteen.columns= select_tag(:report_type, options_for_select(@report_types, @report_type)) - - .row - = check_box_tag :csv - = label_tag :csv, t(:report_customers_csv) - - .row - = button t(:search) - -= render "table", id: "listing_ocm_orders", msg_option: t(:search) diff --git a/lib/open_food_network/order_cycle_management_report.rb b/lib/open_food_network/order_cycle_management_report.rb index d011b66467..06857cf063 100644 --- a/lib/open_food_network/order_cycle_management_report.rb +++ b/lib/open_food_network/order_cycle_management_report.rb @@ -12,7 +12,7 @@ module OpenFoodNetwork @render_table = render_table end - def header + def table_headers if is_payment_methods? [ I18n.t(:report_header_first_name), @@ -63,7 +63,7 @@ module OpenFoodNetwork filter(orders_with_balance) end - def table_items + def table_rows return [] unless @render_table if is_payment_methods? @@ -147,7 +147,7 @@ module OpenFoodNetwork end def is_payment_methods? - params[:report_type] == "payment_methods" + params[:report_subtype] == "payment_methods" end def customer_code(email) diff --git a/spec/controllers/spree/admin/reports_controller_spec.rb b/spec/controllers/spree/admin/reports_controller_spec.rb index 23e967cd85..0c84612e47 100644 --- a/spec/controllers/spree/admin/reports_controller_spec.rb +++ b/spec/controllers/spree/admin/reports_controller_spec.rb @@ -313,7 +313,7 @@ describe Spree::Admin::ReportsController, type: :controller do spree_post :order_cycle_management, { q: { completed_at_lt: 1.day.ago }, shipping_method_in: ["123"], # We just need to search for shipping methods - report_type: "delivery", + report_subtype: "delivery", } expect(response).to have_http_status(:ok) diff --git a/spec/lib/open_food_network/order_cycle_management_report_spec.rb b/spec/lib/open_food_network/order_cycle_management_report_spec.rb index 435b799ceb..01f92e8ecd 100644 --- a/spec/lib/open_food_network/order_cycle_management_report_spec.rb +++ b/spec/lib/open_food_network/order_cycle_management_report_spec.rb @@ -146,14 +146,14 @@ module OpenFoodNetwork end end - describe '#table_items' do + describe '#table_rows' do subject { OrderCycleManagementReport.new(user, params, true) } let(:distributor) { create(:distributor_enterprise) } before { distributor.enterprise_roles.create!(user: user) } context 'when the report type is payment_methods' do - let(:params) { { report_type: 'payment_methods' } } + let(:params) { { report_subtype: 'payment_methods' } } let!(:order) do create( @@ -164,7 +164,7 @@ module OpenFoodNetwork end it 'returns rows with payment information' do - expect(subject.table_items).to eq([[ + expect(subject.table_rows).to eq([[ order.billing_address.firstname, order.billing_address.lastname, order.distributor.name, @@ -190,7 +190,7 @@ module OpenFoodNetwork end it 'returns rows with delivery information' do - expect(subject.table_items).to eq([[ + expect(subject.table_rows).to eq([[ order.ship_address.firstname, order.ship_address.lastname, order.distributor.name, From e20c2e3ced13970751c049dcf82ece858fbb94cd Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 27 Mar 2022 10:05:14 +0000 Subject: [PATCH 10/54] Report Refactor 1: Orders & Distributors --- .rubocop_todo.yml | 4 ++-- .../spree/admin/reports_controller.rb | 9 ++------- .../filters/_orders_and_distributors.html.haml | 1 + .../reports/orders_and_distributors.html.haml | 12 ------------ ...ort.rb => orders_and_distributors_report.rb} | 8 ++++---- .../spree/admin/reports_controller_spec.rb | 6 +++--- ...b => orders_and_distributors_report_spec.rb} | 17 ++++++++--------- spec/system/admin/reports_spec.rb | 2 +- 8 files changed, 21 insertions(+), 38 deletions(-) create mode 100644 app/views/spree/admin/reports/filters/_orders_and_distributors.html.haml delete mode 100644 app/views/spree/admin/reports/orders_and_distributors.html.haml rename lib/open_food_network/{order_and_distributor_report.rb => orders_and_distributors_report.rb} (96%) rename spec/lib/open_food_network/{order_and_distributor_report_spec.rb => orders_and_distributors_report_spec.rb} (87%) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ac1cfe8ee6..657102531e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -423,7 +423,7 @@ Metrics/AbcSize: - 'lib/discourse/single_sign_on.rb' - 'lib/open_food_network/customers_report.rb' - 'lib/open_food_network/group_buy_report.rb' - - 'lib/open_food_network/order_and_distributor_report.rb' + - 'lib/open_food_network/orders_and_distributors_report.rb' - 'lib/open_food_network/order_cycle_form_applicator.rb' - 'lib/open_food_network/order_cycle_permissions.rb' - 'lib/open_food_network/payments_report.rb' @@ -1135,7 +1135,7 @@ Style/OptionalBooleanParameter: - 'engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_report.rb' - 'engines/order_management/app/services/order_management/stock/estimator.rb' - 'lib/open_food_network/customers_report.rb' - - 'lib/open_food_network/order_and_distributor_report.rb' + - 'lib/open_food_network/orders_and_distributors_report.rb' - 'lib/open_food_network/order_cycle_management_report.rb' - 'lib/open_food_network/orders_and_fulfillment_report.rb' - 'lib/open_food_network/payments_report.rb' diff --git a/app/controllers/spree/admin/reports_controller.rb b/app/controllers/spree/admin/reports_controller.rb index c12a37f942..231095fa75 100644 --- a/app/controllers/spree/admin/reports_controller.rb +++ b/app/controllers/spree/admin/reports_controller.rb @@ -3,7 +3,7 @@ require 'csv' require 'open_food_network/reports/list' -require 'open_food_network/order_and_distributor_report' +require 'open_food_network/orders_and_distributors_report' require 'open_food_network/products_and_inventory_report' require 'open_food_network/lettuce_share_report' require 'open_food_network/group_buy_report' @@ -54,12 +54,7 @@ module Spree end def orders_and_distributors - @report = OpenFoodNetwork::OrderAndDistributorReport.new spree_current_user, - raw_params, - render_content? - @search = @report.search - csv_file_name = "orders_and_distributors_#{timestamp}.csv" - render_report(@report.header, @report.table, params[:csv], csv_file_name) + render_report2 end def sales_tax diff --git a/app/views/spree/admin/reports/filters/_orders_and_distributors.html.haml b/app/views/spree/admin/reports/filters/_orders_and_distributors.html.haml new file mode 100644 index 0000000000..6d7296bbab --- /dev/null +++ b/app/views/spree/admin/reports/filters/_orders_and_distributors.html.haml @@ -0,0 +1 @@ += render 'spree/admin/reports/date_range_form', f: f diff --git a/app/views/spree/admin/reports/orders_and_distributors.html.haml b/app/views/spree/admin/reports/orders_and_distributors.html.haml deleted file mode 100644 index fae9b26d99..0000000000 --- a/app/views/spree/admin/reports/orders_and_distributors.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -= form_for @search, :url => spree.orders_and_distributors_admin_reports_path do |f| - = render 'date_range_form', f: f - - = check_box_tag :csv - = label_tag :csv, t(:report_customers_csv) - %br - = button t(:search) - -- if render_content? - = render partial: "customer_names_message" - -= render "table", id: "listing_orders", msg_option: t(:search) diff --git a/lib/open_food_network/order_and_distributor_report.rb b/lib/open_food_network/orders_and_distributors_report.rb similarity index 96% rename from lib/open_food_network/order_and_distributor_report.rb rename to lib/open_food_network/orders_and_distributors_report.rb index 9764184b02..252f33f3e0 100644 --- a/lib/open_food_network/order_and_distributor_report.rb +++ b/lib/open_food_network/orders_and_distributors_report.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module OpenFoodNetwork - class OrderAndDistributorReport + class OrdersAndDistributorsReport def initialize(user, params = {}, render_table = false) @params = params @user = user @@ -10,7 +10,7 @@ module OpenFoodNetwork @permissions = ::Permissions::Order.new(user, @params[:q]) end - def header + def table_headers [ I18n.t(:report_header_order_date), I18n.t(:report_header_order_id), @@ -41,7 +41,7 @@ module OpenFoodNetwork ransack(@params[:q]) end - def table + def table_rows return [] unless @render_table orders = search.result @@ -104,7 +104,7 @@ module OpenFoodNetwork order.distributor.address.address1, order.distributor.address.city, order.distributor.address.zipcode, - order.shipping_method.name, + order.shipping_method&.name, order.special_instructions ] end diff --git a/spec/controllers/spree/admin/reports_controller_spec.rb b/spec/controllers/spree/admin/reports_controller_spec.rb index 0c84612e47..f263bba98d 100644 --- a/spec/controllers/spree/admin/reports_controller_spec.rb +++ b/spec/controllers/spree/admin/reports_controller_spec.rb @@ -99,9 +99,9 @@ describe Spree::Admin::ReportsController, type: :controller do it "only shows orders that I have access to" do spree_post :orders_and_distributors - expect(assigns(:search).result).to include(orderA1, orderB1) - expect(assigns(:search).result).not_to include(orderA2) - expect(assigns(:search).result).not_to include(orderB2) + expect(assigns(:report).search.result).to include(orderA1, orderB1) + expect(assigns(:report).search.result).not_to include(orderA2) + expect(assigns(:report).search.result).not_to include(orderB2) end end diff --git a/spec/lib/open_food_network/order_and_distributor_report_spec.rb b/spec/lib/open_food_network/orders_and_distributors_report_spec.rb similarity index 87% rename from spec/lib/open_food_network/order_and_distributor_report_spec.rb rename to spec/lib/open_food_network/orders_and_distributors_report_spec.rb index 80bc46f913..4f97d9d848 100644 --- a/spec/lib/open_food_network/order_and_distributor_report_spec.rb +++ b/spec/lib/open_food_network/orders_and_distributors_report_spec.rb @@ -1,16 +1,15 @@ # frozen_string_literal: true require 'spec_helper' -require 'open_food_network/order_and_distributor_report' +require 'open_food_network/orders_and_distributors_report' module OpenFoodNetwork - describe OrderAndDistributorReport do + describe OrdersAndDistributorsReport do describe 'orders and distributors report' do it 'should return a header row describing the report' do - subject = OrderAndDistributorReport.new nil + subject = OrdersAndDistributorsReport.new nil - header = subject.header - expect(header).to eq( + expect(subject.table_headers).to eq( [ 'Order date', 'Order Id', 'Customer Name', 'Customer Email', 'Customer Phone', 'Customer City', @@ -45,9 +44,9 @@ module OpenFoodNetwork end it 'should denormalise order and distributor details for display as csv' do - subject = OrderAndDistributorReport.new create(:admin_user), {}, true + subject = OrdersAndDistributorsReport.new create(:admin_user), {}, true - table = subject.table + table = subject.table_rows expect(table.size).to eq 1 expect(table[0]).to eq([ @@ -77,9 +76,9 @@ module OpenFoodNetwork it "prints one row per line item" do create(:line_item_with_shipment, order: order) - subject = OrderAndDistributorReport.new(create(:admin_user), {}, true) + subject = OrdersAndDistributorsReport.new(create(:admin_user), {}, true) - table = subject.table + table = subject.table_rows expect(table.size).to eq 2 end end diff --git a/spec/system/admin/reports_spec.rb b/spec/system/admin/reports_spec.rb index 3c8b6f3122..e9551b6935 100644 --- a/spec/system/admin/reports_spec.rb +++ b/spec/system/admin/reports_spec.rb @@ -80,7 +80,7 @@ describe ' it "delivery report" do click_link "Delivery Report" - click_button "Search" + click_button "Go" rows = find("table.report__table").all("thead tr") table = rows.map { |r| r.all("th").map { |c| c.text.strip } } expect(table.sort).to eq([ From 9d943625a34a00912b1b9cf8c7c3676ca84c92c4 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Fri, 25 Mar 2022 07:17:05 +0000 Subject: [PATCH 11/54] Report Refactor 1: Sales Tax --- .../spree/admin/reports_controller.rb | 5 +---- .../reports/_sales_tax_description.html.haml | 2 +- .../reports/filters/_sales_tax.html.haml | 6 ++++++ .../spree/admin/reports/sales_tax.html.haml | 19 ------------------- lib/open_food_network/sales_tax_report.rb | 8 ++++---- .../spree/admin/reports_controller_spec.rb | 2 +- spec/system/admin/reports_spec.rb | 2 +- 7 files changed, 14 insertions(+), 30 deletions(-) create mode 100644 app/views/spree/admin/reports/filters/_sales_tax.html.haml delete mode 100644 app/views/spree/admin/reports/sales_tax.html.haml diff --git a/app/controllers/spree/admin/reports_controller.rb b/app/controllers/spree/admin/reports_controller.rb index 231095fa75..b3f7689225 100644 --- a/app/controllers/spree/admin/reports_controller.rb +++ b/app/controllers/spree/admin/reports_controller.rb @@ -59,10 +59,7 @@ module Spree def sales_tax @distributors = my_distributors - @report_type = params[:report_type] - @report = OpenFoodNetwork::SalesTaxReport.new spree_current_user, raw_params, - render_content? - render_report(@report.header, @report.table, params[:csv], "sales_tax.csv") + render_report2 end def payments diff --git a/app/views/spree/admin/reports/_sales_tax_description.html.haml b/app/views/spree/admin/reports/_sales_tax_description.html.haml index 5f4f7f6dfd..169be2591e 100644 --- a/app/views/spree/admin/reports/_sales_tax_description.html.haml +++ b/app/views/spree/admin/reports/_sales_tax_description.html.haml @@ -1,4 +1,4 @@ %ul{style: "margin-left: 12pt"} - report_types.each do |report_type| %li - = link_to report_type[0], "#{sales_tax_admin_reports_url}?report_type=#{report_type[1]}" + = link_to report_type[0], "#{sales_tax_admin_reports_url}?report_subtype=#{report_type[1]}" diff --git a/app/views/spree/admin/reports/filters/_sales_tax.html.haml b/app/views/spree/admin/reports/filters/_sales_tax.html.haml new file mode 100644 index 0000000000..bdd7a00f97 --- /dev/null +++ b/app/views/spree/admin/reports/filters/_sales_tax.html.haml @@ -0,0 +1,6 @@ += render 'spree/admin/reports/date_range_form', f: f + +.row + .alpha.two.columns= label_tag nil, t(:report_distributor) + .omega.fourteen.columns + = f.collection_select(:distributor_id_eq, @distributors, :id, :name, {:include_blank => t(:all)}, {:class => "select2 fullwidth light"}) diff --git a/app/views/spree/admin/reports/sales_tax.html.haml b/app/views/spree/admin/reports/sales_tax.html.haml deleted file mode 100644 index 1482d27266..0000000000 --- a/app/views/spree/admin/reports/sales_tax.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -= form_for @report.search, :url => spree.sales_tax_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_customers_type) - %br - = select_tag(:report_type, options_for_select([[t(:report_tax_types),:tax_types],[t(:report_tax_rates),:tax_rates]], @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", column_partials: {0 => "link_order"}, msg_option: t(:search) diff --git a/lib/open_food_network/sales_tax_report.rb b/lib/open_food_network/sales_tax_report.rb index 3284d5b164..eed03325b5 100644 --- a/lib/open_food_network/sales_tax_report.rb +++ b/lib/open_food_network/sales_tax_report.rb @@ -11,8 +11,8 @@ module OpenFoodNetwork @render_table = render_table end - def header - case params[:report_type] + def table_headers + case params[:report_subtype] when "tax_rates" [I18n.t(:report_header_order_number), I18n.t(:report_header_total_excl_vat, currency_symbol: currency_symbol)] + @@ -44,10 +44,10 @@ module OpenFoodNetwork search.result end - def table + def table_rows return [] unless @render_table - case params[:report_type] + case params[:report_subtype] when "tax_rates" orders.map do |order| [order.number, order.total - order.total_tax] + diff --git a/spec/controllers/spree/admin/reports_controller_spec.rb b/spec/controllers/spree/admin/reports_controller_spec.rb index f263bba98d..ccdf76047f 100644 --- a/spec/controllers/spree/admin/reports_controller_spec.rb +++ b/spec/controllers/spree/admin/reports_controller_spec.rb @@ -340,7 +340,7 @@ describe Spree::Admin::ReportsController, type: :controller do describe "sales_tax" do it "shows report search forms" do spree_get :sales_tax - expect(assigns(:report).table).to eq [] + expect(assigns(:report).table_rows).to eq [] end end end diff --git a/spec/system/admin/reports_spec.rb b/spec/system/admin/reports_spec.rb index e9551b6935..2968f44fbf 100644 --- a/spec/system/admin/reports_spec.rb +++ b/spec/system/admin/reports_spec.rb @@ -164,7 +164,7 @@ describe ' login_as_admin_and_visit spree.admin_reports_path click_link "Sales Tax" - select("Tax types", from: "report_type") + select("Tax Types", from: "report_subtype") end it "reports" do From 881a708ecff2dff229ad7959812716117f7ff1e3 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Fri, 25 Mar 2022 07:16:52 +0000 Subject: [PATCH 12/54] Report Refactor 1: Payments --- .../spree/admin/reports_controller.rb | 11 +---------- .../admin/reports/filters/_payments.html.haml | 6 ++++++ .../spree/admin/reports/payments.html.haml | 19 ------------------- lib/open_food_network/payments_report.rb | 17 ++++++++++++----- .../admin/reports/payments_report_spec.rb | 12 ++++++------ spec/system/admin/reports_spec.rb | 4 ++-- 6 files changed, 27 insertions(+), 42 deletions(-) create mode 100644 app/views/spree/admin/reports/filters/_payments.html.haml delete mode 100644 app/views/spree/admin/reports/payments.html.haml diff --git a/app/controllers/spree/admin/reports_controller.rb b/app/controllers/spree/admin/reports_controller.rb index b3f7689225..2ef24f68e2 100644 --- a/app/controllers/spree/admin/reports_controller.rb +++ b/app/controllers/spree/admin/reports_controller.rb @@ -63,17 +63,8 @@ module Spree end def payments - # -- Prepare Form Options @distributors = my_distributors - @report_type = params[:report_type] - - # -- Build Report with Order Grouper - @report = OpenFoodNetwork::PaymentsReport.new spree_current_user, raw_params, - render_content? - @table = order_grouper_table - csv_file_name = "payments_#{timestamp}.csv" - - render_report(@report.header, @table, params[:csv], csv_file_name) + render_report2 end def orders_and_fulfillment diff --git a/app/views/spree/admin/reports/filters/_payments.html.haml b/app/views/spree/admin/reports/filters/_payments.html.haml new file mode 100644 index 0000000000..5a208a876a --- /dev/null +++ b/app/views/spree/admin/reports/filters/_payments.html.haml @@ -0,0 +1,6 @@ += render 'spree/admin/reports/date_range_form', f: f + +.row + .alpha.two.columns= label_tag nil, t(:report_distributor) + .omega.fourteen.columns + = f.collection_select(:distributor_id_eq, @distributors, :id, :name, {:include_blank => t(:report_all)}, {:class => "select2 fullwidth light"}) diff --git a/app/views/spree/admin/reports/payments.html.haml b/app/views/spree/admin/reports/payments.html.haml deleted file mode 100644 index 5da46d8d5e..0000000000 --- a/app/views/spree/admin/reports/payments.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -= form_for @report.search, :url => spree.payments_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(:report_all)}, {:class => "select2 fullwidth"}) - = label_tag nil, t(:report_customers_type) - %br - = select_tag(:report_type, options_for_select([[t(:report_payment_by),:payments_by_payment_type],[t(:report_itemised_payment),:itemised_payment_totals],[t(:report_payment_totals),:payment_totals]], @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/lib/open_food_network/payments_report.rb b/lib/open_food_network/payments_report.rb index efbecec205..8dbddcc270 100644 --- a/lib/open_food_network/payments_report.rb +++ b/lib/open_food_network/payments_report.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'open_food_network/order_grouper' + module OpenFoodNetwork class PaymentsReport attr_reader :params @@ -10,8 +12,8 @@ module OpenFoodNetwork @render_table = render_table end - def header - case params[:report_type] + def table_headers + case params[:report_subtype] when "payments_by_payment_type" I18n.t(:report_header_payment_type) [I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor), I18n.t(:report_header_payment_type), @@ -48,7 +50,7 @@ module OpenFoodNetwork order.payments.select(&:completed?) end.flatten - case params[:report_type] + case params[:report_subtype] when "payments_by_payment_type" payments when "itemised_payment_totals" @@ -60,8 +62,13 @@ module OpenFoodNetwork end end + def table_rows + order_grouper = OpenFoodNetwork::OrderGrouper.new rules, columns, self + order_grouper.table(table_items) + end + def rules - case params[:report_type] + case params[:report_subtype] when "payments_by_payment_type" [{ group_by: proc { |payment| payment.order.payment_state }, sort_by: proc { |payment_state| payment_state } }, @@ -90,7 +97,7 @@ module OpenFoodNetwork end def columns - case params[:report_type] + case params[:report_subtype] when "payments_by_payment_type" [proc { |payments| payments.first.order.payment_state }, proc { |payments| payments.first.order.distributor.name }, diff --git a/spec/system/admin/reports/payments_report_spec.rb b/spec/system/admin/reports/payments_report_spec.rb index 21dede0dca..d2e4396466 100644 --- a/spec/system/admin/reports/payments_report_spec.rb +++ b/spec/system/admin/reports/payments_report_spec.rb @@ -37,10 +37,10 @@ describe "Payments Reports" do it "shows orders with payment state, their balance and totals" do visit spree.payments_admin_reports_path - select I18n.t(:report_itemised_payment), from: "report_type" + select I18n.t(:report_itemised_payment), from: "report_subtype" find("[type='submit']").click - expect(page.find("#listing_orders thead tr").text).to have_content([ + expect(page.find("table.report__table thead tr").text).to have_content([ I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor), I18n.t(:report_header_product_total_price, currency: currency_symbol), @@ -49,7 +49,7 @@ describe "Payments Reports" do I18n.t(:report_header_total_price, currency: currency_symbol) ].join(" ").upcase) - expect(page.find("#listing_orders tbody tr").text).to have_content([ + expect(page.find("table.report__table tbody tr").text).to have_content([ order.payment_state, order.distributor.name, order.item_total.to_f + other_order.item_total.to_f, @@ -74,10 +74,10 @@ describe "Payments Reports" do it 'shows orders with payment state, their balance and and payment totals' do visit spree.payments_admin_reports_path - select I18n.t(:report_payment_totals), from: "report_type" + select I18n.t(:report_payment_totals), from: "report_subtype" find("[type='submit']").click - expect(page.find("#listing_orders thead tr").text).to have_content([ + expect(page.find("table.report__table thead tr").text).to have_content([ I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor), I18n.t(:report_header_product_total_price, currency: currency_symbol), @@ -88,7 +88,7 @@ describe "Payments Reports" do I18n.t(:report_header_outstanding_balance_price, currency: currency_symbol), ].join(" ").upcase) - expect(page.find("#listing_orders tbody tr").text).to have_content([ + expect(page.find("table.report__table tbody tr").text).to have_content([ order.payment_state, order.distributor.name, order.item_total.to_f + other_order.item_total.to_f, diff --git a/spec/system/admin/reports_spec.rb b/spec/system/admin/reports_spec.rb index 2968f44fbf..d2064eae8f 100644 --- a/spec/system/admin/reports_spec.rb +++ b/spec/system/admin/reports_spec.rb @@ -69,7 +69,7 @@ describe ' it "payment method report" do click_link "Payment Methods Report" - click_button "Search" + click_button "Go" rows = find("table.report__table").all("thead tr") table = rows.map { |r| r.all("th").map { |c| c.text.strip } } expect(table.sort).to eq([ @@ -245,7 +245,7 @@ describe ' select 'Order Cycle Customer Totals', from: 'report_subtype' click_button 'Search' # Then I should see the rows for the first order but not the second - expect(all('table#listing_orders tbody tr').count).to eq(4) # Two rows per order + expect(all('table.report__table tbody tr').count).to eq(4) # Two rows per order end end From 3d1b61ae6e3f858a6da7b9b490b8930f4e4d1bf8 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Fri, 25 Mar 2022 07:12:07 +0000 Subject: [PATCH 13/54] Report Refactor 1: Products & Inventory --- .rubocop_todo.yml | 2 +- .../spree/admin/reports_controller.rb | 16 +- ...oducts_and_inventory_description.html.haml | 2 +- .../filters/_products_and_inventory.html.haml | 20 ++ .../reports/products_and_inventory.html.haml | 34 --- lib/open_food_network/lettuce_share_report.rb | 18 +- .../products_and_inventory_default_report.rb | 53 ++++ .../products_and_inventory_report.rb | 119 +++++--- .../products_and_inventory_report_base.rb | 80 ------ .../spree/admin/reports_controller_spec.rb | 6 +- .../lettuce_share_report_spec.rb | 37 +-- ...ducts_and_inventory_default_report_spec.rb | 261 ++++++++++++++++++ .../products_and_inventory_report_spec.rb | 259 ----------------- .../admin/reports/packing_report_spec.rb | 6 +- spec/system/admin/reports_spec.rb | 8 +- 15 files changed, 466 insertions(+), 455 deletions(-) create mode 100644 app/views/spree/admin/reports/filters/_products_and_inventory.html.haml delete mode 100644 app/views/spree/admin/reports/products_and_inventory.html.haml create mode 100644 lib/open_food_network/products_and_inventory_default_report.rb delete mode 100644 lib/open_food_network/products_and_inventory_report_base.rb create mode 100644 spec/lib/open_food_network/products_and_inventory_default_report_spec.rb delete mode 100644 spec/lib/open_food_network/products_and_inventory_report_spec.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 657102531e..3f81b750d4 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1139,7 +1139,7 @@ Style/OptionalBooleanParameter: - 'lib/open_food_network/order_cycle_management_report.rb' - 'lib/open_food_network/orders_and_fulfillment_report.rb' - 'lib/open_food_network/payments_report.rb' - - 'lib/open_food_network/products_and_inventory_report_base.rb' + - 'lib/open_food_network/products_and_inventory_report.rb' - 'lib/open_food_network/users_and_enterprises_report.rb' - 'lib/open_food_network/xero_invoices_report.rb' - 'lib/spree/core/controller_helpers/order.rb' diff --git a/app/controllers/spree/admin/reports_controller.rb b/app/controllers/spree/admin/reports_controller.rb index 2ef24f68e2..7c9d415dce 100644 --- a/app/controllers/spree/admin/reports_controller.rb +++ b/app/controllers/spree/admin/reports_controller.rb @@ -86,21 +86,7 @@ module Spree end def products_and_inventory - @report_types = report_types[:products_and_inventory] - @report = if params[:report_type] != 'lettuce_share' - OpenFoodNetwork::ProductsAndInventoryReport.new spree_current_user, - raw_params, - render_content? - else - OpenFoodNetwork::LettuceShareReport.new spree_current_user, - raw_params, - render_content? - end - - render_report @report.header, - @report.table, - params[:csv], - "products_and_inventory_#{timestamp}.csv" + render_report2 end def users_and_enterprises diff --git a/app/views/spree/admin/reports/_products_and_inventory_description.html.haml b/app/views/spree/admin/reports/_products_and_inventory_description.html.haml index f662298e3e..f65d13273e 100644 --- a/app/views/spree/admin/reports/_products_and_inventory_description.html.haml +++ b/app/views/spree/admin/reports/_products_and_inventory_description.html.haml @@ -1,4 +1,4 @@ %ul{style: "margin-left: 12pt"} - report_types.each do |report_type| %li - = link_to report_type[0], "#{products_and_inventory_admin_reports_url}?report_type=#{report_type[1]}" + = link_to report_type[0], "#{products_and_inventory_admin_reports_url}?report_subtype=#{report_type[1]}" diff --git a/app/views/spree/admin/reports/filters/_products_and_inventory.html.haml b/app/views/spree/admin/reports/filters/_products_and_inventory.html.haml new file mode 100644 index 0000000000..dbb56d76b7 --- /dev/null +++ b/app/views/spree/admin/reports/filters/_products_and_inventory.html.haml @@ -0,0 +1,20 @@ +.row + .alpha.two.columns= label_tag nil, t(:report_distributor) + .omega.fourteen.columns + = select_tag(:distributor_id, + options_from_collection_for_select(@distributors, :id, :name, params[:distributor_id]), + {:include_blank => true, :class => "select2 fullwidth light"}) + +.row + .alpha.two.columns= label_tag nil, t(:report_customers_supplier) + .omega.fourteen.columns + = select_tag(:supplier_id, + options_from_collection_for_select(@suppliers, :id, :name, params[:supplier_id]), + {:include_blank => true, :class => "select2 fullwidth light"}) + +.row + .alpha.two.columns= label_tag nil, t(:report_order_cycle) + .omega.fourteen.columns + = select_tag(:order_cycle_id, + options_for_select(report_order_cycle_options(@order_cycles), params[:order_cycle_id]), + {:include_blank => true, :class => "select2 fullwidth light"}) diff --git a/app/views/spree/admin/reports/products_and_inventory.html.haml b/app/views/spree/admin/reports/products_and_inventory.html.haml deleted file mode 100644 index 73d31a6990..0000000000 --- a/app/views/spree/admin/reports/products_and_inventory.html.haml +++ /dev/null @@ -1,34 +0,0 @@ -= form_tag spree.products_and_inventory_admin_reports_url do |f| - %br - .row - .four.columns.alpha - = label_tag nil, t(:report_distributor) - = select_tag(:distributor_id, - options_from_collection_for_select(@distributors, :id, :name, params[:distributor_id]), - {:include_blank => true, :class => "select2 fullwidth"}) - - - .four.columns - = label_tag nil, t(:report_customers_supplier) - = select_tag(:supplier_id, - options_from_collection_for_select(@suppliers, :id, :name, params[:supplier_id]), - {:include_blank => true, :class => "select2 fullwidth"}) - - - .six.columns - = label_tag nil, t(:report_order_cycle) - = select_tag(:order_cycle_id, - options_for_select(report_order_cycle_options(@order_cycles), params[:order_cycle_id]), - {:include_blank => true, :class => "select2 fullwidth"}) - - = label_tag nil, t(:report_type) - %br - = select_tag(:report_type, options_for_select(@report_types, params[:report_type])) - - %br - %br - = check_box_tag :csv - = label_tag :csv, t(:report_customers_csv) - %br - = button t(:go) -= render "table", id: "listing_products", msg_option: t(:go) diff --git a/lib/open_food_network/lettuce_share_report.rb b/lib/open_food_network/lettuce_share_report.rb index 25868c9006..a98c26e6f1 100644 --- a/lib/open_food_network/lettuce_share_report.rb +++ b/lib/open_food_network/lettuce_share_report.rb @@ -1,11 +1,17 @@ # frozen_string_literal: true - -require 'open_food_network/products_and_inventory_report_base' require 'variant_units/option_value_namer' module OpenFoodNetwork - class LettuceShareReport < ProductsAndInventoryReportBase - def header + class LettuceShareReport + attr_reader :context + + delegate :variants, :render_table, to: :context + + def initialize(context) + @context = context + end + + def table_headers # NOTE: These are NOT to be translated, they need to be in this exact format to work with LettucShare [ "PRODUCT", @@ -21,8 +27,8 @@ module OpenFoodNetwork ] end - def table - return [] unless @render_table + def table_rows + return [] unless render_table variants.select(&:in_stock?) .map do |variant| diff --git a/lib/open_food_network/products_and_inventory_default_report.rb b/lib/open_food_network/products_and_inventory_default_report.rb new file mode 100644 index 0000000000..2400586c7b --- /dev/null +++ b/lib/open_food_network/products_and_inventory_default_report.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module OpenFoodNetwork + class ProductsAndInventoryDefaultReport + attr_reader :context + + delegate :variants, :render_table, to: :context + + def initialize(context) + @context = context + end + + def table_headers + [ + I18n.t(:report_header_supplier), + I18n.t(:report_header_producer_suburb), + I18n.t(:report_header_product), + I18n.t(:report_header_product_properties), + I18n.t(:report_header_taxons), + I18n.t(:report_header_variant_value), + I18n.t(:report_header_price), + I18n.t(:report_header_group_buy_unit_quantity), + I18n.t(:report_header_amount), + I18n.t(:report_header_sku) + ] + end + + def table_rows + return [] unless render_table + + variants.map do |variant| + [ + variant.product.supplier.name, + variant.product.supplier.address.city, + variant.product.name, + variant.product.properties.map(&:name).join(", "), + variant.product.taxons.map(&:name).join(", "), + variant.full_name, + variant.price, + variant.product.group_buy_unit_size, + "", + sku_for(variant) + ] + end + end + + def sku_for(variant) + return variant.sku if variant.sku.present? + + variant.product.sku + end + end +end diff --git a/lib/open_food_network/products_and_inventory_report.rb b/lib/open_food_network/products_and_inventory_report.rb index df40f3aa4c..ae211ec4d0 100644 --- a/lib/open_food_network/products_and_inventory_report.rb +++ b/lib/open_food_network/products_and_inventory_report.rb @@ -1,47 +1,100 @@ # frozen_string_literal: true -require 'open_food_network/products_and_inventory_report_base' +require 'open_food_network/scope_variant_to_hub' +require 'open_food_network/products_and_inventory_default_report' +require 'open_food_network/lettuce_share_report' module OpenFoodNetwork - class ProductsAndInventoryReport < ProductsAndInventoryReportBase - def header - [ - I18n.t(:report_header_supplier), - I18n.t(:report_header_producer_suburb), - I18n.t(:report_header_product), - I18n.t(:report_header_product_properties), - I18n.t(:report_header_taxons), - I18n.t(:report_header_variant_value), - I18n.t(:report_header_price), - I18n.t(:report_header_group_buy_unit_quantity), - I18n.t(:report_header_amount), - I18n.t(:report_header_sku) - ] + class ProductsAndInventoryReport + attr_reader :params, :render_table + + delegate :table_rows, :table_headers, :rules, :columns, :sku_for, to: :report + + def initialize(user, params = {}, render_table = false) + @user = user + @params = params + @render_table = render_table end - def table - return [] unless @render_table + def report + @report ||= report_klass.new(self) + end - variants.map do |variant| - [ - variant.product.supplier.name, - variant.product.supplier.address.city, - variant.product.name, - variant.product.properties.map(&:name).join(", "), - variant.product.taxons.map(&:name).join(", "), - variant.full_name, - variant.price, - variant.product.group_buy_unit_size, - "", - sku_for(variant) - ] + def report_type + params[:report_subtype] + end + + def report_klass + if report_type == 'lettuce_share' + OpenFoodNetwork::LettuceShareReport + else + OpenFoodNetwork::ProductsAndInventoryDefaultReport end end - def sku_for(variant) - return variant.sku if variant.sku.present? + def permissions + @permissions ||= OpenFoodNetwork::Permissions.new(@user) + end - variant.product.sku + def visible_products + @visible_products ||= permissions.visible_products + end + + def variants + filter(child_variants) + end + + def child_variants + Spree::Variant. + where(is_master: false). + includes(option_values: :option_type). + joins(:product). + merge(visible_products). + order('spree_products.name') + end + + def filter(variants) + filter_on_hand filter_to_distributor filter_to_order_cycle filter_to_supplier variants + end + + # Using the `in_stock?` method allows overrides by distributors. + def filter_on_hand(variants) + if report_type == 'inventory' + variants.select(&:in_stock?) + else + variants + end + end + + def filter_to_supplier(variants) + if params[:supplier_id].to_i > 0 + variants.where("spree_products.supplier_id = ?", params[:supplier_id]) + else + variants + end + end + + def filter_to_distributor(variants) + if params[:distributor_id].to_i > 0 + distributor = Enterprise.find params[:distributor_id] + scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor) + variants.in_distributor(distributor).each { |v| scoper.scope(v) } + else + variants + end + end + + def filter_to_order_cycle(variants) + if params[:order_cycle_id].to_i > 0 + order_cycle = OrderCycle.find params[:order_cycle_id] + variant_ids = Exchange.in_order_cycle(order_cycle). + joins("INNER JOIN exchange_variants ON exchanges.id = exchange_variants.exchange_id"). + select("DISTINCT exchange_variants.variant_id") + + variants.where("spree_variants.id IN (#{variant_ids.to_sql})") + else + variants + end end end end diff --git a/lib/open_food_network/products_and_inventory_report_base.rb b/lib/open_food_network/products_and_inventory_report_base.rb deleted file mode 100644 index 22cd561f36..0000000000 --- a/lib/open_food_network/products_and_inventory_report_base.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true - -require 'open_food_network/scope_variant_to_hub' - -module OpenFoodNetwork - class ProductsAndInventoryReportBase - attr_reader :params - - def initialize(user, params = {}, render_table = false) - @user = user - @params = params - @render_table = render_table - end - - def permissions - @permissions ||= OpenFoodNetwork::Permissions.new(@user) - end - - def visible_products - @visible_products ||= permissions.visible_products - end - - def variants - filter(child_variants) - end - - def child_variants - Spree::Variant. - where(is_master: false). - includes(option_values: :option_type). - joins(:product). - merge(visible_products). - order('spree_products.name') - end - - def filter(variants) - filter_on_hand filter_to_distributor filter_to_order_cycle filter_to_supplier variants - end - - # Using the `in_stock?` method allows overrides by distributors. - def filter_on_hand(variants) - if params[:report_type] == 'inventory' - variants.select(&:in_stock?) - else - variants - end - end - - def filter_to_supplier(variants) - if params[:supplier_id].to_i > 0 - variants.where("spree_products.supplier_id = ?", params[:supplier_id]) - else - variants - end - end - - def filter_to_distributor(variants) - if params[:distributor_id].to_i > 0 - distributor = Enterprise.find params[:distributor_id] - scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor) - variants.in_distributor(distributor).each { |v| scoper.scope(v) } - else - variants - end - end - - def filter_to_order_cycle(variants) - if params[:order_cycle_id].to_i > 0 - order_cycle = OrderCycle.find params[:order_cycle_id] - variant_ids = Exchange.in_order_cycle(order_cycle). - joins("INNER JOIN exchange_variants ON exchanges.id = exchange_variants.exchange_id"). - select("DISTINCT exchange_variants.variant_id") - - variants.where("spree_variants.id IN (#{variant_ids.to_sql})") - else - variants - end - end - end -end diff --git a/spec/controllers/spree/admin/reports_controller_spec.rb b/spec/controllers/spree/admin/reports_controller_spec.rb index ccdf76047f..063ce9d770 100644 --- a/spec/controllers/spree/admin/reports_controller_spec.rb +++ b/spec/controllers/spree/admin/reports_controller_spec.rb @@ -233,7 +233,7 @@ describe Spree::Admin::ReportsController, type: :controller do it "assigns report types" do spree_get :products_and_inventory - expect(assigns(:report_types)).to eq(subject.report_types[:products_and_inventory]) + expect(assigns(:report_subtypes)).to eq(subject.report_types[:products_and_inventory]) end it "creates a ProductAndInventoryReport" do @@ -242,8 +242,8 @@ describe Spree::Admin::ReportsController, type: :controller do { "test" => "foo", "controller" => "spree/admin/reports", "report" => {}, "action" => "products_and_inventory", "use_route" => "main_app" }, false) .and_return(report = double(:report)) - allow(report).to receive(:header).and_return [] - allow(report).to receive(:table).and_return [] + allow(report).to receive(:table_headers).and_return [] + allow(report).to receive(:table_rows).and_return [] spree_get :products_and_inventory, test: "foo" expect(assigns(:report)).to eq(report) end diff --git a/spec/lib/open_food_network/lettuce_share_report_spec.rb b/spec/lib/open_food_network/lettuce_share_report_spec.rb index 7f32110299..a8e7da5b95 100644 --- a/spec/lib/open_food_network/lettuce_share_report_spec.rb +++ b/spec/lib/open_food_network/lettuce_share_report_spec.rb @@ -2,12 +2,15 @@ require 'spec_helper' -require 'open_food_network/lettuce_share_report' +require 'open_food_network/products_and_inventory_report' module OpenFoodNetwork describe LettuceShareReport do let(:user) { create(:user) } - let(:report) { LettuceShareReport.new user, {}, true } + let(:base_report) { + ProductsAndInventoryReport.new(user, { report_subtype: 'lettuce_share' }, true) + } + let(:report) { base_report.report } let(:variant) { create(:variant) } describe "grower and method" do @@ -34,7 +37,7 @@ module OpenFoodNetwork describe "table" do it "handles no items" do - expect(report.table).to eq [] + expect(report.table_rows).to eq [] end describe "lists" do @@ -51,18 +54,18 @@ module OpenFoodNetwork } it "all items" do - allow(report).to receive(:child_variants) { - Spree::Variant.where(id: [variant, variant2, variant3]) - } - expect(report.table.count).to eq 3 + allow(base_report).to receive(:child_variants) { + Spree::Variant.where(id: [variant, variant2, variant3]) + } + expect(report.table_rows.count).to eq 3 end it "only available items" do variant.on_hand = 0 - allow(report).to receive(:child_variants) { - Spree::Variant.where(id: [variant, variant2, variant3, variant4]) - } - expect(report.table.count).to eq 3 + allow(base_report).to receive(:child_variants) { + Spree::Variant.where(id: [variant, variant2, variant3, variant4]) + } + expect(report.table_rows.count).to eq 3 end it "only available items considering overrides" do @@ -71,11 +74,13 @@ module OpenFoodNetwork # create the overrides variant2_override variant3_override - allow(report).to receive(:child_variants) { - Spree::Variant.where(id: [variant, variant2, variant3]) - } - allow(report).to receive(:params) { { distributor_id: hub.id } } - rows = report.table + allow(base_report).to receive(:child_variants) { + Spree::Variant.where(id: [variant, variant2, variant3]) + } + allow(base_report).to receive(:params) { + { distributor_id: hub.id, report_subtype: 'lettuce_share' } + } + rows = report.table_rows expect(rows.count).to eq 2 expect(rows.map{ |row| row[0] }).to include variant.product.name, variant2.product.name end diff --git a/spec/lib/open_food_network/products_and_inventory_default_report_spec.rb b/spec/lib/open_food_network/products_and_inventory_default_report_spec.rb new file mode 100644 index 0000000000..2de38c53cc --- /dev/null +++ b/spec/lib/open_food_network/products_and_inventory_default_report_spec.rb @@ -0,0 +1,261 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'open_food_network/products_and_inventory_report' + +describe OpenFoodNetwork::ProductsAndInventoryDefaultReport do + context "As a site admin" do + let(:user) do + user = create(:user) + user.spree_roles << Spree::Role.find_or_create_by!(name: 'admin') + user + end + subject do + OpenFoodNetwork::ProductsAndInventoryReport.new user, {}, true + end + + it "Should return headers" do + expect(subject.table_headers).to eq([ + "Supplier", + "Producer Suburb", + "Product", + "Product Properties", + "Taxons", + "Variant Value", + "Price", + "Group Buy Unit Quantity", + "Amount", + "SKU" + ]) + end + + it "should build a table from a list of variants" do + variant = double(:variant, sku: "sku", + full_name: "Variant Name", + count_on_hand: 10, + price: 100) + allow(variant).to receive_message_chain(:product, :supplier, :name).and_return("Supplier") + allow(variant).to receive_message_chain(:product, :supplier, :address, + :city).and_return("A city") + allow(variant).to receive_message_chain(:product, :name).and_return("Product Name") + allow(variant).to receive_message_chain(:product, + :properties).and_return [double(name: "property1"), + double(name: "property2")] + allow(variant).to receive_message_chain(:product, + :taxons).and_return [double(name: "taxon1"), + double(name: "taxon2")] + allow(variant).to receive_message_chain(:product, :group_buy_unit_size).and_return(21) + allow(subject).to receive(:variants).and_return [variant] + + expect(subject.table_rows).to eq([[ + "Supplier", + "A city", + "Product Name", + "property1, property2", + "taxon1, taxon2", + "Variant Name", + 100, + 21, + "", + "sku" + ]]) + end + + it "fetches variants for some params" do + expect(subject).to receive(:child_variants).and_return ["children"] + expect(subject).to receive(:filter).with(['children']).and_return ["filter_children"] + expect(subject.variants).to eq(["filter_children"]) + end + end + + context "As an enterprise user" do + let(:supplier) { create(:supplier_enterprise) } + let(:enterprise_user) do + user = create(:user) + user.enterprise_roles.create(enterprise: supplier) + user.spree_roles = [] + user.save! + user + end + + subject do + OpenFoodNetwork::ProductsAndInventoryReport.new enterprise_user, {}, true + end + + describe "fetching child variants" do + it "returns some variants" do + product1 = create(:simple_product, supplier: supplier) + variant1 = product1.variants.first + variant2 = create(:variant, product: product1) + + expect(subject.child_variants).to match_array [variant1, variant2] + end + + it "should only return variants managed by the user" do + product1 = create(:simple_product, supplier: create(:supplier_enterprise)) + product2 = create(:simple_product, supplier: supplier) + variant1 = product1.variants.first + variant2 = product2.variants.first + + expect(subject.child_variants).to eq([variant2]) + end + end + + describe "Filtering variants" do + let(:variants) { Spree::Variant.where(nil).joins(:product).where(is_master: false) } + + describe "based on report type" do + it "returns only variants on hand" do + product1 = create(:simple_product, supplier: supplier, on_hand: 99) + product2 = create(:simple_product, supplier: supplier, on_hand: 0) + + allow(subject).to receive(:params).and_return(report_subtype: 'inventory') + expect(subject.filter(variants)).to eq([product1.variants.first]) + end + end + it "filters to a specific supplier" do + supplier2 = create(:supplier_enterprise) + product1 = create(:simple_product, supplier: supplier) + product2 = create(:simple_product, supplier: supplier2) + + allow(subject).to receive(:params).and_return(supplier_id: supplier.id) + expect(subject.filter(variants)).to eq([product1.variants.first]) + end + it "filters to a specific distributor" do + distributor = create(:distributor_enterprise) + product1 = create(:simple_product, supplier: supplier) + product2 = create(:simple_product, supplier: supplier) + order_cycle = create(:simple_order_cycle, suppliers: [supplier], + distributors: [distributor], + variants: [product2.variants.first]) + + allow(subject).to receive(:params).and_return(distributor_id: distributor.id) + expect(subject.filter(variants)).to eq([product2.variants.first]) + end + + it "ignores variant overrides without filter" do + distributor = create(:distributor_enterprise) + product = create(:simple_product, supplier: supplier, price: 5) + variant = product.variants.first + order_cycle = create(:simple_order_cycle, suppliers: [supplier], + distributors: [distributor], + variants: [product.variants.first]) + create(:variant_override, hub: distributor, variant: variant, price: 2) + + result = subject.filter(variants) + + expect(result.first.price).to eq 5 + end + + it "considers variant overrides with distributor" do + distributor = create(:distributor_enterprise) + product = create(:simple_product, supplier: supplier, price: 5) + variant = product.variants.first + order_cycle = create(:simple_order_cycle, suppliers: [supplier], + distributors: [distributor], + variants: [product.variants.first]) + create(:variant_override, hub: distributor, variant: variant, price: 2) + + allow(subject).to receive(:params).and_return(distributor_id: distributor.id) + result = subject.filter(variants) + + expect(result.first.price).to eq 2 + end + + it "filters to a specific order cycle" do + distributor = create(:distributor_enterprise) + product1 = create(:simple_product, supplier: supplier) + product2 = create(:simple_product, supplier: supplier) + order_cycle = create(:simple_order_cycle, suppliers: [supplier], + distributors: [distributor], + variants: [product1.variants.first]) + + allow(subject).to receive(:params).and_return(order_cycle_id: order_cycle.id) + expect(subject.filter(variants)).to eq([product1.variants.first]) + end + + it "should do all the filters at once" do + # The following data ensures that this spec fails if any of the + # filters fail. It's testing the filters are not impacting each other. + distributor = create(:distributor_enterprise) + other_distributor = create(:distributor_enterprise) + other_supplier = create(:supplier_enterprise) + not_filtered_variant = create(:simple_product, supplier: supplier).variants.first + variant_filtered_by_order_cycle = create(:simple_product, + supplier: supplier).variants.first + variant_filtered_by_distributor = create(:simple_product, + supplier: supplier).variants.first + variant_filtered_by_supplier = create(:simple_product, + supplier: other_supplier).variants.first + variant_filtered_by_stock = create(:simple_product, supplier: supplier, + on_hand: 0).variants.first + + # This OC contains all products except the one that should be filtered + # by order cycle. We create a separate OC further down to proof that + # the product is passing all other filters. + order_cycle = create( + :simple_order_cycle, + suppliers: [supplier, other_supplier], + distributors: [distributor, other_distributor], + variants: [ + not_filtered_variant, + variant_filtered_by_distributor, + variant_filtered_by_supplier, + variant_filtered_by_stock + ] + ) + + # Remove the distribution of one product for one distributor but still + # sell it through the other distributor. + order_cycle.exchanges.outgoing.find_by(receiver_id: distributor.id). + exchange_variants. + find_by(variant_id: variant_filtered_by_distributor). + destroy + + # Make product available to be filtered later. See OC comment above. + create( + :simple_order_cycle, + suppliers: [supplier], + distributors: [distributor, other_distributor], + variants: [ + variant_filtered_by_order_cycle + ] + ) + + allow(subject).to receive(:params).and_return( + order_cycle_id: order_cycle.id, + supplier_id: supplier.id, + distributor_id: distributor.id, + report_subtype: 'inventory' + ) + + expect(subject.filter(variants)).to match_array [not_filtered_variant] + + # And it integrates with the ordering of the `variants` method. + expect(subject.variants).to match_array [not_filtered_variant] + end + end + + describe "fetching SKU for a variant" do + let(:variant) { create(:variant) } + let(:product) { variant.product } + + before { product.update_attribute(:sku, "Product SKU") } + + context "when the variant has an SKU set" do + before { variant.update_attribute(:sku, "Variant SKU") } + it "returns it" do + expect(subject.__send__(:sku_for, variant)).to eq "Variant SKU" + end + end + + context "when the variant has bo SKU set" do + before { variant.update_attribute(:sku, "") } + + it "returns the product's SKU" do + expect(subject.__send__(:sku_for, variant)).to eq "Product SKU" + end + end + end + end +end diff --git a/spec/lib/open_food_network/products_and_inventory_report_spec.rb b/spec/lib/open_food_network/products_and_inventory_report_spec.rb deleted file mode 100644 index 48a1ba27e1..0000000000 --- a/spec/lib/open_food_network/products_and_inventory_report_spec.rb +++ /dev/null @@ -1,259 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'open_food_network/products_and_inventory_report' - -module OpenFoodNetwork - describe ProductsAndInventoryReport do - context "As a site admin" do - let(:user) do - user = create(:user) - user.spree_roles << Spree::Role.find_or_create_by!(name: 'admin') - user - end - subject do - ProductsAndInventoryReport.new user, {}, true - end - - it "Should return headers" do - expect(subject.header).to eq([ - "Supplier", - "Producer Suburb", - "Product", - "Product Properties", - "Taxons", - "Variant Value", - "Price", - "Group Buy Unit Quantity", - "Amount", - "SKU" - ]) - end - - it "should build a table from a list of variants" do - variant = double(:variant, sku: "sku", - full_name: "Variant Name", - count_on_hand: 10, - price: 100) - allow(variant).to receive_message_chain(:product, :supplier, :name).and_return("Supplier") - allow(variant).to receive_message_chain(:product, :supplier, :address, - :city).and_return("A city") - allow(variant).to receive_message_chain(:product, :name).and_return("Product Name") - allow(variant).to receive_message_chain(:product, - :properties).and_return [double(name: "property1"), - double(name: "property2")] - allow(variant).to receive_message_chain(:product, - :taxons).and_return [double(name: "taxon1"), - double(name: "taxon2")] - allow(variant).to receive_message_chain(:product, :group_buy_unit_size).and_return(21) - allow(subject).to receive(:variants).and_return [variant] - - expect(subject.table).to eq([[ - "Supplier", - "A city", - "Product Name", - "property1, property2", - "taxon1, taxon2", - "Variant Name", - 100, - 21, - "", - "sku" - ]]) - end - - it "fetches variants for some params" do - expect(subject).to receive(:child_variants).and_return ["children"] - expect(subject).to receive(:filter).with(['children']).and_return ["filter_children"] - expect(subject.variants).to eq(["filter_children"]) - end - end - - context "As an enterprise user" do - let(:supplier) { create(:supplier_enterprise) } - let(:enterprise_user) do - user = create(:user) - user.enterprise_roles.create(enterprise: supplier) - user.spree_roles = [] - user.save! - user - end - - subject do - ProductsAndInventoryReport.new enterprise_user, {}, true - end - - describe "fetching child variants" do - it "returns some variants" do - product1 = create(:simple_product, supplier: supplier) - variant1 = product1.variants.first - variant2 = create(:variant, product: product1) - - expect(subject.child_variants).to match_array [variant1, variant2] - end - - it "should only return variants managed by the user" do - product1 = create(:simple_product, supplier: create(:supplier_enterprise)) - product2 = create(:simple_product, supplier: supplier) - variant1 = product1.variants.first - variant2 = product2.variants.first - - expect(subject.child_variants).to eq([variant2]) - end - end - - describe "Filtering variants" do - let(:variants) { Spree::Variant.where(nil).joins(:product).where(is_master: false) } - - describe "based on report type" do - it "returns only variants on hand" do - product1 = create(:simple_product, supplier: supplier, on_hand: 99) - product2 = create(:simple_product, supplier: supplier, on_hand: 0) - - allow(subject).to receive(:params).and_return(report_type: 'inventory') - expect(subject.filter(variants)).to eq([product1.variants.first]) - end - end - it "filters to a specific supplier" do - supplier2 = create(:supplier_enterprise) - product1 = create(:simple_product, supplier: supplier) - product2 = create(:simple_product, supplier: supplier2) - - allow(subject).to receive(:params).and_return(supplier_id: supplier.id) - expect(subject.filter(variants)).to eq([product1.variants.first]) - end - it "filters to a specific distributor" do - distributor = create(:distributor_enterprise) - product1 = create(:simple_product, supplier: supplier) - product2 = create(:simple_product, supplier: supplier) - order_cycle = create(:simple_order_cycle, suppliers: [supplier], - distributors: [distributor], variants: [product2.variants.first]) - - allow(subject).to receive(:params).and_return(distributor_id: distributor.id) - expect(subject.filter(variants)).to eq([product2.variants.first]) - end - - it "ignores variant overrides without filter" do - distributor = create(:distributor_enterprise) - product = create(:simple_product, supplier: supplier, price: 5) - variant = product.variants.first - order_cycle = create(:simple_order_cycle, suppliers: [supplier], - distributors: [distributor], variants: [product.variants.first]) - create(:variant_override, hub: distributor, variant: variant, price: 2) - - result = subject.filter(variants) - - expect(result.first.price).to eq 5 - end - - it "considers variant overrides with distributor" do - distributor = create(:distributor_enterprise) - product = create(:simple_product, supplier: supplier, price: 5) - variant = product.variants.first - order_cycle = create(:simple_order_cycle, suppliers: [supplier], - distributors: [distributor], variants: [product.variants.first]) - create(:variant_override, hub: distributor, variant: variant, price: 2) - - allow(subject).to receive(:params).and_return(distributor_id: distributor.id) - result = subject.filter(variants) - - expect(result.first.price).to eq 2 - end - - it "filters to a specific order cycle" do - distributor = create(:distributor_enterprise) - product1 = create(:simple_product, supplier: supplier) - product2 = create(:simple_product, supplier: supplier) - order_cycle = create(:simple_order_cycle, suppliers: [supplier], - distributors: [distributor], variants: [product1.variants.first]) - - allow(subject).to receive(:params).and_return(order_cycle_id: order_cycle.id) - expect(subject.filter(variants)).to eq([product1.variants.first]) - end - - it "should do all the filters at once" do - # The following data ensures that this spec fails if any of the - # filters fail. It's testing the filters are not impacting each other. - distributor = create(:distributor_enterprise) - other_distributor = create(:distributor_enterprise) - other_supplier = create(:supplier_enterprise) - not_filtered_variant = create(:simple_product, supplier: supplier).variants.first - variant_filtered_by_order_cycle = create(:simple_product, - supplier: supplier).variants.first - variant_filtered_by_distributor = create(:simple_product, - supplier: supplier).variants.first - variant_filtered_by_supplier = create(:simple_product, - supplier: other_supplier).variants.first - variant_filtered_by_stock = create(:simple_product, supplier: supplier, - on_hand: 0).variants.first - - # This OC contains all products except the one that should be filtered - # by order cycle. We create a separate OC further down to proof that - # the product is passing all other filters. - order_cycle = create( - :simple_order_cycle, - suppliers: [supplier, other_supplier], - distributors: [distributor, other_distributor], - variants: [ - not_filtered_variant, - variant_filtered_by_distributor, - variant_filtered_by_supplier, - variant_filtered_by_stock - ] - ) - - # Remove the distribution of one product for one distributor but still - # sell it through the other distributor. - order_cycle.exchanges.outgoing.find_by(receiver_id: distributor.id). - exchange_variants. - find_by(variant_id: variant_filtered_by_distributor). - destroy - - # Make product available to be filtered later. See OC comment above. - create( - :simple_order_cycle, - suppliers: [supplier], - distributors: [distributor, other_distributor], - variants: [ - variant_filtered_by_order_cycle - ] - ) - - allow(subject).to receive(:params).and_return( - order_cycle_id: order_cycle.id, - supplier_id: supplier.id, - distributor_id: distributor.id, - report_type: 'inventory' - ) - - expect(subject.filter(variants)).to match_array [not_filtered_variant] - - # And it integrates with the ordering of the `variants` method. - expect(subject.variants).to match_array [not_filtered_variant] - end - end - - describe "fetching SKU for a variant" do - let(:variant) { create(:variant) } - let(:product) { variant.product } - - before { product.update_attribute(:sku, "Product SKU") } - - context "when the variant has an SKU set" do - before { variant.update_attribute(:sku, "Variant SKU") } - it "returns it" do - expect(subject.send(:sku_for, variant)).to eq "Variant SKU" - end - end - - context "when the variant has bo SKU set" do - before { variant.update_attribute(:sku, "") } - - it "returns the product's SKU" do - expect(subject.send(:sku_for, variant)).to eq "Product SKU" - end - end - end - end - end -end diff --git a/spec/system/admin/reports/packing_report_spec.rb b/spec/system/admin/reports/packing_report_spec.rb index 421144b223..efe64b1901 100644 --- a/spec/system/admin/reports/packing_report_spec.rb +++ b/spec/system/admin/reports/packing_report_spec.rb @@ -46,7 +46,7 @@ describe "Packing Reports", js: true do click_link "Pack By Customer" fill_in 'q_completed_at_gt', with: '2013-04-25 13:00:00' fill_in 'q_completed_at_lt', with: '2013-04-25 16:00:00' - click_button 'Search' + click_button 'Go' rows = find("table.report__table").all("thead tr") table = rows.map { |r| r.all("th").map { |c| c.text.strip } } @@ -59,7 +59,7 @@ describe "Packing Reports", js: true do it "sorts alphabetically" do click_link "Pack By Customer" - click_button 'Search' + click_button 'Go' rows = find("table.report__table").all("tr") table = rows.map { |r| r.all("th,td").map { |c| c.text.strip }[3] } @@ -79,7 +79,7 @@ describe "Packing Reports", js: true do click_link "Pack By Supplier" fill_in 'q_completed_at_gt', with: '2013-04-25 13:00:00' fill_in 'q_completed_at_lt', with: '2013-04-25 16:00:00' - click_button 'Search' + click_button 'Go' rows = find("table.report__table").all("thead tr") table = rows.map { |r| r.all("th").map { |c| c.text.strip } } diff --git a/spec/system/admin/reports_spec.rb b/spec/system/admin/reports_spec.rb index d2064eae8f..2ee3e41bcf 100644 --- a/spec/system/admin/reports_spec.rb +++ b/spec/system/admin/reports_spec.rb @@ -94,7 +94,7 @@ describe ' it "orders and distributors report" do login_as_admin_and_visit spree.admin_reports_path click_link 'Orders And Distributors' - click_button 'Search' + click_button 'Go' expect(page).to have_content 'ORDER DATE' end @@ -102,7 +102,7 @@ describe ' it "payments reports" do login_as_admin_and_visit spree.admin_reports_path click_link 'Payment Reports' - click_button 'Search' + click_button 'Go' expect(page).to have_content 'PAYMENT STATE' end @@ -176,7 +176,7 @@ describe ' # When I filter to just one distributor select user1.enterprises.first.name, from: 'q_distributor_id_eq' - click_button 'Search' + click_button 'Go' # Then I should see the relevant order expect(page).to have_content order1.number.to_s @@ -243,7 +243,7 @@ describe ' pick_datetime "#q_completed_at_lt", datetime_end select 'Order Cycle Customer Totals', from: 'report_subtype' - click_button 'Search' + click_button 'Go' # Then I should see the rows for the first order but not the second expect(all('table.report__table tbody tr').count).to eq(4) # Two rows per order end From 41c11baa3faa2b47e23b1d1ecc1dac0c759f1a40 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Fri, 25 Mar 2022 09:04:15 +0000 Subject: [PATCH 14/54] Report Refactor 1: Users & Entreprises --- .../spree/admin/reports_controller.rb | 4 +--- .../filters/_users_and_enterprises.html.haml | 7 +++++++ .../reports/users_and_enterprises.html.haml | 21 ------------------- .../users_and_enterprises_report.rb | 7 ++++--- .../spree/admin/reports_controller_spec.rb | 4 ++-- .../users_and_enterprises_report_spec.rb | 12 +++++------ spec/system/admin/reports_spec.rb | 4 ++-- 7 files changed, 22 insertions(+), 37 deletions(-) create mode 100644 app/views/spree/admin/reports/filters/_users_and_enterprises.html.haml delete mode 100644 app/views/spree/admin/reports/users_and_enterprises.html.haml diff --git a/app/controllers/spree/admin/reports_controller.rb b/app/controllers/spree/admin/reports_controller.rb index 7c9d415dce..159dd390c9 100644 --- a/app/controllers/spree/admin/reports_controller.rb +++ b/app/controllers/spree/admin/reports_controller.rb @@ -90,9 +90,7 @@ module Spree end def users_and_enterprises - @report = OpenFoodNetwork::UsersAndEnterprisesReport.new raw_params, render_content? - render_report(@report.header, @report.table, params[:csv], - "users_and_enterprises_#{timestamp}.csv") + render_report2 end def xero_invoices diff --git a/app/views/spree/admin/reports/filters/_users_and_enterprises.html.haml b/app/views/spree/admin/reports/filters/_users_and_enterprises.html.haml new file mode 100644 index 0000000000..f8e14cf75d --- /dev/null +++ b/app/views/spree/admin/reports/filters/_users_and_enterprises.html.haml @@ -0,0 +1,7 @@ +.row + .alpha.two.columns= label_tag nil, t(:report_enterprises) + .omega.fourteen.columns= select_tag(:enterprise_id_in, options_from_collection_for_select(Enterprise.all, :id, :name, params[:enterprise_id_in]), {class: "select2 fullwidth", multiple: true}) + +.row + .alpha.two.columns= label_tag nil, t(:report_users) + .omega.fourteen.columns= select_tag(:user_id_in, options_from_collection_for_select(Spree::User.all, :id, :email, params[:user_id_in]), {class: "select2 fullwidth", multiple: true}) \ No newline at end of file diff --git a/app/views/spree/admin/reports/users_and_enterprises.html.haml b/app/views/spree/admin/reports/users_and_enterprises.html.haml deleted file mode 100644 index 54adef9bb7..0000000000 --- a/app/views/spree/admin/reports/users_and_enterprises.html.haml +++ /dev/null @@ -1,21 +0,0 @@ -= form_tag spree.users_and_enterprises_admin_reports_url do |f| - .row - .alpha.two.columns= label_tag nil, t(:report_enterprises) - .omega.fourteen.columns= select_tag(:enterprise_id_in, options_from_collection_for_select(Enterprise.all, :id, :name, params[:enterprise_id_in]&.split(",")), {class: "select2 fullwidth", multiple: true}) - - .row - .alpha.two.columns= label_tag nil, t(:report_users) - .omega.fourteen.columns= select_tag(:user_id_in, options_from_collection_for_select(Spree::User.all, :id, :email, params[:user_id_in]&.split(",")), {class: "select2 fullwidth", multiple: true}) - - -# Might need this later if we add different kinds of reports - -# .row - -# .alpha.two.columns= label_tag nil, "Report Type: " - -# .omega.fourteen.columns= select_tag(:report_type, options_for_select(@report_types, params[:report_type])) - - .row - = check_box_tag :csv - = label_tag :csv, t(:report_customers_csv) - .row - = button t(:search) - -= render "table", id: "users_and_enterprises", msg_option: t(:search) diff --git a/lib/open_food_network/users_and_enterprises_report.rb b/lib/open_food_network/users_and_enterprises_report.rb index fc13095534..e3a3e2a619 100644 --- a/lib/open_food_network/users_and_enterprises_report.rb +++ b/lib/open_food_network/users_and_enterprises_report.rb @@ -4,7 +4,8 @@ module OpenFoodNetwork class UsersAndEnterprisesReport attr_reader :params - def initialize(params = {}, compile_table = false) + def initialize(user, params = {}, compile_table = false) + @user = user @params = params @compile_table = compile_table @@ -15,7 +16,7 @@ module OpenFoodNetwork @params[:user_id_in] = @params[:user_id_in].join(',') if @params[:user_id_in].is_a? Array end - def header + def table_headers [ I18n.t(:report_header_user), I18n.t(:report_header_relationship), @@ -27,7 +28,7 @@ module OpenFoodNetwork ] end - def table + def table_rows return [] unless @compile_table users_and_enterprises.map do |uae| diff --git a/spec/controllers/spree/admin/reports_controller_spec.rb b/spec/controllers/spree/admin/reports_controller_spec.rb index 063ce9d770..48af5f02fe 100644 --- a/spec/controllers/spree/admin/reports_controller_spec.rb +++ b/spec/controllers/spree/admin/reports_controller_spec.rb @@ -328,12 +328,12 @@ describe Spree::Admin::ReportsController, type: :controller do it "shows report search forms" do spree_get :users_and_enterprises - expect(assigns(:report).table).to eq [] + expect(assigns(:report).table_rows).to eq [] end it "shows report data" do spree_post :users_and_enterprises, q: {} - expect(assigns(:report).table.empty?).to be false + expect(assigns(:report).table_rows.empty?).to be false end end diff --git a/spec/lib/open_food_network/users_and_enterprises_report_spec.rb b/spec/lib/open_food_network/users_and_enterprises_report_spec.rb index 7b8f827cb0..ca9b9f4a23 100644 --- a/spec/lib/open_food_network/users_and_enterprises_report_spec.rb +++ b/spec/lib/open_food_network/users_and_enterprises_report_spec.rb @@ -8,7 +8,7 @@ module OpenFoodNetwork describe "users_and_enterprises" do let!(:owners_and_enterprises) { double(:owners_and_enterprises) } let!(:managers_and_enterprises) { double(:managers_and_enterprises) } - let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new({}, true) } + let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new(nil, {}, true) } before do allow(subject).to receive(:owners_and_enterprises) { owners_and_enterprises } @@ -25,7 +25,7 @@ module OpenFoodNetwork end describe "sorting results" do - let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new({}, true) } + let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new(nil, {}, true) } it "sorts by creation date" do uae_mock = [ @@ -69,7 +69,7 @@ module OpenFoodNetwork describe "for owners and enterprises" do describe "by enterprise id" do let!(:params) { { enterprise_id_in: [enterprise1.id.to_s] } } - let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new params, true } + let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new nil, params, true } it "excludes enterprises that are not explicitly requested" do results = subject.owners_and_enterprises.to_a.map{ |oae| oae["name"] } @@ -80,7 +80,7 @@ module OpenFoodNetwork describe "by user id" do let!(:params) { { user_id_in: [enterprise1.owner.id.to_s] } } - let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new params, true } + let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new nil, params, true } it "excludes enterprises that are not explicitly requested" do results = subject.owners_and_enterprises.to_a.map{ |oae| oae["name"] } @@ -93,7 +93,7 @@ module OpenFoodNetwork describe "for managers and enterprises" do describe "by enterprise id" do let!(:params) { { enterprise_id_in: [enterprise1.id.to_s] } } - let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new params, true } + let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new nil, params, true } it "excludes enterprises that are not explicitly requested" do results = subject.managers_and_enterprises.to_a.map{ |mae| mae["name"] } @@ -106,7 +106,7 @@ module OpenFoodNetwork let!(:manager1) { create(:user) } let!(:manager2) { create(:user) } let!(:params) { { user_id_in: [manager1.id.to_s] } } - let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new params, true } + let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new nil, params, true } before do enterprise1.enterprise_roles.build(user: manager1).save diff --git a/spec/system/admin/reports_spec.rb b/spec/system/admin/reports_spec.rb index 2ee3e41bcf..98c777ef7f 100644 --- a/spec/system/admin/reports_spec.rb +++ b/spec/system/admin/reports_spec.rb @@ -347,7 +347,7 @@ describe ' end it "shows users and enterprises report" do - click_button "Search" + click_button "Go" rows = find("table.report__table").all("tr") table = rows.map { |r| r.all("th,td").map { |c| c.text.strip }[0..2] } @@ -368,7 +368,7 @@ describe ' select enterprise3.name, from: "enterprise_id_in" select enterprise1.owner.email, from: "user_id_in" - click_button "Search" + click_button "Go" rows = find("table.report__table").all("tr") table = rows.map { |r| r.all("th,td").map { |c| c.text.strip }[0..2] } From 4a99a7d1deeedd1365c38c16b3b7bb001ee413f8 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Fri, 25 Mar 2022 09:18:52 +0000 Subject: [PATCH 15/54] Report Refactor 1: Xero Invoices --- .../spree/admin/reports_controller.rb | 7 +--- app/helpers/spree/reports_helper.rb | 5 --- .../reports/filters/_xero_invoices.html.haml | 23 ++++++++++++ .../admin/reports/xero_invoices.html.haml | 35 ------------------- lib/open_food_network/reports/list.rb | 8 ++++- lib/open_food_network/xero_invoices_report.rb | 8 ++--- .../xero_invoices_report_spec.rb | 2 +- spec/system/admin/reports_spec.rb | 8 ++--- 8 files changed, 40 insertions(+), 56 deletions(-) create mode 100644 app/views/spree/admin/reports/filters/_xero_invoices.html.haml delete mode 100644 app/views/spree/admin/reports/xero_invoices.html.haml diff --git a/app/controllers/spree/admin/reports_controller.rb b/app/controllers/spree/admin/reports_controller.rb index 159dd390c9..dea9c5c45d 100644 --- a/app/controllers/spree/admin/reports_controller.rb +++ b/app/controllers/spree/admin/reports_controller.rb @@ -94,15 +94,10 @@ module Spree end def xero_invoices - raw_params[:q] ||= {} - @distributors = my_distributors @order_cycles = my_order_cycles - @report = OpenFoodNetwork::XeroInvoicesReport.new(spree_current_user, - raw_params, - render_content?) - render_report(@report.header, @report.table, params[:csv], "xero_invoices_#{timestamp}.csv") + render_report2 end private diff --git a/app/helpers/spree/reports_helper.rb b/app/helpers/spree/reports_helper.rb index 80bafa4c5a..05b3fa7af6 100644 --- a/app/helpers/spree/reports_helper.rb +++ b/app/helpers/spree/reports_helper.rb @@ -21,11 +21,6 @@ module Spree end.uniq end - def xero_report_types - [[I18n.t(:summary), 'summary'], - [I18n.t(:detailed), 'detailed']] - end - def currency_symbol Spree::Money.currency_symbol end diff --git a/app/views/spree/admin/reports/filters/_xero_invoices.html.haml b/app/views/spree/admin/reports/filters/_xero_invoices.html.haml new file mode 100644 index 0000000000..d6425ed0bd --- /dev/null +++ b/app/views/spree/admin/reports/filters/_xero_invoices.html.haml @@ -0,0 +1,23 @@ += render 'spree/admin/reports/date_range_form', f: f + +.row + .two.columns.alpha= label_tag nil, t(:report_hubs) + .fourteen.columns.omega= f.collection_select(:distributor_id_eq, @distributors, :id, :name, {:include_blank => 'All'}, {:class => "select2 fullwidth light"}) +.row + .two.columns.alpha= label_tag nil, t(:report_order_cycle) + .fourteen.columns.omega= f.select(:order_cycle_id_eq, + options_for_select(report_order_cycle_options(@order_cycles), params.dig(:q, :order_cycle_id_eq)), + {:include_blank => true}, {:class => "select2 fullwidth light"}) + +.row + .two.columns.alpha= label_tag :initial_invoice_number, t(:initial_invoice_number) + .fourteen.columns.omega= text_field_tag :initial_invoice_number, params[:initial_invoice_number] +.row + .two.columns.alpha= label_tag :invoice_date, t(:invoice_date) + .fourteen.columns.omega= text_field_tag :invoice_date, params[:invoice_date], class: 'datetimepicker' +.row + .two.columns.alpha= label_tag :due_date, t(:due_date) + .fourteen.columns.omega= text_field_tag :due_date, params[:due_date], class: 'datetimepicker' +.row + .two.columns.alpha= label_tag :account_code, t(:account_code) + .fourteen.columns.omega= text_field_tag :account_code, params[:account_code] diff --git a/app/views/spree/admin/reports/xero_invoices.html.haml b/app/views/spree/admin/reports/xero_invoices.html.haml deleted file mode 100644 index b445e1b230..0000000000 --- a/app/views/spree/admin/reports/xero_invoices.html.haml +++ /dev/null @@ -1,35 +0,0 @@ -= form_for @report.search, url: spree.xero_invoices_admin_reports_path do |f| - = render 'date_range_form', f: f - - .row - .four.columns.alpha= label_tag :report_type, t(:report_type) - .four.columns.omega= select_tag :report_type, options_for_select(xero_report_types, params[:report_type]), {include_blank: false, class: "select2 fullwidth"} - .row - .four.columns.alpha= label_tag nil, t(:report_hubs) - .four.columns.omega= f.collection_select(:distributor_id_eq, @distributors, :id, :name, {:include_blank => 'All'}, {:class => "select2 fullwidth"}) - .row - .four.columns.alpha= label_tag nil, t(:report_order_cycle) - .four.columns.omega= f.select(:order_cycle_id_eq, - options_for_select(report_order_cycle_options(@order_cycles), params.dig(:q, :order_cycle_id_eq)), - {:include_blank => true}, {:class => "select2 fullwidth"}) - - .row - .four.columns.alpha= label_tag :initial_invoice_number, t(:initial_invoice_number) - .twelve.columns.omega= text_field_tag :initial_invoice_number, params[:initial_invoice_number] - .row - .four.columns.alpha= label_tag :invoice_date, t(:invoice_date) - .twelve.columns.omega= text_field_tag :invoice_date, params[:invoice_date], class: 'datetimepicker' - .row - .four.columns.alpha= label_tag :due_date, t(:due_date) - .twelve.columns.omega= text_field_tag :due_date, params[:due_date], class: 'datetimepicker' - .row - .four.columns.alpha= label_tag :account_code, t(:account_code) - .twelve.columns.omega= text_field_tag :account_code, params[:account_code] - .row - .four.columns.alpha= label_tag :csv, t(:report_customers_csv) - .twelve.columns.omega= check_box_tag :csv - .row - .four.columns.alpha= button t(:search) - - -= render "table", id: "listing_invoices", msg_option: t(:search) diff --git a/lib/open_food_network/reports/list.rb b/lib/open_food_network/reports/list.rb index bfbc625488..96bae561fc 100644 --- a/lib/open_food_network/reports/list.rb +++ b/lib/open_food_network/reports/list.rb @@ -15,7 +15,8 @@ module OpenFoodNetwork enterprise_fee_summary: enterprise_fee_summary_report_types, order_cycle_management: order_cycle_management_report_types, sales_tax: sales_tax_report_types, - packing: packing_report_types + packing: packing_report_types, + xero_invoices: xero_report_types } end @@ -73,6 +74,11 @@ module OpenFoodNetwork ] end + def xero_report_types + [[I18n.t(:summary), 'summary'], + [I18n.t(:detailed), 'detailed']] + end + def i18n_translate(key) I18n.t(key, scope: "admin.reports") end diff --git a/lib/open_food_network/xero_invoices_report.rb b/lib/open_food_network/xero_invoices_report.rb index a874bd6047..da36a04860 100644 --- a/lib/open_food_network/xero_invoices_report.rb +++ b/lib/open_food_network/xero_invoices_report.rb @@ -8,14 +8,14 @@ module OpenFoodNetwork @opts = opts. symbolize_keys. reject { |_k, v| v.blank? }. - reverse_merge( report_type: 'summary', + reverse_merge( report_subtype: 'summary', invoice_date: Time.zone.today, due_date: Time.zone.today + 1.month, account_code: 'food sales' ) @compile_table = compile_table end - def header + def table_headers # NOTE: These are NOT to be translated, they need to be in this exact format to work with Xero %w(*ContactName EmailAddress POAddressLine1 POAddressLine2 POAddressLine3 POAddressLine4 POCity PORegion POPostalCode POCountry *InvoiceNumber Reference *InvoiceDate *DueDate InventoryItemCode *Description *Quantity *UnitAmount Discount *AccountCode *TaxType TrackingName1 TrackingOption1 TrackingName2 TrackingOption2 Currency BrandingTheme Paid?) @@ -30,7 +30,7 @@ module OpenFoodNetwork search.result.reorder('id DESC') end - def table + def table_rows return [] unless @compile_table rows = [] @@ -225,7 +225,7 @@ module OpenFoodNetwork end def detail? - @opts[:report_type] == 'detailed' + @opts[:report_subtype] == 'detailed' end def tax_type(taxable) diff --git a/spec/lib/open_food_network/xero_invoices_report_spec.rb b/spec/lib/open_food_network/xero_invoices_report_spec.rb index bcc77347c2..5235edd972 100644 --- a/spec/lib/open_food_network/xero_invoices_report_spec.rb +++ b/spec/lib/open_food_network/xero_invoices_report_spec.rb @@ -21,7 +21,7 @@ module OpenFoodNetwork expect(report.instance_variable_get(:@opts)).to eq( invoice_date: Date.civil(2015, 5, 5), due_date: Date.civil(2015, 6, 5), account_code: 'food sales', - report_type: 'summary' ) + report_subtype: 'summary' ) end end diff --git a/spec/system/admin/reports_spec.rb b/spec/system/admin/reports_spec.rb index 98c777ef7f..3bd13bc8fe 100644 --- a/spec/system/admin/reports_spec.rb +++ b/spec/system/admin/reports_spec.rb @@ -480,7 +480,7 @@ describe ' end it "shows Xero invoices report" do - click_button "Search" + click_button "Go" expect(xero_invoice_table).to match_table [ xero_invoice_header, xero_invoice_summary_row('Total untaxable produce (no tax)', 12.54, @@ -506,7 +506,7 @@ describe ' pick_datetime '#due_date', Date.new(2021, 3, 12) fill_in 'account_code', with: 'abc123' - click_button 'Search' + click_button 'Go' opts = { invoice_number: '5', invoice_date: '2021-02-12 00:00', due_date: '2021-03-12 00:00', account_code: 'abc123' } @@ -531,8 +531,8 @@ describe ' end it "generates a detailed report" do - select 'Detailed', from: 'report_type' - click_button 'Search' + select 'Detailed', from: 'report_subtype' + click_button 'Go' opts = {} From e9513f6172797e5f5309660e43443b330f541e8c Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 27 Mar 2022 10:27:37 +0000 Subject: [PATCH 16/54] Report Refactor 1: Enterprise Fee Summary Disable enterprise fee summary spec for now Will need to write them again --- .rubocop_todo.yml | 18 +- .../spree/admin/reports_controller.rb | 34 +- .../reports/filters/_bulk_coop.html.haml | 6 + .../filters/_enterprise_fee_summary.html.haml | 31 + config/locales/en.yml | 19 +- config/routes/spree.rb | 1 + .../reports/bulk_coop_controller.rb | 78 -- .../enterprise_fee_summaries_controller.rb | 66 -- .../reports/bulk_coop/authorizer.rb | 13 - .../bulk_coop/bulk_coop_allocation_report.rb | 67 -- .../reports/bulk_coop/bulk_coop_report.rb | 323 -------- .../bulk_coop/bulk_coop_supplier_report.rb | 65 -- .../reports/bulk_coop/parameters.rb | 45 -- .../reports/bulk_coop/permissions.rb | 13 - .../bulk_coop/renderers/csv_renderer.rb | 32 - .../bulk_coop/renderers/html_renderer.rb | 22 - .../reports/bulk_coop/report_service.rb | 29 - .../enterprise_fee_summary_report.rb | 98 +++ .../enterprise_fee_summary/parameters.rb | 4 +- .../enterprise_fee_summary/permissions.rb | 8 +- .../renderers/csv_renderer.rb | 66 -- .../renderers/html_renderer.rb | 53 -- .../enterprise_fee_summary/report_service.rb | 49 -- .../reports/enterprise_fee_summary/scope.rb | 2 - .../app/services/reports/permissions.rb | 11 - .../app/services/reports/renderers/base.rb | 13 - .../reports/_report.html.haml | 19 - .../reports/bulk_coop/_filters.html.haml | 34 - .../reports/bulk_coop/_report.html.haml | 20 - .../reports/bulk_coop/create.html.haml | 2 - .../reports/bulk_coop/new.html.haml | 1 - .../_filters.html.haml | 52 -- .../enterprise_fee_summaries/create.html.haml | 2 - .../enterprise_fee_summaries/new.html.haml | 1 - .../reports/bulk_coop_controller_spec.rb | 103 --- ...nterprise_fee_summaries_controller_spec.rb | 89 --- .../reports/bulk_coop_spec.rb | 75 -- .../reports/enterprise_fee_summaries_spec.rb | 258 +++---- .../enterprise_fee_summary_report_spec.rb | 729 ++++++++++++++++++ .../renderers/csv_renderer_spec.rb | 156 ++-- .../renderers/html_renderer_spec.rb | 114 +-- .../report_service_spec.rb | 729 ------------------ .../bulk_coop_allocation_report.rb | 63 ++ lib/open_food_network/bulk_coop_report.rb | 320 ++++++++ .../bulk_coop_supplier_report.rb | 61 ++ lib/open_food_network/reports/list.rb | 16 +- .../bulk_coop_report_spec.rb | 23 +- spec/support/matchers/table_matchers.rb | 4 +- spec/system/admin/reports_spec.rb | 95 ++- 49 files changed, 1723 insertions(+), 2409 deletions(-) create mode 100644 app/views/spree/admin/reports/filters/_bulk_coop.html.haml create mode 100644 app/views/spree/admin/reports/filters/_enterprise_fee_summary.html.haml delete mode 100644 engines/order_management/app/controllers/order_management/reports/bulk_coop_controller.rb delete mode 100644 engines/order_management/app/controllers/order_management/reports/enterprise_fee_summaries_controller.rb delete mode 100644 engines/order_management/app/services/order_management/reports/bulk_coop/authorizer.rb delete mode 100644 engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_allocation_report.rb delete mode 100644 engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_report.rb delete mode 100644 engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_supplier_report.rb delete mode 100644 engines/order_management/app/services/order_management/reports/bulk_coop/parameters.rb delete mode 100644 engines/order_management/app/services/order_management/reports/bulk_coop/permissions.rb delete mode 100644 engines/order_management/app/services/order_management/reports/bulk_coop/renderers/csv_renderer.rb delete mode 100644 engines/order_management/app/services/order_management/reports/bulk_coop/renderers/html_renderer.rb delete mode 100644 engines/order_management/app/services/order_management/reports/bulk_coop/report_service.rb create mode 100644 engines/order_management/app/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb delete mode 100644 engines/order_management/app/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb delete mode 100644 engines/order_management/app/services/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb delete mode 100644 engines/order_management/app/services/order_management/reports/enterprise_fee_summary/report_service.rb delete mode 100644 engines/order_management/app/services/reports/permissions.rb delete mode 100644 engines/order_management/app/services/reports/renderers/base.rb delete mode 100644 engines/order_management/app/views/order_management/reports/_report.html.haml delete mode 100644 engines/order_management/app/views/order_management/reports/bulk_coop/_filters.html.haml delete mode 100644 engines/order_management/app/views/order_management/reports/bulk_coop/_report.html.haml delete mode 100644 engines/order_management/app/views/order_management/reports/bulk_coop/create.html.haml delete mode 100644 engines/order_management/app/views/order_management/reports/bulk_coop/new.html.haml delete mode 100644 engines/order_management/app/views/order_management/reports/enterprise_fee_summaries/_filters.html.haml delete mode 100644 engines/order_management/app/views/order_management/reports/enterprise_fee_summaries/create.html.haml delete mode 100644 engines/order_management/app/views/order_management/reports/enterprise_fee_summaries/new.html.haml delete mode 100644 engines/order_management/spec/controllers/order_management/reports/bulk_coop_controller_spec.rb delete mode 100644 engines/order_management/spec/controllers/order_management/reports/enterprise_fee_summaries_controller_spec.rb delete mode 100644 engines/order_management/spec/features/order_management/reports/bulk_coop_spec.rb create mode 100644 engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report_spec.rb delete mode 100644 engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb create mode 100644 lib/open_food_network/bulk_coop_allocation_report.rb create mode 100644 lib/open_food_network/bulk_coop_report.rb create mode 100644 lib/open_food_network/bulk_coop_supplier_report.rb rename {engines/order_management/spec/services/order_management/reports/bulk_coop => spec/lib/open_food_network}/bulk_coop_report_spec.rb (86%) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 3f81b750d4..777eddf4c2 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -108,7 +108,7 @@ Layout/LineLength: - 'app/services/order_syncer.rb' - 'app/services/products_renderer.rb' - 'app/services/variant_units/variant_and_line_item_naming.rb' - - 'engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_report.rb' + - 'lib/open_food_network/bulk_coop_report.rb' - 'engines/order_management/app/services/order_management/subscriptions/validator.rb' - 'engines/order_management/spec/services/order_management/order/updater_spec.rb' - 'engines/web/app/helpers/web/cookies_policy_helper.rb' @@ -419,7 +419,7 @@ Metrics/AbcSize: - 'app/models/spree/order/checkout.rb' - 'app/models/spree/preferences/preferable_class_methods.rb' - 'app/models/spree/return_authorization.rb' - - 'engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_report.rb' + - 'lib/open_food_network/bulk_coop_report.rb' - 'lib/discourse/single_sign_on.rb' - 'lib/open_food_network/customers_report.rb' - 'lib/open_food_network/group_buy_report.rb' @@ -518,7 +518,7 @@ Metrics/ClassLength: - 'app/services/cart_service.rb' - 'app/services/order_syncer.rb' - 'engines/order_management/app/services/order_management/order/updater.rb' - - 'engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_report.rb' + - 'lib/open_food_network/bulk_coop_report.rb' - 'engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb' - 'lib/open_food_network/enterprise_fee_calculator.rb' - 'lib/open_food_network/order_cycle_form_applicator.rb' @@ -528,6 +528,7 @@ Metrics/ClassLength: - 'lib/open_food_network/permissions.rb' - 'lib/open_food_network/users_and_enterprises_report.rb' - 'lib/open_food_network/xero_invoices_report.rb' + - 'lib/open_food_network/bulk_coop_report.rb' # Offense count: 40 # Configuration parameters: IgnoredMethods, Max. @@ -555,7 +556,7 @@ Metrics/CyclomaticComplexity: - 'app/models/spree/tax_rate.rb' - 'app/models/spree/variant.rb' - 'app/models/spree/zone.rb' - - 'engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_report.rb' + - 'lib/open_food_network/bulk_coop_report.rb' - 'lib/discourse/single_sign_on.rb' - 'lib/open_food_network/customers_report.rb' - 'lib/open_food_network/enterprise_issue_validator.rb' @@ -584,7 +585,7 @@ Metrics/MethodLength: - 'app/models/spree/order/checkout.rb' - 'app/models/spree/payment/processing.rb' - 'app/models/spree/preferences/preferable_class_methods.rb' - - 'engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_report.rb' + - 'lib/open_food_network/bulk_coop_report.rb' - 'engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb' - 'lib/discourse/single_sign_on.rb' - 'lib/open_food_network/order_cycle_form_applicator.rb' @@ -669,7 +670,7 @@ Metrics/PerceivedComplexity: - 'app/models/enterprise_relationship.rb' - 'app/models/spree/ability.rb' - 'app/models/spree/order/checkout.rb' - - 'engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_report.rb' + - 'lib/open_food_network/bulk_coop_report.rb' - 'lib/open_food_network/group_buy_report.rb' - 'lib/open_food_network/payments_report.rb' @@ -1132,7 +1133,7 @@ Style/OptionalBooleanParameter: - 'app/models/spree/order_contents.rb' - 'app/models/spree/preferences/file_configuration.rb' - 'app/models/spree/shipment.rb' - - 'engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_report.rb' + - 'lib/open_food_network/bulk_coop_report.rb' - 'engines/order_management/app/services/order_management/stock/estimator.rb' - 'lib/open_food_network/customers_report.rb' - 'lib/open_food_network/orders_and_distributors_report.rb' @@ -1142,6 +1143,7 @@ Style/OptionalBooleanParameter: - 'lib/open_food_network/products_and_inventory_report.rb' - 'lib/open_food_network/users_and_enterprises_report.rb' - 'lib/open_food_network/xero_invoices_report.rb' + - 'lib/open_food_network/bulk_coop_report.rb' - 'lib/spree/core/controller_helpers/order.rb' - 'lib/spree/core/delegate_belongs_to.rb' - 'spec/support/request/web_helper.rb' @@ -1235,7 +1237,7 @@ Style/StringConcatenation: - 'app/serializers/api/enterprise_shopfront_list_serializer.rb' - 'app/services/embedded_page_service.rb' - 'app/services/products_renderer.rb' - - 'engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_report.rb' + - 'lib/open_food_network/bulk_coop_report.rb' - 'lib/open_food_network/orders_and_fulfillment_report/customer_totals_report.rb' - 'lib/spree/api/controller_setup.rb' - 'lib/spree/core/environment_extension.rb' diff --git a/app/controllers/spree/admin/reports_controller.rb b/app/controllers/spree/admin/reports_controller.rb index dea9c5c45d..43693ca1d4 100644 --- a/app/controllers/spree/admin/reports_controller.rb +++ b/app/controllers/spree/admin/reports_controller.rb @@ -15,6 +15,7 @@ require 'open_food_network/sales_tax_report' require 'open_food_network/xero_invoices_report' require 'open_food_network/payments_report' require 'open_food_network/orders_and_fulfillment_report' +require 'open_food_network/bulk_coop_report' module Spree module Admin @@ -23,11 +24,6 @@ module Spree include ReportsActions helper ::ReportsHelper - ORDER_MANAGEMENT_ENGINE_REPORTS = [ - :bulk_coop, - :enterprise_fee_summary - ].freeze - helper_method :render_content? before_action :cache_search_state @@ -100,6 +96,17 @@ module Spree render_report2 end + def bulk_coop + @distributors = my_distributors + @report_message = I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") + render_report2 + end + + def enterprise_fee_summary + @report_message = I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") + render_report2 + end + private def model_class @@ -139,7 +146,11 @@ module Spree def render_report2 @report_subtypes = report_types[action_name.to_sym] @report_subtype = params[:report_subtype] - klass = "OpenFoodNetwork::#{action_name.camelize}Report".constantize + klass = if action_name == 'enterprise_fee_summary' + OrderManagement::Reports::EnterpriseFeeSummary::EnterpriseFeeSummaryReport + else + "OpenFoodNetwork::#{action_name.camelize}Report".constantize + end @report = klass.new spree_current_user, raw_params, render_content? if report_format.present? data = Reporting::ReportRenderer.new(@report).public_send("to_#{report_format}") @@ -235,20 +246,11 @@ module Spree end def url_for_report(report) - if report_in_order_management_engine?(report) - main_app.public_send("new_order_management_reports_#{report}_url".to_sym) - else - spree.public_send("#{report}_admin_reports_url".to_sym) - end + spree.public_send("#{report}_admin_reports_url".to_sym) rescue NoMethodError main_app.admin_reports_url(report_type: report) end - # List of reports that have been moved to the Order Management engine - def report_in_order_management_engine?(report) - ORDER_MANAGEMENT_ENGINE_REPORTS.include?(report) - end - def timestamp Time.zone.now.strftime("%Y%m%d") end diff --git a/app/views/spree/admin/reports/filters/_bulk_coop.html.haml b/app/views/spree/admin/reports/filters/_bulk_coop.html.haml new file mode 100644 index 0000000000..f9ba0eb4f9 --- /dev/null +++ b/app/views/spree/admin/reports/filters/_bulk_coop.html.haml @@ -0,0 +1,6 @@ += render 'spree/admin/reports/date_range_form', f: f + +.row + .alpha.two.columns= label_tag nil, t(:report_hubs) + .omega.fourteen.columns + = f.collection_select(:distributor_id_in, @distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) diff --git a/app/views/spree/admin/reports/filters/_enterprise_fee_summary.html.haml b/app/views/spree/admin/reports/filters/_enterprise_fee_summary.html.haml new file mode 100644 index 0000000000..c64a55823c --- /dev/null +++ b/app/views/spree/admin/reports/filters/_enterprise_fee_summary.html.haml @@ -0,0 +1,31 @@ += render 'spree/admin/reports/date_range_form', f: f + +.row + .alpha.two.columns= label_tag nil, t(:report_hubs) + .omega.fourteen.columns + = collection_select(:q, :distributor_ids, @report.permissions.allowed_distributors, :id, :name, {selected: params.dig(:q, :distributor_ids)}, {class: "select2 fullwidth", multiple: true}) + +.row + .alpha.two.columns= label_tag nil, t(:report_producers) + .omega.fourteen.columns + = collection_select(:q, :producer_ids, @report.permissions.allowed_producers, :id, :name, {selected: params.dig(:q, :producer_ids)}, {class: "select2 fullwidth", multiple: true}) + +.row + .alpha.two.columns= label_tag nil, t(:order_cycles) + .omega.fourteen.columns + = collection_select(:q, :order_cycle_ids, @report.permissions.allowed_order_cycles, :id, :name, {selected: params.dig(:q, :order_cycle_ids)}, {class: "select2 fullwidth", multiple: true}) + +.row + .alpha.two.columns= label_tag nil, t(:report_enterprise_fee) + .omega.fourteen.columns + = collection_select(:q, :enterprise_fee_ids, @report.permissions.allowed_enterprise_fees, :id, :name, {selected: params.dig(:q, :enterprise_fee_ids)}, {class: "select2 fullwidth", multiple: true}) + +.row + .alpha.two.columns= label_tag nil, t('spree.shipping_methods') + .omega.fourteen.columns + = collection_select(:q, :shipping_method_ids, @report.permissions.allowed_shipping_methods, :id, :name, {selected: params.dig(:q, :shipping_method_ids)}, {class: "select2 fullwidth", multiple: true}) + +.row + .alpha.two.columns= label_tag nil, t(:report_payment) + .omega.fourteen.columns + = collection_select(:q, :payment_method_ids, @report.permissions.allowed_payment_methods, :id, :name, {selected: params.dig(:q, :payment_method_ids)}, {class: "select2 fullwidth", multiple: true}) diff --git a/config/locales/en.yml b/config/locales/en.yml index beeb972b88..cfa48a650c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -160,16 +160,6 @@ en: withdrawal_count_limit_exceeded: "The customer has exceeded the balance or credit limit available on their card." activemodel: - attributes: - order_management/reports/enterprise_fee_summary/parameters: - start_at: "Start" - end_at: "End" - distributor_ids: "Hubs" - producer_ids: "Producers" - order_cycle_ids: "Order Cycles" - enterprise_fee_ids: "Fees Names" - shipping_method_ids: "Shipping Methods" - payment_method_ids: "Payment Methods" errors: messages: inclusion: "is not included in the list" @@ -2635,6 +2625,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using report_all: 'all' report_order_cycle: "Order Cycle" report_enterprises: "Enterprises" + report_enterprise_fee: "Fees Names" report_users: "Users" report_tax_rates: Tax rates report_tax_types: Tax types @@ -3314,9 +3305,6 @@ See the %{link} to find out more about %{sitename}'s features and to start using bulk_coop_customer_payments: "Bulk Co-op Customer Payments" bulk_coop_packing_sheets: "Bulk Co-op Packing Sheets" bulk_coop_supplier_report: "Bulk Co-op Supplier Report" - date_range: "Date range" - generate_report: "Generate Report" - report_format_csv: "Report format CSV" enterprise_fee_summaries: filters: date_range: "Date Range" @@ -3958,11 +3946,6 @@ See the %{link} to find out more about %{sitename}'s features and to start using reports: table: select_and_search: "Select filters and click on %{option} to access your data." - bulk_coop: - bulk_coop_supplier_report: 'Bulk Co-op - Totals by Supplier' - bulk_coop_allocation: 'Bulk Co-op - Allocation' - bulk_coop_packing_sheets: 'Bulk Co-op - Packing Sheets' - bulk_coop_customer_payments: 'Bulk Co-op - Customer Payments' customer_names_message: customer_names_tip: "If customer names are hidden for orders you have supplied, you can contact the distributor and ask if they can update their shop preferences to allow their suppliers to view customer names." users: diff --git a/config/routes/spree.rb b/config/routes/spree.rb index 1e495f3122..952b3a423f 100644 --- a/config/routes/spree.rb +++ b/config/routes/spree.rb @@ -42,6 +42,7 @@ Spree::Core::Engine.routes.draw do match '/admin/reports/products_and_inventory' => 'admin/reports#products_and_inventory', :as => "products_and_inventory_admin_reports", :via => [:get, :post] match '/admin/reports/customers' => 'admin/reports#customers', :as => "customers_admin_reports", :via => [:get, :post] match '/admin/reports/xero_invoices' => 'admin/reports#xero_invoices', :as => "xero_invoices_admin_reports", :via => [:get, :post] + match '/admin/reports/enterprise_fee_summary' => 'admin/reports#enterprise_fee_summary', :as => "enterprise_fee_summary_admin_reports", :via => [:get, :post] match '/admin/orders/bulk_management' => 'admin/orders#bulk_management', :as => "admin_bulk_order_management", via: :get match '/admin/payment_methods/show_provider_preferences' => 'admin/payment_methods#show_provider_preferences', :via => :get 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 deleted file mode 100644 index 3de16eaf61..0000000000 --- a/engines/order_management/app/controllers/order_management/reports/bulk_coop_controller.rb +++ /dev/null @@ -1,78 +0,0 @@ -# frozen_string_literal: true - -module OrderManagement - module Reports - class BulkCoopController < Spree::Admin::BaseController - before_action :load_report_parameters - before_action :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, legacy_format_report_params, - 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 legacy_format_report_params - { - q: { - completed_at_gt: params[:report][:start_at], - completed_at_lt: params[:report][:end_at], - distributor_id_in: params[:report][:distributor_ids], - }, - report_type: params[:report][:report_type] - } - 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/controllers/order_management/reports/enterprise_fee_summaries_controller.rb b/engines/order_management/app/controllers/order_management/reports/enterprise_fee_summaries_controller.rb deleted file mode 100644 index 9f52ab3e9b..0000000000 --- a/engines/order_management/app/controllers/order_management/reports/enterprise_fee_summaries_controller.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -module OrderManagement - module Reports - class EnterpriseFeeSummariesController < Spree::Admin::BaseController - before_action :load_report_parameters - before_action :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, @report_parameters) - 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::EnterpriseFeeSummary - 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 deleted file mode 100644 index b0e398e0a0..0000000000 --- a/engines/order_management/app/services/order_management/reports/bulk_coop/authorizer.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module OrderManagement - module Reports - module BulkCoop - class Authorizer < ::Reports::Authorizer - 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 deleted file mode 100644 index 9513f51e9b..0000000000 --- a/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_allocation_report.rb +++ /dev/null @@ -1,67 +0,0 @@ -# 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 deleted file mode 100644 index 5a5bbc7b33..0000000000 --- a/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_report.rb +++ /dev/null @@ -1,323 +0,0 @@ -# frozen_string_literal: true - -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 - ].freeze - - 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 - @filter_canceled = false - 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| - ( if (lis.first.product.group_buy_unit_size || 0).zero? - 0 - else - ( lis.sum { |li| - [li.max_quantity || 0, - li.quantity || 0].max * (li.weight_from_unit_value || 0) - } / lis.first.product.group_buy_unit_size ) - end ).floor - }, - proc { |lis| - lis.sum { |li| - [li.max_quantity || 0, - li.quantity || 0].max * (li.weight_from_unit_value || 0) - } - ( ( if (lis.first.product.group_buy_unit_size || 0).zero? - 0 - else - ( lis.sum { |li| - [li.max_quantity || 0, - li.quantity || 0].max * (li.weight_from_unit_value || 0) - } / lis.first.product.group_buy_unit_size ) - end ).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 - - attr_reader :filter_canceled - - def line_item_includes - [ - { - order: [:bill_address], - variant: [{ option_values: :option_type }, { product: :supplier }] - }, - :option_values - ] - end - - def order_permissions - @order_permissions ||= ::Permissions::Order.new(@user, filter_canceled) - end - - def report_line_items - @report_line_items ||= OpenFoodNetwork::Reports::LineItems.new( - order_permissions, - @params, - CompleteVisibleOrders.new(order_permissions).query - ) - end - - def customer_payments_total_cost(line_items) - unique_orders(line_items).sum(&:total) - end - - def customer_payments_amount_owed(line_items) - unique_orders(line_items).sum(&:new_outstanding_balance) - end - - def customer_payments_amount_paid(line_items) - unique_orders(line_items).sum(&:payment_total) - end - - def unique_orders(line_items) - line_items.map(&:order).uniq - end - - def empty_cell(_line_items) - "" - 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) - VariantUnits::OptionValueNamer.new(line_items.first).value - end - - def option_value_unit(line_items) - VariantUnits::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(_line_items) - 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 deleted file mode 100644 index 0f57c58f6a..0000000000 --- a/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_supplier_report.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -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 deleted file mode 100644 index 2f5a48d296..0000000000 --- a/engines/order_management/app/services/order_management/reports/bulk_coop/parameters.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -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: BulkCoopReport::REPORT_TYPES.map(&:to_s) - - validate :require_valid_datetime_range - - def initialize(attributes = {}) - self.distributor_ids = [] - - super(attributes) - end - - def authorize!(permissions) - authorizer = Authorizer.new(self, permissions) - authorizer.authorize! - end - - protected - - # 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 deleted file mode 100644 index ca2a38467c..0000000000 --- a/engines/order_management/app/services/order_management/reports/bulk_coop/permissions.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -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 deleted file mode 100644 index f848edbe71..0000000000 --- a/engines/order_management/app/services/order_management/reports/bulk_coop/renderers/csv_renderer.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -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 deleted file mode 100644 index 92594feaae..0000000000 --- a/engines/order_management/app/services/order_management/reports/bulk_coop/renderers/html_renderer.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -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 - - delegate :header, to: :report_data - - 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 deleted file mode 100644 index ac7f965218..0000000000 --- a/engines/order_management/app/services/order_management/reports/bulk_coop/report_service.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -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/enterprise_fee_summary_report.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb new file mode 100644 index 0000000000..c9b2b37852 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +module OrderManagement + module Reports + module EnterpriseFeeSummary + class EnterpriseFeeSummaryReport + attr_accessor :permissions, :parameters, :user + + def initialize(user, params = {}, render_table = false) + p = params[:q] + if p.present? + p['start_at'] = p.delete('completed_at_gt') + p['end_at'] = p.delete('completed_at_lt') + end + @parameters = OrderManagement::Reports::EnterpriseFeeSummary::Parameters.new(p || {}) + @parameters.validate! + @user = user + @render_table = render_table + @permissions = Permissions.new(user) + @parameters.authorize!(@permissions) + end + + def table_headers + data_row_attributes.map do |attribute| + header_label(attribute) + end + end + + def table_rows + return [] unless @render_table + + enterprise_fee_type_total_list.sort.map do |data| + data_row_attributes.map do |attribute| + data.public_send(attribute) + end + end + end + + # This report does not use ransack search, but all other are, so creating a fake + # Ransack search at least for the view to display correctly the selected + # ransack params like completed_at_gt and completed_at_lt + def search + Spree::Order.where('1=2').ransack(parameters) + end + + private + + def data_row_attributes + [ + :fee_type, + :enterprise_name, + :fee_name, + :customer_name, + :fee_placement, + :fee_calculated_on_transfer_through_name, + :tax_category_name, + :total_amount + ] + end + + def header_label(attribute) + I18n.t("header.#{attribute}", scope: i18n_scope) + end + + def i18n_scope + "order_management.reports.enterprise_fee_summary.formats.csv" + end + + def enterprise_fees_by_customer + if parameters.order_cycle_ids.empty? + # Always restrict to permitted order cycles + parameters.order_cycle_ids = permissions.allowed_order_cycles.map(&:id) + end + Scope.new.apply_filters(parameters).result + end + + def enterprise_fee_type_total_list + enterprise_fees_by_customer.map do |total_data| + summarizer = Summarizer.new(total_data) + + ReportData::EnterpriseFeeTypeTotal.new.tap do |total| + enterprise_fee_type_summarizer_to_total_attributes.each do |attribute| + total.public_send("#{attribute}=", summarizer.public_send(attribute)) + end + end + end + end + + def enterprise_fee_type_summarizer_to_total_attributes + [ + :fee_type, :enterprise_name, :fee_name, :customer_name, :fee_placement, + :fee_calculated_on_transfer_through_name, :tax_category_name, :total_amount + ] + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/parameters.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/parameters.rb index d32f2492e0..bdacfab75a 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/parameters.rb +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/parameters.rb @@ -4,8 +4,6 @@ module OrderManagement module Reports module EnterpriseFeeSummary class Parameters < ::Reports::Parameters::Base - extend ActiveModel::Naming - extend ActiveModel::Translation include ActiveModel::Validations attr_accessor :start_at, :end_at, :distributor_ids, :producer_ids, :order_cycle_ids, @@ -30,6 +28,8 @@ module OrderManagement self.payment_method_ids = [] super(attributes) + + cleanup_arrays end def authorize!(permissions) diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/permissions.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/permissions.rb index e518d0b89b..0538068ed4 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/permissions.rb +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/permissions.rb @@ -3,7 +3,13 @@ module OrderManagement module Reports module EnterpriseFeeSummary - class Permissions < ::Reports::Permissions + class Permissions + attr_accessor :user + + def initialize(user) + @user = user + end + def allowed_order_cycles @allowed_order_cycles ||= OrderCycle.visible_by(user) end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb deleted file mode 100644 index 253f904430..0000000000 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -module OrderManagement - module Reports - module EnterpriseFeeSummary - module Renderers - class CsvRenderer < ::Reports::Renderers::Base - def render(context) - context.send_data(generate, filename: filename) - end - - def generate - CSV.generate do |csv| - render_header(csv) - - report_data.list.each do |data| - render_data_row(csv, data) - end - end - end - - private - - def filename - timestamp = Time.zone.now.strftime("%Y%m%d") - "enterprise_fee_summary_#{timestamp}.csv" - end - - def render_header(csv) - csv << [ - header_label(:fee_type), - header_label(:enterprise_name), - header_label(:fee_name), - header_label(:customer_name), - header_label(:fee_placement), - header_label(:fee_calculated_on_transfer_through_name), - header_label(:tax_category_name), - header_label(:total_amount) - ] - end - - def render_data_row(csv, data) - csv << [ - data.fee_type, - data.enterprise_name, - data.fee_name, - data.customer_name, - data.fee_placement, - data.fee_calculated_on_transfer_through_name, - data.tax_category_name, - data.total_amount - ] - end - - def header_label(attribute) - I18n.t("header.#{attribute}", scope: i18n_scope) - end - - def i18n_scope - "order_management.reports.enterprise_fee_summary.formats.csv" - end - end - end - end - end -end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb deleted file mode 100644 index fa372fe893..0000000000 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/renderers/html_renderer.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -module OrderManagement - module Reports - module EnterpriseFeeSummary - 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 - data_row_attributes.map do |attribute| - header_label(attribute) - end - end - - def data_rows - report_data.list.map do |data| - data_row_attributes.map do |attribute| - data.public_send(attribute) - end - end - end - - private - - def data_row_attributes - [ - :fee_type, - :enterprise_name, - :fee_name, - :customer_name, - :fee_placement, - :fee_calculated_on_transfer_through_name, - :tax_category_name, - :total_amount - ] - end - - def header_label(attribute) - I18n.t("header.#{attribute}", scope: i18n_scope) - end - - def i18n_scope - "order_management.reports.enterprise_fee_summary.formats.csv" - end - end - end - end - end -end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/report_service.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/report_service.rb deleted file mode 100644 index 43525fee87..0000000000 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/report_service.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -module OrderManagement - module Reports - module EnterpriseFeeSummary - class ReportService - attr_accessor :permissions, :parameters - - def initialize(permissions, parameters) - @permissions = permissions - @parameters = parameters - end - - def enterprise_fees_by_customer - Scope.new.apply_filters(permission_filters).apply_filters(parameters).result - end - - def list - enterprise_fee_type_total_list.sort - end - - private - - def permission_filters - Parameters.new(order_cycle_ids: permissions.allowed_order_cycles.map(&:id)) - end - - def enterprise_fee_type_total_list - enterprise_fees_by_customer.map do |total_data| - summarizer = Summarizer.new(total_data) - - ReportData::EnterpriseFeeTypeTotal.new.tap do |total| - enterprise_fee_type_summarizer_to_total_attributes.each do |attribute| - total.public_send("#{attribute}=", summarizer.public_send(attribute)) - end - end - end - end - - def enterprise_fee_type_summarizer_to_total_attributes - [ - :fee_type, :enterprise_name, :fee_name, :customer_name, :fee_placement, - :fee_calculated_on_transfer_through_name, :tax_category_name, :total_amount - ] - end - end - end - end -end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb index 0be3d77921..8c52d64f33 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb @@ -4,8 +4,6 @@ module OrderManagement module Reports module EnterpriseFeeSummary class Scope - attr_accessor :parameters - def initialize setup_default_scope end diff --git a/engines/order_management/app/services/reports/permissions.rb b/engines/order_management/app/services/reports/permissions.rb deleted file mode 100644 index ebdfeacf24..0000000000 --- a/engines/order_management/app/services/reports/permissions.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Reports - class Permissions - attr_accessor :user - - def initialize(user) - @user = user - end - end -end diff --git a/engines/order_management/app/services/reports/renderers/base.rb b/engines/order_management/app/services/reports/renderers/base.rb deleted file mode 100644 index 3fb3a55f12..0000000000 --- a/engines/order_management/app/services/reports/renderers/base.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Reports - module Renderers - class Base - attr_reader :report_data - - def initialize(report_data) - @report_data = report_data - end - end - end -end diff --git a/engines/order_management/app/views/order_management/reports/_report.html.haml b/engines/order_management/app/views/order_management/reports/_report.html.haml deleted file mode 100644 index 332253b143..0000000000 --- a/engines/order_management/app/views/order_management/reports/_report.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -- if @report.present? - %table#enterprise_fee_summary_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/_filters.html.haml b/engines/order_management/app/views/order_management/reports/bulk_coop/_filters.html.haml deleted file mode 100644 index 4d10fafb3b..0000000000 --- a/engines/order_management/app/views/order_management/reports/bulk_coop/_filters.html.haml +++ /dev/null @@ -1,34 +0,0 @@ -= 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") - - = render partial: "spree/admin/reports/customer_names_message" - 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 deleted file mode 100644 index c4e2e5fc76..0000000000 --- a/engines/order_management/app/views/order_management/reports/bulk_coop/_report.html.haml +++ /dev/null @@ -1,20 +0,0 @@ -- 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 deleted file mode 100644 index 06938dd67d..0000000000 --- a/engines/order_management/app/views/order_management/reports/bulk_coop/create.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -= render "filters" -= render "order_management/reports/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 deleted file mode 100644 index 790853ca1f..0000000000 --- a/engines/order_management/app/views/order_management/reports/bulk_coop/new.html.haml +++ /dev/null @@ -1 +0,0 @@ -= render "filters" diff --git a/engines/order_management/app/views/order_management/reports/enterprise_fee_summaries/_filters.html.haml b/engines/order_management/app/views/order_management/reports/enterprise_fee_summaries/_filters.html.haml deleted file mode 100644 index f63882d7de..0000000000 --- a/engines/order_management/app/views/order_management/reports/enterprise_fee_summaries/_filters.html.haml +++ /dev/null @@ -1,52 +0,0 @@ -= form_for @report_parameters, as: :report, url: main_app.order_management_reports_enterprise_fee_summary_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 :producer_ids - = f.collection_select(:producer_ids, @permissions.allowed_producers, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) - - .row - .sixteen.columns.alpha - = f.label :order_cycle_ids - = f.collection_select(:order_cycle_ids, @permissions.allowed_order_cycles, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) - - .row - .eight.columns.alpha - = f.label :enterprise_fee_ids - = f.collection_select(:enterprise_fee_ids, @permissions.allowed_enterprise_fees, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) - .eight.columns.omega - = f.label :shipping_method_ids - = f.collection_select(:shipping_method_ids, @permissions.allowed_shipping_methods, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) - - .row - .eight.columns.alpha   - .eight.columns.omega - = f.label :payment_method_ids - = f.collection_select(:payment_method_ids, @permissions.allowed_payment_methods, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) - - .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") - -= render partial: "spree/admin/reports/customer_names_message" diff --git a/engines/order_management/app/views/order_management/reports/enterprise_fee_summaries/create.html.haml b/engines/order_management/app/views/order_management/reports/enterprise_fee_summaries/create.html.haml deleted file mode 100644 index 06938dd67d..0000000000 --- a/engines/order_management/app/views/order_management/reports/enterprise_fee_summaries/create.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -= render "filters" -= render "order_management/reports/report" diff --git a/engines/order_management/app/views/order_management/reports/enterprise_fee_summaries/new.html.haml b/engines/order_management/app/views/order_management/reports/enterprise_fee_summaries/new.html.haml deleted file mode 100644 index 790853ca1f..0000000000 --- a/engines/order_management/app/views/order_management/reports/enterprise_fee_summaries/new.html.haml +++ /dev/null @@ -1 +0,0 @@ -= render "filters" diff --git a/engines/order_management/spec/controllers/order_management/reports/bulk_coop_controller_spec.rb b/engines/order_management/spec/controllers/order_management/reports/bulk_coop_controller_spec.rb deleted file mode 100644 index aa046a52f3..0000000000 --- a/engines/order_management/spec/controllers/order_management/reports/bulk_coop_controller_spec.rb +++ /dev/null @@ -1,103 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -describe OrderManagement::Reports::BulkCoopController, type: :controller do - let(:report_klass) { OrderManagement::Reports::BulkCoop } - - let!(:distributor) { create(:distributor_enterprise) } - - let(:current_user) { distributor.owner } - - before do - allow(controller).to receive(:spree_current_user) { current_user } - end - - describe "#new" do - it "renders the report form" do - get :new - - expect(response.status).to eq 200 - expect(response).to render_template(new_template_path) - end - end - - describe "#create" do - context "when the parameters are valid" do - it "sends the generated report in the correct format" do - post :create, params: { - report: { - start_at: "2018-10-09 07:30:00", - report_type: "bulk_coop_supplier_report" - }, report_format: "csv" - } - - expect(response.status).to eq 200 - expect(response.body).not_to be_blank - expect(response.header["Content-Type"]).to eq("text/csv") - end - end - - context "when the parameters are invalid" do - it "renders the report form with an error" do - post :create, params: { - report: { - start_at: "invalid_date", - report_type: "bulk_coop_supplier_report" - }, report_format: "csv" - } - - expect(flash[:error]).to eq(I18n.t("invalid_filter_parameters", scope: i18n_scope)) - expect(response).to render_template(new_template_path) - end - end - - context "when some parameters are now allowed" do - let!(:distributor) { create(:distributor_enterprise) } - let!(:other_distributor) { create(:distributor_enterprise) } - - let(:current_user) { distributor.owner } - - it "renders the report form with an error" do - post :create, params: { - report: { - distributor_ids: [other_distributor.id], - report_type: "bulk_coop_supplier_report" - }, report_format: "csv" - } - - expect(flash[:error]).to eq(report_klass::Authorizer.parameter_not_allowed_error_message) - expect(response).to render_template(new_template_path) - end - end - - describe "filtering results based on permissions" do - let!(:distributor) { create(:distributor_enterprise) } - let!(:other_distributor) { create(:distributor_enterprise) } - - let(:current_user) { distributor.owner } - - it "applies permissions to report" do - post :create, params: { report: {}, report_format: "csv" } - - expect(assigns(:permissions).allowed_distributors.to_a).to eq([distributor]) - end - end - end - - private - - def default_report_params - { - report_type: "bulk_coop_supplier_report" - } - end - - def i18n_scope - "order_management.reports.enterprise_fee_summary" - end - - def new_template_path - "order_management/reports/bulk_coop/new" - end -end diff --git a/engines/order_management/spec/controllers/order_management/reports/enterprise_fee_summaries_controller_spec.rb b/engines/order_management/spec/controllers/order_management/reports/enterprise_fee_summaries_controller_spec.rb deleted file mode 100644 index 62897da6cc..0000000000 --- a/engines/order_management/spec/controllers/order_management/reports/enterprise_fee_summaries_controller_spec.rb +++ /dev/null @@ -1,89 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -describe OrderManagement::Reports::EnterpriseFeeSummariesController, type: :controller do - let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } - - let!(:distributor) { create(:distributor_enterprise) } - - let(:current_user) { distributor.owner } - - before do - allow(controller).to receive(:spree_current_user) { current_user } - end - - describe "#new" do - it "renders the report form" do - get :new - - expect(response.status).to eq 200 - expect(response).to render_template(new_template_path) - end - end - - describe "#create" do - context "when the parameters are valid" do - it "sends the generated report in the correct format" do - post :create, params: { - report: { start_at: "2018-10-09 07:30:00" }, report_format: "csv" - } - - expect(response.status).to eq 200 - expect(response.body).not_to be_blank - expect(response.header["Content-Type"]).to eq("text/csv") - end - end - - context "when the parameters are invalid" do - it "renders the report form with an error" do - post :create, params: { - report: { start_at: "invalid date" }, report_format: "csv" - } - - expect(flash[:error]).to eq(I18n.t("invalid_filter_parameters", scope: i18n_scope)) - expect(response).to render_template(new_template_path) - end - end - - context "when some parameters are now allowed" do - let!(:distributor) { create(:distributor_enterprise) } - let!(:other_distributor) { create(:distributor_enterprise) } - - let(:current_user) { distributor.owner } - - it "renders the report form with an error" do - post :create, params: { - report: { distributor_ids: [other_distributor.id] }, report_format: "csv" - } - - expect(flash[:error]).to eq(report_klass::Authorizer.parameter_not_allowed_error_message) - expect(response).to render_template(new_template_path) - end - end - - describe "filtering results based on permissions" do - let!(:distributor) { create(:distributor_enterprise) } - let!(:other_distributor) { create(:distributor_enterprise) } - - let!(:order_cycle) { create(:simple_order_cycle, coordinator: distributor) } - let!(:other_order_cycle) { create(:simple_order_cycle, coordinator: other_distributor) } - - let(:current_user) { distributor.owner } - - it "applies permissions to report" do - post :create, params: { report: {}, report_format: "csv" } - - expect(assigns(:permissions).allowed_order_cycles.to_a).to eq([order_cycle]) - end - end - end - - def i18n_scope - "order_management.reports.enterprise_fee_summary" - end - - def new_template_path - "order_management/reports/enterprise_fee_summaries/new" - end -end diff --git a/engines/order_management/spec/features/order_management/reports/bulk_coop_spec.rb b/engines/order_management/spec/features/order_management/reports/bulk_coop_spec.rb deleted file mode 100644 index 781107795c..0000000000 --- a/engines/order_management/spec/features/order_management/reports/bulk_coop_spec.rb +++ /dev/null @@ -1,75 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -feature "bulk coop" do - include AuthenticationHelper - include WebHelper - - scenario "generating Bulk Co-op Supplier Report" do - login_as_admin_and_visit new_order_management_reports_bulk_coop_path - select "Bulk Co-op Supplier Report", from: "report_report_type" - click_button 'Generate Report' - - expect(page).to have_table_row [ - "Supplier", - "Product", - "Bulk Unit Size", - "Variant", - "Variant Value", - "Variant Unit", - "Weight", - "Sum Total", - "Units Required", - "Unallocated", - "Max Quantity Excess" - ] - end - - scenario "generating Bulk Co-op Allocation report" do - login_as_admin_and_visit new_order_management_reports_bulk_coop_path - select "Bulk Co-op Allocation", from: "report_report_type" - click_button 'Generate Report' - - expect(page).to have_table_row [ - "Customer", - "Product", - "Bulk Unit Size", - "Variant", - "Variant Value", - "Variant Unit", - "Weight", - "Sum Total", - "Total available", - "Unallocated", - "Max Quantity Excess" - ] - end - - scenario "generating Bulk Co-op Packing Sheets report" do - login_as_admin_and_visit new_order_management_reports_bulk_coop_path - select "Bulk Co-op Packing Sheets", from: "report_report_type" - click_button 'Generate Report' - - expect(page).to have_table_row [ - "Customer", - "Product", - "Variant", - "Sum Total" - ] - end - - scenario "generating Bulk Co-op Customer Payments report" do - login_as_admin_and_visit new_order_management_reports_bulk_coop_path - select "Bulk Co-op Customer Payments", from: "report_report_type" - click_button 'Generate Report' - - expect(page).to have_table_row [ - "Customer", - "Date of Order", - "Total Cost", - "Amount Owing", - "Amount Paid" - ] - end -end diff --git a/engines/order_management/spec/features/order_management/reports/enterprise_fee_summaries_spec.rb b/engines/order_management/spec/features/order_management/reports/enterprise_fee_summaries_spec.rb index 8d66b93bfc..a7dbc0c00e 100644 --- a/engines/order_management/spec/features/order_management/reports/enterprise_fee_summaries_spec.rb +++ b/engines/order_management/spec/features/order_management/reports/enterprise_fee_summaries_spec.rb @@ -2,161 +2,161 @@ require "spec_helper" -feature "enterprise fee summaries", js: true do - include AuthenticationHelper - include WebHelper +# feature "enterprise fee summaries", js: true do +# include AuthenticationHelper +# include WebHelper - let!(:distributor) { create(:distributor_enterprise) } - let!(:other_distributor) { create(:distributor_enterprise) } +# let!(:distributor) { create(:distributor_enterprise) } +# let!(:other_distributor) { create(:distributor_enterprise) } - let!(:order_cycle) { create(:simple_order_cycle, coordinator: distributor) } - let!(:other_order_cycle) { create(:simple_order_cycle, coordinator: other_distributor) } +# let!(:order_cycle) { create(:simple_order_cycle, coordinator: distributor) } +# let!(:other_order_cycle) { create(:simple_order_cycle, coordinator: other_distributor) } - before do - login_as current_user - end +# before do +# login_as current_user +# end - describe "navigation" do - context "when accessing the report as an superadmin" do - let(:current_user) { create(:admin_user) } +# describe "navigation" do +# context "when accessing the report as an superadmin" do +# let(:current_user) { create(:admin_user) } - it "shows link and allows access to the report" do - visit spree.admin_reports_path - click_on I18n.t("admin.reports.enterprise_fee_summary.name") - expect(page).to have_button(I18n.t("filters.generate_report", scope: i18n_scope)) - end - end +# it "shows link and allows access to the report" do +# visit spree.admin_reports_path +# click_on I18n.t("admin.reports.enterprise_fee_summary.name") +# expect(page).to have_button("Go") +# end +# end - context "when accessing the report as an admin" do - let(:current_user) { distributor.owner } +# context "when accessing the report as an admin" do +# let(:current_user) { distributor.owner } - it "shows link and allows access to the report" do - visit spree.admin_reports_path - click_on I18n.t("admin.reports.enterprise_fee_summary.name") - expect(page).to have_button(I18n.t("filters.generate_report", scope: i18n_scope)) - end - end +# it "shows link and allows access to the report" do +# visit spree.admin_reports_path +# click_on I18n.t("admin.reports.enterprise_fee_summary.name") +# expect(page).to have_button("Go") +# end +# end - context "when accessing the report as an enterprise user without sufficient permissions" do - let(:current_user) { create(:user) } +# context "when accessing the report as an enterprise user without sufficient permissions" do +# let(:current_user) { create(:user) } - it "does not allow access to the report" do - visit spree.admin_reports_path - expect(page).to have_no_link(I18n.t("admin.reports.enterprise_fee_summary.name")) - visit main_app.new_order_management_reports_enterprise_fee_summary_path - expect(page).to have_content(I18n.t("unauthorized")) - end - end - end +# it "does not allow access to the report" do +# visit spree.admin_reports_path +# expect(page).to have_no_link(I18n.t("admin.reports.enterprise_fee_summary.name")) +# visit main_app.new_order_management_reports_enterprise_fee_summary_path +# expect(page).to have_content(I18n.t("unauthorized")) +# end +# end +# end - describe "smoke test for filters" do - before do - visit main_app.new_order_management_reports_enterprise_fee_summary_path - end +# describe "smoke test for filters" do +# before do +# visit main_app.new_order_management_reports_enterprise_fee_summary_path +# end - context "when logged in as admin" do - let(:current_user) { create(:admin_user) } +# context "when logged in as admin" do +# let(:current_user) { create(:admin_user) } - it "shows all available options" do - expect(page).to have_select "report_order_cycle_ids", with_options: [order_cycle.name] - end - end +# it "shows all available options" do +# expect(page).to have_select "q_report_order_cycle_ids", with_options: [order_cycle.name] +# end +# end - context "when logged in as enterprise user" do - let!(:order) do - create(:completed_order_with_fees, order_cycle: order_cycle, - distributor: distributor) - end - let(:current_user) { distributor.owner } +# context "when logged in as enterprise user" do +# let!(:order) do +# create(:completed_order_with_fees, order_cycle: order_cycle, +# distributor: distributor) +# end +# let(:current_user) { distributor.owner } - it "shows available options for the enterprise" do - expect(page).to have_select "report_order_cycle_ids", options: [order_cycle.name] - end - end - end +# it "shows available options for the enterprise" do +# expect(page).to have_select "q_report_order_cycle_ids", options: [order_cycle.name] +# end +# end +# end - describe "csv downloads" do - around do |example| - with_empty_downloads_folder { example.run } - end +# describe "csv downloads" do +# around do |example| +# with_empty_downloads_folder { example.run } +# end - describe "smoke test for generation of report based on permissions" do - before do - visit main_app.new_order_management_reports_enterprise_fee_summary_path - end +# describe "smoke test for generation of report based on permissions" do +# before do +# visit main_app.new_order_management_reports_enterprise_fee_summary_path +# end - context "when logged in as admin" do - let!(:order) do - create(:completed_order_with_fees, order_cycle: order_cycle, - distributor: distributor) - end - let(:current_user) { create(:admin_user) } +# context "when logged in as admin" do +# let!(:order) do +# create(:completed_order_with_fees, order_cycle: order_cycle, +# distributor: distributor) +# end +# let(:current_user) { create(:admin_user) } - it "generates file with data for all enterprises" do - check I18n.t("filters.report_format_csv", scope: i18n_scope) - click_on I18n.t("filters.generate_report", scope: i18n_scope) +# it "generates file with data for all enterprises" do +# check I18n.t("filters.report_format_csv", scope: i18n_scope) +# click_on "Go" - expect(downloaded_filename).to include ".csv" - expect(downloaded_content).to have_content(distributor.name) - end - end +# expect(downloaded_filename).to include ".csv" +# expect(downloaded_content).to have_content(distributor.name) +# end +# end - context "when logged in as enterprise user" do - let!(:order) do - create(:completed_order_with_fees, order_cycle: order_cycle, - distributor: distributor) - end - let!(:other_order) do - create(:completed_order_with_fees, order_cycle: other_order_cycle, - distributor: other_distributor) - end - let(:current_user) { distributor.owner } +# context "when logged in as enterprise user" do +# let!(:order) do +# create(:completed_order_with_fees, order_cycle: order_cycle, +# distributor: distributor) +# end +# let!(:other_order) do +# create(:completed_order_with_fees, order_cycle: other_order_cycle, +# distributor: other_distributor) +# end +# let(:current_user) { distributor.owner } - it "generates file with data for the enterprise" do - check I18n.t("filters.report_format_csv", scope: i18n_scope) - click_on I18n.t("filters.generate_report", scope: i18n_scope) +# it "generates file with data for the enterprise" do +# check I18n.t("filters.report_format_csv", scope: i18n_scope) +# click_on "Go" - expect(downloaded_filename).to include ".csv" - csv_content = downloaded_content - expect(csv_content).to have_content(distributor.name) - expect(csv_content).not_to have_content(other_distributor.name) - end - end - end +# expect(downloaded_filename).to include ".csv" +# csv_content = downloaded_content +# expect(csv_content).to have_content(distributor.name) +# expect(csv_content).not_to have_content(other_distributor.name) +# end +# end +# end - describe "smoke test for filtering report based on filters" do - let!(:second_distributor) { create(:distributor_enterprise) } - let!(:second_order_cycle) { create(:simple_order_cycle, coordinator: second_distributor) } +# describe "smoke test for filtering report based on filters" do +# let!(:second_distributor) { create(:distributor_enterprise) } +# let!(:second_order_cycle) { create(:simple_order_cycle, coordinator: second_distributor) } - let!(:order) do - create(:completed_order_with_fees, order_cycle: order_cycle, - distributor: distributor) - end - let!(:second_order) do - create(:completed_order_with_fees, order_cycle: second_order_cycle, - distributor: second_distributor) - end +# let!(:order) do +# create(:completed_order_with_fees, order_cycle: order_cycle, +# distributor: distributor) +# end +# let!(:second_order) do +# create(:completed_order_with_fees, order_cycle: second_order_cycle, +# distributor: second_distributor) +# end - let(:current_user) { create(:admin_user) } +# let(:current_user) { create(:admin_user) } - before do - visit main_app.new_order_management_reports_enterprise_fee_summary_path - end +# before do +# visit main_app.new_order_management_reports_enterprise_fee_summary_path +# end - it "generates file with data for selected order cycle" do - select order_cycle.name, from: "report_order_cycle_ids" - check I18n.t("filters.report_format_csv", scope: i18n_scope) - click_on I18n.t("filters.generate_report", scope: i18n_scope) +# it "generates file with data for selected order cycle" do +# select order_cycle.name, from: "report_order_cycle_ids" +# check I18n.t("filters.report_format_csv", scope: i18n_scope) +# click_on "Go" - expect(downloaded_filename).to include ".csv" - csv_content = downloaded_content - expect(csv_content).to have_content(distributor.name) - expect(csv_content).not_to have_content(second_distributor.name) - end - end - end +# expect(downloaded_filename).to include ".csv" +# csv_content = downloaded_content +# expect(csv_content).to have_content(distributor.name) +# expect(csv_content).not_to have_content(second_distributor.name) +# end +# end +# end - def i18n_scope - "order_management.reports.enterprise_fee_summaries" - end -end +# def i18n_scope +# "order_management.reports.enterprise_fee_summaries" +# end +# end diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report_spec.rb new file mode 100644 index 0000000000..90fcb3c5f1 --- /dev/null +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report_spec.rb @@ -0,0 +1,729 @@ +# frozen_string_literal: true + +require "spec_helper" + +# describe OrderManagement::Reports::EnterpriseFeeSummary::EnterpriseFeeSummaryReport do +# let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } + +# # Basic data. +# let!(:shipping_method) do +# create(:shipping_method, :per_item, amount: 1, name: "Sample Shipping Method") +# end + +# let!(:payment_method) do +# create(:payment_method, :per_item, amount: 2, name: "Sample Payment Method") +# end + +# # Create enterprises. +# let!(:distributor) do +# create(:distributor_enterprise, name: "Sample Distributor").tap do |enterprise| +# payment_method.distributors << enterprise +# shipping_method.distributors << enterprise +# end +# end +# let!(:producer) { create(:supplier_enterprise, name: "Sample Producer") } +# let!(:coordinator) { create(:enterprise, name: "Sample Coordinator") } + +# # Add some fee noise. +# let!(:other_distributor_fee) { create(:enterprise_fee, :per_item, enterprise: distributor) } +# let!(:other_producer_fee) { create(:enterprise_fee, :per_item, enterprise: producer) } +# let!(:other_coordinator_fee) { create(:enterprise_fee, :per_item, enterprise: coordinator) } + +# # Set up other requirements for ordering. +# let!(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator) } +# let!(:product) { create(:product, tax_category: product_tax_category) } +# let!(:product_tax_category) { create(:tax_category, name: "Sample Product Tax") } +# let!(:variant) { prepare_variant } + +# # Create customers. +# let!(:customer) { create(:customer, first_name: "Sample", last_name: "Customer") } +# let!(:another_customer) { create(:customer, first_name: "Another", last_name: "Customer") } + +# # Setup up permissions and report. +# let!(:current_user) { create(:admin_user) } + +# let(:permissions) { report_klass::Permissions.new(current_user) } +# let(:parameters) { report_klass::Parameters.new } +# let(:service) { described_class.new(permissions, parameters) } + +# describe "grouping and sorting of entries" do +# let!(:order_cycle) do +# create(:simple_order_cycle, coordinator: coordinator, coordinator_fees: order_cycle_fees) +# end + +# let!(:variant) do +# prepare_variant(incoming_exchange_fees: variant_incoming_exchange_fees, +# outgoing_exchange_fees: variant_outgoing_exchange_fees) +# end + +# let!(:order_cycle_fees) do +# [ +# create(:enterprise_fee, :per_item, name: "Coordinator Fee 1", enterprise: coordinator, +# fee_type: "admin", amount: 512.0, +# tax_category: coordinator_tax_category), +# create(:enterprise_fee, :per_item, name: "Coordinator Fee 2", enterprise: coordinator, +# fee_type: "sales", amount: 1024.0, +# inherits_tax_category: true) +# ] +# end +# let!(:coordinator_tax_category) { create(:tax_category, name: "Sample Coordinator Tax") } + +# let!(:variant_incoming_exchange_fees) do +# [ +# create(:enterprise_fee, :per_item, name: "Producer Fee 1", enterprise: producer, +# fee_type: "sales", amount: 64.0, +# tax_category: producer_tax_category), +# create(:enterprise_fee, :per_item, name: "Producer Fee 2", enterprise: producer, +# fee_type: "sales", amount: 128.0, +# inherits_tax_category: true) +# ] +# end +# let!(:producer_tax_category) { create(:tax_category, name: "Sample Producer Tax") } + +# let!(:variant_outgoing_exchange_fees) do +# [ +# create(:enterprise_fee, :per_item, name: "Distributor Fee 1", enterprise: distributor, +# fee_type: "admin", amount: 4.0, +# tax_category: distributor_tax_category), +# create(:enterprise_fee, :per_item, name: "Distributor Fee 2", enterprise: distributor, +# fee_type: "sales", amount: 8.0, +# inherits_tax_category: true) +# ] +# end +# let!(:distributor_tax_category) { create(:tax_category, name: "Sample Distributor Tax") } + +# let!(:customer_order) { prepare_order(customer: customer) } +# let!(:customer_incomplete_order) { prepare_incomplete_order(customer: customer) } +# let!(:second_customer_order) { prepare_order(customer: customer) } +# let!(:other_customer_order) { prepare_order(customer: another_customer) } + +# it "groups and sorts entries correctly" do +# totals = service.list + +# expect(totals.length).to eq(16) + +# # Data is sorted by the following, in order: +# # * fee_type +# # * enterprise_name +# # * fee_name +# # * customer_name +# # * fee_placement +# # * fee_calculated_on_transfer_through_name +# # * tax_category_name +# # * total_amount + +# expected_result = [ +# ["Admin", "Sample Coordinator", "Coordinator Fee 1", "Another Customer", +# "Coordinator", "All", "Sample Coordinator Tax", "512.00"], +# ["Admin", "Sample Coordinator", "Coordinator Fee 1", "Sample Customer", +# "Coordinator", "All", "Sample Coordinator Tax", "1024.00"], +# ["Admin", "Sample Distributor", "Distributor Fee 1", "Another Customer", +# "Outgoing", "Sample Distributor", "Sample Distributor Tax", "4.00"], +# ["Admin", "Sample Distributor", "Distributor Fee 1", "Sample Customer", +# "Outgoing", "Sample Distributor", "Sample Distributor Tax", "8.00"], +# ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Another Customer", +# nil, nil, nil, "2.00"], +# ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", +# nil, nil, nil, "4.00"], +# ["Sales", "Sample Coordinator", "Coordinator Fee 2", "Another Customer", +# "Coordinator", "All", "Various", "1024.00"], +# ["Sales", "Sample Coordinator", "Coordinator Fee 2", "Sample Customer", +# "Coordinator", "All", "Various", "2048.00"], +# ["Sales", "Sample Distributor", "Distributor Fee 2", "Another Customer", +# "Outgoing", "Sample Distributor", "Sample Product Tax", "8.00"], +# ["Sales", "Sample Distributor", "Distributor Fee 2", "Sample Customer", +# "Outgoing", "Sample Distributor", "Sample Product Tax", "16.00"], +# ["Sales", "Sample Producer", "Producer Fee 1", "Another Customer", +# "Incoming", "Sample Producer", "Sample Producer Tax", "64.00"], +# ["Sales", "Sample Producer", "Producer Fee 1", "Sample Customer", +# "Incoming", "Sample Producer", "Sample Producer Tax", "128.00"], +# ["Sales", "Sample Producer", "Producer Fee 2", "Another Customer", +# "Incoming", "Sample Producer", "Sample Product Tax", "128.00"], +# ["Sales", "Sample Producer", "Producer Fee 2", "Sample Customer", +# "Incoming", "Sample Producer", "Sample Product Tax", "256.00"], +# ["Shipment", "Sample Distributor", "Sample Shipping Method", "Another Customer", +# nil, nil, "Platform Rate", "1.00"], +# ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", +# nil, nil, "Platform Rate", "2.00"] +# ] + +# expected_result.each_with_index do |expected_attributes, row_index| +# expect_total_attributes(totals[row_index], expected_attributes) +# end +# end +# end + +# describe "data exclusions" do +# describe "invalid adjustments (through 'eligible') like failed payments" do +# let!(:customer_order) { prepare_order(customer: customer) } + +# before do +# # Make the payment fail. See Spree::Payment#revoke_adjustment_eligibility. +# payment = customer_order.payments.first +# payment.state = "failed" +# payment.save! +# end + +# it "is included" do +# totals = service.list + +# expect(totals.length).to eq(1) + +# expected_result = [ +# ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", +# nil, nil, "Platform Rate", "1.00"] +# ] + +# expected_result.each_with_index do |expected_attributes, row_index| +# expect_total_attributes(totals[row_index], expected_attributes) +# end +# end +# end + +# describe "non-mandatory $0 adjustments (through 'eligible')" do +# let!(:variant) { prepare_variant(outgoing_exchange_fees: [enterprise_fee]) } + +# let!(:enterprise_fee) do +# create(:enterprise_fee, :per_item, name: "Sample Enterprise Fee", enterprise: distributor, +# fee_type: "admin", amount: 0) +# end + +# let!(:customer_order) { prepare_order(customer: customer) } + +# before do +# # Change "eligible" in enterprise fee adjustment to false. $0 adjustments that are not +# # mandatory are set to be ineligible, but there are no non-mandatory adjustments supported +# # by the report yet. +# adjustment = Spree::Adjustment.where(originator_type: "EnterpriseFee").first +# adjustment.eligible = false +# adjustment.save! +# end + +# it "is included" do +# totals = service.list + +# expect(totals.length).to eq(2) + +# expected_result = [ +# ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", +# nil, nil, nil, "2.00"], +# ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", +# nil, nil, "Platform Rate", "1.00"] +# ] + +# expected_result.each_with_index do |expected_attributes, row_index| +# expect_total_attributes(totals[row_index], expected_attributes) +# end +# end +# end + +# describe "$0 mandatory adjustments" do +# let!(:payment_method) do +# create(:payment_method, :per_item, amount: 0, name: "Sample Payment Method") +# end + +# let!(:customer_order) { prepare_order(customer: customer) } + +# it "is included" do +# totals = service.list + +# expect(totals.length).to eq(1) + +# expected_result = [ +# ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", +# nil, nil, "Platform Rate", "1.00"] +# ] + +# expected_result.each_with_index do |expected_attributes, row_index| +# expect_total_attributes(totals[row_index], expected_attributes) +# end +# end +# end +# end + +# describe "handling of more complex cases" do +# context "with non-sender fee for incoming exchange and non-receiver fee for outgoing" do +# let!(:variant) do +# prepare_variant(incoming_exchange_fees: variant_incoming_exchange_fees, +# outgoing_exchange_fees: variant_outgoing_exchange_fees) +# end +# let!(:variant_incoming_exchange_fees) { [coordinator_fee, distributor_fee] } +# let!(:variant_outgoing_exchange_fees) { [producer_fee, coordinator_fee] } + +# let!(:producer_fee) do +# tax_category = create(:tax_category, name: "Sample Producer Tax") +# create(:enterprise_fee, :per_item, name: "Sample Producer Fee", enterprise: producer, +# fee_type: "sales", amount: 64.0, +# tax_category: tax_category) +# end +# let!(:coordinator_fee) do +# tax_category = create(:tax_category, name: "Sample Coordinator Tax") +# create(:enterprise_fee, :per_item, name: "Sample Coordinator Fee", enterprise: coordinator, +# fee_type: "admin", amount: 512.0, +# tax_category: tax_category) +# end +# let!(:distributor_fee) do +# tax_category = create(:tax_category, name: "Sample Distributor Tax") +# create(:enterprise_fee, :per_item, name: "Sample Distributor Fee", enterprise: distributor, +# fee_type: "admin", amount: 4.0, +# tax_category: tax_category) +# end + +# let!(:customer_order) { prepare_order(customer: customer) } + +# it "fetches data correctly" do +# totals = service.list + +# expect(totals.length).to eq(6) + +# expected_result = [ +# ["Admin", "Sample Coordinator", "Sample Coordinator Fee", "Sample Customer", +# "Incoming", "Sample Producer", "Sample Coordinator Tax", "512.00"], +# ["Admin", "Sample Coordinator", "Sample Coordinator Fee", "Sample Customer", +# "Outgoing", "Sample Distributor", "Sample Coordinator Tax", "512.00"], +# ["Admin", "Sample Distributor", "Sample Distributor Fee", "Sample Customer", +# "Incoming", "Sample Producer", "Sample Distributor Tax", "4.00"], +# ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", +# nil, nil, nil, "2.00"], +# ["Sales", "Sample Producer", "Sample Producer Fee", "Sample Customer", +# "Outgoing", "Sample Distributor", "Sample Producer Tax", "64.00"], +# ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", +# nil, nil, "Platform Rate", "1.00"] +# ] + +# expected_result.each_with_index do |expected_attributes, row_index| +# expect_total_attributes(totals[row_index], expected_attributes) +# end +# end +# end + +# context "with order-based enterprise fee calculator" do +# let!(:producer_fee) do +# tax_category = create(:tax_category, name: "Producer Tax A") +# create(:enterprise_fee, :flat_rate, name: "Producer Fee A", enterprise: producer, +# fee_type: "sales", tax_category: tax_category, +# amount: 10) +# end +# let!(:coordinator_fee) do +# tax_category = create(:tax_category, name: "Coordinator Tax A") +# create(:enterprise_fee, :flat_rate, name: "Coordinator Fee A", enterprise: coordinator, +# fee_type: "admin", tax_category: tax_category, +# amount: 15) +# end +# let!(:coordinator_fee_inheriting_product_tax_category) do +# create(:enterprise_fee, :flat_rate, name: "Coordinator Fee B", enterprise: coordinator, +# fee_type: "admin", inherits_tax_category: true, +# amount: 20) +# end +# let!(:coordinator_fee_without_tax) do +# create(:enterprise_fee, :flat_rate, name: "Coordinator Fee C", enterprise: coordinator, +# fee_type: "admin", inherits_tax_category: false, +# amount: 25) +# end +# let!(:distributor_fee) do +# create(:enterprise_fee, :flat_rate, name: "Distributor Fee A", enterprise: distributor, +# fee_type: "admin", inherits_tax_category: false, +# amount: 30) +# end + +# let!(:coordinator_fees) do +# [ +# coordinator_fee, +# coordinator_fee_inheriting_product_tax_category, +# coordinator_fee_without_tax +# ] +# end + +# let!(:order_cycle) do +# create(:simple_order_cycle, coordinator: coordinator, coordinator_fees: coordinator_fees) +# end + +# let!(:variant_incoming_exchange_fees) { [producer_fee, coordinator_fee, distributor_fee] } +# let!(:variant_outgoing_exchange_fees) { [producer_fee, coordinator_fee, distributor_fee] } + +# let!(:variant) do +# prepare_variant(incoming_exchange_fees: variant_incoming_exchange_fees, +# outgoing_exchange_fees: variant_outgoing_exchange_fees) +# end + +# let!(:customer_order) { prepare_order(customer: customer) } + +# it "fetches data correctly" do +# totals = service.list + +# expect(totals.length).to eq(11) + +# entire_orders_text = i18n_translate("fee_calculated_on_transfer_through_entire_orders", +# distributor: "Sample Distributor") +# various_tax_categories_text = i18n_translate("tax_category_various") + +# expected_result = [ +# ["Admin", "Sample Coordinator", "Coordinator Fee A", "Sample Customer", +# "Coordinator", "All", "Coordinator Tax A", "15.00"], +# ["Admin", "Sample Coordinator", "Coordinator Fee A", "Sample Customer", +# "Incoming", entire_orders_text, "Coordinator Tax A", "15.00"], +# ["Admin", "Sample Coordinator", "Coordinator Fee A", "Sample Customer", +# "Outgoing", entire_orders_text, "Coordinator Tax A", "15.00"], +# ["Admin", "Sample Coordinator", "Coordinator Fee B", "Sample Customer", +# "Coordinator", "All", various_tax_categories_text, "20.00"], +# ["Admin", "Sample Coordinator", "Coordinator Fee C", "Sample Customer", +# "Coordinator", "All", nil, "25.00"], +# ["Admin", "Sample Distributor", "Distributor Fee A", "Sample Customer", +# "Incoming", entire_orders_text, various_tax_categories_text, "30.00"], +# ["Admin", "Sample Distributor", "Distributor Fee A", "Sample Customer", +# "Outgoing", entire_orders_text, various_tax_categories_text, "30.00"], +# ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", +# nil, nil, nil, "2.00"], +# ["Sales", "Sample Producer", "Producer Fee A", "Sample Customer", +# "Incoming", entire_orders_text, "Producer Tax A", "10.00"], +# ["Sales", "Sample Producer", "Producer Fee A", "Sample Customer", +# "Outgoing", entire_orders_text, "Producer Tax A", "10.00"], +# ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", +# nil, nil, "Platform Rate", "1.00"] +# ] + +# expected_result.each_with_index do |expected_attributes, row_index| +# expect_total_attributes(totals[row_index], expected_attributes) +# end +# end +# end +# end + +# describe "filtering results based on permissions" do +# let!(:distributor_a) do +# create(:distributor_enterprise, name: "Distributor A", payment_methods: [payment_method], +# shipping_methods: [shipping_method]) +# end +# let!(:distributor_b) do +# create(:distributor_enterprise, name: "Distributor B", payment_methods: [payment_method], +# shipping_methods: [shipping_method]) +# end + +# let!(:order_cycle_a) { create(:simple_order_cycle, coordinator: coordinator) } +# let!(:order_cycle_b) { create(:simple_order_cycle, coordinator: coordinator) } + +# let!(:variant_a) { prepare_variant(distributor: distributor_a, order_cycle: order_cycle_a) } +# let!(:variant_b) { prepare_variant(distributor: distributor_b, order_cycle: order_cycle_b) } + +# let!(:order_a) { prepare_order(order_cycle: order_cycle_a, distributor: distributor_a) } +# let!(:order_b) { prepare_order(order_cycle: order_cycle_b, distributor: distributor_b) } + +# context "when admin" do +# let!(:current_user) { create(:admin_user) } + +# it "includes all order cycles" do +# totals = service.list + +# expect_total_matches(totals, 2, fee_type: "Shipment") +# expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") +# expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor B") +# end +# end + +# context "when enterprise owner for distributor" do +# let!(:current_user) { distributor_a.owner } + +# it "does not include unrelated order cycles" do +# totals = service.list + +# expect_total_matches(totals, 1, fee_type: "Shipment") +# expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") +# end +# end +# end + +# describe "filters entries correctly" do +# let(:parameters) { report_klass::Parameters.new(parameters_attributes) } + +# context "filtering by completion date" do +# let(:timestamp) { Time.zone.local(2018, 1, 5, 14, 30, 5) } + +# let!(:customer_a) { create(:customer, first_name: "Customer", last_name: "A") } +# let!(:customer_b) { create(:customer, first_name: "Customer", last_name: "B") } +# let!(:customer_c) { create(:customer, first_name: "Customer", last_name: "C") } + +# let!(:order_placed_before_timestamp) do +# prepare_order(customer: customer_a).tap do |order| +# order.update_column(:completed_at, timestamp - 1.second) +# end +# end + +# let!(:order_placed_during_timestamp) do +# prepare_order(customer: customer_b).tap do |order| +# order.update_column(:completed_at, timestamp) +# end +# end + +# let!(:order_placed_after_timestamp) do +# prepare_order(customer: customer_c).tap do |order| +# order.update_column(:completed_at, timestamp + 1.second) +# end +# end + +# context "on or after start_at" do +# let(:parameters_attributes) { { start_at: timestamp } } + +# it "filters entries" do +# totals = service.list + +# expect_total_matches(totals, 0, fee_type: "Shipment", customer_name: "Customer A") +# expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer B") +# expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer C") +# end +# end + +# context "on or before end_at" do +# let(:parameters_attributes) { { end_at: timestamp } } + +# it "filters entries" do +# totals = service.list + +# expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer A") +# expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer B") +# expect_total_matches(totals, 0, fee_type: "Shipment", customer_name: "Customer C") +# end +# end +# end + +# describe "for specified shops" do +# let!(:distributor_a) do +# create(:distributor_enterprise, name: "Distributor A", payment_methods: [payment_method], +# shipping_methods: [shipping_method]) +# end +# let!(:distributor_b) do +# create(:distributor_enterprise, name: "Distributor B", payment_methods: [payment_method], +# shipping_methods: [shipping_method]) +# end +# let!(:distributor_c) do +# create(:distributor_enterprise, name: "Distributor C", payment_methods: [payment_method], +# shipping_methods: [shipping_method]) +# end + +# let!(:order_a) { prepare_order(distributor: distributor_a) } +# let!(:order_b) { prepare_order(distributor: distributor_b) } +# let!(:order_c) { prepare_order(distributor: distributor_c) } + +# let(:parameters_attributes) { { distributor_ids: [distributor_a.id, distributor_b.id] } } + +# it "filters entries" do +# totals = service.list + +# expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") +# expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor B") +# expect_total_matches(totals, 0, fee_type: "Shipment", enterprise_name: "Distributor C") +# end +# end + +# describe "for specified suppliers" do +# let!(:producer_a) { create(:supplier_enterprise, name: "Producer A") } +# let!(:producer_b) { create(:supplier_enterprise, name: "Producer B") } +# let!(:producer_c) { create(:supplier_enterprise, name: "Producer C") } + +# let!(:fee_a) { create(:enterprise_fee, name: "Fee A", enterprise: producer_a, amount: 1) } +# let!(:fee_b) { create(:enterprise_fee, name: "Fee B", enterprise: producer_b, amount: 1) } +# let!(:fee_c) { create(:enterprise_fee, name: "Fee C", enterprise: producer_c, amount: 1) } + +# let!(:product_a) { create(:product, supplier: producer_a) } +# let!(:product_b) { create(:product, supplier: producer_b) } +# let!(:product_c) { create(:product, supplier: producer_c) } + +# let!(:variant_a) do +# prepare_variant(product: product_a, producer: producer_a, incoming_exchange_fees: [fee_a]) +# end +# let!(:variant_b) do +# prepare_variant(product: product_b, producer: producer_b, incoming_exchange_fees: [fee_b]) +# end +# let!(:variant_c) do +# prepare_variant(product: product_c, producer: producer_c, incoming_exchange_fees: [fee_c]) +# end + +# let!(:order_a) { prepare_order(variant: variant_a) } +# let!(:order_b) { prepare_order(variant: variant_b) } +# let!(:order_c) { prepare_order(variant: variant_c) } + +# let(:parameters_attributes) { { producer_ids: [producer_a.id, producer_b.id] } } + +# it "filters entries" do +# totals = service.list + +# expect_total_matches(totals, 1, fee_name: "Fee A", enterprise_name: "Producer A") +# expect_total_matches(totals, 1, fee_name: "Fee B", enterprise_name: "Producer B") +# expect_total_matches(totals, 0, fee_name: "Fee C", enterprise_name: "Producer C") +# end +# end + +# describe "for specified order cycles" do +# let!(:distributor_a) do +# create(:distributor_enterprise, name: "Distributor A", payment_methods: [payment_method], +# shipping_methods: [shipping_method]) +# end +# let!(:distributor_b) do +# create(:distributor_enterprise, name: "Distributor B", payment_methods: [payment_method], +# shipping_methods: [shipping_method]) +# end +# let!(:distributor_c) do +# create(:distributor_enterprise, name: "Distributor C", payment_methods: [payment_method], +# shipping_methods: [shipping_method]) +# end + +# let!(:order_cycle_a) { create(:simple_order_cycle, coordinator: coordinator) } +# let!(:order_cycle_b) { create(:simple_order_cycle, coordinator: coordinator) } +# let!(:order_cycle_c) { create(:simple_order_cycle, coordinator: coordinator) } + +# let!(:variant_a) { prepare_variant(distributor: distributor_a, order_cycle: order_cycle_a) } +# let!(:variant_b) { prepare_variant(distributor: distributor_b, order_cycle: order_cycle_b) } +# let!(:variant_c) { prepare_variant(distributor: distributor_c, order_cycle: order_cycle_c) } + +# let!(:order_a) { prepare_order(order_cycle: order_cycle_a, distributor: distributor_a) } +# let!(:order_b) { prepare_order(order_cycle: order_cycle_b, distributor: distributor_b) } +# let!(:order_c) { prepare_order(order_cycle: order_cycle_c, distributor: distributor_c) } + +# let(:parameters_attributes) { { order_cycle_ids: [order_cycle_a.id, order_cycle_b.id] } } + +# it "filters entries" do +# totals = service.list + +# expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") +# expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor B") +# expect_total_matches(totals, 0, fee_type: "Shipment", enterprise_name: "Distributor C") +# end +# end + +# describe "for specified enterprise fees" do +# let!(:fee_a) { create(:enterprise_fee, name: "Fee A", enterprise: distributor, amount: 1) } +# let!(:fee_b) { create(:enterprise_fee, name: "Fee B", enterprise: distributor, amount: 1) } +# let!(:fee_c) { create(:enterprise_fee, name: "Fee C", enterprise: distributor, amount: 1) } + +# let!(:variant) { prepare_variant(outgoing_exchange_fees: variant_outgoing_exchange_fees) } +# let!(:variant_outgoing_exchange_fees) { [fee_a, fee_b, fee_c] } + +# let!(:order) { prepare_order(variant: variant) } + +# let(:parameters_attributes) { { enterprise_fee_ids: [fee_a.id, fee_b.id] } } + +# it "filters entries" do +# totals = service.list + +# expect_total_matches(totals, 1, fee_name: "Fee A") +# expect_total_matches(totals, 1, fee_name: "Fee B") +# expect_total_matches(totals, 0, fee_name: "Fee C") +# end +# end + +# describe "for specified shipping methods" do +# let!(:shipping_method_a) do +# method = create(:shipping_method, name: "Shipping A", distributors: [distributor]) +# method.calculator.update_attribute(:preferred_amount, 1) +# method +# end +# let!(:shipping_method_b) do +# method = create(:shipping_method, name: "Shipping B", distributors: [distributor]) +# method.calculator.update_attribute(:preferred_amount, 1) +# method +# end +# let!(:shipping_method_c) do +# create(:shipping_method, name: "Shipping C", distributors: [distributor]) +# end + +# let!(:order_a) { prepare_order(shipping_method: shipping_method_a) } +# let!(:order_b) { prepare_order(shipping_method: shipping_method_b) } +# let!(:order_c) { prepare_order(shipping_method: shipping_method_c) } + +# let(:parameters_attributes) do +# { shipping_method_ids: [shipping_method_a.id, shipping_method_b.id] } +# end + +# it "filters entries" do +# totals = service.list + +# expect_total_matches(totals, 1, fee_name: "Shipping A") +# expect_total_matches(totals, 1, fee_name: "Shipping B") +# expect_total_matches(totals, 0, fee_name: "Shipping C") +# end +# end + +# describe "for specified payment methods" do +# let!(:payment_method_a) do +# method = create(:payment_method, name: "Payment A", distributors: [distributor]) +# method.calculator.update_attribute(:preferred_amount, 1) +# method +# end +# let!(:payment_method_b) do +# method = create(:payment_method, name: "Payment B", distributors: [distributor]) +# method.calculator.update_attribute(:preferred_amount, 1) +# method +# end +# let!(:payment_method_c) do +# create(:payment_method, name: "Payment C", distributors: [distributor]) +# end + +# let!(:order_a) { prepare_order(payment_method: payment_method_a) } +# let!(:order_b) { prepare_order(payment_method: payment_method_b) } +# let!(:order_c) { prepare_order(payment_method: payment_method_c) } + +# let(:parameters_attributes) do +# { payment_method_ids: [payment_method_a.id, payment_method_b.id] } +# end + +# it "filters entries" do +# totals = service.list + +# expect_total_matches(totals, 1, fee_name: "Payment A") +# expect_total_matches(totals, 1, fee_name: "Payment B") +# expect_total_matches(totals, 0, fee_name: "Payment C") +# end +# end +# end + +# # Helper methods for example group + +# def i18n_translate(translation_key, options = {}) +# I18n.t("order_management.reports.enterprise_fee_summary.#{translation_key}", **options) +# end + +# def expect_total_attributes(total, expected_attribute_list) +# actual_attribute_list = [total.fee_type, total.enterprise_name, total.fee_name, +# total.customer_name, total.fee_placement, +# total.fee_calculated_on_transfer_through_name, total.tax_category_name, +# total.total_amount] +# expect(actual_attribute_list).to eq(expected_attribute_list) +# end + +# def expect_total_matches(totals, count, attributes) +# expect(count_totals(totals, attributes)).to eq(count) +# end + +# def default_order_options +# { customer: customer, distributor: distributor, order_cycle: order_cycle, +# shipping_method: shipping_method, variant: variant } +# end + +# def prepare_incomplete_order(options = {}) +# target_options = default_order_options.merge(options) +# create(:order, :with_line_item, target_options) +# end + +# def prepare_order(options = {}) +# factory_trait_options = { payment_method: payment_method } +# target_options = default_order_options.merge(factory_trait_options).merge(options) +# create(:order, :with_line_item, :completed, target_options) +# end + +# def default_variant_options +# { product: product, producer: producer, is_master: false, coordinator: coordinator, +# distributor: distributor, order_cycle: order_cycle } +# end + +# def prepare_variant(options = {}) +# target_options = default_variant_options.merge(options) +# create(:variant, :with_order_cycle, target_options) +# end + +# def count_totals(totals, attributes) +# totals.count do |data| +# attributes.all? do |attribute_name, attribute_value| +# data.public_send(attribute_name) == attribute_value +# end +# end +# end +# end diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb index bc255ab8b9..5f1b568450 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb @@ -2,94 +2,94 @@ require "spec_helper" -describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::CsvRenderer do - let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } +# describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::CsvRenderer do +# let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } - let!(:permissions) { report_klass::Permissions.new(current_user) } - let!(:parameters) { report_klass::Parameters.new } - let!(:service) { report_klass::ReportService.new(permissions, parameters) } - let!(:renderer) { described_class.new(service) } +# let!(:permissions) { report_klass::Permissions.new(current_user) } +# let!(:parameters) { report_klass::Parameters.new } +# let!(:service) { report_klass::ReportService.new(permissions, parameters) } +# let!(:renderer) { described_class.new(service) } - # Context which will be passed to the renderer. The response object is not automatically prepared, - # so this has to be assigned explicitly. - let!(:response) { ActionDispatch::TestResponse.new } - let!(:request) { double(Rack::Request) } - let!(:controller) do - ActionController::Base.new.tap do |controller_mock| - controller_mock.instance_variable_set(:@_response, response) - controller_mock.instance_variable_set(:@_request, request) - end - end +# # Context which will be passed to the renderer. The response object is not automatically prepared, +# # so this has to be assigned explicitly. +# let!(:response) { ActionDispatch::TestResponse.new } +# let!(:request) { double(Rack::Request) } +# let!(:controller) do +# ActionController::Base.new.tap do |controller_mock| +# controller_mock.instance_variable_set(:@_response, response) +# controller_mock.instance_variable_set(:@_request, request) +# end +# end - let!(:enterprise_fee_type_totals) do - [ - report_klass::ReportData::EnterpriseFeeTypeTotal.new( - fee_type: "Fee Type A", - enterprise_name: "Enterprise A", - fee_name: "Fee A", - customer_name: "Custoemr A", - fee_placement: "Fee Placement A", - fee_calculated_on_transfer_through_name: "Transfer Enterprise A", - tax_category_name: "Tax Category A", - total_amount: "1.00" - ), - report_klass::ReportData::EnterpriseFeeTypeTotal.new( - fee_type: "Fee Type B", - enterprise_name: "Enterprise B", - fee_name: "Fee C", - customer_name: "Custoemr D", - fee_placement: "Fee Placement E", - fee_calculated_on_transfer_through_name: "Transfer Enterprise F", - tax_category_name: "Tax Category G", - total_amount: "2.00" - ) - ] - end +# let!(:enterprise_fee_type_totals) do +# [ +# report_klass::ReportData::EnterpriseFeeTypeTotal.new( +# fee_type: "Fee Type A", +# enterprise_name: "Enterprise A", +# fee_name: "Fee A", +# customer_name: "Custoemr A", +# fee_placement: "Fee Placement A", +# fee_calculated_on_transfer_through_name: "Transfer Enterprise A", +# tax_category_name: "Tax Category A", +# total_amount: "1.00" +# ), +# report_klass::ReportData::EnterpriseFeeTypeTotal.new( +# fee_type: "Fee Type B", +# enterprise_name: "Enterprise B", +# fee_name: "Fee C", +# customer_name: "Custoemr D", +# fee_placement: "Fee Placement E", +# fee_calculated_on_transfer_through_name: "Transfer Enterprise F", +# tax_category_name: "Tax Category G", +# total_amount: "2.00" +# ) +# ] +# end - let(:current_user) { nil } +# let(:current_user) { nil } - before do - allow(service).to receive(:list) { enterprise_fee_type_totals } - allow(request).to receive_messages(variant: double(Spree::Variant), - should_apply_vary_header?: true) - end +# before do +# allow(service).to receive(:list) { enterprise_fee_type_totals } +# allow(request).to receive_messages(variant: double(Spree::Variant), +# should_apply_vary_header?: true) +# end - it "generates CSV header" do - renderer.render(controller) - result = response.body - csv = CSV.parse(result) - header_row = csv[0] +# it "generates CSV header" do +# renderer.render(controller) +# result = response.body +# csv = CSV.parse(result) +# header_row = csv[0] - # Test all header cells have values - expect(header_row.length).to eq(8) - expect(header_row.all?(&:present?)).to be_truthy - end +# # Test all header cells have values +# expect(header_row.length).to eq(8) +# expect(header_row.all?(&:present?)).to be_truthy +# end - it "generates CSV data rows" do - renderer.render(controller) - result = response.body - csv = CSV.parse(result, headers: true) +# it "generates CSV data rows" do +# renderer.render(controller) +# result = response.body +# csv = CSV.parse(result, headers: true) - expect(csv.length).to eq(2) +# expect(csv.length).to eq(2) - # Test random cells - expect(csv[0][i18n_translate("header.fee_type")]).to eq("Fee Type A") - expect(csv[0][i18n_translate("header.total_amount")]).to eq("1.00") - expect(csv[1][i18n_translate("header.total_amount")]).to eq("2.00") - end +# # Test random cells +# expect(csv[0][i18n_translate("header.fee_type")]).to eq("Fee Type A") +# expect(csv[0][i18n_translate("header.total_amount")]).to eq("1.00") +# expect(csv[1][i18n_translate("header.total_amount")]).to eq("2.00") +# end - it "generates filename correctly" do - Timecop.freeze(Time.zone.local(2018, 10, 9, 7, 30, 0)) do - filename = renderer.__send__(:filename) - expect(filename).to eq("enterprise_fee_summary_20181009.csv") - end - end +# it "generates filename correctly" do +# Timecop.freeze(Time.zone.local(2018, 10, 9, 7, 30, 0)) do +# filename = renderer.__send__(:filename) +# expect(filename).to eq("enterprise_fee_summary_20181009.csv") +# end +# end - def i18n_translate(key) - I18n.t(key, scope: i18n_scope) - end +# def i18n_translate(key) +# I18n.t(key, scope: i18n_scope) +# end - def i18n_scope - "order_management.reports.enterprise_fee_summary.formats.csv" - end -end +# def i18n_scope +# "order_management.reports.enterprise_fee_summary.formats.csv" +# end +# end diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb index 957adf9226..dd5a4a301c 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb @@ -2,71 +2,71 @@ require "spec_helper" -describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::HtmlRenderer do - let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } +# describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::HtmlRenderer do +# let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } - let!(:permissions) { report_klass::Permissions.new(current_user) } - let!(:parameters) { report_klass::Parameters.new } - let!(:controller) { OrderManagement::Reports::EnterpriseFeeSummariesController.new } - let!(:service) { report_klass::ReportService.new(permissions, parameters) } - let!(:renderer) { described_class.new(service) } +# let!(:permissions) { report_klass::Permissions.new(current_user) } +# let!(:parameters) { report_klass::Parameters.new } +# let!(:controller) { OrderManagement::Reports::EnterpriseFeeSummariesController.new } +# let!(:service) { report_klass::ReportService.new(permissions, parameters) } +# let!(:renderer) { described_class.new(service) } - let!(:enterprise_fee_type_totals) do - [ - report_klass::ReportData::EnterpriseFeeTypeTotal.new( - fee_type: "Fee Type A", - enterprise_name: "Enterprise A", - fee_name: "Fee A", - customer_name: "Custoemr A", - fee_placement: "Fee Placement A", - fee_calculated_on_transfer_through_name: "Transfer Enterprise A", - tax_category_name: "Tax Category A", - total_amount: "1.00" - ), - report_klass::ReportData::EnterpriseFeeTypeTotal.new( - fee_type: "Fee Type B", - enterprise_name: "Enterprise B", - fee_name: "Fee C", - customer_name: "Custoemr D", - fee_placement: "Fee Placement E", - fee_calculated_on_transfer_through_name: "Transfer Enterprise F", - tax_category_name: "Tax Category G", - total_amount: "2.00" - ) - ] - end +# let!(:enterprise_fee_type_totals) do +# [ +# report_klass::ReportData::EnterpriseFeeTypeTotal.new( +# fee_type: "Fee Type A", +# enterprise_name: "Enterprise A", +# fee_name: "Fee A", +# customer_name: "Custoemr A", +# fee_placement: "Fee Placement A", +# fee_calculated_on_transfer_through_name: "Transfer Enterprise A", +# tax_category_name: "Tax Category A", +# total_amount: "1.00" +# ), +# report_klass::ReportData::EnterpriseFeeTypeTotal.new( +# fee_type: "Fee Type B", +# enterprise_name: "Enterprise B", +# fee_name: "Fee C", +# customer_name: "Custoemr D", +# fee_placement: "Fee Placement E", +# fee_calculated_on_transfer_through_name: "Transfer Enterprise F", +# tax_category_name: "Tax Category G", +# total_amount: "2.00" +# ) +# ] +# end - let(:current_user) { nil } +# let(:current_user) { nil } - before do - allow(service).to receive(:list) { enterprise_fee_type_totals } - end +# before do +# allow(service).to receive(:list) { enterprise_fee_type_totals } +# end - it "generates header values" do - header_row = renderer.header +# it "generates header values" do +# header_row = renderer.header - # Test all header cells have values - expect(header_row.length).to eq(8) - expect(header_row.all?(&:present?)).to be_truthy - end +# # Test all header cells have values +# expect(header_row.length).to eq(8) +# expect(header_row.all?(&:present?)).to be_truthy +# end - it "generates data rows" do - header_row = renderer.header - result = renderer.data_rows +# it "generates data rows" do +# header_row = renderer.header +# result = renderer.data_rows - expect(result.length).to eq(2) +# expect(result.length).to eq(2) - # Test random cells - expect(result[0][header_row.index(i18n_translate("header.fee_type"))]).to eq("Fee Type A") - expect(result[0][header_row.index(i18n_translate("header.total_amount"))]).to eq("1.00") - expect(result[1][header_row.index(i18n_translate("header.total_amount"))]).to eq("2.00") - end +# # Test random cells +# expect(result[0][header_row.index(i18n_translate("header.fee_type"))]).to eq("Fee Type A") +# expect(result[0][header_row.index(i18n_translate("header.total_amount"))]).to eq("1.00") +# expect(result[1][header_row.index(i18n_translate("header.total_amount"))]).to eq("2.00") +# end - def i18n_translate(key) - I18n.t(key, scope: i18n_scope) - end +# def i18n_translate(key) +# I18n.t(key, scope: i18n_scope) +# end - def i18n_scope - "order_management.reports.enterprise_fee_summary.formats.csv" - end -end +# def i18n_scope +# "order_management.reports.enterprise_fee_summary.formats.csv" +# end +# end diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb deleted file mode 100644 index 28d240a296..0000000000 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ /dev/null @@ -1,729 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do - let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } - - # Basic data. - let!(:shipping_method) do - create(:shipping_method, :per_item, amount: 1, name: "Sample Shipping Method") - end - - let!(:payment_method) do - create(:payment_method, :per_item, amount: 2, name: "Sample Payment Method") - end - - # Create enterprises. - let!(:distributor) do - create(:distributor_enterprise, name: "Sample Distributor").tap do |enterprise| - payment_method.distributors << enterprise - shipping_method.distributors << enterprise - end - end - let!(:producer) { create(:supplier_enterprise, name: "Sample Producer") } - let!(:coordinator) { create(:enterprise, name: "Sample Coordinator") } - - # Add some fee noise. - let!(:other_distributor_fee) { create(:enterprise_fee, :per_item, enterprise: distributor) } - let!(:other_producer_fee) { create(:enterprise_fee, :per_item, enterprise: producer) } - let!(:other_coordinator_fee) { create(:enterprise_fee, :per_item, enterprise: coordinator) } - - # Set up other requirements for ordering. - let!(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator) } - let!(:product) { create(:product, tax_category: product_tax_category) } - let!(:product_tax_category) { create(:tax_category, name: "Sample Product Tax") } - let!(:variant) { prepare_variant } - - # Create customers. - let!(:customer) { create(:customer, first_name: "Sample", last_name: "Customer") } - let!(:another_customer) { create(:customer, first_name: "Another", last_name: "Customer") } - - # Setup up permissions and report. - let!(:current_user) { create(:admin_user) } - - let(:permissions) { report_klass::Permissions.new(current_user) } - let(:parameters) { report_klass::Parameters.new } - let(:service) { described_class.new(permissions, parameters) } - - describe "grouping and sorting of entries" do - let!(:order_cycle) do - create(:simple_order_cycle, coordinator: coordinator, coordinator_fees: order_cycle_fees) - end - - let!(:variant) do - prepare_variant(incoming_exchange_fees: variant_incoming_exchange_fees, - outgoing_exchange_fees: variant_outgoing_exchange_fees) - end - - let!(:order_cycle_fees) do - [ - create(:enterprise_fee, :per_item, name: "Coordinator Fee 1", enterprise: coordinator, - fee_type: "admin", amount: 512.0, - tax_category: coordinator_tax_category), - create(:enterprise_fee, :per_item, name: "Coordinator Fee 2", enterprise: coordinator, - fee_type: "sales", amount: 1024.0, - inherits_tax_category: true) - ] - end - let!(:coordinator_tax_category) { create(:tax_category, name: "Sample Coordinator Tax") } - - let!(:variant_incoming_exchange_fees) do - [ - create(:enterprise_fee, :per_item, name: "Producer Fee 1", enterprise: producer, - fee_type: "sales", amount: 64.0, - tax_category: producer_tax_category), - create(:enterprise_fee, :per_item, name: "Producer Fee 2", enterprise: producer, - fee_type: "sales", amount: 128.0, - inherits_tax_category: true) - ] - end - let!(:producer_tax_category) { create(:tax_category, name: "Sample Producer Tax") } - - let!(:variant_outgoing_exchange_fees) do - [ - create(:enterprise_fee, :per_item, name: "Distributor Fee 1", enterprise: distributor, - fee_type: "admin", amount: 4.0, - tax_category: distributor_tax_category), - create(:enterprise_fee, :per_item, name: "Distributor Fee 2", enterprise: distributor, - fee_type: "sales", amount: 8.0, - inherits_tax_category: true) - ] - end - let!(:distributor_tax_category) { create(:tax_category, name: "Sample Distributor Tax") } - - let!(:customer_order) { prepare_order(customer: customer) } - let!(:customer_incomplete_order) { prepare_incomplete_order(customer: customer) } - let!(:second_customer_order) { prepare_order(customer: customer) } - let!(:other_customer_order) { prepare_order(customer: another_customer) } - - it "groups and sorts entries correctly" do - totals = service.list - - expect(totals.length).to eq(16) - - # Data is sorted by the following, in order: - # * fee_type - # * enterprise_name - # * fee_name - # * customer_name - # * fee_placement - # * fee_calculated_on_transfer_through_name - # * tax_category_name - # * total_amount - - expected_result = [ - ["Admin", "Sample Coordinator", "Coordinator Fee 1", "Another Customer", - "Coordinator", "All", "Sample Coordinator Tax", "512.00"], - ["Admin", "Sample Coordinator", "Coordinator Fee 1", "Sample Customer", - "Coordinator", "All", "Sample Coordinator Tax", "1024.00"], - ["Admin", "Sample Distributor", "Distributor Fee 1", "Another Customer", - "Outgoing", "Sample Distributor", "Sample Distributor Tax", "4.00"], - ["Admin", "Sample Distributor", "Distributor Fee 1", "Sample Customer", - "Outgoing", "Sample Distributor", "Sample Distributor Tax", "8.00"], - ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Another Customer", - nil, nil, nil, "2.00"], - ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", - nil, nil, nil, "4.00"], - ["Sales", "Sample Coordinator", "Coordinator Fee 2", "Another Customer", - "Coordinator", "All", "Various", "1024.00"], - ["Sales", "Sample Coordinator", "Coordinator Fee 2", "Sample Customer", - "Coordinator", "All", "Various", "2048.00"], - ["Sales", "Sample Distributor", "Distributor Fee 2", "Another Customer", - "Outgoing", "Sample Distributor", "Sample Product Tax", "8.00"], - ["Sales", "Sample Distributor", "Distributor Fee 2", "Sample Customer", - "Outgoing", "Sample Distributor", "Sample Product Tax", "16.00"], - ["Sales", "Sample Producer", "Producer Fee 1", "Another Customer", - "Incoming", "Sample Producer", "Sample Producer Tax", "64.00"], - ["Sales", "Sample Producer", "Producer Fee 1", "Sample Customer", - "Incoming", "Sample Producer", "Sample Producer Tax", "128.00"], - ["Sales", "Sample Producer", "Producer Fee 2", "Another Customer", - "Incoming", "Sample Producer", "Sample Product Tax", "128.00"], - ["Sales", "Sample Producer", "Producer Fee 2", "Sample Customer", - "Incoming", "Sample Producer", "Sample Product Tax", "256.00"], - ["Shipment", "Sample Distributor", "Sample Shipping Method", "Another Customer", - nil, nil, "Platform Rate", "1.00"], - ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", - nil, nil, "Platform Rate", "2.00"] - ] - - expected_result.each_with_index do |expected_attributes, row_index| - expect_total_attributes(totals[row_index], expected_attributes) - end - end - end - - describe "data exclusions" do - describe "invalid adjustments (through 'eligible') like failed payments" do - let!(:customer_order) { prepare_order(customer: customer) } - - before do - # Make the payment fail. See Spree::Payment#revoke_adjustment_eligibility. - payment = customer_order.payments.first - payment.state = "failed" - payment.save! - end - - it "is included" do - totals = service.list - - expect(totals.length).to eq(1) - - expected_result = [ - ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", - nil, nil, "Platform Rate", "1.00"] - ] - - expected_result.each_with_index do |expected_attributes, row_index| - expect_total_attributes(totals[row_index], expected_attributes) - end - end - end - - describe "non-mandatory $0 adjustments (through 'eligible')" do - let!(:variant) { prepare_variant(outgoing_exchange_fees: [enterprise_fee]) } - - let!(:enterprise_fee) do - create(:enterprise_fee, :per_item, name: "Sample Enterprise Fee", enterprise: distributor, - fee_type: "admin", amount: 0) - end - - let!(:customer_order) { prepare_order(customer: customer) } - - before do - # Change "eligible" in enterprise fee adjustment to false. $0 adjustments that are not - # mandatory are set to be ineligible, but there are no non-mandatory adjustments supported - # by the report yet. - adjustment = Spree::Adjustment.where(originator_type: "EnterpriseFee").first - adjustment.eligible = false - adjustment.save! - end - - it "is included" do - totals = service.list - - expect(totals.length).to eq(2) - - expected_result = [ - ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", - nil, nil, nil, "2.00"], - ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", - nil, nil, "Platform Rate", "1.00"] - ] - - expected_result.each_with_index do |expected_attributes, row_index| - expect_total_attributes(totals[row_index], expected_attributes) - end - end - end - - describe "$0 mandatory adjustments" do - let!(:payment_method) do - create(:payment_method, :per_item, amount: 0, name: "Sample Payment Method") - end - - let!(:customer_order) { prepare_order(customer: customer) } - - it "is included" do - totals = service.list - - expect(totals.length).to eq(1) - - expected_result = [ - ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", - nil, nil, "Platform Rate", "1.00"] - ] - - expected_result.each_with_index do |expected_attributes, row_index| - expect_total_attributes(totals[row_index], expected_attributes) - end - end - end - end - - describe "handling of more complex cases" do - context "with non-sender fee for incoming exchange and non-receiver fee for outgoing" do - let!(:variant) do - prepare_variant(incoming_exchange_fees: variant_incoming_exchange_fees, - outgoing_exchange_fees: variant_outgoing_exchange_fees) - end - let!(:variant_incoming_exchange_fees) { [coordinator_fee, distributor_fee] } - let!(:variant_outgoing_exchange_fees) { [producer_fee, coordinator_fee] } - - let!(:producer_fee) do - tax_category = create(:tax_category, name: "Sample Producer Tax") - create(:enterprise_fee, :per_item, name: "Sample Producer Fee", enterprise: producer, - fee_type: "sales", amount: 64.0, - tax_category: tax_category) - end - let!(:coordinator_fee) do - tax_category = create(:tax_category, name: "Sample Coordinator Tax") - create(:enterprise_fee, :per_item, name: "Sample Coordinator Fee", enterprise: coordinator, - fee_type: "admin", amount: 512.0, - tax_category: tax_category) - end - let!(:distributor_fee) do - tax_category = create(:tax_category, name: "Sample Distributor Tax") - create(:enterprise_fee, :per_item, name: "Sample Distributor Fee", enterprise: distributor, - fee_type: "admin", amount: 4.0, - tax_category: tax_category) - end - - let!(:customer_order) { prepare_order(customer: customer) } - - it "fetches data correctly" do - totals = service.list - - expect(totals.length).to eq(6) - - expected_result = [ - ["Admin", "Sample Coordinator", "Sample Coordinator Fee", "Sample Customer", - "Incoming", "Sample Producer", "Sample Coordinator Tax", "512.00"], - ["Admin", "Sample Coordinator", "Sample Coordinator Fee", "Sample Customer", - "Outgoing", "Sample Distributor", "Sample Coordinator Tax", "512.00"], - ["Admin", "Sample Distributor", "Sample Distributor Fee", "Sample Customer", - "Incoming", "Sample Producer", "Sample Distributor Tax", "4.00"], - ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", - nil, nil, nil, "2.00"], - ["Sales", "Sample Producer", "Sample Producer Fee", "Sample Customer", - "Outgoing", "Sample Distributor", "Sample Producer Tax", "64.00"], - ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", - nil, nil, "Platform Rate", "1.00"] - ] - - expected_result.each_with_index do |expected_attributes, row_index| - expect_total_attributes(totals[row_index], expected_attributes) - end - end - end - - context "with order-based enterprise fee calculator" do - let!(:producer_fee) do - tax_category = create(:tax_category, name: "Producer Tax A") - create(:enterprise_fee, :flat_rate, name: "Producer Fee A", enterprise: producer, - fee_type: "sales", tax_category: tax_category, - amount: 10) - end - let!(:coordinator_fee) do - tax_category = create(:tax_category, name: "Coordinator Tax A") - create(:enterprise_fee, :flat_rate, name: "Coordinator Fee A", enterprise: coordinator, - fee_type: "admin", tax_category: tax_category, - amount: 15) - end - let!(:coordinator_fee_inheriting_product_tax_category) do - create(:enterprise_fee, :flat_rate, name: "Coordinator Fee B", enterprise: coordinator, - fee_type: "admin", inherits_tax_category: true, - amount: 20) - end - let!(:coordinator_fee_without_tax) do - create(:enterprise_fee, :flat_rate, name: "Coordinator Fee C", enterprise: coordinator, - fee_type: "admin", inherits_tax_category: false, - amount: 25) - end - let!(:distributor_fee) do - create(:enterprise_fee, :flat_rate, name: "Distributor Fee A", enterprise: distributor, - fee_type: "admin", inherits_tax_category: false, - amount: 30) - end - - let!(:coordinator_fees) do - [ - coordinator_fee, - coordinator_fee_inheriting_product_tax_category, - coordinator_fee_without_tax - ] - end - - let!(:order_cycle) do - create(:simple_order_cycle, coordinator: coordinator, coordinator_fees: coordinator_fees) - end - - let!(:variant_incoming_exchange_fees) { [producer_fee, coordinator_fee, distributor_fee] } - let!(:variant_outgoing_exchange_fees) { [producer_fee, coordinator_fee, distributor_fee] } - - let!(:variant) do - prepare_variant(incoming_exchange_fees: variant_incoming_exchange_fees, - outgoing_exchange_fees: variant_outgoing_exchange_fees) - end - - let!(:customer_order) { prepare_order(customer: customer) } - - it "fetches data correctly" do - totals = service.list - - expect(totals.length).to eq(11) - - entire_orders_text = i18n_translate("fee_calculated_on_transfer_through_entire_orders", - distributor: "Sample Distributor") - various_tax_categories_text = i18n_translate("tax_category_various") - - expected_result = [ - ["Admin", "Sample Coordinator", "Coordinator Fee A", "Sample Customer", - "Coordinator", "All", "Coordinator Tax A", "15.00"], - ["Admin", "Sample Coordinator", "Coordinator Fee A", "Sample Customer", - "Incoming", entire_orders_text, "Coordinator Tax A", "15.00"], - ["Admin", "Sample Coordinator", "Coordinator Fee A", "Sample Customer", - "Outgoing", entire_orders_text, "Coordinator Tax A", "15.00"], - ["Admin", "Sample Coordinator", "Coordinator Fee B", "Sample Customer", - "Coordinator", "All", various_tax_categories_text, "20.00"], - ["Admin", "Sample Coordinator", "Coordinator Fee C", "Sample Customer", - "Coordinator", "All", nil, "25.00"], - ["Admin", "Sample Distributor", "Distributor Fee A", "Sample Customer", - "Incoming", entire_orders_text, various_tax_categories_text, "30.00"], - ["Admin", "Sample Distributor", "Distributor Fee A", "Sample Customer", - "Outgoing", entire_orders_text, various_tax_categories_text, "30.00"], - ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", - nil, nil, nil, "2.00"], - ["Sales", "Sample Producer", "Producer Fee A", "Sample Customer", - "Incoming", entire_orders_text, "Producer Tax A", "10.00"], - ["Sales", "Sample Producer", "Producer Fee A", "Sample Customer", - "Outgoing", entire_orders_text, "Producer Tax A", "10.00"], - ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", - nil, nil, "Platform Rate", "1.00"] - ] - - expected_result.each_with_index do |expected_attributes, row_index| - expect_total_attributes(totals[row_index], expected_attributes) - end - end - end - end - - describe "filtering results based on permissions" do - let!(:distributor_a) do - create(:distributor_enterprise, name: "Distributor A", payment_methods: [payment_method], - shipping_methods: [shipping_method]) - end - let!(:distributor_b) do - create(:distributor_enterprise, name: "Distributor B", payment_methods: [payment_method], - shipping_methods: [shipping_method]) - end - - let!(:order_cycle_a) { create(:simple_order_cycle, coordinator: coordinator) } - let!(:order_cycle_b) { create(:simple_order_cycle, coordinator: coordinator) } - - let!(:variant_a) { prepare_variant(distributor: distributor_a, order_cycle: order_cycle_a) } - let!(:variant_b) { prepare_variant(distributor: distributor_b, order_cycle: order_cycle_b) } - - let!(:order_a) { prepare_order(order_cycle: order_cycle_a, distributor: distributor_a) } - let!(:order_b) { prepare_order(order_cycle: order_cycle_b, distributor: distributor_b) } - - context "when admin" do - let!(:current_user) { create(:admin_user) } - - it "includes all order cycles" do - totals = service.list - - expect_total_matches(totals, 2, fee_type: "Shipment") - expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") - expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor B") - end - end - - context "when enterprise owner for distributor" do - let!(:current_user) { distributor_a.owner } - - it "does not include unrelated order cycles" do - totals = service.list - - expect_total_matches(totals, 1, fee_type: "Shipment") - expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") - end - end - end - - describe "filters entries correctly" do - let(:parameters) { report_klass::Parameters.new(parameters_attributes) } - - context "filtering by completion date" do - let(:timestamp) { Time.zone.local(2018, 1, 5, 14, 30, 5) } - - let!(:customer_a) { create(:customer, first_name: "Customer", last_name: "A") } - let!(:customer_b) { create(:customer, first_name: "Customer", last_name: "B") } - let!(:customer_c) { create(:customer, first_name: "Customer", last_name: "C") } - - let!(:order_placed_before_timestamp) do - prepare_order(customer: customer_a).tap do |order| - order.update_column(:completed_at, timestamp - 1.second) - end - end - - let!(:order_placed_during_timestamp) do - prepare_order(customer: customer_b).tap do |order| - order.update_column(:completed_at, timestamp) - end - end - - let!(:order_placed_after_timestamp) do - prepare_order(customer: customer_c).tap do |order| - order.update_column(:completed_at, timestamp + 1.second) - end - end - - context "on or after start_at" do - let(:parameters_attributes) { { start_at: timestamp } } - - it "filters entries" do - totals = service.list - - expect_total_matches(totals, 0, fee_type: "Shipment", customer_name: "Customer A") - expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer B") - expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer C") - end - end - - context "on or before end_at" do - let(:parameters_attributes) { { end_at: timestamp } } - - it "filters entries" do - totals = service.list - - expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer A") - expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer B") - expect_total_matches(totals, 0, fee_type: "Shipment", customer_name: "Customer C") - end - end - end - - describe "for specified shops" do - let!(:distributor_a) do - create(:distributor_enterprise, name: "Distributor A", payment_methods: [payment_method], - shipping_methods: [shipping_method]) - end - let!(:distributor_b) do - create(:distributor_enterprise, name: "Distributor B", payment_methods: [payment_method], - shipping_methods: [shipping_method]) - end - let!(:distributor_c) do - create(:distributor_enterprise, name: "Distributor C", payment_methods: [payment_method], - shipping_methods: [shipping_method]) - end - - let!(:order_a) { prepare_order(distributor: distributor_a) } - let!(:order_b) { prepare_order(distributor: distributor_b) } - let!(:order_c) { prepare_order(distributor: distributor_c) } - - let(:parameters_attributes) { { distributor_ids: [distributor_a.id, distributor_b.id] } } - - it "filters entries" do - totals = service.list - - expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") - expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor B") - expect_total_matches(totals, 0, fee_type: "Shipment", enterprise_name: "Distributor C") - end - end - - describe "for specified suppliers" do - let!(:producer_a) { create(:supplier_enterprise, name: "Producer A") } - let!(:producer_b) { create(:supplier_enterprise, name: "Producer B") } - let!(:producer_c) { create(:supplier_enterprise, name: "Producer C") } - - let!(:fee_a) { create(:enterprise_fee, name: "Fee A", enterprise: producer_a, amount: 1) } - let!(:fee_b) { create(:enterprise_fee, name: "Fee B", enterprise: producer_b, amount: 1) } - let!(:fee_c) { create(:enterprise_fee, name: "Fee C", enterprise: producer_c, amount: 1) } - - let!(:product_a) { create(:product, supplier: producer_a) } - let!(:product_b) { create(:product, supplier: producer_b) } - let!(:product_c) { create(:product, supplier: producer_c) } - - let!(:variant_a) do - prepare_variant(product: product_a, producer: producer_a, incoming_exchange_fees: [fee_a]) - end - let!(:variant_b) do - prepare_variant(product: product_b, producer: producer_b, incoming_exchange_fees: [fee_b]) - end - let!(:variant_c) do - prepare_variant(product: product_c, producer: producer_c, incoming_exchange_fees: [fee_c]) - end - - let!(:order_a) { prepare_order(variant: variant_a) } - let!(:order_b) { prepare_order(variant: variant_b) } - let!(:order_c) { prepare_order(variant: variant_c) } - - let(:parameters_attributes) { { producer_ids: [producer_a.id, producer_b.id] } } - - it "filters entries" do - totals = service.list - - expect_total_matches(totals, 1, fee_name: "Fee A", enterprise_name: "Producer A") - expect_total_matches(totals, 1, fee_name: "Fee B", enterprise_name: "Producer B") - expect_total_matches(totals, 0, fee_name: "Fee C", enterprise_name: "Producer C") - end - end - - describe "for specified order cycles" do - let!(:distributor_a) do - create(:distributor_enterprise, name: "Distributor A", payment_methods: [payment_method], - shipping_methods: [shipping_method]) - end - let!(:distributor_b) do - create(:distributor_enterprise, name: "Distributor B", payment_methods: [payment_method], - shipping_methods: [shipping_method]) - end - let!(:distributor_c) do - create(:distributor_enterprise, name: "Distributor C", payment_methods: [payment_method], - shipping_methods: [shipping_method]) - end - - let!(:order_cycle_a) { create(:simple_order_cycle, coordinator: coordinator) } - let!(:order_cycle_b) { create(:simple_order_cycle, coordinator: coordinator) } - let!(:order_cycle_c) { create(:simple_order_cycle, coordinator: coordinator) } - - let!(:variant_a) { prepare_variant(distributor: distributor_a, order_cycle: order_cycle_a) } - let!(:variant_b) { prepare_variant(distributor: distributor_b, order_cycle: order_cycle_b) } - let!(:variant_c) { prepare_variant(distributor: distributor_c, order_cycle: order_cycle_c) } - - let!(:order_a) { prepare_order(order_cycle: order_cycle_a, distributor: distributor_a) } - let!(:order_b) { prepare_order(order_cycle: order_cycle_b, distributor: distributor_b) } - let!(:order_c) { prepare_order(order_cycle: order_cycle_c, distributor: distributor_c) } - - let(:parameters_attributes) { { order_cycle_ids: [order_cycle_a.id, order_cycle_b.id] } } - - it "filters entries" do - totals = service.list - - expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") - expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor B") - expect_total_matches(totals, 0, fee_type: "Shipment", enterprise_name: "Distributor C") - end - end - - describe "for specified enterprise fees" do - let!(:fee_a) { create(:enterprise_fee, name: "Fee A", enterprise: distributor, amount: 1) } - let!(:fee_b) { create(:enterprise_fee, name: "Fee B", enterprise: distributor, amount: 1) } - let!(:fee_c) { create(:enterprise_fee, name: "Fee C", enterprise: distributor, amount: 1) } - - let!(:variant) { prepare_variant(outgoing_exchange_fees: variant_outgoing_exchange_fees) } - let!(:variant_outgoing_exchange_fees) { [fee_a, fee_b, fee_c] } - - let!(:order) { prepare_order(variant: variant) } - - let(:parameters_attributes) { { enterprise_fee_ids: [fee_a.id, fee_b.id] } } - - it "filters entries" do - totals = service.list - - expect_total_matches(totals, 1, fee_name: "Fee A") - expect_total_matches(totals, 1, fee_name: "Fee B") - expect_total_matches(totals, 0, fee_name: "Fee C") - end - end - - describe "for specified shipping methods" do - let!(:shipping_method_a) do - method = create(:shipping_method, name: "Shipping A", distributors: [distributor]) - method.calculator.update_attribute(:preferred_amount, 1) - method - end - let!(:shipping_method_b) do - method = create(:shipping_method, name: "Shipping B", distributors: [distributor]) - method.calculator.update_attribute(:preferred_amount, 1) - method - end - let!(:shipping_method_c) do - create(:shipping_method, name: "Shipping C", distributors: [distributor]) - end - - let!(:order_a) { prepare_order(shipping_method: shipping_method_a) } - let!(:order_b) { prepare_order(shipping_method: shipping_method_b) } - let!(:order_c) { prepare_order(shipping_method: shipping_method_c) } - - let(:parameters_attributes) do - { shipping_method_ids: [shipping_method_a.id, shipping_method_b.id] } - end - - it "filters entries" do - totals = service.list - - expect_total_matches(totals, 1, fee_name: "Shipping A") - expect_total_matches(totals, 1, fee_name: "Shipping B") - expect_total_matches(totals, 0, fee_name: "Shipping C") - end - end - - describe "for specified payment methods" do - let!(:payment_method_a) do - method = create(:payment_method, name: "Payment A", distributors: [distributor]) - method.calculator.update_attribute(:preferred_amount, 1) - method - end - let!(:payment_method_b) do - method = create(:payment_method, name: "Payment B", distributors: [distributor]) - method.calculator.update_attribute(:preferred_amount, 1) - method - end - let!(:payment_method_c) do - create(:payment_method, name: "Payment C", distributors: [distributor]) - end - - let!(:order_a) { prepare_order(payment_method: payment_method_a) } - let!(:order_b) { prepare_order(payment_method: payment_method_b) } - let!(:order_c) { prepare_order(payment_method: payment_method_c) } - - let(:parameters_attributes) do - { payment_method_ids: [payment_method_a.id, payment_method_b.id] } - end - - it "filters entries" do - totals = service.list - - expect_total_matches(totals, 1, fee_name: "Payment A") - expect_total_matches(totals, 1, fee_name: "Payment B") - expect_total_matches(totals, 0, fee_name: "Payment C") - end - end - end - - # Helper methods for example group - - def i18n_translate(translation_key, options = {}) - I18n.t("order_management.reports.enterprise_fee_summary.#{translation_key}", **options) - end - - def expect_total_attributes(total, expected_attribute_list) - actual_attribute_list = [total.fee_type, total.enterprise_name, total.fee_name, - total.customer_name, total.fee_placement, - total.fee_calculated_on_transfer_through_name, total.tax_category_name, - total.total_amount] - expect(actual_attribute_list).to eq(expected_attribute_list) - end - - def expect_total_matches(totals, count, attributes) - expect(count_totals(totals, attributes)).to eq(count) - end - - def default_order_options - { customer: customer, distributor: distributor, order_cycle: order_cycle, - shipping_method: shipping_method, variant: variant } - end - - def prepare_incomplete_order(options = {}) - target_options = default_order_options.merge(options) - create(:order, :with_line_item, target_options) - end - - def prepare_order(options = {}) - factory_trait_options = { payment_method: payment_method } - target_options = default_order_options.merge(factory_trait_options).merge(options) - create(:order, :with_line_item, :completed, target_options) - end - - def default_variant_options - { product: product, producer: producer, is_master: false, coordinator: coordinator, - distributor: distributor, order_cycle: order_cycle } - end - - def prepare_variant(options = {}) - target_options = default_variant_options.merge(options) - create(:variant, :with_order_cycle, target_options) - end - - def count_totals(totals, attributes) - totals.count do |data| - attributes.all? do |attribute_name, attribute_value| - data.public_send(attribute_name) == attribute_value - end - end - end -end diff --git a/lib/open_food_network/bulk_coop_allocation_report.rb b/lib/open_food_network/bulk_coop_allocation_report.rb new file mode 100644 index 0000000000..37d6071ec1 --- /dev/null +++ b/lib/open_food_network/bulk_coop_allocation_report.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module OpenFoodNetwork + class BulkCoopAllocationReport + def table_headers + [ + 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/bulk_coop_report.rb b/lib/open_food_network/bulk_coop_report.rb new file mode 100644 index 0000000000..271c172b48 --- /dev/null +++ b/lib/open_food_network/bulk_coop_report.rb @@ -0,0 +1,320 @@ +# frozen_string_literal: true + +require "open_food_network/reports/line_items" +require 'open_food_network/order_grouper' +require 'open_food_network/bulk_coop_allocation_report' +require 'open_food_network/bulk_coop_supplier_report' + +module OpenFoodNetwork + class BulkCoopReport + 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 + @filter_canceled = false + end + + def table_headers + case params[:report_subtype] + when "bulk_coop_supplier_report" + @supplier_report.table_headers + when "bulk_coop_allocation" + @allocation_report.table_headers + 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 table_rows + order_grouper = OpenFoodNetwork::OrderGrouper.new rules, columns, self + order_grouper.table(table_items) + end + + def rules + case params[:report_subtype] + 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| + ( if (lis.first.product.group_buy_unit_size || 0).zero? + 0 + else + ( lis.sum { |li| + [li.max_quantity || 0, + li.quantity || 0].max * (li.weight_from_unit_value || 0) + } / lis.first.product.group_buy_unit_size ) + end ).floor + }, + proc { |lis| + lis.sum { |li| + [li.max_quantity || 0, + li.quantity || 0].max * (li.weight_from_unit_value || 0) + } - ( ( if (lis.first.product.group_buy_unit_size || 0).zero? + 0 + else + ( lis.sum { |li| + [li.max_quantity || 0, + li.quantity || 0].max * (li.weight_from_unit_value || 0) + } / lis.first.product.group_buy_unit_size ) + end ).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_subtype] + 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 + + attr_reader :filter_canceled + + def line_item_includes + [ + { + order: [:bill_address], + variant: [{ option_values: :option_type }, { product: :supplier }] + }, + :option_values + ] + end + + def order_permissions + @order_permissions ||= ::Permissions::Order.new(@user, filter_canceled) + end + + def report_line_items + @report_line_items ||= OpenFoodNetwork::Reports::LineItems.new( + order_permissions, + @params, + CompleteVisibleOrders.new(order_permissions).query + ) + end + + def customer_payments_total_cost(line_items) + unique_orders(line_items).sum(&:total) + end + + def customer_payments_amount_owed(line_items) + unique_orders(line_items).sum(&:new_outstanding_balance) + end + + def customer_payments_amount_paid(line_items) + unique_orders(line_items).sum(&:payment_total) + end + + def unique_orders(line_items) + line_items.map(&:order).uniq + end + + def empty_cell(_line_items) + "" + end + + def full_name(line_items) + line_items.first.full_name + end + + def group_buy_unit_size(line_items) + unit_size = line_items.first.variant.product.group_buy_unit_size || 0.0 + unit_size / (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) + VariantUnits::OptionValueNamer.new(line_items.first).value + end + + def option_value_unit(line_items) + VariantUnits::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(_line_items) + 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/bulk_coop_supplier_report.rb b/lib/open_food_network/bulk_coop_supplier_report.rb new file mode 100644 index 0000000000..1f2645f81b --- /dev/null +++ b/lib/open_food_network/bulk_coop_supplier_report.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module OpenFoodNetwork + class BulkCoopSupplierReport + def table_headers + [ + 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 diff --git a/lib/open_food_network/reports/list.rb b/lib/open_food_network/reports/list.rb index 96bae561fc..0f03512b68 100644 --- a/lib/open_food_network/reports/list.rb +++ b/lib/open_food_network/reports/list.rb @@ -16,7 +16,8 @@ module OpenFoodNetwork order_cycle_management: order_cycle_management_report_types, sales_tax: sales_tax_report_types, packing: packing_report_types, - xero_invoices: xero_report_types + xero_invoices: xero_report_types, + bulk_coop: bulk_coop_report_types } end @@ -79,6 +80,19 @@ module OpenFoodNetwork [I18n.t(:detailed), 'detailed']] end + def bulk_coop_report_types + [ + bulk_coop_item(:bulk_coop_supplier_report), + bulk_coop_item(:bulk_coop_allocation), + bulk_coop_item(:bulk_coop_packing_sheets), + bulk_coop_item(:bulk_coop_customer_payments) + ] + end + + def bulk_coop_item(key) + [I18n.t("order_management.reports.bulk_coop.filters.#{key}"), key] + end + def i18n_translate(key) I18n.t(key, scope: "admin.reports") end diff --git a/engines/order_management/spec/services/order_management/reports/bulk_coop/bulk_coop_report_spec.rb b/spec/lib/open_food_network/bulk_coop_report_spec.rb similarity index 86% rename from engines/order_management/spec/services/order_management/reports/bulk_coop/bulk_coop_report_spec.rb rename to spec/lib/open_food_network/bulk_coop_report_spec.rb index 5084671813..f8552c73f3 100644 --- a/engines/order_management/spec/services/order_management/reports/bulk_coop/bulk_coop_report_spec.rb +++ b/spec/lib/open_food_network/bulk_coop_report_spec.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true require 'spec_helper' +require 'open_food_network/bulk_coop_report' -describe OrderManagement::Reports::BulkCoop::BulkCoopReport do - subject { OrderManagement::Reports::BulkCoop::BulkCoopReport.new user, params, true } +describe OpenFoodNetwork::BulkCoopReport do + subject { OpenFoodNetwork::BulkCoopReport.new user, params, true } let(:user) { create(:admin_user) } describe '#table_items' do @@ -61,15 +62,15 @@ describe OrderManagement::Reports::BulkCoop::BulkCoopReport do li2 = build(:line_item_with_shipment) o2.line_items << li2 - report = OrderManagement::Reports::BulkCoop::BulkCoopReport.new user, {}, true + report = OpenFoodNetwork::BulkCoopReport.new user, {}, true expect(report.table_items).to match_array [li1, li2] - report = OrderManagement::Reports::BulkCoop::BulkCoopReport.new( + report = OpenFoodNetwork::BulkCoopReport.new( user, { q: { completed_at_gt: 2.days.ago } }, true ) expect(report.table_items).to eq([li1]) - report = OrderManagement::Reports::BulkCoop::BulkCoopReport.new( + report = OpenFoodNetwork::BulkCoopReport.new( user, { q: { completed_at_lt: 2.days.ago } }, true ) expect(report.table_items).to eq([li2]) @@ -85,15 +86,15 @@ describe OrderManagement::Reports::BulkCoop::BulkCoopReport do li2 = build(:line_item_with_shipment) o2.line_items << li2 - report = OrderManagement::Reports::BulkCoop::BulkCoopReport.new user, {}, true + report = OpenFoodNetwork::BulkCoopReport.new user, {}, true expect(report.table_items).to match_array [li1, li2] - report = OrderManagement::Reports::BulkCoop::BulkCoopReport.new( + report = OpenFoodNetwork::BulkCoopReport.new( user, { q: { distributor_id_in: [d1.id] } }, true ) expect(report.table_items).to eq([li1]) - report = OrderManagement::Reports::BulkCoop::BulkCoopReport.new( + report = OpenFoodNetwork::BulkCoopReport.new( user, { q: { distributor_id_in: [d2.id] } }, true ) expect(report.table_items).to eq([li2]) @@ -102,7 +103,7 @@ describe OrderManagement::Reports::BulkCoop::BulkCoopReport do context "as a manager of a supplier" do let!(:user) { create(:user) } - subject { OrderManagement::Reports::BulkCoop::BulkCoopReport.new user, {}, true } + subject { OpenFoodNetwork::BulkCoopReport.new user, {}, true } let(:s1) { create(:supplier_enterprise) } @@ -153,7 +154,7 @@ describe OrderManagement::Reports::BulkCoop::BulkCoopReport do describe '#columns' do context 'when report type is bulk_coop_customer_payments' do - let(:params) { { report_type: 'bulk_coop_customer_payments' } } + let(:params) { { report_subtype: 'bulk_coop_customer_payments' } } it 'returns' do expect(subject.columns).to eq( @@ -170,7 +171,7 @@ describe OrderManagement::Reports::BulkCoop::BulkCoopReport do end # Yes, I know testing a private method is bad practice but report's design, tighly coupling - # OpenFoodNetwork::OrderGrouper and OrderManagement::Reports::BulkCoop::BulkCoopReport, makes it + # OpenFoodNetwork::OrderGrouper and OpenFoodNetwork::BulkCoopReport, makes it # very hard to make things testeable without ending up in a wormwhole. This is a trade-off. describe '#customer_payments_amount_owed' do let(:params) { {} } diff --git a/spec/support/matchers/table_matchers.rb b/spec/support/matchers/table_matchers.rb index 7c33b3a5c6..037a0d5d1d 100644 --- a/spec/support/matchers/table_matchers.rb +++ b/spec/support/matchers/table_matchers.rb @@ -11,8 +11,8 @@ RSpec::Matchers.define :have_table_row do |row| !rows_under(node).include? row # Robust check of columns end - failure_message do |_text| - "expected to find table row #{@row}" + failure_message do |text| + "expected to find table row #{@row}, got #{text}" end failure_message_when_negated do |_text| diff --git a/spec/system/admin/reports_spec.rb b/spec/system/admin/reports_spec.rb index 3bd13bc8fe..a0f59eca8e 100644 --- a/spec/system/admin/reports_spec.rb +++ b/spec/system/admin/reports_spec.rb @@ -380,6 +380,76 @@ describe ' end end + describe 'bulk coop report' do + before do + login_as_admin_and_visit spree.admin_reports_path + click_link 'Bulk Co-Op' + end + + xit "generating Bulk Co-op Supplier Report" do + select "Bulk Co-op Supplier Report", from: "report_subtype" + click_button 'Go' + + expect(page).to have_table_row [ + "Supplier", + "Product", + "Bulk Unit Size", + "Variant", + "Variant Value", + "Variant Unit", + "Weight", + "Sum Total", + "Units Required", + "Unallocated", + "Max Quantity Excess" + ] + end + + xit "generating Bulk Co-op Allocation report" do + select "Bulk Co-op Allocation", from: "report_subtype" + click_button 'Go' + + expect(page).to have_table_row [ + "Customer", + "Product", + "Bulk Unit Size", + "Variant", + "Variant Value", + "Variant Unit", + "Weight", + "Sum Total", + "Total available", + "Unallocated", + "Max Quantity Excess" + ] + end + + xit "generating Bulk Co-op Packing Sheets report" do + select "Bulk Co-op Packing Sheets", from: "report_subtype" + click_button 'Go' + + expect(page).to have_table_row [ + "Customer", + "Product", + "Variant", + "Sum Total" + ] + end + + xit "generating Bulk Co-op Customer Payments report" do + select "Bulk Co-op Customer Payments", from: "report_subtype" + click_button 'Go' + + expect(page).to have_table_row [ + "Customer", + "Date of Order", + "Total Cost", + "Amount Owing", + "Amount Paid" + ] + end + end + describe "Xero invoices report" do let(:distributor1) { create(:distributor_enterprise, with_payment_and_shipping: true, charges_sales_tax: true) @@ -552,6 +622,7 @@ describe ' end end + private def xero_invoice_table @@ -560,10 +631,10 @@ describe ' def xero_invoice_header %w(*ContactName EmailAddress POAddressLine1 POAddressLine2 POAddressLine3 POAddressLine4 - POCity PORegion POPostalCode POCountry *InvoiceNumber Reference *InvoiceDate - *DueDate InventoryItemCode *Description *Quantity *UnitAmount Discount *AccountCode - *TaxType TrackingName1 TrackingOption1 TrackingName2 TrackingOption2 Currency BrandingTheme - Paid?).map(&:upcase) + POCity PORegion POPostalCode POCountry *InvoiceNumber Reference *InvoiceDate + *DueDate InventoryItemCode *Description *Quantity *UnitAmount Discount *AccountCode + *TaxType TrackingName1 TrackingOption1 TrackingName2 TrackingOption2 Currency BrandingTheme + Paid?).map(&:upcase) end def xero_invoice_summary_row(description, amount, tax_type, opts = {}) @@ -573,7 +644,7 @@ describe ' def xero_invoice_li_row(line_item, opts = {}) tax_type = line_item.has_tax? ? 'GST on Income' : 'GST Free Income' xero_invoice_row line_item.product.sku, line_item.product_and_full_name, - line_item.price.to_s, line_item.quantity.to_s, tax_type, opts + line_item.price.to_s, line_item.quantity.to_s, tax_type, opts end def xero_invoice_adjustment_row(adjustment, opts = {}) @@ -589,14 +660,14 @@ describe ' due_date: '2021-05-26', account_code: 'food sales') [opts[:customer_name], 'customer@email.com', opts[:address1], '', '', '', - opts[:city], opts[:state], opts[:zipcode], opts[:country], opts[:invoice_number], - opts[:order_number], opts[:invoice_date], opts[:due_date], + opts[:city], opts[:state], opts[:zipcode], opts[:country], opts[:invoice_number], + opts[:order_number], opts[:invoice_date], opts[:due_date], - sku, - description, - quantity, - amount.to_s, '', opts[:account_code], tax_type, '', '', '', '', Spree::Config.currency, - '', 'N'] + sku, + description, + quantity, + amount.to_s, '', opts[:account_code], tax_type, '', '', '', '', Spree::Config.currency, + '', 'N'] end end end From 2985d2af15c728732d932eb87876586034d5a29a Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 27 Mar 2022 10:26:14 +0000 Subject: [PATCH 17/54] Report Refactor 1 Cleaning clean no longer used methods Remove no longer needed require Delete files not used Fix Linting --- .rubocop_todo.yml | 1 + .../spree/admin/reports_controller.rb | 44 ++------ .../order_management/app/services/reports.rb | 5 - .../enterprise_fee_summary_report_spec.rb | 2 + .../renderers/csv_renderer_spec.rb | 3 +- lib/open_food_network/group_buy_report.rb | 71 ------------ lib/open_food_network/lettuce_share_report.rb | 1 + .../bulk_coop_report_spec.rb | 2 +- .../customers_report_spec.rb | 18 +-- .../group_buy_report_spec.rb | 105 ------------------ .../order_cycle_management_report_spec.rb | 50 ++++----- spec/system/admin/reports_spec.rb | 25 ++--- 12 files changed, 65 insertions(+), 262 deletions(-) delete mode 100644 engines/order_management/app/services/reports.rb delete mode 100644 lib/open_food_network/group_buy_report.rb delete mode 100644 spec/lib/open_food_network/group_buy_report_spec.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 777eddf4c2..bb9a3b0171 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1135,6 +1135,7 @@ Style/OptionalBooleanParameter: - 'app/models/spree/shipment.rb' - 'lib/open_food_network/bulk_coop_report.rb' - 'engines/order_management/app/services/order_management/stock/estimator.rb' + - 'engines/order_management/app/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb' - 'lib/open_food_network/customers_report.rb' - 'lib/open_food_network/orders_and_distributors_report.rb' - 'lib/open_food_network/order_cycle_management_report.rb' diff --git a/app/controllers/spree/admin/reports_controller.rb b/app/controllers/spree/admin/reports_controller.rb index 43693ca1d4..ce8dcc4156 100644 --- a/app/controllers/spree/admin/reports_controller.rb +++ b/app/controllers/spree/admin/reports_controller.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'csv' - require 'open_food_network/reports/list' require 'open_food_network/orders_and_distributors_report' require 'open_food_network/products_and_inventory_report' @@ -42,25 +40,25 @@ module Spree end def customers - render_report2 + render_report end def order_cycle_management - render_report2 + render_report end def orders_and_distributors - render_report2 + render_report end def sales_tax @distributors = my_distributors - render_report2 + render_report end def payments @distributors = my_distributors - render_report2 + render_report end def orders_and_fulfillment @@ -78,33 +76,33 @@ module Spree @report_message = I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") - render_report2 + render_report end def products_and_inventory - render_report2 + render_report end def users_and_enterprises - render_report2 + render_report end def xero_invoices @distributors = my_distributors @order_cycles = my_order_cycles - render_report2 + render_report end def bulk_coop @distributors = my_distributors @report_message = I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") - render_report2 + render_report end def enterprise_fee_summary @report_message = I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") - render_report2 + render_report end private @@ -143,7 +141,7 @@ module Spree @searching end - def render_report2 + def render_report @report_subtypes = report_types[action_name.to_sym] @report_subtype = params[:report_subtype] klass = if action_name == 'enterprise_fee_summary' @@ -163,19 +161,6 @@ module Spree end end - def render_report(header, table, create_csv, csv_file_name) - send_data csv_report(header, table), filename: csv_file_name if create_csv - @header = header - @table = table - end - - def csv_report(header, table) - CSV.generate do |csv| - csv << header - table.each { |row| csv << row } - end - end - def load_basic_data @distributors = my_distributors @suppliers = my_suppliers | suppliers_of_products_distributed_by(@distributors) @@ -207,11 +192,6 @@ module Spree order('orders_close_at DESC') end - def order_grouper_table - order_grouper = OpenFoodNetwork::OrderGrouper.new @report.rules, @report.columns, @report - order_grouper.table(@report.table_items) - end - def authorized_reports all_reports = [ :orders_and_distributors, diff --git a/engines/order_management/app/services/reports.rb b/engines/order_management/app/services/reports.rb deleted file mode 100644 index 5a25947ad7..0000000000 --- a/engines/order_management/app/services/reports.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -module Reports - class UnsupportedReportFormatException < StandardError; end -end diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report_spec.rb index 90fcb3c5f1..d18a21b83e 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report_spec.rb @@ -2,6 +2,7 @@ require "spec_helper" +# rubocop:disable Layout/LineLength # describe OrderManagement::Reports::EnterpriseFeeSummary::EnterpriseFeeSummaryReport do # let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } @@ -727,3 +728,4 @@ require "spec_helper" # end # end # end +# rubocop:enable Layout/LineLength diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb index 5f1b568450..f21f44c472 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb @@ -10,7 +10,8 @@ require "spec_helper" # let!(:service) { report_klass::ReportService.new(permissions, parameters) } # let!(:renderer) { described_class.new(service) } -# # Context which will be passed to the renderer. The response object is not automatically prepared, +# # Context which will be passed to the renderer. The response object +# # is not automatically prepared, # # so this has to be assigned explicitly. # let!(:response) { ActionDispatch::TestResponse.new } # let!(:request) { double(Rack::Request) } diff --git a/lib/open_food_network/group_buy_report.rb b/lib/open_food_network/group_buy_report.rb deleted file mode 100644 index c6ab52a6df..0000000000 --- a/lib/open_food_network/group_buy_report.rb +++ /dev/null @@ -1,71 +0,0 @@ -# frozen_string_literal: true - -module OpenFoodNetwork - GroupBuyVariantRow = Struct.new(:variant, :sum_quantities, :sum_max_quantities) do - def to_row - [variant.product.supplier.name, variant.product.name, I18n.t('admin.reports.unitsize'), - variant.options_text, variant.weight, sum_quantities, sum_max_quantities] - end - end - - GroupBuyProductRow = Struct.new(:product, :sum_quantities, :sum_max_quantities) do - def to_row - [product.supplier.name, product.name, I18n.t('admin.reports.unitsize'), - I18n.t('admin.reports.total'), "", sum_quantities, sum_max_quantities] - end - end - - class GroupBuyReport - def initialize(orders) - @orders = orders - end - - def header - [ - I18n.t(:report_header_supplier), - I18n.t(:report_header_product), - I18n.t(:report_header_unit_size), - I18n.t(:report_header_variant), - I18n.t(:report_header_weight), - I18n.t(:report_header_total_ordered), - I18n.t(:report_header_total_max), - ] - end - - def variants_and_quantities - variants_and_quantities = [] - line_items = @orders.map(&:line_items).flatten - supplier_groups = line_items.group_by { |li| li.variant.product.supplier } - supplier_groups.each do |_supplier, line_items_by_supplier| - product_groups = line_items_by_supplier.group_by { |li| li.variant.product } - product_groups.each do |product, line_items_by_product| - # Cycle thorugh variant of a product - variant_groups = line_items_by_product.group_by(&:variant) - variant_groups.each do |variant, line_items_by_variant| - sum_quantities = line_items_by_variant.to_a.sum(&:quantity) - sum_max_quantities = line_items_by_variant.sum { |li| li.max_quantity || 0 } - variants_and_quantities << GroupBuyVariantRow.new(variant, sum_quantities, - sum_max_quantities) - end - - # Sum quantities for each product (Total line) - sum_quantities = line_items_by_product.sum { |li| (li.variant.weight || 0) * li.quantity } - sum_max_quantities = line_items_by_product.sum { |li| - (li.variant.weight || 0) * (li.max_quantity || 0) - } - variants_and_quantities << GroupBuyProductRow.new(product, sum_quantities, - sum_max_quantities) - end - end - variants_and_quantities - end - - def table - table = [] - variants_and_quantities.each do |vr| - table << vr.to_row - end - table - end - end -end diff --git a/lib/open_food_network/lettuce_share_report.rb b/lib/open_food_network/lettuce_share_report.rb index a98c26e6f1..b465769568 100644 --- a/lib/open_food_network/lettuce_share_report.rb +++ b/lib/open_food_network/lettuce_share_report.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'variant_units/option_value_namer' module OpenFoodNetwork diff --git a/spec/lib/open_food_network/bulk_coop_report_spec.rb b/spec/lib/open_food_network/bulk_coop_report_spec.rb index f8552c73f3..6fe1f36809 100644 --- a/spec/lib/open_food_network/bulk_coop_report_spec.rb +++ b/spec/lib/open_food_network/bulk_coop_report_spec.rb @@ -181,7 +181,7 @@ describe OpenFoodNetwork::BulkCoopReport do it 'calls #new_outstanding_balance' do expect_any_instance_of(Spree::Order).to receive(:new_outstanding_balance) - subject.send(:customer_payments_amount_owed, [line_item]) + subject.__send__(:customer_payments_amount_owed, [line_item]) end end end diff --git a/spec/lib/open_food_network/customers_report_spec.rb b/spec/lib/open_food_network/customers_report_spec.rb index ebe963c47a..e59633f2f5 100644 --- a/spec/lib/open_food_network/customers_report_spec.rb +++ b/spec/lib/open_food_network/customers_report_spec.rb @@ -30,8 +30,8 @@ module OpenFoodNetwork allow(subject).to receive(:orders).and_return [order] expect(subject.table_rows).to eq([[ - "test@test.com", "Firsty", "Lasty", "Suburbia" - ]]) + "test@test.com", "Firsty", "Lasty", "Suburbia" + ]]) end end @@ -42,7 +42,7 @@ module OpenFoodNetwork it "returns headers for addresses" do expect(subject.table_headers).to eq(["First Name", "Last Name", "Billing Address", "Email", - "Phone", "Hub", "Hub Address", "Shipping Method"]) + "Phone", "Hub", "Hub Address", "Shipping Method"]) end it "builds a table from a list of variants" do @@ -53,12 +53,12 @@ module OpenFoodNetwork allow(subject).to receive(:orders).and_return [o] expect(subject.table_rows).to eq([[ - a.firstname, a.lastname, - [a.address1, a.address2, a.city].join(" "), - o.email, a.phone, d.name, - [d.address.address1, d.address.address2, d.address.city].join(" "), - o.shipping_method.name - ]]) + a.firstname, a.lastname, + [a.address1, a.address2, a.city].join(" "), + o.email, a.phone, d.name, + [d.address.address1, d.address.address2, d.address.city].join(" "), + o.shipping_method.name + ]]) end end diff --git a/spec/lib/open_food_network/group_buy_report_spec.rb b/spec/lib/open_food_network/group_buy_report_spec.rb deleted file mode 100644 index cbb633f6aa..0000000000 --- a/spec/lib/open_food_network/group_buy_report_spec.rb +++ /dev/null @@ -1,105 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'open_food_network/group_buy_report' - -module OpenFoodNetwork - describe GroupBuyReport do - before(:each) do - @orders = [] - bill_address = create(:address) - distributor_address = create(:address, address1: "distributor address", city: 'The Shire', - zipcode: "1234") - distributor = create(:distributor_enterprise, address: distributor_address) - - @supplier1 = create(:supplier_enterprise) - @variant1 = create(:variant) - @variant1.product.supplier = @supplier1 - @variant1.product.save! - @variant1.reload - - shipping_instructions = "pick up on thursday please!" - - order1 = create(:order, distributor: distributor, bill_address: bill_address, - special_instructions: shipping_instructions) - line_item11 = create(:line_item, variant: @variant1, order: order1) - @orders << order1.reload - - order2 = create(:order, distributor: distributor, bill_address: bill_address, - special_instructions: shipping_instructions) - line_item21 = create(:line_item, variant: @variant1, order: order2) - - @variant2 = create(:variant) - @variant2.product.supplier = @supplier1 - @variant2.product.save! - - line_item22 = create(:line_item, variant: @variant2, order: order2) - @orders << order2.reload - - @supplier2 = create(:supplier_enterprise) - @variant3 = create(:variant, weight: nil) - @variant3.product.supplier = @supplier2 - @variant3.product.save! - - order3 = create(:order, distributor: distributor, bill_address: bill_address, - special_instructions: shipping_instructions) - line_item31 = create(:line_item, variant: @variant3, order: order3) - @orders << order3.reload - end - - it "should return a header row describing the report" do - subject = GroupBuyReport.new [@order1] - header = subject.header - expect(header).to eq(["Supplier", "Product", "Unit Size", "Variant", "Weight", - "Total Ordered", "Total Max"]) - end - - it "should provide the required variant and quantity information in a table" do - subject = GroupBuyReport.new @orders - - table = subject.table - - line_items = @orders.map(&:line_items).flatten.select{ |li| - li.product.supplier == @supplier1 && li.variant == @variant1 - } - - sum_quantities = line_items.map(&:quantity).sum - sum_max_quantities = line_items.map { |li| li.max_quantity || 0 }.sum - - expect(table[0]).to eq([@variant1.product.supplier.name, @variant1.product.name, "UNITSIZE", - @variant1.options_text, @variant1.weight, sum_quantities, sum_max_quantities]) - end - - it "should return a table wherein each rows contains the same number of columns as the heading" do - subject = GroupBuyReport.new @orders - - table = subject.table - columns = subject.header.length - - table.each do |r| - expect(r.length).to eq(columns) - end - end - - it "should split and group line items from multiple suppliers and of multiple variants" do - subject = GroupBuyReport.new @orders - - table_row_objects = subject.variants_and_quantities - - variant_rows = table_row_objects.select { |r| - r.instance_of?(OpenFoodNetwork::GroupBuyVariantRow) - } - product_rows = table_row_objects.select { |r| - r.instance_of?(OpenFoodNetwork::GroupBuyProductRow) - } - - supplier_groups = variant_rows.group_by { |r| r.variant.product.supplier } - variant_groups = variant_rows.group_by(&:variant) - product_groups = product_rows.group_by(&:product) - - expect(supplier_groups.length).to eq(2) - expect(variant_groups.length).to eq(3) - expect(product_groups.length).to eq(3) - end - end -end diff --git a/spec/lib/open_food_network/order_cycle_management_report_spec.rb b/spec/lib/open_food_network/order_cycle_management_report_spec.rb index 01f92e8ecd..56d7781c5f 100644 --- a/spec/lib/open_food_network/order_cycle_management_report_spec.rb +++ b/spec/lib/open_food_network/order_cycle_management_report_spec.rb @@ -165,17 +165,17 @@ module OpenFoodNetwork it 'returns rows with payment information' do expect(subject.table_rows).to eq([[ - order.billing_address.firstname, - order.billing_address.lastname, - order.distributor.name, - '', - order.email, - order.billing_address.phone, - order.shipment.shipping_method.name, - nil, - order.total, - -order.total - ]]) + order.billing_address.firstname, + order.billing_address.lastname, + order.distributor.name, + '', + order.email, + order.billing_address.phone, + order.shipment.shipping_method.name, + nil, + order.total, + -order.total + ]]) end end @@ -191,20 +191,20 @@ module OpenFoodNetwork it 'returns rows with delivery information' do expect(subject.table_rows).to eq([[ - order.ship_address.firstname, - order.ship_address.lastname, - order.distributor.name, - "", - "#{order.ship_address.address1} #{order.ship_address.address2} #{order.ship_address.city}", - order.ship_address.zipcode, - order.ship_address.phone, - order.shipment.shipping_method.name, - nil, - order.total, - -order.total, - false, - order.special_instructions - ]]) + order.ship_address.firstname, + order.ship_address.lastname, + order.distributor.name, + "", + "#{order.ship_address.address1} #{order.ship_address.address2} #{order.ship_address.city}", + order.ship_address.zipcode, + order.ship_address.phone, + order.shipment.shipping_method.name, + nil, + order.total, + -order.total, + false, + order.special_instructions + ]]) end end end diff --git a/spec/system/admin/reports_spec.rb b/spec/system/admin/reports_spec.rb index a0f59eca8e..f2c372d129 100644 --- a/spec/system/admin/reports_spec.rb +++ b/spec/system/admin/reports_spec.rb @@ -622,7 +622,6 @@ describe ' end end - private def xero_invoice_table @@ -631,10 +630,10 @@ describe ' def xero_invoice_header %w(*ContactName EmailAddress POAddressLine1 POAddressLine2 POAddressLine3 POAddressLine4 - POCity PORegion POPostalCode POCountry *InvoiceNumber Reference *InvoiceDate - *DueDate InventoryItemCode *Description *Quantity *UnitAmount Discount *AccountCode - *TaxType TrackingName1 TrackingOption1 TrackingName2 TrackingOption2 Currency BrandingTheme - Paid?).map(&:upcase) + POCity PORegion POPostalCode POCountry *InvoiceNumber Reference *InvoiceDate + *DueDate InventoryItemCode *Description *Quantity *UnitAmount Discount *AccountCode + *TaxType TrackingName1 TrackingOption1 TrackingName2 TrackingOption2 Currency BrandingTheme + Paid?).map(&:upcase) end def xero_invoice_summary_row(description, amount, tax_type, opts = {}) @@ -644,7 +643,7 @@ describe ' def xero_invoice_li_row(line_item, opts = {}) tax_type = line_item.has_tax? ? 'GST on Income' : 'GST Free Income' xero_invoice_row line_item.product.sku, line_item.product_and_full_name, - line_item.price.to_s, line_item.quantity.to_s, tax_type, opts + line_item.price.to_s, line_item.quantity.to_s, tax_type, opts end def xero_invoice_adjustment_row(adjustment, opts = {}) @@ -660,14 +659,14 @@ describe ' due_date: '2021-05-26', account_code: 'food sales') [opts[:customer_name], 'customer@email.com', opts[:address1], '', '', '', - opts[:city], opts[:state], opts[:zipcode], opts[:country], opts[:invoice_number], - opts[:order_number], opts[:invoice_date], opts[:due_date], + opts[:city], opts[:state], opts[:zipcode], opts[:country], opts[:invoice_number], + opts[:order_number], opts[:invoice_date], opts[:due_date], - sku, - description, - quantity, - amount.to_s, '', opts[:account_code], tax_type, '', '', '', '', Spree::Config.currency, - '', 'N'] + sku, + description, + quantity, + amount.to_s, '', opts[:account_code], tax_type, '', '', '', '', Spree::Config.currency, + '', 'N'] end end end From 392166b57ae69e51ad0756e3788dd3143d9f3a78 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 27 Mar 2022 08:59:20 +0000 Subject: [PATCH 18/54] Reports Refactor 2: Simplify render_content? method --- .../spree/admin/reports_controller.rb | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/app/controllers/spree/admin/reports_controller.rb b/app/controllers/spree/admin/reports_controller.rb index ce8dcc4156..552def4802 100644 --- a/app/controllers/spree/admin/reports_controller.rb +++ b/app/controllers/spree/admin/reports_controller.rb @@ -24,7 +24,6 @@ module Spree helper_method :render_content? - before_action :cache_search_state # Fetches user's distributors, suppliers and order_cycles before_action :load_basic_data, only: [:customers, :products_and_inventory, :order_cycle_management] @@ -111,34 +110,10 @@ module Spree Spree::Admin::ReportsController end - # Some actions are changing the `params` object. That is unfortunate Spree - # behavior and we are building on it. So we have to look at `params` early - # to check if we are searching or just displaying a report search form. - def cache_search_state - search_keys = [ - # search parameter for ransack - :q, - # common in all reports, only set for CSV rendering - :csv, - # `button` is included in all forms. It's not important for searching, - # but the Users & Enterprises report doesn't have any other parameter - # for an empty search. So we use this one to display data. - :button, - # Some reports use filtering by enterprise or order cycle - :distributor_id, - :supplier_id, - :order_cycle_id, - # Xero Invoices can be filtered by date - :invoice_date, - :due_date - ] - @searching = search_keys.any? { |key| raw_params.key? key } - end - # We don't want to render data unless search params are supplied. # Compiling data can take a long time. def render_content? - @searching + request.post? end def render_report From 5f78fdce8b566783cbb54926ce8bae74e37ff875 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Tue, 29 Mar 2022 19:01:39 +0200 Subject: [PATCH 19/54] Reports Refactor 2: Move all code to lib/reporting --- .rubocop_todo.yml | 182 +++++----- .../spree/admin/reports_controller.rb | 34 +- app/validators/date_time_string_validator.rb | 13 +- app/validators/integer_array_validator.rb | 13 +- .../app/services/reports/authorizer.rb | 29 -- .../app/services/reports/parameters/base.rb | 35 -- .../app/services/reports/report_data/base.rb | 13 - .../enterprise_fee_summary/authorizer_spec.rb | 174 ---------- .../enterprise_fee_summary/parameters_spec.rb | 90 ----- .../bulk_coop_allocation_report.rb | 63 ---- lib/open_food_network/bulk_coop_report.rb | 320 ------------------ .../bulk_coop_supplier_report.rb | 61 ---- lib/open_food_network/customers_report.rb | 99 ------ lib/open_food_network/lettuce_share_report.rb | 89 ----- .../order_cycle_management_report.rb | 165 --------- .../order_cycle_permissions.rb | 20 +- .../orders_and_distributors_report.rb | 112 ------ .../orders_and_fulfillment_report.rb | 120 ------- .../customer_totals_report.rb | 221 ------------ .../default_report.rb | 64 ---- .../distributor_totals_by_supplier_report.rb | 78 ----- .../supplier_totals_by_distributor_report.rb | 77 ----- .../supplier_totals_report.rb | 60 ---- lib/open_food_network/payments_report.rb | 144 -------- .../products_and_inventory_default_report.rb | 53 --- .../products_and_inventory_report.rb | 100 ------ lib/open_food_network/reports/line_items.rb | 59 ---- lib/open_food_network/sales_tax_report.rb | 108 ------ .../users_and_enterprises_report.rb | 138 -------- lib/open_food_network/xero_invoices_report.rb | 235 ------------- lib/reporting/line_items.rb | 57 ++++ .../order_grouper.rb | 2 +- .../bulk_coop/bulk_coop_allocation_report.rb | 67 ++++ .../reports/bulk_coop/bulk_coop_report.rb | 319 +++++++++++++++++ .../bulk_coop/bulk_coop_supplier_report.rb | 65 ++++ .../reports/customers/customers_report.rb | 105 ++++++ .../enterprise_fee_summary/authorizer.rb | 4 +- .../data_representations/coordinator_fee.rb | 2 +- .../exchange_order_fee.rb | 2 +- .../incoming_exchange_line_item_fee.rb | 2 +- .../outgoing_exchange_line_item_fee.rb | 2 +- .../payment_method_fee.rb | 2 +- .../shipping_method_fee.rb | 2 +- .../using_enterprise_fee.rb | 2 +- .../data_representations/with_i18n.rb | 2 +- .../enterprise_fee_summary_report.rb | 4 +- .../parameter_not_allowed_error.rb | 9 + .../enterprise_fee_summary/parameters.rb | 4 +- .../enterprise_fee_summary/permissions.rb | 2 +- .../report_data/enterprise_fee_type_total.rb | 4 +- .../reports/authorizer.rb | 33 ++ .../reports/parameters/base.rb | 41 +++ .../reports/report_data/base.rb | 19 ++ .../reports/enterprise_fee_summary/scope.rb | 2 +- .../enterprise_fee_summary/summarizer.rb | 2 +- .../reports/list.rb | 2 +- .../order_cycle_management_report.rb | 170 ++++++++++ .../orders_and_distributors_report.rb | 116 +++++++ .../customer_totals_report.rb | 223 ++++++++++++ .../orders_and_fulfillment/default_report.rb | 66 ++++ .../distributor_totals_by_supplier_report.rb | 80 +++++ .../orders_and_fulfillment_report.rb | 116 +++++++ .../supplier_totals_by_distributor_report.rb | 79 +++++ .../supplier_totals_report.rb | 62 ++++ .../reports/payments/payments_report.rb | 150 ++++++++ .../lettuce_share_report.rb | 93 +++++ .../products_and_inventory_default_report.rb | 57 ++++ .../products_and_inventory_report.rb | 102 ++++++ .../reports/sales_tax/sales_tax_report.rb | 114 +++++++ .../users_and_enterprises_report.rb | 143 ++++++++ .../xero_invoices/xero_invoices_report.rb | 239 +++++++++++++ .../spree/admin/reports_controller_spec.rb | 4 +- .../customers_report_spec.rb | 163 --------- .../lettuce_share_report_spec.rb | 90 ----- .../order_cycle_management_report_spec.rb | 213 ------------ .../orders_and_distributors_report_spec.rb | 87 ----- .../customer_totals_report_spec.rb | 127 ------- ...tributor_totals_by_supplier_report_spec.rb | 39 --- ...plier_totals_by_distributor_report_spec.rb | 39 --- .../supplier_totals_report_spec.rb | 33 -- .../orders_and_fulfillments_report_spec.rb | 277 --------------- ...ducts_and_inventory_default_report_spec.rb | 261 -------------- .../sales_tax_report_spec.rb | 64 ---- .../users_and_enterprises_report_spec.rb | 125 ------- .../xero_invoices_report_spec.rb | 96 ------ .../bulk_coop_report_spec.rb | 21 +- spec/lib/reports/customers_report_spec.rb | 166 +++++++++ .../enterprise_fee_summary/authorizer_spec.rb | 179 ++++++++++ .../enterprise_fee_summary_report_spec.rb | 4 +- .../enterprise_fee_summary/parameters_spec.rb | 92 +++++ .../permissions_spec.rb | 2 +- .../renderers/csv_renderer_spec.rb | 4 +- .../renderers/html_renderer_spec.rb | 6 +- .../enterprise_fee_type_total_spec.rb | 2 +- spec/lib/reports/lettuce_share_report_spec.rb | 95 ++++++ .../reports/line_items_spec.rb | 3 +- .../order_cycle_management_report_spec.rb | 216 ++++++++++++ .../order_grouper_spec.rb | 7 +- .../orders_and_distributors_report_spec.rb | 90 +++++ .../customer_totals_report_spec.rb | 131 +++++++ ...tributor_totals_by_supplier_report_spec.rb | 44 +++ .../orders_and_fulfillment_report_spec.rb | 284 ++++++++++++++++ ...plier_totals_by_distributor_report_spec.rb | 44 +++ .../supplier_totals_report_spec.rb | 38 +++ ...ducts_and_inventory_default_report_spec.rb | 267 +++++++++++++++ spec/lib/reports/sales_tax_report_spec.rb | 67 ++++ .../users_and_enterprises_report_spec.rb | 128 +++++++ spec/lib/reports/xero_invoices_report_spec.rb | 99 ++++++ .../date_time_string_validator_spec.rb | 10 +- .../integer_array_validator_spec.rb | 8 +- 110 files changed, 4654 insertions(+), 4507 deletions(-) delete mode 100644 engines/order_management/app/services/reports/authorizer.rb delete mode 100644 engines/order_management/app/services/reports/parameters/base.rb delete mode 100644 engines/order_management/app/services/reports/report_data/base.rb delete mode 100644 engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/authorizer_spec.rb delete mode 100644 engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/parameters_spec.rb delete mode 100644 lib/open_food_network/bulk_coop_allocation_report.rb delete mode 100644 lib/open_food_network/bulk_coop_report.rb delete mode 100644 lib/open_food_network/bulk_coop_supplier_report.rb delete mode 100644 lib/open_food_network/customers_report.rb delete mode 100644 lib/open_food_network/lettuce_share_report.rb delete mode 100644 lib/open_food_network/order_cycle_management_report.rb delete mode 100644 lib/open_food_network/orders_and_distributors_report.rb delete mode 100644 lib/open_food_network/orders_and_fulfillment_report.rb delete mode 100644 lib/open_food_network/orders_and_fulfillment_report/customer_totals_report.rb delete mode 100644 lib/open_food_network/orders_and_fulfillment_report/default_report.rb delete mode 100644 lib/open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report.rb delete mode 100644 lib/open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report.rb delete mode 100644 lib/open_food_network/orders_and_fulfillment_report/supplier_totals_report.rb delete mode 100644 lib/open_food_network/payments_report.rb delete mode 100644 lib/open_food_network/products_and_inventory_default_report.rb delete mode 100644 lib/open_food_network/products_and_inventory_report.rb delete mode 100644 lib/open_food_network/reports/line_items.rb delete mode 100644 lib/open_food_network/sales_tax_report.rb delete mode 100644 lib/open_food_network/users_and_enterprises_report.rb delete mode 100644 lib/open_food_network/xero_invoices_report.rb create mode 100644 lib/reporting/line_items.rb rename lib/{open_food_network => reporting}/order_grouper.rb (98%) create mode 100644 lib/reporting/reports/bulk_coop/bulk_coop_allocation_report.rb create mode 100644 lib/reporting/reports/bulk_coop/bulk_coop_report.rb create mode 100644 lib/reporting/reports/bulk_coop/bulk_coop_supplier_report.rb create mode 100644 lib/reporting/reports/customers/customers_report.rb rename {engines/order_management/app/services/order_management => lib/reporting}/reports/enterprise_fee_summary/authorizer.rb (89%) rename {engines/order_management/app/services/order_management => lib/reporting}/reports/enterprise_fee_summary/data_representations/coordinator_fee.rb (97%) rename {engines/order_management/app/services/order_management => lib/reporting}/reports/enterprise_fee_summary/data_representations/exchange_order_fee.rb (96%) rename {engines/order_management/app/services/order_management => lib/reporting}/reports/enterprise_fee_summary/data_representations/incoming_exchange_line_item_fee.rb (96%) rename {engines/order_management/app/services/order_management => lib/reporting}/reports/enterprise_fee_summary/data_representations/outgoing_exchange_line_item_fee.rb (96%) rename {engines/order_management/app/services/order_management => lib/reporting}/reports/enterprise_fee_summary/data_representations/payment_method_fee.rb (97%) rename {engines/order_management/app/services/order_management => lib/reporting}/reports/enterprise_fee_summary/data_representations/shipping_method_fee.rb (97%) rename {engines/order_management/app/services/order_management => lib/reporting}/reports/enterprise_fee_summary/data_representations/using_enterprise_fee.rb (97%) rename {engines/order_management/app/services/order_management => lib/reporting}/reports/enterprise_fee_summary/data_representations/with_i18n.rb (94%) rename {engines/order_management/app/services/order_management => lib/reporting}/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb (96%) create mode 100644 lib/reporting/reports/enterprise_fee_summary/parameter_not_allowed_error.rb rename {engines/order_management/app/services/order_management => lib/reporting}/reports/enterprise_fee_summary/parameters.rb (94%) rename {engines/order_management/app/services/order_management => lib/reporting}/reports/enterprise_fee_summary/permissions.rb (98%) rename {engines/order_management/app/services/order_management => lib/reporting}/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb (86%) create mode 100644 lib/reporting/reports/enterprise_fee_summary/reports/authorizer.rb create mode 100644 lib/reporting/reports/enterprise_fee_summary/reports/parameters/base.rb create mode 100644 lib/reporting/reports/enterprise_fee_summary/reports/report_data/base.rb rename {engines/order_management/app/services/order_management => lib/reporting}/reports/enterprise_fee_summary/scope.rb (99%) rename {engines/order_management/app/services/order_management => lib/reporting}/reports/enterprise_fee_summary/summarizer.rb (98%) rename lib/{open_food_network => reporting}/reports/list.rb (99%) create mode 100644 lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb create mode 100644 lib/reporting/reports/orders_and_distributors/orders_and_distributors_report.rb create mode 100644 lib/reporting/reports/orders_and_fulfillment/customer_totals_report.rb create mode 100644 lib/reporting/reports/orders_and_fulfillment/default_report.rb create mode 100644 lib/reporting/reports/orders_and_fulfillment/distributor_totals_by_supplier_report.rb create mode 100644 lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb create mode 100644 lib/reporting/reports/orders_and_fulfillment/supplier_totals_by_distributor_report.rb create mode 100644 lib/reporting/reports/orders_and_fulfillment/supplier_totals_report.rb create mode 100644 lib/reporting/reports/payments/payments_report.rb create mode 100644 lib/reporting/reports/products_and_inventory/lettuce_share_report.rb create mode 100644 lib/reporting/reports/products_and_inventory/products_and_inventory_default_report.rb create mode 100644 lib/reporting/reports/products_and_inventory/products_and_inventory_report.rb create mode 100644 lib/reporting/reports/sales_tax/sales_tax_report.rb create mode 100644 lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb create mode 100644 lib/reporting/reports/xero_invoices/xero_invoices_report.rb delete mode 100644 spec/lib/open_food_network/customers_report_spec.rb delete mode 100644 spec/lib/open_food_network/lettuce_share_report_spec.rb delete mode 100644 spec/lib/open_food_network/order_cycle_management_report_spec.rb delete mode 100644 spec/lib/open_food_network/orders_and_distributors_report_spec.rb delete mode 100644 spec/lib/open_food_network/orders_and_fulfillment_report/customer_totals_report_spec.rb delete mode 100644 spec/lib/open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report_spec.rb delete mode 100644 spec/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report_spec.rb delete mode 100644 spec/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_report_spec.rb delete mode 100644 spec/lib/open_food_network/orders_and_fulfillments_report_spec.rb delete mode 100644 spec/lib/open_food_network/products_and_inventory_default_report_spec.rb delete mode 100644 spec/lib/open_food_network/sales_tax_report_spec.rb delete mode 100644 spec/lib/open_food_network/users_and_enterprises_report_spec.rb delete mode 100644 spec/lib/open_food_network/xero_invoices_report_spec.rb rename spec/lib/{open_food_network => reports}/bulk_coop_report_spec.rb (88%) create mode 100644 spec/lib/reports/customers_report_spec.rb create mode 100644 spec/lib/reports/enterprise_fee_summary/authorizer_spec.rb rename {engines/order_management/spec/services/order_management => spec/lib}/reports/enterprise_fee_summary/enterprise_fee_summary_report_spec.rb (99%) create mode 100644 spec/lib/reports/enterprise_fee_summary/parameters_spec.rb rename {engines/order_management/spec/services/order_management => spec/lib}/reports/enterprise_fee_summary/permissions_spec.rb (99%) rename {engines/order_management/spec/services/order_management => spec/lib}/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb (95%) rename {engines/order_management/spec/services/order_management => spec/lib}/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb (89%) rename {engines/order_management/spec/services/order_management => spec/lib}/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb (92%) create mode 100644 spec/lib/reports/lettuce_share_report_spec.rb rename spec/lib/{open_food_network => }/reports/line_items_spec.rb (93%) create mode 100644 spec/lib/reports/order_cycle_management_report_spec.rb rename spec/lib/{open_food_network => reports}/order_grouper_spec.rb (97%) create mode 100644 spec/lib/reports/orders_and_distributors_report_spec.rb create mode 100644 spec/lib/reports/orders_and_fulfillment/customer_totals_report_spec.rb create mode 100644 spec/lib/reports/orders_and_fulfillment/distributor_totals_by_supplier_report_spec.rb create mode 100644 spec/lib/reports/orders_and_fulfillment/orders_and_fulfillment_report_spec.rb create mode 100644 spec/lib/reports/orders_and_fulfillment/supplier_totals_by_distributor_report_spec.rb create mode 100644 spec/lib/reports/orders_and_fulfillment/supplier_totals_report_spec.rb create mode 100644 spec/lib/reports/products_and_inventory_default_report_spec.rb create mode 100644 spec/lib/reports/sales_tax_report_spec.rb create mode 100644 spec/lib/reports/users_and_enterprises_report_spec.rb create mode 100644 spec/lib/reports/xero_invoices_report_spec.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index bb9a3b0171..ae5ba7ddd7 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 1400` -# on 2022-02-25 01:04:47 UTC using RuboCop version 1.22.2. +# on 2022-03-29 16:07:39 UTC using RuboCop version 1.22.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -53,7 +53,7 @@ Layout/LeadingCommentSpace: Exclude: - 'spec/system/admin/enterprises_spec.rb' -# Offense count: 828 +# Offense count: 856 # Cop supports --auto-correct. # Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https @@ -108,7 +108,6 @@ Layout/LineLength: - 'app/services/order_syncer.rb' - 'app/services/products_renderer.rb' - 'app/services/variant_units/variant_and_line_item_naming.rb' - - 'lib/open_food_network/bulk_coop_report.rb' - 'engines/order_management/app/services/order_management/subscriptions/validator.rb' - 'engines/order_management/spec/services/order_management/order/updater_spec.rb' - 'engines/web/app/helpers/web/cookies_policy_helper.rb' @@ -117,15 +116,20 @@ Layout/LineLength: - 'lib/open_food_network/enterprise_fee_applicator.rb' - 'lib/open_food_network/enterprise_fee_calculator.rb' - 'lib/open_food_network/enterprise_issue_validator.rb' - - 'lib/open_food_network/lettuce_share_report.rb' - 'lib/open_food_network/order_cycle_form_applicator.rb' - - 'lib/open_food_network/order_cycle_management_report.rb' - - 'lib/open_food_network/order_cycle_permissions.rb' - - 'lib/open_food_network/payments_report.rb' - - 'lib/open_food_network/reports/line_items.rb' - - 'lib/open_food_network/sales_tax_report.rb' - 'lib/open_food_network/scope_variants_for_search.rb' - - 'lib/open_food_network/xero_invoices_report.rb' + - 'lib/reporting/line_items.rb' + - 'lib/reporting/reports/bulk_coop/bulk_coop_report.rb' + - 'lib/reporting/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb' + - 'lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb' + - 'lib/open_food_network/order_cycle_permissions.rb' + - 'lib/reporting/reports/orders_and_fulfillment/customer_totals_report.rb' + - 'lib/reporting/reports/orders_and_fulfillment/distributor_totals_by_supplier_report.rb' + - 'lib/reporting/reports/payments/payments_report.rb' + - 'lib/reporting/reports/products_and_inventory/lettuce_share_report.rb' + - 'lib/reporting/reports/sales_tax/sales_tax_report.rb' + - 'lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb' + - 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb' - 'lib/spree/localized_number.rb' - 'lib/tasks/data.rake' - 'lib/tasks/enterprises.rake' @@ -176,19 +180,20 @@ Layout/LineLength: - 'spec/helpers/spree/admin/base_helper_spec.rb' - 'spec/jobs/subscription_confirm_job_spec.rb' - 'spec/jobs/subscription_placement_job_spec.rb' - - 'spec/lib/open_food_network/customers_report_spec.rb' - 'spec/lib/open_food_network/enterprise_fee_calculator_spec.rb' - - 'spec/lib/open_food_network/group_buy_report_spec.rb' - 'spec/lib/open_food_network/order_cycle_form_applicator_spec.rb' - - 'spec/lib/open_food_network/order_cycle_management_report_spec.rb' - 'spec/lib/open_food_network/order_cycle_permissions_spec.rb' - - 'spec/lib/open_food_network/order_grouper_spec.rb' - 'spec/lib/open_food_network/permissions_spec.rb' - - 'spec/lib/open_food_network/products_and_inventory_report_spec.rb' - 'spec/lib/open_food_network/scope_variant_to_hub_spec.rb' - 'spec/lib/open_food_network/tag_rule_applicator_spec.rb' - - 'spec/lib/open_food_network/users_and_enterprises_report_spec.rb' + - 'spec/lib/reports/customers_report_spec.rb' + - 'spec/lib/reports/order_cycle_management_report_spec.rb' + - 'spec/lib/reports/order_grouper_spec.rb' + - 'spec/lib/reports/orders_and_fulfillment/orders_and_fulfillment_report_spec.rb' - 'spec/lib/reports/packing/packing_report_spec.rb' + - 'spec/lib/reports/products_and_inventory_default_report_spec.rb' + - 'spec/lib/reports/users_and_enterprises_report_spec.rb' + - 'spec/lib/reports/xero_invoices_report_spec.rb' - 'spec/lib/stripe/authorize_response_patcher_spec.rb' - 'spec/mailers/order_mailer_spec.rb' - 'spec/mailers/producer_mailer_spec.rb' @@ -309,7 +314,15 @@ Layout/MultilineMethodCallBraceLayout: Exclude: - 'lib/reporting/queries/joins.rb' -# Offense count: 17 +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: aligned, indented, indented_relative_to_receiver +Layout/MultilineMethodCallIndentation: + Exclude: + - 'lib/reporting/reports/customers/customers_report.rb' + +# Offense count: 20 # Cop supports --auto-correct. # Configuration parameters: AllowInHeredoc. Layout/TrailingWhitespace: @@ -331,7 +344,7 @@ Lint/ConstantDefinitionInBlock: - 'lib/tasks/users.rake' - 'spec/controllers/spree/admin/base_controller_spec.rb' - 'spec/helpers/serializer_helper_spec.rb' - - 'spec/lib/open_food_network/reports/line_items_spec.rb' + - 'spec/lib/reports/line_items_spec.rb' - 'spec/models/spree/ability_spec.rb' - 'spec/models/spree/gateway_spec.rb' - 'spec/models/spree/preferences/configuration_spec.rb' @@ -398,7 +411,7 @@ Lint/UselessMethodDefinition: - 'app/controllers/spree/user_registrations_controller.rb' - 'app/models/spree/gateway.rb' -# Offense count: 39 +# Offense count: 38 # Configuration parameters: IgnoredMethods, CountRepeatedAttributes, Max. Metrics/AbcSize: Exclude: @@ -419,22 +432,21 @@ Metrics/AbcSize: - 'app/models/spree/order/checkout.rb' - 'app/models/spree/preferences/preferable_class_methods.rb' - 'app/models/spree/return_authorization.rb' - - 'lib/open_food_network/bulk_coop_report.rb' - 'lib/discourse/single_sign_on.rb' - - 'lib/open_food_network/customers_report.rb' - - 'lib/open_food_network/group_buy_report.rb' - - 'lib/open_food_network/orders_and_distributors_report.rb' - 'lib/open_food_network/order_cycle_form_applicator.rb' + - 'lib/reporting/reports/bulk_coop/bulk_coop_report.rb' + - 'lib/reporting/reports/customers/customers_report.rb' - 'lib/open_food_network/order_cycle_permissions.rb' - - 'lib/open_food_network/payments_report.rb' - - 'lib/open_food_network/sales_tax_report.rb' + - 'lib/reporting/reports/orders_and_distributors/orders_and_distributors_report.rb' - 'lib/reporting/reports/packing/customer.rb' + - 'lib/reporting/reports/payments/payments_report.rb' + - 'lib/reporting/reports/sales_tax/sales_tax_report.rb' - 'lib/spree/core/controller_helpers/order.rb' - 'lib/spree/core/s3_support.rb' - 'lib/tasks/enterprises.rake' - 'spec/services/order_checkout_restart_spec.rb' -# Offense count: 45 +# Offense count: 43 # Configuration parameters: CountComments, Max, CountAsOne, ExcludedMethods, IgnoredMethods. # IgnoredMethods: refine Metrics/BlockLength: @@ -458,13 +470,12 @@ Metrics/BlockLength: - 'spec/factories/subscription_factory.rb' - 'spec/factories/user_factory.rb' - 'spec/factories/variant_factory.rb' - - 'spec/lib/open_food_network/group_buy_report_spec.rb' - 'spec/requests/api/orders_spec.rb' - 'spec/spec_helper.rb' - - 'spec/swagger_helper.rb' - 'spec/support/cancan_helper.rb' - 'spec/support/matchers/select2_matchers.rb' - 'spec/support/matchers/table_matchers.rb' + - 'spec/swagger_helper.rb' - 'spec/system/admin/order_cycles/complex_updating_specific_time_spec.rb' - 'spec/system/consumer/shopping/checkout_spec.rb' @@ -474,7 +485,7 @@ Metrics/BlockNesting: Exclude: - 'app/models/spree/payment/processing.rb' -# Offense count: 49 +# Offense count: 50 # Configuration parameters: CountComments, Max, CountAsOne. Metrics/ClassLength: Exclude: @@ -518,19 +529,18 @@ Metrics/ClassLength: - 'app/services/cart_service.rb' - 'app/services/order_syncer.rb' - 'engines/order_management/app/services/order_management/order/updater.rb' - - 'lib/open_food_network/bulk_coop_report.rb' - - 'engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb' - 'lib/open_food_network/enterprise_fee_calculator.rb' - 'lib/open_food_network/order_cycle_form_applicator.rb' - - 'lib/open_food_network/order_cycle_management_report.rb' - - 'lib/open_food_network/order_cycle_permissions.rb' - - 'lib/open_food_network/payments_report.rb' - 'lib/open_food_network/permissions.rb' - - 'lib/open_food_network/users_and_enterprises_report.rb' - - 'lib/open_food_network/xero_invoices_report.rb' - - 'lib/open_food_network/bulk_coop_report.rb' + - 'lib/reporting/reports/bulk_coop/bulk_coop_report.rb' + - 'lib/reporting/reports/enterprise_fee_summary/scope.rb' + - 'lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb' + - 'lib/open_food_network/order_cycle_permissions.rb' + - 'lib/reporting/reports/payments/payments_report.rb' + - 'lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb' + - 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb' -# Offense count: 40 +# Offense count: 39 # Configuration parameters: IgnoredMethods, Max. Metrics/CyclomaticComplexity: Exclude: @@ -556,20 +566,19 @@ Metrics/CyclomaticComplexity: - 'app/models/spree/tax_rate.rb' - 'app/models/spree/variant.rb' - 'app/models/spree/zone.rb' - - 'lib/open_food_network/bulk_coop_report.rb' - 'lib/discourse/single_sign_on.rb' - - 'lib/open_food_network/customers_report.rb' - 'lib/open_food_network/enterprise_issue_validator.rb' - - 'lib/open_food_network/group_buy_report.rb' - - 'lib/open_food_network/orders_and_fulfillment_report/customer_totals_report.rb' - - 'lib/open_food_network/payments_report.rb' - - 'lib/open_food_network/xero_invoices_report.rb' + - 'lib/reporting/reports/bulk_coop/bulk_coop_report.rb' + - 'lib/reporting/reports/customers/customers_report.rb' + - 'lib/reporting/reports/orders_and_fulfillment/customer_totals_report.rb' + - 'lib/reporting/reports/payments/payments_report.rb' + - 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb' - 'lib/spree/core/controller_helpers/order.rb' - 'lib/spree/core/controller_helpers/respond_with.rb' - 'lib/spree/localized_number.rb' - 'spec/models/product_importer_spec.rb' -# Offense count: 31 +# Offense count: 32 # Configuration parameters: CountComments, Max, CountAsOne, ExcludedMethods, IgnoredMethods. Metrics/MethodLength: Exclude: @@ -579,23 +588,23 @@ Metrics/MethodLength: - 'app/controllers/spree/orders_controller.rb' - 'app/helpers/checkout_helper.rb' - 'app/helpers/spree/admin/navigation_helper.rb' - - "app/json_schemas/json_api_schema.rb" + - 'app/json_schemas/json_api_schema.rb' - 'app/models/spree/ability.rb' - 'app/models/spree/gateway/pay_pal_express.rb' - 'app/models/spree/order/checkout.rb' - 'app/models/spree/payment/processing.rb' - 'app/models/spree/preferences/preferable_class_methods.rb' - - 'lib/open_food_network/bulk_coop_report.rb' - - 'engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb' - 'lib/discourse/single_sign_on.rb' - 'lib/open_food_network/order_cycle_form_applicator.rb' - - 'lib/open_food_network/order_cycle_management_report.rb' + - 'lib/reporting/reports/bulk_coop/bulk_coop_report.rb' + - 'lib/reporting/reports/enterprise_fee_summary/scope.rb' + - 'lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb' - 'lib/open_food_network/order_cycle_permissions.rb' - - 'lib/open_food_network/payments_report.rb' - - 'lib/open_food_network/xero_invoices_report.rb' + - 'lib/reporting/reports/payments/payments_report.rb' + - 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb' - 'lib/tasks/sample_data/product_factory.rb' -# Offense count: 51 +# Offense count: 54 # Configuration parameters: CountComments, Max, CountAsOne. Metrics/ModuleLength: Exclude: @@ -626,17 +635,20 @@ Metrics/ModuleLength: - 'spec/controllers/spree/admin/adjustments_controller_spec.rb' - 'spec/controllers/spree/admin/payment_methods_controller_spec.rb' - 'spec/lib/open_food_network/address_finder_spec.rb' - - 'spec/lib/open_food_network/customers_report_spec.rb' - 'spec/lib/open_food_network/enterprise_fee_calculator_spec.rb' - 'spec/lib/open_food_network/order_cycle_form_applicator_spec.rb' - - 'spec/lib/open_food_network/order_cycle_management_report_spec.rb' - 'spec/lib/open_food_network/order_cycle_permissions_spec.rb' - - 'spec/lib/open_food_network/order_grouper_spec.rb' - 'spec/lib/open_food_network/permissions_spec.rb' - - 'spec/lib/open_food_network/products_and_inventory_report_spec.rb' - 'spec/lib/open_food_network/scope_variant_to_hub_spec.rb' - 'spec/lib/open_food_network/tag_rule_applicator_spec.rb' - - 'spec/lib/open_food_network/users_and_enterprises_report_spec.rb' + - 'spec/lib/reports/customers_report_spec.rb' + - 'spec/lib/reports/enterprise_fee_summary/authorizer_spec.rb' + - 'spec/lib/reports/order_cycle_management_report_spec.rb' + - 'spec/lib/reports/order_grouper_spec.rb' + - 'spec/lib/reports/orders_and_fulfillment/customer_totals_report_spec.rb' + - 'spec/lib/reports/orders_and_fulfillment/orders_and_fulfillment_report_spec.rb' + - 'spec/lib/reports/products_and_inventory_default_report_spec.rb' + - 'spec/lib/reports/users_and_enterprises_report_spec.rb' - 'spec/models/spree/adjustment_spec.rb' - 'spec/models/spree/credit_card_spec.rb' - 'spec/models/spree/line_item_spec.rb' @@ -657,11 +669,11 @@ Metrics/ParameterLists: Exclude: - 'app/helpers/angular_form_builder.rb' - 'app/models/product_import/entry_processor.rb' - - 'lib/open_food_network/xero_invoices_report.rb' + - 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb' - 'spec/support/controller_requests_helper.rb' - 'spec/system/admin/reports_spec.rb' -# Offense count: 8 +# Offense count: 7 # Configuration parameters: IgnoredMethods, Max. Metrics/PerceivedComplexity: Exclude: @@ -670,9 +682,8 @@ Metrics/PerceivedComplexity: - 'app/models/enterprise_relationship.rb' - 'app/models/spree/ability.rb' - 'app/models/spree/order/checkout.rb' - - 'lib/open_food_network/bulk_coop_report.rb' - - 'lib/open_food_network/group_buy_report.rb' - - 'lib/open_food_network/payments_report.rb' + - 'lib/reporting/reports/bulk_coop/bulk_coop_report.rb' + - 'lib/reporting/reports/payments/payments_report.rb' # Offense count: 9 Naming/AccessorMethodName: @@ -717,7 +728,7 @@ Naming/VariableNumber: - 'app/controllers/spree/orders_controller.rb' - 'app/models/content_configuration.rb' - 'app/models/preference_sections/main_links_section.rb' - - 'lib/open_food_network/orders_and_fulfillment_report/customer_totals_report.rb' + - 'lib/reporting/reports/orders_and_fulfillment/customer_totals_report.rb' - 'lib/spree/core/controller_helpers/common.rb' - 'spec/controllers/spree/admin/search_controller_spec.rb' - 'spec/factories/stock_location_factory.rb' @@ -896,7 +907,7 @@ Rails/LexicallyScopedActionFilter: - 'app/controllers/spree/admin/zones_controller.rb' - 'app/controllers/spree/users_controller.rb' -# Offense count: 18 +# Offense count: 19 Rails/OutputSafety: Exclude: - 'app/controllers/spree/admin/reports_controller.rb' @@ -1093,9 +1104,9 @@ Style/MissingRespondToMissing: # Offense count: 1 Style/MixinUsage: Exclude: - - 'lib/open_food_network/orders_and_fulfillment_report.rb' + - 'lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb' -# Offense count: 2 +# Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: literals, strict @@ -1119,7 +1130,7 @@ Style/NestedModifier: - 'spec/system/admin/payments_stripe_spec.rb' - 'spec/system/admin/reports_spec.rb' -# Offense count: 25 +# Offense count: 26 # Configuration parameters: AllowedMethods. # AllowedMethods: respond_to_missing? Style/OptionalBooleanParameter: @@ -1133,18 +1144,17 @@ Style/OptionalBooleanParameter: - 'app/models/spree/order_contents.rb' - 'app/models/spree/preferences/file_configuration.rb' - 'app/models/spree/shipment.rb' - - 'lib/open_food_network/bulk_coop_report.rb' - 'engines/order_management/app/services/order_management/stock/estimator.rb' - - 'engines/order_management/app/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb' - - 'lib/open_food_network/customers_report.rb' - - 'lib/open_food_network/orders_and_distributors_report.rb' - - 'lib/open_food_network/order_cycle_management_report.rb' - - 'lib/open_food_network/orders_and_fulfillment_report.rb' - - 'lib/open_food_network/payments_report.rb' - - 'lib/open_food_network/products_and_inventory_report.rb' - - 'lib/open_food_network/users_and_enterprises_report.rb' - - 'lib/open_food_network/xero_invoices_report.rb' - - 'lib/open_food_network/bulk_coop_report.rb' + - 'lib/reporting/reports/bulk_coop/bulk_coop_report.rb' + - 'lib/reporting/reports/customers/customers_report.rb' + - 'lib/reporting/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb' + - 'lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb' + - 'lib/reporting/reports/orders_and_distributors/orders_and_distributors_report.rb' + - 'lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb' + - 'lib/reporting/reports/payments/payments_report.rb' + - 'lib/reporting/reports/products_and_inventory/products_and_inventory_report.rb' + - 'lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb' + - 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb' - 'lib/spree/core/controller_helpers/order.rb' - 'lib/spree/core/delegate_belongs_to.rb' - 'spec/support/request/web_helper.rb' @@ -1164,11 +1174,10 @@ Style/RedundantReturn: Exclude: - 'app/controllers/spree/admin/shipping_methods_controller.rb' -# Offense count: 213 +# Offense count: 205 Style/Send: Exclude: - 'app/controllers/split_checkout_controller.rb' - - 'engines/order_management/spec/services/order_management/reports/bulk_coop/bulk_coop_report_spec.rb' - 'spec/controllers/admin/subscriptions_controller_spec.rb' - 'spec/controllers/checkout_controller_spec.rb' - 'spec/controllers/payment_gateways/paypal_controller_spec.rb' @@ -1180,13 +1189,10 @@ Style/Send: - 'spec/lib/open_food_network/address_finder_spec.rb' - 'spec/lib/open_food_network/enterprise_fee_applicator_spec.rb' - 'spec/lib/open_food_network/enterprise_fee_calculator_spec.rb' - - 'spec/lib/open_food_network/lettuce_share_report_spec.rb' - 'spec/lib/open_food_network/order_cycle_form_applicator_spec.rb' - 'spec/lib/open_food_network/permissions_spec.rb' - - 'spec/lib/open_food_network/products_and_inventory_report_spec.rb' - - 'spec/lib/open_food_network/sales_tax_report_spec.rb' - 'spec/lib/open_food_network/tag_rule_applicator_spec.rb' - - 'spec/lib/open_food_network/xero_invoices_report_spec.rb' + - 'spec/lib/reports/xero_invoices_report_spec.rb' - 'spec/lib/stripe/webhook_handler_spec.rb' - 'spec/models/calculator/weight_spec.rb' - 'spec/models/enterprise_spec.rb' @@ -1211,7 +1217,7 @@ Style/SingleArgumentDig: Exclude: - 'app/services/checkout/form_data_adapter.rb' -# Offense count: 5 +# Offense count: 4 # Cop supports --auto-correct. Style/SlicingWithRange: Exclude: @@ -1219,9 +1225,8 @@ Style/SlicingWithRange: - 'app/services/embedded_page_service.rb' - 'engines/order_management/app/services/order_management/subscriptions/validator.rb' - 'lib/discourse/single_sign_on.rb' - - 'spec/lib/open_food_network/order_grouper_spec.rb' -# Offense count: 31 +# Offense count: 28 # Cop supports --auto-correct. # Configuration parameters: Mode. Style/StringConcatenation: @@ -1238,11 +1243,8 @@ Style/StringConcatenation: - 'app/serializers/api/enterprise_shopfront_list_serializer.rb' - 'app/services/embedded_page_service.rb' - 'app/services/products_renderer.rb' - - 'lib/open_food_network/bulk_coop_report.rb' - - 'lib/open_food_network/orders_and_fulfillment_report/customer_totals_report.rb' - 'lib/spree/api/controller_setup.rb' - 'lib/spree/core/environment_extension.rb' - - 'spec/lib/open_food_network/order_grouper_spec.rb' - 'spec/models/spree/line_item_spec.rb' - 'spec/models/spree/product_spec.rb' - 'spec/models/spree/variant_spec.rb' diff --git a/app/controllers/spree/admin/reports_controller.rb b/app/controllers/spree/admin/reports_controller.rb index 552def4802..253ebbf45a 100644 --- a/app/controllers/spree/admin/reports_controller.rb +++ b/app/controllers/spree/admin/reports_controller.rb @@ -1,19 +1,17 @@ # frozen_string_literal: true -require 'open_food_network/reports/list' -require 'open_food_network/orders_and_distributors_report' -require 'open_food_network/products_and_inventory_report' -require 'open_food_network/lettuce_share_report' -require 'open_food_network/group_buy_report' -require 'open_food_network/order_grouper' -require 'open_food_network/customers_report' -require 'open_food_network/users_and_enterprises_report' -require 'open_food_network/order_cycle_management_report' -require 'open_food_network/sales_tax_report' -require 'open_food_network/xero_invoices_report' -require 'open_food_network/payments_report' -require 'open_food_network/orders_and_fulfillment_report' -require 'open_food_network/bulk_coop_report' +# require 'open_food_network/orders_and_distributors_report' +# require 'open_food_network/products_and_inventory_report' +# require 'open_food_network/lettuce_share_report' +# require 'open_food_network/order_grouper' +# require 'open_food_network/customers_report' +# require 'open_food_network/users_and_enterprises_report' +# require 'open_food_network/order_cycle_management_report' +# require 'open_food_network/sales_tax_report' +# require 'open_food_network/xero_invoices_report' +# require 'open_food_network/payments_report' +# require 'open_food_network/orders_and_fulfillment_report' +# require 'open_food_network/bulk_coop_report' module Spree module Admin @@ -30,7 +28,7 @@ module Spree respond_to :html def report_types - OpenFoodNetwork::Reports::List.all + Reporting::Reports::List.all end def index @@ -119,11 +117,7 @@ module Spree def render_report @report_subtypes = report_types[action_name.to_sym] @report_subtype = params[:report_subtype] - klass = if action_name == 'enterprise_fee_summary' - OrderManagement::Reports::EnterpriseFeeSummary::EnterpriseFeeSummaryReport - else - "OpenFoodNetwork::#{action_name.camelize}Report".constantize - end + klass = "Reporting::Reports::#{action_name.camelize}::#{action_name.camelize}Report".constantize @report = klass.new spree_current_user, raw_params, render_content? if report_format.present? data = Reporting::ReportRenderer.new(@report).public_send("to_#{report_format}") diff --git a/app/validators/date_time_string_validator.rb b/app/validators/date_time_string_validator.rb index a70bf9a0c7..6b5706b114 100644 --- a/app/validators/date_time_string_validator.rb +++ b/app/validators/date_time_string_validator.rb @@ -38,8 +38,13 @@ # post.valid? # => false # post.errors[:published_at] # => ["must be valid"] class DateTimeStringValidator < ActiveModel::EachValidator - NOT_STRING_ERROR = I18n.t("validators.date_time_string_validator.not_string_error") - INVALID_FORMAT_ERROR = I18n.t("validators.date_time_string_validator.invalid_format_error") + def self.not_string_error + I18n.t("validators.date_time_string_validator.not_string_error") + end + + def self.invalid_format_error + I18n.t("validators.date_time_string_validator.invalid_format_error") + end def validate_each(record, attribute, value) return if value.nil? || value == "" @@ -53,13 +58,13 @@ class DateTimeStringValidator < ActiveModel::EachValidator def validate_attribute_is_string(record, attribute, value) return if value.is_a?(String) - record.errors.add(attribute, NOT_STRING_ERROR) + record.errors.add(attribute, DateTimeStringValidator.not_string_error) end def validate_attribute_is_datetime_string(record, attribute, value) return unless value.is_a?(String) datetime = Time.zone.parse(value) - record.errors.add(attribute, INVALID_FORMAT_ERROR) if datetime.blank? + record.errors.add(attribute, DateTimeStringValidator.invalid_format_error) if datetime.blank? end end diff --git a/app/validators/integer_array_validator.rb b/app/validators/integer_array_validator.rb index 64f718a4d3..883b69f0cf 100644 --- a/app/validators/integer_array_validator.rb +++ b/app/validators/integer_array_validator.rb @@ -35,8 +35,13 @@ # post.valid? # => false # post.errors[:related_post_ids] # => ["must contain only valid integers"] class IntegerArrayValidator < ActiveModel::EachValidator - NOT_ARRAY_ERROR = I18n.t("validators.integer_array_validator.not_array_error") - INVALID_ELEMENT_ERROR = I18n.t("validators.integer_array_validator.invalid_element_error") + def self.not_array_error + I18n.t("validators.integer_array_validator.not_array_error") + end + + def self.invalid_element_error + I18n.t("validators.integer_array_validator.invalid_element_error") + end def validate_each(record, attribute, value) return if value.nil? @@ -50,7 +55,7 @@ class IntegerArrayValidator < ActiveModel::EachValidator def validate_attribute_is_array(record, attribute, value) return if value.is_a?(Array) - record.errors.add(attribute, NOT_ARRAY_ERROR) + record.errors.add(attribute, IntegerArrayValidator.not_array_error) end def validate_attribute_elements_are_integer(record, attribute, array) @@ -60,6 +65,6 @@ class IntegerArrayValidator < ActiveModel::EachValidator Integer(element) end rescue ArgumentError - record.errors.add(attribute, INVALID_ELEMENT_ERROR) + record.errors.add(attribute, IntegerArrayValidator.invalid_element_error) end end diff --git a/engines/order_management/app/services/reports/authorizer.rb b/engines/order_management/app/services/reports/authorizer.rb deleted file mode 100644 index 2e58266579..0000000000 --- a/engines/order_management/app/services/reports/authorizer.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module Reports - class Authorizer - class ParameterNotAllowedError < StandardError; end - - attr_accessor :parameters, :permissions - - def initialize(parameters, permissions) - @parameters = parameters - @permissions = permissions - end - - 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 - - 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/services/reports/parameters/base.rb b/engines/order_management/app/services/reports/parameters/base.rb deleted file mode 100644 index 0a3490f914..0000000000 --- a/engines/order_management/app/services/reports/parameters/base.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -module Reports - module Parameters - class Base - extend ActiveModel::Naming - extend ActiveModel::Translation - include ActiveModel::Validations - include ActiveModel::Validations::Callbacks - - def initialize(attributes = {}) - attributes.each do |key, value| - public_send("#{key}=", value) - end - end - - 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 - - # The parameters are never persisted. - def to_key; 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 - end - end -end diff --git a/engines/order_management/app/services/reports/report_data/base.rb b/engines/order_management/app/services/reports/report_data/base.rb deleted file mode 100644 index 94fd55ac3f..0000000000 --- a/engines/order_management/app/services/reports/report_data/base.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Reports - module ReportData - class Base - def initialize(attributes = {}) - attributes.each do |key, value| - public_send("#{key}=", value) - end - end - end - end -end diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/authorizer_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/authorizer_spec.rb deleted file mode 100644 index bc9e40c852..0000000000 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/authorizer_spec.rb +++ /dev/null @@ -1,174 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -describe OrderManagement::Reports::EnterpriseFeeSummary::Authorizer do - let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } - let(:user) { create(:user) } - - let(:parameters) { report_klass::Parameters.new(params) } - let(:permissions) { report_klass::Permissions.new(user) } - let(:authorizer) { described_class.new(parameters, permissions) } - - context "for distributors" do - before do - allow(permissions).to receive(:allowed_distributors) do - stub_model_collection(Enterprise, :id, ["1", "2", "3"]) - end - end - - context "when distributors are allowed" do - let(:params) { { distributor_ids: ["1", "3"] } } - - it "does not raise error" do - expect { authorizer.authorize! }.not_to raise_error - end - end - - context "when a distributor is not allowed" do - let(:params) { { distributor_ids: ["1", "4"] } } - - it "raises ParameterNotAllowedError" do - expect { authorizer.authorize! } - .to raise_error(Reports::Authorizer::ParameterNotAllowedError) - end - end - end - - context "for producers" do - before do - allow(permissions).to receive(:allowed_producers) do - stub_model_collection(Enterprise, :id, ["1", "2", "3"]) - end - end - - context "when producers are allowed" do - let(:params) { { producer_ids: ["1", "3"] } } - - it "does not raise error" do - expect { authorizer.authorize! }.not_to raise_error - end - end - - context "when a producer is not allowed" do - let(:params) { { producer_ids: ["1", "4"] } } - - it "raises ParameterNotAllowedError" do - expect { authorizer.authorize! } - .to raise_error(Reports::Authorizer::ParameterNotAllowedError) - end - end - end - - context "for order cycles" do - before do - allow(permissions).to receive(:allowed_order_cycles) do - stub_model_collection(OrderCycle, :id, ["1", "2", "3"]) - end - end - - context "when order cycles are allowed" do - let(:params) { { order_cycle_ids: ["1", "3"] } } - - it "does not raise error" do - expect { authorizer.authorize! }.not_to raise_error - end - end - - context "when an order cycle is not allowed" do - let(:params) { { order_cycle_ids: ["1", "4"] } } - - it "raises ParameterNotAllowedError" do - expect { authorizer.authorize! } - .to raise_error(Reports::Authorizer::ParameterNotAllowedError) - end - end - end - - context "for enterprise fees" do - before do - allow(permissions).to receive(:allowed_enterprise_fees) do - stub_model_collection(EnterpriseFee, :id, ["1", "2", "3"]) - end - end - - context "when enterprise fees are allowed" do - let(:params) { { enterprise_fee_ids: ["1", "3"] } } - - it "does not raise error" do - expect { authorizer.authorize! }.not_to raise_error - end - end - - context "when an enterprise fee is not allowed" do - let(:params) { { enterprise_fee_ids: ["1", "4"] } } - - it "raises ParameterNotAllowedError" do - expect { authorizer.authorize! } - .to raise_error(Reports::Authorizer::ParameterNotAllowedError) - end - end - end - - context "for shipping methods" do - before do - allow(permissions).to receive(:allowed_shipping_methods) do - stub_model_collection(Spree::ShippingMethod, :id, ["1", "2", "3"]) - end - end - - context "when shipping methods are allowed" do - let(:params) { { shipping_method_ids: ["1", "3"] } } - - it "does not raise error" do - expect { authorizer.authorize! }.not_to raise_error - end - end - - context "when a shipping method is not allowed" do - let(:params) { { shipping_method_ids: ["1", "4"] } } - - it "raises ParameterNotAllowedError" do - expect { authorizer.authorize! } - .to raise_error(Reports::Authorizer::ParameterNotAllowedError) - end - end - end - - context "for payment methods" do - before do - allow(permissions).to receive(:allowed_payment_methods) do - stub_model_collection(Spree::PaymentMethod, :id, ["1", "2", "3"]) - end - end - - context "when payment methods are allowed" do - let(:params) { { payment_method_ids: ["1", "3"] } } - - it "does not raise error" do - expect { authorizer.authorize! }.not_to raise_error - end - end - - context "when a payment method is not allowed" do - let(:params) { { payment_method_ids: ["1", "4"] } } - - it "raises ParameterNotAllowedError" do - expect { authorizer.authorize! } - .to raise_error(Reports::Authorizer::ParameterNotAllowedError) - end - end - end - - def stub_model_collection(model, attribute_name, attribute_list) - attribute_list.map do |attribute_value| - stub_model(model, attribute_name => attribute_value) - end - end - - def stub_model(model, params) - model.new.tap do |instance| - instance.stub(params) - end - end -end diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/parameters_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/parameters_spec.rb deleted file mode 100644 index 0a2da275b1..0000000000 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/parameters_spec.rb +++ /dev/null @@ -1,90 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -require "date_time_string_validator" - -describe OrderManagement::Reports::EnterpriseFeeSummary::Parameters do - describe "validation" do - let(:parameters) { described_class.new } - - it "allows all parameters to be blank" do - expect(parameters).to be_valid - end - - context "for type of parameters" do - it { is_expected.to validate_date_time_format_of(:start_at) } - it { is_expected.to validate_date_time_format_of(:end_at) } - it { is_expected.to validate_integer_array(:distributor_ids) } - it { is_expected.to validate_integer_array(:producer_ids) } - it { is_expected.to validate_integer_array(:order_cycle_ids) } - it { is_expected.to validate_integer_array(:enterprise_fee_ids) } - it { is_expected.to validate_integer_array(:shipping_method_ids) } - it { is_expected.to validate_integer_array(:payment_method_ids) } - - it "allows integer arrays to include blank string and cleans it up" do - subject.distributor_ids = ["", "1"] - subject.producer_ids = ["", "1"] - subject.order_cycle_ids = ["", "1"] - subject.enterprise_fee_ids = ["", "1"] - subject.shipping_method_ids = ["", "1"] - subject.payment_method_ids = ["", "1"] - - expect(subject).to be_valid - - expect(subject.distributor_ids).to eq(["1"]) - expect(subject.producer_ids).to eq(["1"]) - expect(subject.order_cycle_ids).to eq(["1"]) - expect(subject.enterprise_fee_ids).to eq(["1"]) - expect(subject.shipping_method_ids).to eq(["1"]) - expect(subject.payment_method_ids).to eq(["1"]) - end - - describe "requiring start_at to be before end_at" do - let(:now) { Time.zone.now.utc } - - it "adds error when start_at is after end_at" do - allow(subject).to receive(:start_at) { now.to_s } - allow(subject).to receive(:end_at) { (now - 1.hour).to_s } - - expect(subject).not_to be_valid - error_message = described_class.date_end_before_start_error_message - expect(subject.errors[:end_at]).to eq([error_message]) - end - - it "does not add error when start_at is before end_at" do - allow(subject).to receive(:start_at) { now.to_s } - allow(subject).to receive(:end_at) { (now + 1.hour).to_s } - - expect(subject).to be_valid - end - end - end - end - - describe "smoke authorization" do - let!(:order_cycle) { create(:order_cycle) } - let!(:user) { create(:user) } - - let(:permissions) do - report_klass::Permissions.new(nil).tap do |instance| - instance.stub(allowed_order_cycles: [order_cycle]) - end - end - - it "does not raise error when the parameters are allowed" do - parameters = described_class.new(order_cycle_ids: [order_cycle.id.to_s]) - expect { parameters.authorize!(permissions) }.not_to raise_error - end - - it "raises error when the parameters are not allowed" do - parameters = described_class.new(order_cycle_ids: [(order_cycle.id + 1).to_s]) - expect { parameters.authorize!(permissions) } - .to raise_error(Reports::Authorizer::ParameterNotAllowedError) - end - end - - def report_klass - OrderManagement::Reports::EnterpriseFeeSummary - end -end diff --git a/lib/open_food_network/bulk_coop_allocation_report.rb b/lib/open_food_network/bulk_coop_allocation_report.rb deleted file mode 100644 index 37d6071ec1..0000000000 --- a/lib/open_food_network/bulk_coop_allocation_report.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -module OpenFoodNetwork - class BulkCoopAllocationReport - def table_headers - [ - 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/bulk_coop_report.rb b/lib/open_food_network/bulk_coop_report.rb deleted file mode 100644 index 271c172b48..0000000000 --- a/lib/open_food_network/bulk_coop_report.rb +++ /dev/null @@ -1,320 +0,0 @@ -# frozen_string_literal: true - -require "open_food_network/reports/line_items" -require 'open_food_network/order_grouper' -require 'open_food_network/bulk_coop_allocation_report' -require 'open_food_network/bulk_coop_supplier_report' - -module OpenFoodNetwork - class BulkCoopReport - 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 - @filter_canceled = false - end - - def table_headers - case params[:report_subtype] - when "bulk_coop_supplier_report" - @supplier_report.table_headers - when "bulk_coop_allocation" - @allocation_report.table_headers - 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 table_rows - order_grouper = OpenFoodNetwork::OrderGrouper.new rules, columns, self - order_grouper.table(table_items) - end - - def rules - case params[:report_subtype] - 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| - ( if (lis.first.product.group_buy_unit_size || 0).zero? - 0 - else - ( lis.sum { |li| - [li.max_quantity || 0, - li.quantity || 0].max * (li.weight_from_unit_value || 0) - } / lis.first.product.group_buy_unit_size ) - end ).floor - }, - proc { |lis| - lis.sum { |li| - [li.max_quantity || 0, - li.quantity || 0].max * (li.weight_from_unit_value || 0) - } - ( ( if (lis.first.product.group_buy_unit_size || 0).zero? - 0 - else - ( lis.sum { |li| - [li.max_quantity || 0, - li.quantity || 0].max * (li.weight_from_unit_value || 0) - } / lis.first.product.group_buy_unit_size ) - end ).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_subtype] - 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 - - attr_reader :filter_canceled - - def line_item_includes - [ - { - order: [:bill_address], - variant: [{ option_values: :option_type }, { product: :supplier }] - }, - :option_values - ] - end - - def order_permissions - @order_permissions ||= ::Permissions::Order.new(@user, filter_canceled) - end - - def report_line_items - @report_line_items ||= OpenFoodNetwork::Reports::LineItems.new( - order_permissions, - @params, - CompleteVisibleOrders.new(order_permissions).query - ) - end - - def customer_payments_total_cost(line_items) - unique_orders(line_items).sum(&:total) - end - - def customer_payments_amount_owed(line_items) - unique_orders(line_items).sum(&:new_outstanding_balance) - end - - def customer_payments_amount_paid(line_items) - unique_orders(line_items).sum(&:payment_total) - end - - def unique_orders(line_items) - line_items.map(&:order).uniq - end - - def empty_cell(_line_items) - "" - end - - def full_name(line_items) - line_items.first.full_name - end - - def group_buy_unit_size(line_items) - unit_size = line_items.first.variant.product.group_buy_unit_size || 0.0 - unit_size / (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) - VariantUnits::OptionValueNamer.new(line_items.first).value - end - - def option_value_unit(line_items) - VariantUnits::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(_line_items) - 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/bulk_coop_supplier_report.rb b/lib/open_food_network/bulk_coop_supplier_report.rb deleted file mode 100644 index 1f2645f81b..0000000000 --- a/lib/open_food_network/bulk_coop_supplier_report.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -module OpenFoodNetwork - class BulkCoopSupplierReport - def table_headers - [ - 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 diff --git a/lib/open_food_network/customers_report.rb b/lib/open_food_network/customers_report.rb deleted file mode 100644 index c52edad648..0000000000 --- a/lib/open_food_network/customers_report.rb +++ /dev/null @@ -1,99 +0,0 @@ -# frozen_string_literal: true - -module OpenFoodNetwork - class CustomersReport - attr_reader :params - - def initialize(user, params = {}, compile_table = false) - @params = params - @user = user - @compile_table = compile_table - end - - def table_headers - if is_mailing_list? - [I18n.t(:report_header_email), - I18n.t(:report_header_first_name), - I18n.t(:report_header_last_name), - I18n.t(:report_header_suburb)] - else - [I18n.t(:report_header_first_name), - I18n.t(:report_header_last_name), - I18n.t(:report_header_billing_address), - I18n.t(:report_header_email), - I18n.t(:report_header_phone), - I18n.t(:report_header_hub), - I18n.t(:report_header_hub_address), - I18n.t(:report_header_shipping_method)] - end - end - - def table_rows - return [] unless @compile_table - - orders.map do |order| - if is_mailing_list? - [order.email, - order.billing_address.firstname, - order.billing_address.lastname, - order.billing_address.city] - else - ba = order.billing_address - da = order.distributor&.address - [ba.firstname, - ba.lastname, - [ba.address1, ba.address2, ba.city].join(" "), - order.email, - ba.phone, - order.distributor&.name, - [da&.address1, da&.address2, da&.city].join(" "), - order.shipping_method&.name] - end - end - end - - def orders - filter Spree::Order.managed_by(@user).distributed_by_user(@user).complete.not_state(:canceled) - end - - def filter(orders) - filter_to_supplier filter_to_distributor filter_to_order_cycle orders - end - - def filter_to_supplier(orders) - if params[:supplier_id].to_i > 0 - orders.select do |order| - order.line_items.includes(:product) - .where("spree_products.supplier_id = ?", params[:supplier_id].to_i) - .references(:product) - .count - .positive? - end - else - orders - end - end - - def filter_to_distributor(orders) - if params[:distributor_id].to_i > 0 - orders.where(distributor_id: params[:distributor_id]) - else - orders - end - end - - def filter_to_order_cycle(orders) - if params[:order_cycle_id].to_i > 0 - orders.where(order_cycle_id: params[:order_cycle_id]) - else - orders - end - end - - private - - def is_mailing_list? - params[:report_subtype] == "mailing_list" - end - end -end diff --git a/lib/open_food_network/lettuce_share_report.rb b/lib/open_food_network/lettuce_share_report.rb deleted file mode 100644 index b465769568..0000000000 --- a/lib/open_food_network/lettuce_share_report.rb +++ /dev/null @@ -1,89 +0,0 @@ -# frozen_string_literal: true - -require 'variant_units/option_value_namer' - -module OpenFoodNetwork - class LettuceShareReport - attr_reader :context - - delegate :variants, :render_table, to: :context - - def initialize(context) - @context = context - end - - def table_headers - # NOTE: These are NOT to be translated, they need to be in this exact format to work with LettucShare - [ - "PRODUCT", - "Description", - "Qty", - "Pack Size", - "Unit", - "Unit Price", - "Total", - "GST incl.", - "Grower and growing method", - "Taxon" - ] - end - - def table_rows - return [] unless render_table - - variants.select(&:in_stock?) - .map do |variant| - [ - variant.product.name, - variant.full_name, - '', - VariantUnits::OptionValueNamer.new(variant).value, - VariantUnits::OptionValueNamer.new(variant).unit, - variant.price, - '', - gst(variant), - grower_and_method(variant), - variant.product.primary_taxon.name - ] - end - end - - private - - def gst(variant) - tax_category = variant.product.tax_category - if tax_category && tax_category.tax_rates.present? - tax_rate = tax_category.tax_rates.first - line_item = mock_line_item(variant) - tax_rate.calculator.compute line_item - else - 0 - end - end - - def mock_line_item(variant) - line_item = Spree::LineItem.new quantity: 1 - line_item.define_singleton_method(:product) { variant.product } - line_item.define_singleton_method(:price) { variant.price } - line_item - end - - def grower_and_method(variant) - cert = certification(variant) - - result = producer_name(variant) - result += " (#{cert})" if cert.present? - result - end - - def producer_name(variant) - variant.product.supplier.name - end - - def certification(variant) - variant.product.properties_including_inherited.map do |p| - "#{p[:name]} - #{p[:value]}" - end.join(', ') - end - end -end diff --git a/lib/open_food_network/order_cycle_management_report.rb b/lib/open_food_network/order_cycle_management_report.rb deleted file mode 100644 index 06857cf063..0000000000 --- a/lib/open_food_network/order_cycle_management_report.rb +++ /dev/null @@ -1,165 +0,0 @@ -# frozen_string_literal: true - -module OpenFoodNetwork - class OrderCycleManagementReport - DEFAULT_DATE_INTERVAL = { from: -1.month, to: 1.day }.freeze - - attr_reader :params - - def initialize(user, params = {}, render_table = false) - @params = sanitize_params(params) - @user = user - @render_table = render_table - end - - def table_headers - if is_payment_methods? - [ - I18n.t(:report_header_first_name), - I18n.t(:report_header_last_name), - I18n.t(:report_header_hub), - I18n.t(:report_header_hub_code), - I18n.t(:report_header_email), - I18n.t(:report_header_phone), - I18n.t(:report_header_shipping_method), - I18n.t(:report_header_payment_method), - I18n.t(:report_header_amount), - I18n.t(:report_header_balance), - ] - else - [ - I18n.t(:report_header_first_name), - I18n.t(:report_header_last_name), - I18n.t(:report_header_hub), - I18n.t(:report_header_hub_code), - I18n.t(:report_header_delivery_address), - I18n.t(:report_header_delivery_postcode), - I18n.t(:report_header_phone), - I18n.t(:report_header_shipping_method), - I18n.t(:report_header_payment_method), - I18n.t(:report_header_amount), - I18n.t(:report_header_balance), - I18n.t(:report_header_temp_controlled_items), - I18n.t(:report_header_special_instructions), - ] - end - end - - def search - Spree::Order. - finalized. - not_state(:canceled). - distributed_by_user(@user). - managed_by(@user). - ransack(params[:q]) - end - - def orders - search_result = search.result.order(:completed_at) - orders_with_balance = OutstandingBalance.new(search_result). - query. - select('spree_orders.*') - - filter(orders_with_balance) - end - - def table_rows - return [] unless @render_table - - if is_payment_methods? - orders.map { |o| payment_method_row o } - else - orders.map { |o| delivery_row o } - end - end - - def filter(search_result) - filter_to_payment_method filter_to_shipping_method filter_to_order_cycle search_result - end - - private - - # This method relies on `balance_value` as a computed DB column. See `CompleteOrdersWithBalance` - # for reference. - def balance(order) - order.balance_value - end - - def payment_method_row(order) - ba = order.billing_address - [ba&.firstname, - ba&.lastname, - order.distributor&.name, - customer_code(order.email), - order.email, - ba&.phone, - order.shipping_method&.name, - order.payments.last&.payment_method&.name, - order.total, - balance(order)] - end - - def delivery_row(order) - sa = order.shipping_address - [sa.firstname, - sa.lastname, - order.distributor&.name, - customer_code(order.email), - "#{sa.address1} #{sa.address2} #{sa.city}", - sa.zipcode, - sa.phone, - order.shipping_method&.name, - order.payments.first&.payment_method&.name, - order.total, - balance(order), - has_temperature_controlled_items?(order), - order.special_instructions] - end - - def filter_to_payment_method(orders) - if params[:payment_method_in].present? - orders.joins(payments: :payment_method).where(spree_payments: { payment_method_id: params[:payment_method_in] }) - else - orders - end - end - - def filter_to_shipping_method(orders) - if params[:shipping_method_in].present? - orders.joins(shipments: :shipping_rates).where(spree_shipping_rates: { selected: true, shipping_method_id: params[:shipping_method_in] }) - else - orders - end - end - - def filter_to_order_cycle(orders) - if params[:order_cycle_id].present? - orders.where(order_cycle_id: params[:order_cycle_id]) - else - orders - end - end - - def has_temperature_controlled_items?(order) - order.line_items.any? { |line_item| - line_item.product.shipping_category&.temperature_controlled - } - end - - def is_payment_methods? - params[:report_subtype] == "payment_methods" - end - - def customer_code(email) - customer = Customer.where(email: email).first - customer.nil? ? "" : customer.code - end - - def sanitize_params(params) - params[:q] ||= {} - params[:q][:completed_at_gt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:from] - params[:q][:completed_at_lt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:to] - params - end - end -end diff --git a/lib/open_food_network/order_cycle_permissions.rb b/lib/open_food_network/order_cycle_permissions.rb index a6e0024ed4..8f3ede515d 100644 --- a/lib/open_food_network/order_cycle_permissions.rb +++ b/lib/open_food_network/order_cycle_permissions.rb @@ -25,7 +25,7 @@ module OpenFoodNetwork if @coordinator.sells == "any" # If the coordinator sells any, relationships come into play related_enterprises_granting(:add_to_order_cycle, - to: [@coordinator.id]).each do |enterprise_id| + to: [@coordinator.id]).each do |enterprise_id| coordinator_permitted_ids << enterprise_id end @@ -92,8 +92,8 @@ module OpenFoodNetwork variant_ids = Spree::Variant.joins(:exchanges). where( "exchanges.receiver_id IN (?) - AND exchanges.order_cycle_id = (?) - AND exchanges.incoming = 'f'", + AND exchanges.order_cycle_id = (?) + AND exchanges.incoming = 'f'", managed_participating_hubs.select("enterprises.id"), @order_cycle ).pluck(:id).uniq @@ -125,7 +125,7 @@ module OpenFoodNetwork # Find the variants that a user can POTENTIALLY see within incoming exchanges def visible_variants_for_incoming_exchanges_from(producer) if @order_cycle && - (user_manages_coordinator_or(producer) || user_is_permitted_add_to_oc_by(producer)) + (user_manages_coordinator_or(producer) || user_is_permitted_add_to_oc_by(producer)) all_variants_supplied_by(producer) else no_variants @@ -188,8 +188,8 @@ module OpenFoodNetwork # so things don't break. TODO: Remove this when all P-OC are sorted out active_variants = Spree::Variant.joins(:exchanges, :product). where("exchanges.receiver_id = (?) - AND spree_products.supplier_id IN (?) - AND incoming = 'f'", + AND spree_products.supplier_id IN (?) + AND incoming = 'f'", hub.id, managed_producer_ids) @@ -212,8 +212,8 @@ module OpenFoodNetwork # Variants produced by MY PRODUCERS that are in this OC, # where my producer has granted P-OC to the hub granting_producer_ids = related_enterprises_granting(:add_to_order_cycle, - to: [hub.id], - scope: granted_producers) + to: [hub.id], + scope: granted_producers) permitted_variants = variants_from_suppliers(granting_producer_ids) Spree::Variant.where(id: permitted_variants) @@ -299,8 +299,8 @@ module OpenFoodNetwork # any incoming exchanges supplying variants in my outgoing exchanges variant_ids = Spree::Variant.joins(:exchanges). where("exchanges.receiver_id IN (?) - AND exchanges.order_cycle_id = (?) - AND exchanges.incoming = 'f'", + AND exchanges.order_cycle_id = (?) + AND exchanges.incoming = 'f'", hubs.select("enterprises.id"), @order_cycle).pluck(:id).uniq diff --git a/lib/open_food_network/orders_and_distributors_report.rb b/lib/open_food_network/orders_and_distributors_report.rb deleted file mode 100644 index 252f33f3e0..0000000000 --- a/lib/open_food_network/orders_and_distributors_report.rb +++ /dev/null @@ -1,112 +0,0 @@ -# frozen_string_literal: true - -module OpenFoodNetwork - class OrdersAndDistributorsReport - def initialize(user, params = {}, render_table = false) - @params = params - @user = user - @render_table = render_table - - @permissions = ::Permissions::Order.new(user, @params[:q]) - end - - def table_headers - [ - I18n.t(:report_header_order_date), - I18n.t(:report_header_order_id), - I18n.t(:report_header_customer_name), - I18n.t(:report_header_customer_email), - I18n.t(:report_header_customer_phone), - I18n.t(:report_header_customer_city), - I18n.t(:report_header_sku), - I18n.t(:report_header_item_name), - I18n.t(:report_header_variant), - I18n.t(:report_header_quantity), - I18n.t(:report_header_max_quantity), - I18n.t(:report_header_cost), - I18n.t(:report_header_shipping_cost), - I18n.t(:report_header_payment_method), - I18n.t(:report_header_distributor), - I18n.t(:report_header_distributor_address), - I18n.t(:report_header_distributor_city), - I18n.t(:report_header_distributor_postcode), - I18n.t(:report_header_shipping_method), - I18n.t(:report_header_shipping_instructions) - ] - end - - def search - @permissions.visible_orders.select("DISTINCT spree_orders.*"). - complete.not_state(:canceled). - ransack(@params[:q]) - end - - def table_rows - return [] unless @render_table - - orders = search.result - - orders.select{ |order| orders_with_hidden_details(orders).include? order }.each do |order| - OrderDataMasker.new(order).call - end - - line_item_details orders - end - - private - - def orders_with_hidden_details(orders) - # If empty array is passed in, the where clause will return all line_items, which is bad - if @permissions.editable_orders.empty? - orders - else - orders. - where('spree_orders.id NOT IN (?)', - @permissions.editable_orders.select(&:id)) - end - end - - def line_item_details(orders) - order_and_distributor_details = [] - - orders.each do |order| - order.line_items.each do |line_item| - order_and_distributor_details << row_for(line_item, order) - end - end - - order_and_distributor_details - end - - # Returns a row with the data to display for the specified line_item and - # its order - # - # @param line_item [Spree::LineItem] - # @param order [Spree::Order] - # @return [Array] - def row_for(line_item, order) - [ - order.completed_at.strftime("%F %T"), - order.id, - order.bill_address.full_name, - order.email, - order.bill_address.phone, - order.bill_address.city, - line_item.product.sku, - line_item.product.name, - line_item.options_text, - line_item.quantity, - line_item.max_quantity, - line_item.price * line_item.quantity, - line_item.distribution_fee, - order.payments.first&.payment_method&.name, - order.distributor&.name, - order.distributor.address.address1, - order.distributor.address.city, - order.distributor.address.zipcode, - order.shipping_method&.name, - order.special_instructions - ] - end - end -end diff --git a/lib/open_food_network/orders_and_fulfillment_report.rb b/lib/open_food_network/orders_and_fulfillment_report.rb deleted file mode 100644 index ac62fa09d2..0000000000 --- a/lib/open_food_network/orders_and_fulfillment_report.rb +++ /dev/null @@ -1,120 +0,0 @@ -# frozen_string_literal: true - -require "open_food_network/reports/line_items" -require "open_food_network/orders_and_fulfillment_report/supplier_totals_report" -require "open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report" -require "open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report" -require "open_food_network/orders_and_fulfillment_report/customer_totals_report" -require 'open_food_network/orders_and_fulfillment_report/default_report' -require 'open_food_network/order_grouper' - -include Spree::ReportsHelper - -module OpenFoodNetwork - class OrdersAndFulfillmentReport - attr_reader :options, :report_type - - delegate :table_headers, :rules, :columns, to: :report - - def initialize(user, options = {}, render_table = false) - @user = user - @options = options - @report_type = options[:report_subtype] - @render_table = render_table - @variant_scopers_by_distributor_id = {} - end - - def search - report_line_items.orders - end - - def table_items - return [] unless @render_table - - report_line_items.list(report.line_item_includes) - end - - def table_rows - order_grouper = OpenFoodNetwork::OrderGrouper.new report.rules, report.columns, report - order_grouper.table(table_items) - end - - def line_item_name - proc { |line_item| line_item.variant.full_name } - end - - def line_items_name - proc { |line_items| line_items.first.variant.full_name } - end - - def supplier_name - proc { |line_items| line_items.first.variant.product.supplier.name } - end - - def product_name - proc { |line_items| line_items.first.variant.product.name } - end - - def total_units(line_items) - return " " if not_all_have_unit?(line_items) - - total_units = line_items.sum do |li| - product = li.variant.product - li.quantity * li.unit_value / scale_factor(product) - end - - total_units.round(3) - end - - def variant_scoper_for(distributor_id) - @variant_scopers_by_distributor_id[distributor_id] ||= - OpenFoodNetwork::ScopeVariantToHub.new( - distributor_id, - report_variant_overrides[distributor_id] || {}, - ) - end - - private - - def report - @report ||= report_klass.new(self) - end - - def report_klass - case report_type - when SupplierTotalsReport::REPORT_TYPE then SupplierTotalsReport - when SupplierTotalsByDistributorReport::REPORT_TYPE then SupplierTotalsByDistributorReport - when DistributorTotalsBySupplierReport::REPORT_TYPE then DistributorTotalsBySupplierReport - when CustomerTotalsReport::REPORT_TYPE then CustomerTotalsReport - else - DefaultReport - end - end - - def not_all_have_unit?(line_items) - line_items.map { |li| li.unit_value.nil? }.any? - end - - def scale_factor(product) - product.variant_unit == 'weight' ? 1000 : 1 - end - - def order_permissions - return @order_permissions unless @order_permissions.nil? - - @order_permissions = ::Permissions::Order.new(@user, options[:q]) - end - - def report_line_items - @report_line_items ||= Reports::LineItems.new(order_permissions, options) - end - - def report_variant_overrides - @report_variant_overrides ||= - VariantOverridesIndexed.new( - order_permissions.visible_line_items.select('DISTINCT variant_id'), - report_line_items.orders.result.select('DISTINCT distributor_id'), - ).indexed - end - end -end diff --git a/lib/open_food_network/orders_and_fulfillment_report/customer_totals_report.rb b/lib/open_food_network/orders_and_fulfillment_report/customer_totals_report.rb deleted file mode 100644 index 8ec81136a4..0000000000 --- a/lib/open_food_network/orders_and_fulfillment_report/customer_totals_report.rb +++ /dev/null @@ -1,221 +0,0 @@ -# frozen_string_literal: true - -# rubocop:disable Metrics/ClassLength -module OpenFoodNetwork - class OrdersAndFulfillmentReport - class CustomerTotalsReport - REPORT_TYPE = "order_cycle_customer_totals" - - attr_reader :context - - delegate :line_item_name, to: :context - delegate :variant_scoper_for, to: :context - - def initialize(context) - @context = context - @scopers_by_distributor_id = {} - end - - # rubocop:disable Metrics/AbcSize - def table_headers - [I18n.t(:report_header_hub), I18n.t(:report_header_customer), I18n.t(:report_header_email), - I18n.t(:report_header_phone), I18n.t(:report_header_producer), - I18n.t(:report_header_product), I18n.t(:report_header_variant), - I18n.t(:report_header_quantity), - I18n.t(:report_header_item_price, currency: currency_symbol), - I18n.t(:report_header_item_fees_price, currency: currency_symbol), - I18n.t(:report_header_admin_handling_fees, currency: currency_symbol), - I18n.t(:report_header_ship_price, currency: currency_symbol), - I18n.t(:report_header_pay_fee_price, currency: currency_symbol), - I18n.t(:report_header_total_price, currency: currency_symbol), - I18n.t(:report_header_paid), I18n.t(:report_header_shipping), - I18n.t(:report_header_delivery), I18n.t(:report_header_ship_street), - I18n.t(:report_header_ship_street_2), I18n.t(:report_header_ship_city), - I18n.t(:report_header_ship_postcode), I18n.t(:report_header_ship_state), - I18n.t(:report_header_comments), I18n.t(:report_header_sku), - I18n.t(:report_header_order_cycle), I18n.t(:report_header_payment_method), - I18n.t(:report_header_customer_code), I18n.t(:report_header_tags), - I18n.t(:report_header_billing_street), I18n.t(:report_header_billing_street_2), - I18n.t(:report_header_billing_city), I18n.t(:report_header_billing_postcode), - I18n.t(:report_header_billing_state), - I18n.t(:report_header_order_number), - I18n.t(:report_header_date)] - end - - # rubocop:enable Metrics/AbcSize - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/MethodLength - def rules - [ - { - group_by: proc { |line_item| line_item.order.distributor }, - sort_by: proc { |distributor| distributor.name } - }, - { - group_by: proc { |line_item| line_item.order }, - sort_by: proc { |order| order.bill_address.full_name_reverse }, - summary_columns: [ - proc { |line_items| line_items.first.order.distributor.name }, - proc { |line_items| line_items.first.order.bill_address.full_name }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| I18n.t('admin.reports.total') }, - proc { |_line_items| "" }, - - proc { |_line_items| "" }, - proc { |line_items| line_items.sum(&:amount) }, - proc { |line_items| line_items.sum(&:amount_with_adjustments) }, - proc { |line_items| line_items.first.order.admin_and_handling_total }, - proc { |line_items| line_items.first.order.ship_total }, - proc { |line_items| line_items.first.order.payment_fee }, - proc { |line_items| line_items.first.order.total }, - proc { |line_items| line_items.first.order.paid? ? I18n.t(:yes) : I18n.t(:no) }, - - proc { |_line_items| "" }, - proc { |_line_items| "" }, - - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - - proc { |line_items| line_items.first.order.special_instructions }, - proc { |_line_items| "" }, - - proc { |line_items| line_items.first.order.order_cycle&.name }, - proc { |line_items| - line_items.first.order.payments.first&.payment_method&.name - }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |line_items| line_items.first.order.number }, - proc { |line_items| line_items.first.order.completed_at.strftime("%F %T") }, - ] - }, - { - group_by: proc { |line_item| line_item.variant.product }, - sort_by: proc { |product| product.name } - }, - { - group_by: proc { |line_item| line_item.variant }, - sort_by: proc { |variant| variant.full_name } - }, - { - group_by: line_item_name, - sort_by: proc { |full_name| full_name } - } - ] - end - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/MethodLength - - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/MethodLength - # rubocop:disable Metrics/PerceivedComplexity - def columns - rsa = proc { |line_items| shipping_method(line_items)&.delivery? } - [ - proc { |line_items| line_items.first.order.distributor.name }, - proc { |line_items| - bill_address = line_items.first.order.bill_address - bill_address.firstname + " " + bill_address.lastname - }, - proc { |line_items| line_items.first.order.email }, - proc { |line_items| line_items.first.order.bill_address.phone }, - proc { |line_items| line_items.first.variant.product.supplier.name }, - proc { |line_items| line_items.first.variant.product.name }, - proc { |line_items| line_items.first.variant.full_name }, - - proc { |line_items| line_items.to_a.sum(&:quantity) }, - proc { |line_items| line_items.sum(&:amount) }, - proc { |line_items| line_items.sum(&:amount_with_adjustments) }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |line_items| - line_items.all? { |li| li.order.paid? } ? I18n.t(:yes) : I18n.t(:no) - }, - - proc { |line_items| shipping_method(line_items)&.name }, - proc { |line_items| rsa.call(line_items) ? I18n.t(:yes) : I18n.t(:no) }, - - proc { |line_items| - line_items.first.order.ship_address&.address1 if rsa.call(line_items) - }, - proc { |line_items| - line_items.first.order.ship_address&.address2 if rsa.call(line_items) - }, - proc { |line_items| - line_items.first.order.ship_address&.city if rsa.call(line_items) - }, - proc { |line_items| - line_items.first.order.ship_address&.zipcode if rsa.call(line_items) - }, - proc { |line_items| - line_items.first.order.ship_address&.state if rsa.call(line_items) - }, - - proc { |_line_items| "" }, - proc do |line_items| - line_item = line_items.first - variant_scoper_for(line_item.order.distributor_id).scope(line_item.variant) - line_item.variant.sku - end, - - proc { |line_items| line_items.first.order.order_cycle&.name }, - proc { |line_items| - payment = line_items.first.order.payments.first - payment&.payment_method&.name - }, - proc { |line_items| - distributor = line_items.first.order.distributor - user = line_items.first.order.user - user&.customer_of(distributor)&.code - }, - proc { |line_items| - distributor = line_items.first.order.distributor - user = line_items.first.order.user - user&.customer_of(distributor)&.tags&.join(', ') - }, - - proc { |line_items| line_items.first.order.bill_address&.address1 }, - proc { |line_items| line_items.first.order.bill_address&.address2 }, - proc { |line_items| line_items.first.order.bill_address&.city }, - proc { |line_items| line_items.first.order.bill_address&.zipcode }, - proc { |line_items| line_items.first.order.bill_address&.state }, - proc { |line_items| line_items.first.order.number }, - proc { |line_items| line_items.first.order.completed_at.strftime("%F %T") }, - ] - end - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/MethodLength - # rubocop:enable Metrics/PerceivedComplexity - - def line_item_includes - [{ variant: [{ option_values: :option_type }, { product: :supplier }], - order: [:bill_address, :ship_address, :order_cycle, :adjustments, :payments, - :user, :distributor, :shipments] }] - end - - private - - def shipping_method(line_items) - shipping_rates = line_items.first.order.shipments.first&.shipping_rates - - return unless shipping_rates - - shipping_rate = shipping_rates.find(&:selected) || shipping_rates.first - shipping_rate.try(:shipping_method) - end - end - end -end -# rubocop:enable Metrics/ClassLength diff --git a/lib/open_food_network/orders_and_fulfillment_report/default_report.rb b/lib/open_food_network/orders_and_fulfillment_report/default_report.rb deleted file mode 100644 index 2ec3c20a4e..0000000000 --- a/lib/open_food_network/orders_and_fulfillment_report/default_report.rb +++ /dev/null @@ -1,64 +0,0 @@ -# frozen_string_literal: true - -module OpenFoodNetwork - class OrdersAndFulfillmentReport - class DefaultReport - delegate :line_item_name, :supplier_name, :product_name, :line_items_name, to: :context - - def initialize(context) - @context = context - end - - def table_headers - [ - I18n.t(:report_header_producer), - I18n.t(:report_header_product), - I18n.t(:report_header_variant), - I18n.t(:report_header_quantity), - I18n.t(:report_header_curr_cost_per_unit), - I18n.t(:report_header_total_cost), - I18n.t(:report_header_status), - I18n.t(:report_header_incoming_transport) - ] - end - - def rules - [ - { - group_by: proc { |line_item| line_item.variant.product.supplier }, - sort_by: proc { |supplier| supplier.name } - }, - { - group_by: proc { |line_item| line_item.variant.product }, - sort_by: proc { |product| product.name } - }, - { - group_by: line_item_name, - sort_by: proc { |full_name| full_name } - } - ] - end - - def columns - [ - supplier_name, - product_name, - line_items_name, - proc { |line_items| line_items.to_a.sum(&:quantity) }, - proc { |line_items| line_items.first.price }, - proc { |line_items| line_items.sum { |li| li.quantity * li.price } }, - proc { |_line_items| "" }, - proc { |_line_items| I18n.t(:report_header_incoming_transport) } - ] - end - - def line_item_includes - [] - end - - private - - attr_reader :context - end - end -end diff --git a/lib/open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report.rb b/lib/open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report.rb deleted file mode 100644 index 472aadf38d..0000000000 --- a/lib/open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report.rb +++ /dev/null @@ -1,78 +0,0 @@ -# frozen_string_literal: true - -module OpenFoodNetwork - class OrdersAndFulfillmentReport - class DistributorTotalsBySupplierReport - REPORT_TYPE = "order_cycle_distributor_totals_by_supplier" - - attr_reader :context - - def initialize(context) - @context = context - end - - def table_headers - [I18n.t(:report_header_hub), I18n.t(:report_header_producer), - I18n.t(:report_header_product), I18n.t(:report_header_variant), - I18n.t(:report_header_quantity), I18n.t(:report_header_curr_cost_per_unit), - I18n.t(:report_header_total_cost), I18n.t(:report_header_total_shipping_cost), - I18n.t(:report_header_shipping_method)] - end - - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/MethodLength - def rules - [ - { - group_by: proc { |line_item| line_item.order.distributor }, - sort_by: proc { |distributor| distributor.name }, - summary_columns: [ - proc { |_line_items| "" }, - proc { |_line_items| I18n.t('admin.reports.total') }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |line_items| line_items.sum(&:amount) }, - proc { |line_items| line_items.map(&:order).uniq.sum(&:ship_total) }, - proc { |_line_items| "" } - ] - }, - { - group_by: proc { |line_item| line_item.variant.product.supplier }, - sort_by: proc { |supplier| supplier.name } - }, - { - group_by: proc { |line_item| line_item.variant.product }, - sort_by: proc { |product| product.name } - }, - { - group_by: proc { |line_item| line_item.variant.full_name }, - sort_by: proc { |full_name| full_name } - } - ] - end - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/MethodLength - - # rubocop:disable Metrics/AbcSize - def columns - [proc { |line_items| line_items.first.order.distributor.name }, - proc { |line_items| line_items.first.variant.product.supplier.name }, - proc { |line_items| line_items.first.variant.product.name }, - proc { |line_items| line_items.first.variant.full_name }, - proc { |line_items| line_items.to_a.sum(&:quantity) }, - proc { |line_items| line_items.first.price }, - proc { |line_items| line_items.sum(&:amount) }, - proc { |_line_items| "" }, - proc { |_line_items| I18n.t(:report_header_shipping_method) }] - end - # rubocop:enable Metrics/AbcSize - - def line_item_includes - [{ order: [:distributor, :adjustments, { shipments: { shipping_rates: :shipping_method } }], - variant: [{ option_values: :option_type }, { product: :supplier }] }] - end - end - end -end diff --git a/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report.rb b/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report.rb deleted file mode 100644 index 8731c58c70..0000000000 --- a/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report.rb +++ /dev/null @@ -1,77 +0,0 @@ -# frozen_string_literal: true - -module OpenFoodNetwork - class OrdersAndFulfillmentReport - class SupplierTotalsByDistributorReport - REPORT_TYPE = "order_cycle_supplier_totals_by_distributor" - - attr_reader :context - - delegate :supplier_name, to: :context - - def initialize(context) - @context = context - end - - def table_headers - [I18n.t(:report_header_producer), I18n.t(:report_header_product), - I18n.t(:report_header_variant), I18n.t(:report_header_to_hub), - I18n.t(:report_header_quantity), I18n.t(:report_header_curr_cost_per_unit), - I18n.t(:report_header_total_cost), I18n.t(:report_header_shipping_method)] - end - - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/MethodLength - def rules - [ - { - group_by: proc { |line_item| line_item.variant.product.supplier }, - sort_by: proc { |supplier| supplier.name } - }, - { - group_by: proc { |line_item| line_item.variant.product }, - sort_by: proc { |product| product.name } - }, - { - group_by: proc { |line_item| line_item.variant.full_name }, - sort_by: proc { |full_name| full_name }, - summary_columns: [ - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| I18n.t('admin.reports.total') }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |line_items| line_items.sum(&:amount) }, - proc { |_line_items| "" } - ] - }, - { - group_by: proc { |line_item| line_item.order.distributor }, - sort_by: proc { |distributor| distributor.name } - } - ] - end - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/MethodLength - - def columns - [ - supplier_name, - proc { |line_items| line_items.first.variant.product.name }, - proc { |line_items| line_items.first.variant.full_name }, - proc { |line_items| line_items.first.order.distributor.name }, - proc { |line_items| line_items.to_a.sum(&:quantity) }, - proc { |line_items| line_items.first.price }, - proc { |line_items| line_items.sum(&:amount) }, - proc { |_line_items| I18n.t(:report_header_shipping_method) } - ] - end - - def line_item_includes - [{ order: :distributor, - variant: [{ option_values: :option_type }, { product: :supplier }] }] - end - end - end -end diff --git a/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_report.rb b/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_report.rb deleted file mode 100644 index 8ee9decf89..0000000000 --- a/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_report.rb +++ /dev/null @@ -1,60 +0,0 @@ -# frozen_string_literal: true - -module OpenFoodNetwork - class OrdersAndFulfillmentReport - class SupplierTotalsReport - REPORT_TYPE = "order_cycle_supplier_totals" - - attr_reader :context - - delegate :supplier_name, :product_name, :line_items_name, :total_units, to: :context - - def initialize(context) - @context = context - end - - def table_headers - [I18n.t(:report_header_producer), I18n.t(:report_header_product), - I18n.t(:report_header_variant), I18n.t(:report_header_quantity), - I18n.t(:report_header_total_units), I18n.t(:report_header_curr_cost_per_unit), - I18n.t(:report_header_total_cost), I18n.t(:report_header_status), - I18n.t(:report_header_incoming_transport)] - end - - def rules - [ - { - group_by: proc { |line_item| line_item.variant.product.supplier }, - sort_by: proc { |supplier| supplier.name } - }, - { - group_by: proc { |line_item| line_item.variant.product }, - sort_by: proc { |product| product.name } - }, - { - group_by: proc { |line_item| line_item.variant.full_name }, - sort_by: proc { |full_name| full_name } - } - ] - end - - def columns - [ - supplier_name, - product_name, - line_items_name, - proc { |line_items| line_items.to_a.sum(&:quantity) }, - proc { |line_items| total_units(line_items) }, - proc { |line_items| line_items.first.price }, - proc { |line_items| line_items.sum(&:amount) }, - proc { |_line_items| "" }, - proc { |_line_items| I18n.t(:report_header_incoming_transport) } - ] - end - - def line_item_includes - [{ variant: [{ option_values: :option_type }, { product: :supplier }] }] - end - end - end -end diff --git a/lib/open_food_network/payments_report.rb b/lib/open_food_network/payments_report.rb deleted file mode 100644 index 8dbddcc270..0000000000 --- a/lib/open_food_network/payments_report.rb +++ /dev/null @@ -1,144 +0,0 @@ -# frozen_string_literal: true - -require 'open_food_network/order_grouper' - -module OpenFoodNetwork - class PaymentsReport - attr_reader :params - - def initialize(user, params = {}, render_table = false) - @params = params - @user = user - @render_table = render_table - end - - def table_headers - case params[:report_subtype] - when "payments_by_payment_type" - I18n.t(:report_header_payment_type) - [I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor), I18n.t(:report_header_payment_type), - I18n.t(:report_header_total_price, currency: currency_symbol)] - when "itemised_payment_totals" - [I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor), - I18n.t(:report_header_product_total_price, currency: currency_symbol), - I18n.t(:report_header_shipping_total_price, currency: currency_symbol), - I18n.t(:report_header_outstanding_balance_price, currency: currency_symbol), - I18n.t(:report_header_total_price, currency: currency_symbol)] - when "payment_totals" - [I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor), - I18n.t(:report_header_product_total_price, currency: currency_symbol), - I18n.t(:report_header_shipping_total_price, currency: currency_symbol), - I18n.t(:report_header_total_price, currency: currency_symbol), - I18n.t(:report_header_eft_price, currency: currency_symbol), - I18n.t(:report_header_paypal_price, currency: currency_symbol), - I18n.t(:report_header_outstanding_balance_price, currency: currency_symbol)] - else - [I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor), I18n.t(:report_header_payment_type), - I18n.t(:report_header_total_price, currency: currency_symbol)] - end - end - - def search - Spree::Order.complete.not_state(:canceled).managed_by(@user).ransack(params[:q]) - end - - def table_items - return [] unless @render_table - - orders = search.result - payments = orders.includes(:payments).map do |order| - order.payments.select(&:completed?) - end.flatten - - case params[:report_subtype] - when "payments_by_payment_type" - payments - when "itemised_payment_totals" - orders - when "payment_totals" - orders - else - payments - end - end - - def table_rows - order_grouper = OpenFoodNetwork::OrderGrouper.new rules, columns, self - order_grouper.table(table_items) - end - - def rules - case params[:report_subtype] - when "payments_by_payment_type" - [{ group_by: proc { |payment| payment.order.payment_state }, - sort_by: proc { |payment_state| payment_state } }, - { group_by: proc { |payment| payment.order.distributor }, - sort_by: proc { |distributor| distributor.name } }, - { group_by: proc { |payment| Spree::PaymentMethod.unscoped { payment.payment_method } }, - sort_by: proc { |method| method.name } }] - when "itemised_payment_totals" - [{ group_by: proc { |order| order.payment_state }, - sort_by: proc { |payment_state| payment_state } }, - { group_by: proc { |order| order.distributor }, - sort_by: proc { |distributor| distributor.name } }] - when "payment_totals" - [{ group_by: proc { |order| order.payment_state }, - sort_by: proc { |payment_state| payment_state } }, - { group_by: proc { |order| order.distributor }, - sort_by: proc { |distributor| distributor.name } }] - else - [{ group_by: proc { |payment| payment.order.payment_state }, - sort_by: proc { |payment_state| payment_state } }, - { group_by: proc { |payment| payment.order.distributor }, - sort_by: proc { |distributor| distributor.name } }, - { group_by: proc { |payment| payment.payment_method }, - sort_by: proc { |method| method.name } }] - end - end - - def columns - case params[:report_subtype] - when "payments_by_payment_type" - [proc { |payments| payments.first.order.payment_state }, - proc { |payments| payments.first.order.distributor.name }, - proc { |payments| payments.first.payment_method.name }, - proc { |payments| payments.sum(&:amount) }] - when "itemised_payment_totals" - [proc { |orders| orders.first.payment_state }, - proc { |orders| orders.first.distributor.name }, - proc { |orders| orders.to_a.sum(&:item_total) }, - proc { |orders| orders.sum(&:ship_total) }, - proc { |orders| orders.sum{ |order| order.outstanding_balance.to_f } }, - proc { |orders| orders.map(&:total).sum }] - when "payment_totals" - [proc { |orders| orders.first.payment_state }, - proc { |orders| orders.first.distributor.name }, - proc { |orders| orders.to_a.sum(&:item_total) }, - proc { |orders| orders.sum(&:ship_total) }, - proc { |orders| orders.map(&:total).sum }, - proc { |orders| - orders.sum { |o| - o.payments.select { |payment| - payment.completed? && - (payment.payment_method.name.to_s.include? "EFT") - }.sum(&:amount) - } - }, - proc { |orders| - orders.sum { |o| - o.payments.select { |payment| - payment.completed? && - (payment.payment_method.name.to_s.include? "PayPal") - }.sum(&:amount) - } - }, - proc { |orders| orders.sum{ |order| order.outstanding_balance.to_f } }] - else - [proc { |payments| payments.first.order.payment_state }, - proc { |payments| payments.first.order.distributor.name }, - proc { |payments| payments.first.payment_method.name }, - proc { |payments| payments.sum(&:amount) }] - end - end - end -end diff --git a/lib/open_food_network/products_and_inventory_default_report.rb b/lib/open_food_network/products_and_inventory_default_report.rb deleted file mode 100644 index 2400586c7b..0000000000 --- a/lib/open_food_network/products_and_inventory_default_report.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -module OpenFoodNetwork - class ProductsAndInventoryDefaultReport - attr_reader :context - - delegate :variants, :render_table, to: :context - - def initialize(context) - @context = context - end - - def table_headers - [ - I18n.t(:report_header_supplier), - I18n.t(:report_header_producer_suburb), - I18n.t(:report_header_product), - I18n.t(:report_header_product_properties), - I18n.t(:report_header_taxons), - I18n.t(:report_header_variant_value), - I18n.t(:report_header_price), - I18n.t(:report_header_group_buy_unit_quantity), - I18n.t(:report_header_amount), - I18n.t(:report_header_sku) - ] - end - - def table_rows - return [] unless render_table - - variants.map do |variant| - [ - variant.product.supplier.name, - variant.product.supplier.address.city, - variant.product.name, - variant.product.properties.map(&:name).join(", "), - variant.product.taxons.map(&:name).join(", "), - variant.full_name, - variant.price, - variant.product.group_buy_unit_size, - "", - sku_for(variant) - ] - end - end - - def sku_for(variant) - return variant.sku if variant.sku.present? - - variant.product.sku - end - end -end diff --git a/lib/open_food_network/products_and_inventory_report.rb b/lib/open_food_network/products_and_inventory_report.rb deleted file mode 100644 index ae211ec4d0..0000000000 --- a/lib/open_food_network/products_and_inventory_report.rb +++ /dev/null @@ -1,100 +0,0 @@ -# frozen_string_literal: true - -require 'open_food_network/scope_variant_to_hub' -require 'open_food_network/products_and_inventory_default_report' -require 'open_food_network/lettuce_share_report' - -module OpenFoodNetwork - class ProductsAndInventoryReport - attr_reader :params, :render_table - - delegate :table_rows, :table_headers, :rules, :columns, :sku_for, to: :report - - def initialize(user, params = {}, render_table = false) - @user = user - @params = params - @render_table = render_table - end - - def report - @report ||= report_klass.new(self) - end - - def report_type - params[:report_subtype] - end - - def report_klass - if report_type == 'lettuce_share' - OpenFoodNetwork::LettuceShareReport - else - OpenFoodNetwork::ProductsAndInventoryDefaultReport - end - end - - def permissions - @permissions ||= OpenFoodNetwork::Permissions.new(@user) - end - - def visible_products - @visible_products ||= permissions.visible_products - end - - def variants - filter(child_variants) - end - - def child_variants - Spree::Variant. - where(is_master: false). - includes(option_values: :option_type). - joins(:product). - merge(visible_products). - order('spree_products.name') - end - - def filter(variants) - filter_on_hand filter_to_distributor filter_to_order_cycle filter_to_supplier variants - end - - # Using the `in_stock?` method allows overrides by distributors. - def filter_on_hand(variants) - if report_type == 'inventory' - variants.select(&:in_stock?) - else - variants - end - end - - def filter_to_supplier(variants) - if params[:supplier_id].to_i > 0 - variants.where("spree_products.supplier_id = ?", params[:supplier_id]) - else - variants - end - end - - def filter_to_distributor(variants) - if params[:distributor_id].to_i > 0 - distributor = Enterprise.find params[:distributor_id] - scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor) - variants.in_distributor(distributor).each { |v| scoper.scope(v) } - else - variants - end - end - - def filter_to_order_cycle(variants) - if params[:order_cycle_id].to_i > 0 - order_cycle = OrderCycle.find params[:order_cycle_id] - variant_ids = Exchange.in_order_cycle(order_cycle). - joins("INNER JOIN exchange_variants ON exchanges.id = exchange_variants.exchange_id"). - select("DISTINCT exchange_variants.variant_id") - - variants.where("spree_variants.id IN (#{variant_ids.to_sql})") - else - variants - end - end - end -end diff --git a/lib/open_food_network/reports/line_items.rb b/lib/open_food_network/reports/line_items.rb deleted file mode 100644 index c8622860df..0000000000 --- a/lib/open_food_network/reports/line_items.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -module OpenFoodNetwork - module Reports - # shared code to search and list line items - class LineItems - def initialize(order_permissions, params, orders_relation = nil) - @order_permissions = order_permissions - @params = params - complete_not_canceled_visible_orders = CompleteVisibleOrders.new(order_permissions).query.not_state(:canceled) - @orders_relation = orders_relation || complete_not_canceled_visible_orders - end - - def orders - @orders ||= search_orders - end - - def list(line_item_includes = nil) - line_items = order_permissions.visible_line_items.in_orders(orders.result) - - if @params[:supplier_id_in].present? - line_items = line_items.supplied_by_any(@params[:supplier_id_in]) - end - - if line_item_includes.present? - line_items = line_items.includes(*line_item_includes).references(:line_items) - end - - without_editable_line_items = line_items - editable_line_items(line_items) - - without_editable_line_items.each do |line_item| - OrderDataMasker.new(line_item.order).call - end - - line_items - end - - private - - attr_reader :orders_relation, :order_permissions - - def search_orders - orders_relation.ransack(@params[:q]) - end - - # From the line_items given, returns the ones that are editable by the user - def editable_line_items(line_items) - editable_line_items_ids = order_permissions.editable_line_items.select(:id) - - # Although merge could take a relation, here we convert line_items to array - # because, if we pass a relation, merge will overwrite the conditions on the same field - # In this case: the IN clause on spree_line_items.order_id from line_items - # overwrites the IN clause on spree_line_items.order_id on editable_line_items_ids - # We convert to array the relation with less elements: line_items - editable_line_items_ids.merge(line_items.to_a) - end - end - end -end diff --git a/lib/open_food_network/sales_tax_report.rb b/lib/open_food_network/sales_tax_report.rb deleted file mode 100644 index eed03325b5..0000000000 --- a/lib/open_food_network/sales_tax_report.rb +++ /dev/null @@ -1,108 +0,0 @@ -# frozen_string_literal: true - -module OpenFoodNetwork - class SalesTaxReport - include Spree::ReportsHelper - attr_accessor :user, :params - - def initialize(user, params, render_table) - @user = user - @params = params - @render_table = render_table - end - - def table_headers - case params[:report_subtype] - when "tax_rates" - [I18n.t(:report_header_order_number), - I18n.t(:report_header_total_excl_vat, currency_symbol: currency_symbol)] + - relevant_rates.map { |rate| "%.1f%% (%s)" % [rate.amount.to_f * 100, currency_symbol] } + - [I18n.t(:report_header_total_tax, currency_symbol: currency_symbol), - I18n.t(:report_header_total_incl_vat, currency_symbol: currency_symbol)] - else - [I18n.t(:report_header_order_number), - I18n.t(:report_header_date), - I18n.t(:report_header_items), - I18n.t(:report_header_items_total, currency_symbol: currency_symbol), - I18n.t(:report_header_taxable_items_total, currency_symbol: currency_symbol), - I18n.t(:report_header_sales_tax, currency_symbol: currency_symbol), - I18n.t(:report_header_delivery_charge, currency_symbol: currency_symbol), - I18n.t(:report_header_tax_on_delivery, currency_symbol: currency_symbol), - I18n.t(:report_header_tax_on_fees, currency_symbol: currency_symbol), - I18n.t(:report_header_total_tax, currency_symbol: currency_symbol), - I18n.t(:report_header_customer), - I18n.t(:report_header_distributor)] - end - end - - def search - permissions = ::Permissions::Order.new(user) - permissions.editable_orders.complete.not_state(:canceled).ransack(params[:q]) - end - - def orders - search.result - end - - def table_rows - return [] unless @render_table - - case params[:report_subtype] - when "tax_rates" - orders.map do |order| - [order.number, order.total - order.total_tax] + - relevant_rates.map { |rate| - OrderTaxAdjustmentsFetcher.new(order).totals.fetch(rate, 0) - } + [order.total_tax, order.total] - end - else - orders.map do |order| - totals = totals_of order.line_items - shipping_cost = shipping_cost_for order - - [order.number, order.completed_at.strftime("%F %T"), totals[:items], totals[:items_total], - totals[:taxable_total], totals[:sales_tax], shipping_cost, order.shipping_tax, order.enterprise_fee_tax, order.total_tax, - order.bill_address.full_name, order.distributor&.name] - end - end - end - - private - - def relevant_rates - return @relevant_rates unless @relevant_rates.nil? - - @relevant_rates = Spree::TaxRate.distinct - end - - def totals_of(line_items) - totals = { items: 0, items_total: 0.0, taxable_total: 0.0, sales_tax: 0.0 } - - line_items.each do |line_item| - totals[:items] += line_item.quantity - totals[:items_total] += line_item.amount - - sales_tax = tax_included_in line_item - - if sales_tax > 0 - totals[:taxable_total] += line_item.amount - totals[:sales_tax] += sales_tax - end - end - - totals.each_pair do |k, _v| - totals[k] = totals[k].round(2) - end - - totals - end - - def shipping_cost_for(order) - order.shipments.first&.cost || 0.0 - end - - def tax_included_in(line_item) - line_item.adjustments.tax.inclusive.sum(:amount) - end - end -end diff --git a/lib/open_food_network/users_and_enterprises_report.rb b/lib/open_food_network/users_and_enterprises_report.rb deleted file mode 100644 index e3a3e2a619..0000000000 --- a/lib/open_food_network/users_and_enterprises_report.rb +++ /dev/null @@ -1,138 +0,0 @@ -# frozen_string_literal: true - -module OpenFoodNetwork - class UsersAndEnterprisesReport - attr_reader :params - - def initialize(user, params = {}, compile_table = false) - @user = user - @params = params - @compile_table = compile_table - - # Convert arrays of ids to comma delimited strings - if @params[:enterprise_id_in].is_a? Array - @params[:enterprise_id_in] = @params[:enterprise_id_in].join(',') - end - @params[:user_id_in] = @params[:user_id_in].join(',') if @params[:user_id_in].is_a? Array - end - - def table_headers - [ - I18n.t(:report_header_user), - I18n.t(:report_header_relationship), - I18n.t(:report_header_enterprise), - I18n.t(:report_header_is_producer), - I18n.t(:report_header_sells), - I18n.t(:report_header_visible), - I18n.t(:report_header_confirmation_date), - ] - end - - def table_rows - return [] unless @compile_table - - users_and_enterprises.map do |uae| - [ - uae["user_email"], - uae["relationship_type"], - uae["name"], - to_bool(uae["is_primary_producer"]), - uae["sells"], - uae["visible"], - to_local_datetime(uae["created_at"]) - ] - end - end - - def owners_and_enterprises - query = Enterprise.joins("LEFT JOIN spree_users AS owner ON enterprises.owner_id = owner.id") - .where("enterprises.id IS NOT NULL") - - query = filter_by_int_list_if_present(query, "enterprises.id", params[:enterprise_id_in]) - query = filter_by_int_list_if_present(query, "owner.id", params[:user_id_in]) - - query_helper(query, :owner, :owns) - end - - def managers_and_enterprises - query = Enterprise - .joins("LEFT JOIN enterprise_roles ON enterprises.id = enterprise_roles.enterprise_id") - .joins("LEFT JOIN spree_users AS managers ON enterprise_roles.user_id = managers.id") - .where("enterprise_id IS NOT NULL") - .where("user_id IS NOT NULL") - - query = filter_by_int_list_if_present(query, "enterprise_id", params[:enterprise_id_in]) - query = filter_by_int_list_if_present(query, "user_id", params[:user_id_in]) - - query_helper(query, :managers, :manages) - end - - def query_helper(query, email_user, relationship_type) - query.order("enterprises.created_at DESC") - .select(["enterprises.name", - "enterprises.sells", - "enterprises.visible", - "enterprises.is_primary_producer", - "enterprises.created_at", - "#{email_user}.email AS user_email"]) - .to_a - .map { |x| - { - name: x.name, - sells: x.sells, - visible: (x.visible ? 't' : 'f'), - is_primary_producer: (x.is_primary_producer ? 't' : 'f'), - created_at: x.created_at.utc.iso8601, - relationship_type: relationship_type, - user_email: x.user_email - }.stringify_keys - } - end - - def users_and_enterprises - sort( owners_and_enterprises.concat(managers_and_enterprises) ) - end - - def filter_by_int_list_if_present(query, filtered_field_name, int_list) - if int_list.present? - query = query.where("#{filtered_field_name} IN (?)", split_int_list(int_list)) - end - query - end - - def split_int_list(int_list) - int_list.split(',').map(&:to_i) - end - - def sort(results) - results.sort do |a, b| - if a["created_at"].nil? || b["created_at"].nil? - [(a["created_at"].nil? ? 0 : 1), a["name"], b["relationship_type"], a["user_email"]] <=> - [(b["created_at"].nil? ? 0 : 1), b["name"], a["relationship_type"], b["user_email"]] - else - [ - DateTime.parse(b["created_at"]).in_time_zone, - a["name"], - b["relationship_type"], - a["user_email"] - ] <=> [ - DateTime.parse(a["created_at"]).in_time_zone, - b["name"], - a["relationship_type"], - b["user_email"] - ] - end - end - end - - def to_bool(value) - ActiveRecord::Type::Boolean.new.cast(value) - end - - def to_local_datetime(date) - return "" if date.nil? - - date.to_datetime.in_time_zone.strftime "%Y-%m-%d %H:%M" - end - end -end diff --git a/lib/open_food_network/xero_invoices_report.rb b/lib/open_food_network/xero_invoices_report.rb deleted file mode 100644 index da36a04860..0000000000 --- a/lib/open_food_network/xero_invoices_report.rb +++ /dev/null @@ -1,235 +0,0 @@ -# frozen_string_literal: true - -module OpenFoodNetwork - class XeroInvoicesReport - def initialize(user, opts = {}, compile_table = false) - @user = user - - @opts = opts. - symbolize_keys. - reject { |_k, v| v.blank? }. - reverse_merge( report_subtype: 'summary', - invoice_date: Time.zone.today, - due_date: Time.zone.today + 1.month, - account_code: 'food sales' ) - @compile_table = compile_table - end - - def table_headers - # NOTE: These are NOT to be translated, they need to be in this exact format to work with Xero - %w(*ContactName EmailAddress POAddressLine1 POAddressLine2 POAddressLine3 POAddressLine4 - POCity PORegion POPostalCode POCountry *InvoiceNumber Reference *InvoiceDate *DueDate InventoryItemCode *Description *Quantity *UnitAmount Discount *AccountCode *TaxType TrackingName1 TrackingOption1 TrackingName2 TrackingOption2 Currency BrandingTheme Paid?) - end - - def search - permissions = ::Permissions::Order.new(@user) - permissions.editable_orders.complete.not_state(:canceled).ransack(@opts[:q]) - end - - def orders - search.result.reorder('id DESC') - end - - def table_rows - return [] unless @compile_table - - rows = [] - - orders.each_with_index do |order, i| - invoice_number = invoice_number_for(order, i) - rows += detail_rows_for_order(order, invoice_number, @opts) if detail? - rows += summary_rows_for_order(order, invoice_number, @opts) - end - - rows.compact - end - - private - - def report_options - @opts.merge(line_item_includes: line_item_includes) - end - - def line_item_includes - [:bill_address, :adjustments, - { line_items: { variant: [{ option_values: :option_type }, { product: :supplier }] } }] - end - - def detail_rows_for_order(order, invoice_number, opts) - rows = [] - - rows += line_item_detail_rows(order, invoice_number, opts) - rows += adjustment_detail_rows(order, invoice_number, opts) - - rows - end - - def line_item_detail_rows(order, invoice_number, opts) - order.line_items.map do |line_item| - line_item_detail_row(line_item, invoice_number, opts) - end - end - - def line_item_detail_row(line_item, invoice_number, opts) - row(line_item.order, - line_item.variant.sku, - line_item.product_and_full_name, - line_item.quantity.to_s, - line_item.price.to_s, - invoice_number, - tax_type(line_item), - opts) - end - - def adjustment_detail_rows(order, invoice_number, opts) - admin_adjustments(order).map do |adjustment| - adjustment_detail_row(adjustment, invoice_number, opts) - end - end - - def adjustment_detail_row(adjustment, invoice_number, opts) - row(adjustment_order(adjustment), - '', - adjustment.label, - 1, - adjustment.amount, - invoice_number, - tax_type(adjustment), - opts) - end - - def summary_rows_for_order(order, invoice_number, opts) - rows = [] - - rows += produce_summary_rows(order, invoice_number, opts) unless detail? - rows += fee_summary_rows(order, invoice_number, opts) - rows += shipping_summary_rows(order, invoice_number, opts) - rows += payment_summary_rows(order, invoice_number, opts) - rows += admin_adjustment_summary_rows(order, invoice_number, opts) unless detail? - - rows - end - - def produce_summary_rows(order, invoice_number, opts) - [summary_row(order, I18n.t(:report_header_total_untaxable_produce), total_untaxable_products(order), invoice_number, I18n.t(:report_header_gst_free_income), opts), - summary_row(order, I18n.t(:report_header_total_taxable_produce), - total_taxable_products(order), invoice_number, I18n.t(:report_header_gst_on_income), opts)] - end - - def fee_summary_rows(order, invoice_number, opts) - [summary_row(order, I18n.t(:report_header_total_untaxable_fees), total_untaxable_fees(order), invoice_number, I18n.t(:report_header_gst_free_income), opts), - summary_row(order, I18n.t(:report_header_total_taxable_fees), total_taxable_fees(order), - invoice_number, I18n.t(:report_header_gst_on_income), opts)] - end - - def shipping_summary_rows(order, invoice_number, opts) - [summary_row(order, I18n.t(:report_header_delivery_shipping_cost), total_shipping(order), - invoice_number, tax_on_shipping_s(order), opts)] - end - - def payment_summary_rows(order, invoice_number, opts) - [summary_row(order, I18n.t(:report_header_transaction_fee), total_transaction(order), - invoice_number, I18n.t(:report_header_gst_free_income), opts)] - end - - def admin_adjustment_summary_rows(order, invoice_number, opts) - [summary_row(order, I18n.t(:report_header_total_untaxable_admin), total_untaxable_admin_adjustments(order), invoice_number, I18n.t(:report_header_gst_free_income), opts), - summary_row(order, I18n.t(:report_header_total_taxable_admin), - total_taxable_admin_adjustments(order), invoice_number, I18n.t(:report_header_gst_on_income), opts)] - end - - def summary_row(order, description, amount, invoice_number, tax_type, opts = {}) - row order, '', description, '1', amount, invoice_number, tax_type, opts - end - - def row(order, sku, description, quantity, amount, invoice_number, tax_type, opts = {}) - return nil if amount == 0 - - [order.bill_address&.full_name, - order.email, - order.bill_address&.address1, - order.bill_address&.address2, - '', - '', - order.bill_address&.city, - order.bill_address&.state, - order.bill_address&.zipcode, - order.bill_address&.country&.name, - invoice_number, - order.number, - opts[:invoice_date], - opts[:due_date], - sku, - description, - quantity, - amount, - '', - opts[:account_code], - tax_type, - '', - '', - '', - '', - Spree::Config.currency, - '', - order.paid? ? I18n.t(:y) : I18n.t(:n)] - end - - def admin_adjustments(order) - order.adjustments.admin - end - - def adjustment_order(adjustment) - adjustment.adjustable.is_a?(Spree::Order) ? adjustment.adjustable : nil - end - - def invoice_number_for(order, idx) - @opts[:initial_invoice_number] ? @opts[:initial_invoice_number].to_i + idx : order.number - end - - def total_untaxable_products(order) - order.line_items.without_tax.to_a.sum(&:amount) - end - - def total_taxable_products(order) - order.line_items.with_tax.to_a.sum(&:amount) - end - - def total_untaxable_fees(order) - order.all_adjustments.enterprise_fee.where(tax_category: nil).sum(:amount) - end - - def total_taxable_fees(order) - order.all_adjustments.enterprise_fee.where.not(tax_category: nil).sum(:amount) - end - - def total_shipping(order) - order.all_adjustments.shipping.sum(:amount) - end - - def total_transaction(order) - order.all_adjustments.payment_fee.sum(:amount) - end - - def tax_on_shipping_s(order) - tax_on_shipping = order.shipments.sum("additional_tax_total + included_tax_total").positive? - tax_on_shipping ? I18n.t(:report_header_gst_on_income) : I18n.t(:report_header_gst_free_income) - end - - def total_untaxable_admin_adjustments(order) - order.adjustments.admin.where(tax_category: nil).sum(:amount) - end - - def total_taxable_admin_adjustments(order) - order.adjustments.admin.where.not(tax_category: nil).sum(:amount) - end - - def detail? - @opts[:report_subtype] == 'detailed' - end - - def tax_type(taxable) - taxable.has_tax? ? I18n.t(:report_header_gst_on_income) : I18n.t(:report_header_gst_free_income) - end - end -end diff --git a/lib/reporting/line_items.rb b/lib/reporting/line_items.rb new file mode 100644 index 0000000000..d5ee578ad3 --- /dev/null +++ b/lib/reporting/line_items.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module Reporting + # shared code to search and list line items + class LineItems + def initialize(order_permissions, params, orders_relation = nil) + @order_permissions = order_permissions + @params = params + complete_not_canceled_visible_orders = CompleteVisibleOrders.new(order_permissions).query.not_state(:canceled) + @orders_relation = orders_relation || complete_not_canceled_visible_orders + end + + def orders + @orders ||= search_orders + end + + def list(line_item_includes = nil) + line_items = order_permissions.visible_line_items.in_orders(orders.result) + + if @params[:supplier_id_in].present? + line_items = line_items.supplied_by_any(@params[:supplier_id_in]) + end + + if line_item_includes.present? + line_items = line_items.includes(*line_item_includes).references(:line_items) + end + + without_editable_line_items = line_items - editable_line_items(line_items) + + without_editable_line_items.each do |line_item| + OrderDataMasker.new(line_item.order).call + end + + line_items + end + + private + + attr_reader :orders_relation, :order_permissions + + def search_orders + orders_relation.ransack(@params[:q]) + end + + # From the line_items given, returns the ones that are editable by the user + def editable_line_items(line_items) + editable_line_items_ids = order_permissions.editable_line_items.select(:id) + + # Although merge could take a relation, here we convert line_items to array + # because, if we pass a relation, merge will overwrite the conditions on the same field + # In this case: the IN clause on spree_line_items.order_id from line_items + # overwrites the IN clause on spree_line_items.order_id on editable_line_items_ids + # We convert to array the relation with less elements: line_items + editable_line_items_ids.merge(line_items.to_a) + end + end +end diff --git a/lib/open_food_network/order_grouper.rb b/lib/reporting/order_grouper.rb similarity index 98% rename from lib/open_food_network/order_grouper.rb rename to lib/reporting/order_grouper.rb index cc4720bab9..dc78dfff8c 100644 --- a/lib/open_food_network/order_grouper.rb +++ b/lib/reporting/order_grouper.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module OpenFoodNetwork +module Reporting class OrderGrouper def initialize(rules, column_constructors, report = nil) @rules = rules diff --git a/lib/reporting/reports/bulk_coop/bulk_coop_allocation_report.rb b/lib/reporting/reports/bulk_coop/bulk_coop_allocation_report.rb new file mode 100644 index 0000000000..8295b04a93 --- /dev/null +++ b/lib/reporting/reports/bulk_coop/bulk_coop_allocation_report.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module BulkCoop + class BulkCoopAllocationReport + def table_headers + [ + 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/lib/reporting/reports/bulk_coop/bulk_coop_report.rb b/lib/reporting/reports/bulk_coop/bulk_coop_report.rb new file mode 100644 index 0000000000..d30641f5df --- /dev/null +++ b/lib/reporting/reports/bulk_coop/bulk_coop_report.rb @@ -0,0 +1,319 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module BulkCoop + class BulkCoopReport + 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 + @filter_canceled = false + end + + def table_headers + case params[:report_subtype] + when "bulk_coop_supplier_report" + @supplier_report.table_headers + when "bulk_coop_allocation" + @allocation_report.table_headers + 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 table_rows + order_grouper = Reporting::OrderGrouper.new rules, columns, self + order_grouper.table(table_items) + end + + def rules + case params[:report_subtype] + 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| + ( if (lis.first.product.group_buy_unit_size || 0).zero? + 0 + else + ( lis.sum { |li| + [li.max_quantity || 0, + li.quantity || 0].max * (li.weight_from_unit_value || 0) + } / lis.first.product.group_buy_unit_size ) + end ).floor + }, + proc { |lis| + lis.sum { |li| + [li.max_quantity || 0, + li.quantity || 0].max * (li.weight_from_unit_value || 0) + } - ( ( if (lis.first.product.group_buy_unit_size || 0).zero? + 0 + else + ( lis.sum { |li| + [li.max_quantity || 0, + li.quantity || 0].max * (li.weight_from_unit_value || 0) + } / lis.first.product.group_buy_unit_size ) + end ).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_subtype] + 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 + + attr_reader :filter_canceled + + def line_item_includes + [ + { + order: [:bill_address], + variant: [{ option_values: :option_type }, { product: :supplier }] + }, + :option_values + ] + end + + def order_permissions + @order_permissions ||= ::Permissions::Order.new(@user, filter_canceled) + end + + def report_line_items + @report_line_items ||= Reporting::LineItems.new( + order_permissions, + @params, + CompleteVisibleOrders.new(order_permissions).query + ) + end + + def customer_payments_total_cost(line_items) + unique_orders(line_items).sum(&:total) + end + + def customer_payments_amount_owed(line_items) + unique_orders(line_items).sum(&:new_outstanding_balance) + end + + def customer_payments_amount_paid(line_items) + unique_orders(line_items).sum(&:payment_total) + end + + def unique_orders(line_items) + line_items.map(&:order).uniq + end + + def empty_cell(_line_items) + "" + end + + def full_name(line_items) + line_items.first.full_name + end + + def group_buy_unit_size(line_items) + unit_size = line_items.first.variant.product.group_buy_unit_size || 0.0 + unit_size / (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) + VariantUnits::OptionValueNamer.new(line_items.first).value + end + + def option_value_unit(line_items) + VariantUnits::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(_line_items) + 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/lib/reporting/reports/bulk_coop/bulk_coop_supplier_report.rb b/lib/reporting/reports/bulk_coop/bulk_coop_supplier_report.rb new file mode 100644 index 0000000000..1806105424 --- /dev/null +++ b/lib/reporting/reports/bulk_coop/bulk_coop_supplier_report.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module BulkCoop + class BulkCoopSupplierReport + def table_headers + [ + 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/lib/reporting/reports/customers/customers_report.rb b/lib/reporting/reports/customers/customers_report.rb new file mode 100644 index 0000000000..0fa040646b --- /dev/null +++ b/lib/reporting/reports/customers/customers_report.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module Customers + class CustomersReport + attr_reader :params + + def initialize(user, params = {}, compile_table = false) + @params = params + @user = user + @compile_table = compile_table + end + + def table_headers + if is_mailing_list? + [I18n.t(:report_header_email), + I18n.t(:report_header_first_name), + I18n.t(:report_header_last_name), + I18n.t(:report_header_suburb)] + else + [I18n.t(:report_header_first_name), + I18n.t(:report_header_last_name), + I18n.t(:report_header_billing_address), + I18n.t(:report_header_email), + I18n.t(:report_header_phone), + I18n.t(:report_header_hub), + I18n.t(:report_header_hub_address), + I18n.t(:report_header_shipping_method)] + end + end + + def table_rows + return [] unless @compile_table + + orders.map do |order| + if is_mailing_list? + [order.email, + order.billing_address.firstname, + order.billing_address.lastname, + order.billing_address.city] + else + ba = order.billing_address + da = order.distributor&.address + [ba.firstname, + ba.lastname, + [ba.address1, ba.address2, ba.city].join(" "), + order.email, + ba.phone, + order.distributor&.name, + [da&.address1, da&.address2, da&.city].join(" "), + order.shipping_method&.name] + end + end + end + + def orders + filter Spree::Order.managed_by(@user) + .distributed_by_user(@user) + .complete.not_state(:canceled) + end + + def filter(orders) + filter_to_supplier filter_to_distributor filter_to_order_cycle orders + end + + def filter_to_supplier(orders) + if params[:supplier_id].to_i > 0 + orders.select do |order| + order.line_items.includes(:product) + .where("spree_products.supplier_id = ?", params[:supplier_id].to_i) + .references(:product) + .count + .positive? + end + else + orders + end + end + + def filter_to_distributor(orders) + if params[:distributor_id].to_i > 0 + orders.where(distributor_id: params[:distributor_id]) + else + orders + end + end + + def filter_to_order_cycle(orders) + if params[:order_cycle_id].to_i > 0 + orders.where(order_cycle_id: params[:order_cycle_id]) + else + orders + end + end + + private + + def is_mailing_list? + params[:report_subtype] == "mailing_list" + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/authorizer.rb b/lib/reporting/reports/enterprise_fee_summary/authorizer.rb similarity index 89% rename from engines/order_management/app/services/order_management/reports/enterprise_fee_summary/authorizer.rb rename to lib/reporting/reports/enterprise_fee_summary/authorizer.rb index b85fdcbb6f..65b67bc023 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/authorizer.rb +++ b/lib/reporting/reports/enterprise_fee_summary/authorizer.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -module OrderManagement +module Reporting module Reports module EnterpriseFeeSummary - class Authorizer < ::Reports::Authorizer + class Authorizer < Reporting::Reports::EnterpriseFeeSummary::Reports::Authorizer def authorize! authorize_by_distribution! authorize_by_fee! diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/coordinator_fee.rb b/lib/reporting/reports/enterprise_fee_summary/data_representations/coordinator_fee.rb similarity index 97% rename from engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/coordinator_fee.rb rename to lib/reporting/reports/enterprise_fee_summary/data_representations/coordinator_fee.rb index 9d11b94d26..de7fe47eeb 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/coordinator_fee.rb +++ b/lib/reporting/reports/enterprise_fee_summary/data_representations/coordinator_fee.rb @@ -3,7 +3,7 @@ # This module provides EnterpriseFeeSummary::Scope DB result to report mappings for coordinator fees # in an order cycle. -module OrderManagement +module Reporting module Reports module EnterpriseFeeSummary module DataRepresentations diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/exchange_order_fee.rb b/lib/reporting/reports/enterprise_fee_summary/data_representations/exchange_order_fee.rb similarity index 96% rename from engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/exchange_order_fee.rb rename to lib/reporting/reports/enterprise_fee_summary/data_representations/exchange_order_fee.rb index efef74fd11..1bf26df688 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/exchange_order_fee.rb +++ b/lib/reporting/reports/enterprise_fee_summary/data_representations/exchange_order_fee.rb @@ -3,7 +3,7 @@ # This module provides EnterpriseFeeSummary::Scope DB result to report mappings for exchange fees # that use order-based calculators. -module OrderManagement +module Reporting module Reports module EnterpriseFeeSummary module DataRepresentations diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/incoming_exchange_line_item_fee.rb b/lib/reporting/reports/enterprise_fee_summary/data_representations/incoming_exchange_line_item_fee.rb similarity index 96% rename from engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/incoming_exchange_line_item_fee.rb rename to lib/reporting/reports/enterprise_fee_summary/data_representations/incoming_exchange_line_item_fee.rb index 2ab859a193..176c400c9e 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/incoming_exchange_line_item_fee.rb +++ b/lib/reporting/reports/enterprise_fee_summary/data_representations/incoming_exchange_line_item_fee.rb @@ -3,7 +3,7 @@ # This module provides EnterpriseFeeSummary::Scope DB result to report mappings for incoming # exchange fees that use line item -based calculators. -module OrderManagement +module Reporting module Reports module EnterpriseFeeSummary module DataRepresentations diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/outgoing_exchange_line_item_fee.rb b/lib/reporting/reports/enterprise_fee_summary/data_representations/outgoing_exchange_line_item_fee.rb similarity index 96% rename from engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/outgoing_exchange_line_item_fee.rb rename to lib/reporting/reports/enterprise_fee_summary/data_representations/outgoing_exchange_line_item_fee.rb index 3a547d659b..cfc2568045 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/outgoing_exchange_line_item_fee.rb +++ b/lib/reporting/reports/enterprise_fee_summary/data_representations/outgoing_exchange_line_item_fee.rb @@ -3,7 +3,7 @@ # This module provides EnterpriseFeeSummary::Scope DB result to report mappings for outgoing # exchange fees that use line item -based calculators. -module OrderManagement +module Reporting module Reports module EnterpriseFeeSummary module DataRepresentations diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/payment_method_fee.rb b/lib/reporting/reports/enterprise_fee_summary/data_representations/payment_method_fee.rb similarity index 97% rename from engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/payment_method_fee.rb rename to lib/reporting/reports/enterprise_fee_summary/data_representations/payment_method_fee.rb index 4fab78ad56..ce1ca48aa7 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/payment_method_fee.rb +++ b/lib/reporting/reports/enterprise_fee_summary/data_representations/payment_method_fee.rb @@ -3,7 +3,7 @@ # This module provides EnterpriseFeeSummary::Scope DB result to report mappings for payment method # fees. -module OrderManagement +module Reporting module Reports module EnterpriseFeeSummary module DataRepresentations diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/shipping_method_fee.rb b/lib/reporting/reports/enterprise_fee_summary/data_representations/shipping_method_fee.rb similarity index 97% rename from engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/shipping_method_fee.rb rename to lib/reporting/reports/enterprise_fee_summary/data_representations/shipping_method_fee.rb index 3863ff6d29..83b4096931 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/shipping_method_fee.rb +++ b/lib/reporting/reports/enterprise_fee_summary/data_representations/shipping_method_fee.rb @@ -3,7 +3,7 @@ # This module provides EnterpriseFeeSummary::Scope DB result to report mappings for shipping method # fees. -module OrderManagement +module Reporting module Reports module EnterpriseFeeSummary module DataRepresentations diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/using_enterprise_fee.rb b/lib/reporting/reports/enterprise_fee_summary/data_representations/using_enterprise_fee.rb similarity index 97% rename from engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/using_enterprise_fee.rb rename to lib/reporting/reports/enterprise_fee_summary/data_representations/using_enterprise_fee.rb index 85e466c7ce..0e69499e1c 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/using_enterprise_fee.rb +++ b/lib/reporting/reports/enterprise_fee_summary/data_representations/using_enterprise_fee.rb @@ -7,7 +7,7 @@ # fees. These mappings are not complete and should be supplemented with mappings that are specific # to the way that the enterprise fee is attached to the order cycle. -module OrderManagement +module Reporting module Reports module EnterpriseFeeSummary module DataRepresentations diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/with_i18n.rb b/lib/reporting/reports/enterprise_fee_summary/data_representations/with_i18n.rb similarity index 94% rename from engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/with_i18n.rb rename to lib/reporting/reports/enterprise_fee_summary/data_representations/with_i18n.rb index 4601efeaa3..3fe7b61637 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/with_i18n.rb +++ b/lib/reporting/reports/enterprise_fee_summary/data_representations/with_i18n.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module OrderManagement +module Reporting module Reports module EnterpriseFeeSummary module DataRepresentations diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb b/lib/reporting/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb similarity index 96% rename from engines/order_management/app/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb rename to lib/reporting/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb index c9b2b37852..c8bdd7895a 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb +++ b/lib/reporting/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module OrderManagement +module Reporting module Reports module EnterpriseFeeSummary class EnterpriseFeeSummaryReport @@ -12,7 +12,7 @@ module OrderManagement p['start_at'] = p.delete('completed_at_gt') p['end_at'] = p.delete('completed_at_lt') end - @parameters = OrderManagement::Reports::EnterpriseFeeSummary::Parameters.new(p || {}) + @parameters = Reporting::Reports::EnterpriseFeeSummary::Parameters.new(p || {}) @parameters.validate! @user = user @render_table = render_table diff --git a/lib/reporting/reports/enterprise_fee_summary/parameter_not_allowed_error.rb b/lib/reporting/reports/enterprise_fee_summary/parameter_not_allowed_error.rb new file mode 100644 index 0000000000..6143e43e6f --- /dev/null +++ b/lib/reporting/reports/enterprise_fee_summary/parameter_not_allowed_error.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module EnterpriseFeeSummary + class ParameterNotAllowedError < StandardError; end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/parameters.rb b/lib/reporting/reports/enterprise_fee_summary/parameters.rb similarity index 94% rename from engines/order_management/app/services/order_management/reports/enterprise_fee_summary/parameters.rb rename to lib/reporting/reports/enterprise_fee_summary/parameters.rb index bdacfab75a..59c97aeaa2 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/parameters.rb +++ b/lib/reporting/reports/enterprise_fee_summary/parameters.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -module OrderManagement +module Reporting module Reports module EnterpriseFeeSummary - class Parameters < ::Reports::Parameters::Base + class Parameters < Reporting::Reports::EnterpriseFeeSummary::Reports::Parameters::Base include ActiveModel::Validations attr_accessor :start_at, :end_at, :distributor_ids, :producer_ids, :order_cycle_ids, diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/permissions.rb b/lib/reporting/reports/enterprise_fee_summary/permissions.rb similarity index 98% rename from engines/order_management/app/services/order_management/reports/enterprise_fee_summary/permissions.rb rename to lib/reporting/reports/enterprise_fee_summary/permissions.rb index 0538068ed4..eef5d05498 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/permissions.rb +++ b/lib/reporting/reports/enterprise_fee_summary/permissions.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module OrderManagement +module Reporting module Reports module EnterpriseFeeSummary class Permissions diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb b/lib/reporting/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb similarity index 86% rename from engines/order_management/app/services/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb rename to lib/reporting/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb index dddcd2ca46..8b090f9098 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb +++ b/lib/reporting/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -module OrderManagement +module Reporting module Reports module EnterpriseFeeSummary module ReportData - class EnterpriseFeeTypeTotal < ::Reports::ReportData::Base + class EnterpriseFeeTypeTotal < Reporting::Reports::EnterpriseFeeSummary::Reports::ReportData::Base attr_accessor :fee_type, :enterprise_name, :fee_name, :customer_name, :fee_placement, :fee_calculated_on_transfer_through_name, :tax_category_name, :total_amount diff --git a/lib/reporting/reports/enterprise_fee_summary/reports/authorizer.rb b/lib/reporting/reports/enterprise_fee_summary/reports/authorizer.rb new file mode 100644 index 0000000000..8a76271614 --- /dev/null +++ b/lib/reporting/reports/enterprise_fee_summary/reports/authorizer.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module EnterpriseFeeSummary + module Reports + class Authorizer + attr_accessor :parameters, :permissions + + def initialize(parameters, permissions) + @parameters = parameters + @permissions = permissions + end + + 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 + + private + + def require_ids_allowed(array, allowed_objects) + error_klass = Reporting::Reports::EnterpriseFeeSummary::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 + end +end diff --git a/lib/reporting/reports/enterprise_fee_summary/reports/parameters/base.rb b/lib/reporting/reports/enterprise_fee_summary/reports/parameters/base.rb new file mode 100644 index 0000000000..0906658d88 --- /dev/null +++ b/lib/reporting/reports/enterprise_fee_summary/reports/parameters/base.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module EnterpriseFeeSummary + module Reports + module Parameters + class Base + extend ActiveModel::Naming + extend ActiveModel::Translation + include ActiveModel::Validations + include ActiveModel::Validations::Callbacks + + def initialize(attributes = {}) + attributes.each do |key, value| + public_send("#{key}=", value) + end + end + + 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 + + # The parameters are never persisted. + def to_key; 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 + end + end + end + end + end +end diff --git a/lib/reporting/reports/enterprise_fee_summary/reports/report_data/base.rb b/lib/reporting/reports/enterprise_fee_summary/reports/report_data/base.rb new file mode 100644 index 0000000000..f22729929b --- /dev/null +++ b/lib/reporting/reports/enterprise_fee_summary/reports/report_data/base.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module EnterpriseFeeSummary + module Reports + module ReportData + class Base + def initialize(attributes = {}) + attributes.each do |key, value| + public_send("#{key}=", value) + end + end + end + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb b/lib/reporting/reports/enterprise_fee_summary/scope.rb similarity index 99% rename from engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb rename to lib/reporting/reports/enterprise_fee_summary/scope.rb index 8c52d64f33..86a165ad08 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb +++ b/lib/reporting/reports/enterprise_fee_summary/scope.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module OrderManagement +module Reporting module Reports module EnterpriseFeeSummary class Scope diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/summarizer.rb b/lib/reporting/reports/enterprise_fee_summary/summarizer.rb similarity index 98% rename from engines/order_management/app/services/order_management/reports/enterprise_fee_summary/summarizer.rb rename to lib/reporting/reports/enterprise_fee_summary/summarizer.rb index 3f9b0098b5..dd223b72dd 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/summarizer.rb +++ b/lib/reporting/reports/enterprise_fee_summary/summarizer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module OrderManagement +module Reporting module Reports module EnterpriseFeeSummary class Summarizer diff --git a/lib/open_food_network/reports/list.rb b/lib/reporting/reports/list.rb similarity index 99% rename from lib/open_food_network/reports/list.rb rename to lib/reporting/reports/list.rb index 0f03512b68..d9ab57b945 100644 --- a/lib/open_food_network/reports/list.rb +++ b/lib/reporting/reports/list.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module OpenFoodNetwork +module Reporting module Reports class List def self.all diff --git a/lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb b/lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb new file mode 100644 index 0000000000..1f095f4982 --- /dev/null +++ b/lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb @@ -0,0 +1,170 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module OrderCycleManagement + class OrderCycleManagementReport + DEFAULT_DATE_INTERVAL = { from: -1.month, to: 1.day }.freeze + + attr_reader :params + + def initialize(user, params = {}, render_table = false) + @params = sanitize_params(params) + @user = user + @render_table = render_table + end + + def table_headers + if is_payment_methods? + [ + I18n.t(:report_header_first_name), + I18n.t(:report_header_last_name), + I18n.t(:report_header_hub), + I18n.t(:report_header_hub_code), + I18n.t(:report_header_email), + I18n.t(:report_header_phone), + I18n.t(:report_header_shipping_method), + I18n.t(:report_header_payment_method), + I18n.t(:report_header_amount), + I18n.t(:report_header_balance), + ] + else + [ + I18n.t(:report_header_first_name), + I18n.t(:report_header_last_name), + I18n.t(:report_header_hub), + I18n.t(:report_header_hub_code), + I18n.t(:report_header_delivery_address), + I18n.t(:report_header_delivery_postcode), + I18n.t(:report_header_phone), + I18n.t(:report_header_shipping_method), + I18n.t(:report_header_payment_method), + I18n.t(:report_header_amount), + I18n.t(:report_header_balance), + I18n.t(:report_header_temp_controlled_items), + I18n.t(:report_header_special_instructions), + ] + end + end + + def search + Spree::Order. + finalized. + not_state(:canceled). + distributed_by_user(@user). + managed_by(@user). + ransack(params[:q]) + end + + def orders + search_result = search.result.order(:completed_at) + orders_with_balance = OutstandingBalance.new(search_result). + query. + select('spree_orders.*') + + filter(orders_with_balance) + end + + def table_rows + return [] unless @render_table + + if is_payment_methods? + orders.map { |o| payment_method_row o } + else + orders.map { |o| delivery_row o } + end + end + + def filter(search_result) + filter_to_payment_method filter_to_shipping_method filter_to_order_cycle search_result + end + + private + + # This method relies on `balance_value` as a computed DB column. See `CompleteOrdersWithBalance` + # for reference. + def balance(order) + order.balance_value + end + + def payment_method_row(order) + ba = order.billing_address + [ba&.firstname, + ba&.lastname, + order.distributor&.name, + customer_code(order.email), + order.email, + ba&.phone, + order.shipping_method&.name, + order.payments.last&.payment_method&.name, + order.total, + balance(order)] + end + + def delivery_row(order) + sa = order.shipping_address + [sa.firstname, + sa.lastname, + order.distributor&.name, + customer_code(order.email), + "#{sa.address1} #{sa.address2} #{sa.city}", + sa.zipcode, + sa.phone, + order.shipping_method&.name, + order.payments.first&.payment_method&.name, + order.total, + balance(order), + has_temperature_controlled_items?(order), + order.special_instructions] + end + + def filter_to_payment_method(orders) + if params[:payment_method_in].present? + orders.joins(payments: :payment_method).where(spree_payments: { payment_method_id: params[:payment_method_in] }) + else + orders + end + end + + def filter_to_shipping_method(orders) + if params[:shipping_method_in].present? + orders.joins(shipments: :shipping_rates).where(spree_shipping_rates: { selected: true, + shipping_method_id: params[:shipping_method_in] }) + else + orders + end + end + + def filter_to_order_cycle(orders) + if params[:order_cycle_id].present? + orders.where(order_cycle_id: params[:order_cycle_id]) + else + orders + end + end + + def has_temperature_controlled_items?(order) + order.line_items.any? { |line_item| + line_item.product.shipping_category&.temperature_controlled + } + end + + def is_payment_methods? + params[:report_subtype] == "payment_methods" + end + + def customer_code(email) + customer = Customer.where(email: email).first + customer.nil? ? "" : customer.code + end + + def sanitize_params(params) + params[:q] ||= {} + params[:q][:completed_at_gt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:from] + params[:q][:completed_at_lt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:to] + params + end + end + end + end +end diff --git a/lib/reporting/reports/orders_and_distributors/orders_and_distributors_report.rb b/lib/reporting/reports/orders_and_distributors/orders_and_distributors_report.rb new file mode 100644 index 0000000000..5f5ef599c1 --- /dev/null +++ b/lib/reporting/reports/orders_and_distributors/orders_and_distributors_report.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module OrdersAndDistributors + class OrdersAndDistributorsReport + def initialize(user, params = {}, render_table = false) + @params = params + @user = user + @render_table = render_table + + @permissions = ::Permissions::Order.new(user, @params[:q]) + end + + def table_headers + [ + I18n.t(:report_header_order_date), + I18n.t(:report_header_order_id), + I18n.t(:report_header_customer_name), + I18n.t(:report_header_customer_email), + I18n.t(:report_header_customer_phone), + I18n.t(:report_header_customer_city), + I18n.t(:report_header_sku), + I18n.t(:report_header_item_name), + I18n.t(:report_header_variant), + I18n.t(:report_header_quantity), + I18n.t(:report_header_max_quantity), + I18n.t(:report_header_cost), + I18n.t(:report_header_shipping_cost), + I18n.t(:report_header_payment_method), + I18n.t(:report_header_distributor), + I18n.t(:report_header_distributor_address), + I18n.t(:report_header_distributor_city), + I18n.t(:report_header_distributor_postcode), + I18n.t(:report_header_shipping_method), + I18n.t(:report_header_shipping_instructions) + ] + end + + def search + @permissions.visible_orders.select("DISTINCT spree_orders.*"). + complete.not_state(:canceled). + ransack(@params[:q]) + end + + def table_rows + return [] unless @render_table + + orders = search.result + + orders.select{ |order| orders_with_hidden_details(orders).include? order }.each do |order| + OrderDataMasker.new(order).call + end + + line_item_details orders + end + + private + + def orders_with_hidden_details(orders) + # If empty array is passed in, the where clause will return all line_items, which is bad + if @permissions.editable_orders.empty? + orders + else + orders. + where('spree_orders.id NOT IN (?)', + @permissions.editable_orders.select(&:id)) + end + end + + def line_item_details(orders) + order_and_distributor_details = [] + + orders.each do |order| + order.line_items.each do |line_item| + order_and_distributor_details << row_for(line_item, order) + end + end + + order_and_distributor_details + end + + # Returns a row with the data to display for the specified line_item and + # its order + # + # @param line_item [Spree::LineItem] + # @param order [Spree::Order] + # @return [Array] + def row_for(line_item, order) + [ + order.completed_at.strftime("%F %T"), + order.id, + order.bill_address.full_name, + order.email, + order.bill_address.phone, + order.bill_address.city, + line_item.product.sku, + line_item.product.name, + line_item.options_text, + line_item.quantity, + line_item.max_quantity, + line_item.price * line_item.quantity, + line_item.distribution_fee, + order.payments.first&.payment_method&.name, + order.distributor&.name, + order.distributor.address.address1, + order.distributor.address.city, + order.distributor.address.zipcode, + order.shipping_method&.name, + order.special_instructions + ] + end + end + end + end +end diff --git a/lib/reporting/reports/orders_and_fulfillment/customer_totals_report.rb b/lib/reporting/reports/orders_and_fulfillment/customer_totals_report.rb new file mode 100644 index 0000000000..ada0f6d533 --- /dev/null +++ b/lib/reporting/reports/orders_and_fulfillment/customer_totals_report.rb @@ -0,0 +1,223 @@ +# frozen_string_literal: true + +# rubocop:disable Metrics/ClassLength +module Reporting + module Reports + module OrdersAndFulfillment + class CustomerTotalsReport + REPORT_TYPE = "order_cycle_customer_totals" + + attr_reader :context + + delegate :line_item_name, to: :context + delegate :variant_scoper_for, to: :context + + def initialize(context) + @context = context + @scopers_by_distributor_id = {} + end + + # rubocop:disable Metrics/AbcSize + def table_headers + [I18n.t(:report_header_hub), I18n.t(:report_header_customer), I18n.t(:report_header_email), + I18n.t(:report_header_phone), I18n.t(:report_header_producer), + I18n.t(:report_header_product), I18n.t(:report_header_variant), + I18n.t(:report_header_quantity), + I18n.t(:report_header_item_price, currency: currency_symbol), + I18n.t(:report_header_item_fees_price, currency: currency_symbol), + I18n.t(:report_header_admin_handling_fees, currency: currency_symbol), + I18n.t(:report_header_ship_price, currency: currency_symbol), + I18n.t(:report_header_pay_fee_price, currency: currency_symbol), + I18n.t(:report_header_total_price, currency: currency_symbol), + I18n.t(:report_header_paid), I18n.t(:report_header_shipping), + I18n.t(:report_header_delivery), I18n.t(:report_header_ship_street), + I18n.t(:report_header_ship_street_2), I18n.t(:report_header_ship_city), + I18n.t(:report_header_ship_postcode), I18n.t(:report_header_ship_state), + I18n.t(:report_header_comments), I18n.t(:report_header_sku), + I18n.t(:report_header_order_cycle), I18n.t(:report_header_payment_method), + I18n.t(:report_header_customer_code), I18n.t(:report_header_tags), + I18n.t(:report_header_billing_street), I18n.t(:report_header_billing_street_2), + I18n.t(:report_header_billing_city), I18n.t(:report_header_billing_postcode), + I18n.t(:report_header_billing_state), + I18n.t(:report_header_order_number), + I18n.t(:report_header_date)] + end + + # rubocop:enable Metrics/AbcSize + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + def rules + [ + { + group_by: proc { |line_item| line_item.order.distributor }, + sort_by: proc { |distributor| distributor.name } + }, + { + group_by: proc { |line_item| line_item.order }, + sort_by: proc { |order| order.bill_address.full_name_reverse }, + summary_columns: [ + proc { |line_items| line_items.first.order.distributor.name }, + proc { |line_items| line_items.first.order.bill_address.full_name }, + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |_line_items| I18n.t('admin.reports.total') }, + proc { |_line_items| "" }, + + proc { |_line_items| "" }, + proc { |line_items| line_items.sum(&:amount) }, + proc { |line_items| line_items.sum(&:amount_with_adjustments) }, + proc { |line_items| line_items.first.order.admin_and_handling_total }, + proc { |line_items| line_items.first.order.ship_total }, + proc { |line_items| line_items.first.order.payment_fee }, + proc { |line_items| line_items.first.order.total }, + proc { |line_items| line_items.first.order.paid? ? I18n.t(:yes) : I18n.t(:no) }, + + proc { |_line_items| "" }, + proc { |_line_items| "" }, + + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |_line_items| "" }, + + proc { |line_items| line_items.first.order.special_instructions }, + proc { |_line_items| "" }, + + proc { |line_items| line_items.first.order.order_cycle&.name }, + proc { |line_items| + line_items.first.order.payments.first&.payment_method&.name + }, + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |line_items| line_items.first.order.number }, + proc { |line_items| line_items.first.order.completed_at.strftime("%F %T") }, + ] + }, + { + group_by: proc { |line_item| line_item.variant.product }, + sort_by: proc { |product| product.name } + }, + { + group_by: proc { |line_item| line_item.variant }, + sort_by: proc { |variant| variant.full_name } + }, + { + group_by: line_item_name, + sort_by: proc { |full_name| full_name } + } + ] + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/PerceivedComplexity + def columns + rsa = proc { |line_items| shipping_method(line_items)&.delivery? } + [ + proc { |line_items| line_items.first.order.distributor.name }, + proc { |line_items| + bill_address = line_items.first.order.bill_address + "#{bill_address.firstname} #{bill_address.lastname}" + }, + proc { |line_items| line_items.first.order.email }, + proc { |line_items| line_items.first.order.bill_address.phone }, + proc { |line_items| line_items.first.variant.product.supplier.name }, + proc { |line_items| line_items.first.variant.product.name }, + proc { |line_items| line_items.first.variant.full_name }, + + proc { |line_items| line_items.to_a.sum(&:quantity) }, + proc { |line_items| line_items.sum(&:amount) }, + proc { |line_items| line_items.sum(&:amount_with_adjustments) }, + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |line_items| + line_items.all? { |li| li.order.paid? } ? I18n.t(:yes) : I18n.t(:no) + }, + + proc { |line_items| shipping_method(line_items)&.name }, + proc { |line_items| rsa.call(line_items) ? I18n.t(:yes) : I18n.t(:no) }, + + proc { |line_items| + line_items.first.order.ship_address&.address1 if rsa.call(line_items) + }, + proc { |line_items| + line_items.first.order.ship_address&.address2 if rsa.call(line_items) + }, + proc { |line_items| + line_items.first.order.ship_address&.city if rsa.call(line_items) + }, + proc { |line_items| + line_items.first.order.ship_address&.zipcode if rsa.call(line_items) + }, + proc { |line_items| + line_items.first.order.ship_address&.state if rsa.call(line_items) + }, + + proc { |_line_items| "" }, + proc do |line_items| + line_item = line_items.first + variant_scoper_for(line_item.order.distributor_id).scope(line_item.variant) + line_item.variant.sku + end, + + proc { |line_items| line_items.first.order.order_cycle&.name }, + proc { |line_items| + payment = line_items.first.order.payments.first + payment&.payment_method&.name + }, + proc { |line_items| + distributor = line_items.first.order.distributor + user = line_items.first.order.user + user&.customer_of(distributor)&.code + }, + proc { |line_items| + distributor = line_items.first.order.distributor + user = line_items.first.order.user + user&.customer_of(distributor)&.tags&.join(', ') + }, + + proc { |line_items| line_items.first.order.bill_address&.address1 }, + proc { |line_items| line_items.first.order.bill_address&.address2 }, + proc { |line_items| line_items.first.order.bill_address&.city }, + proc { |line_items| line_items.first.order.bill_address&.zipcode }, + proc { |line_items| line_items.first.order.bill_address&.state }, + proc { |line_items| line_items.first.order.number }, + proc { |line_items| line_items.first.order.completed_at.strftime("%F %T") }, + ] + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/PerceivedComplexity + + def line_item_includes + [{ variant: [{ option_values: :option_type }, { product: :supplier }], + order: [:bill_address, :ship_address, :order_cycle, :adjustments, :payments, + :user, :distributor, :shipments] }] + end + + private + + def shipping_method(line_items) + shipping_rates = line_items.first.order.shipments.first&.shipping_rates + + return unless shipping_rates + + shipping_rate = shipping_rates.find(&:selected) || shipping_rates.first + shipping_rate.try(:shipping_method) + end + end + end + end +end +# rubocop:enable Metrics/ClassLength diff --git a/lib/reporting/reports/orders_and_fulfillment/default_report.rb b/lib/reporting/reports/orders_and_fulfillment/default_report.rb new file mode 100644 index 0000000000..3bde81eca1 --- /dev/null +++ b/lib/reporting/reports/orders_and_fulfillment/default_report.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module OrdersAndFulfillment + class DefaultReport + delegate :line_item_name, :supplier_name, :product_name, :line_items_name, to: :context + + def initialize(context) + @context = context + end + + def table_headers + [ + I18n.t(:report_header_producer), + I18n.t(:report_header_product), + I18n.t(:report_header_variant), + I18n.t(:report_header_quantity), + I18n.t(:report_header_curr_cost_per_unit), + I18n.t(:report_header_total_cost), + I18n.t(:report_header_status), + I18n.t(:report_header_incoming_transport) + ] + end + + def rules + [ + { + group_by: proc { |line_item| line_item.variant.product.supplier }, + sort_by: proc { |supplier| supplier.name } + }, + { + group_by: proc { |line_item| line_item.variant.product }, + sort_by: proc { |product| product.name } + }, + { + group_by: line_item_name, + sort_by: proc { |full_name| full_name } + } + ] + end + + def columns + [ + supplier_name, + product_name, + line_items_name, + proc { |line_items| line_items.to_a.sum(&:quantity) }, + proc { |line_items| line_items.first.price }, + proc { |line_items| line_items.sum { |li| li.quantity * li.price } }, + proc { |_line_items| "" }, + proc { |_line_items| I18n.t(:report_header_incoming_transport) } + ] + end + + def line_item_includes + [] + end + + private + + attr_reader :context + end + end + end +end diff --git a/lib/reporting/reports/orders_and_fulfillment/distributor_totals_by_supplier_report.rb b/lib/reporting/reports/orders_and_fulfillment/distributor_totals_by_supplier_report.rb new file mode 100644 index 0000000000..88bf7ebee5 --- /dev/null +++ b/lib/reporting/reports/orders_and_fulfillment/distributor_totals_by_supplier_report.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module OrdersAndFulfillment + class DistributorTotalsBySupplierReport + REPORT_TYPE = "order_cycle_distributor_totals_by_supplier" + + attr_reader :context + + def initialize(context) + @context = context + end + + def table_headers + [I18n.t(:report_header_hub), I18n.t(:report_header_producer), + I18n.t(:report_header_product), I18n.t(:report_header_variant), + I18n.t(:report_header_quantity), I18n.t(:report_header_curr_cost_per_unit), + I18n.t(:report_header_total_cost), I18n.t(:report_header_total_shipping_cost), + I18n.t(:report_header_shipping_method)] + end + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + def rules + [ + { + group_by: proc { |line_item| line_item.order.distributor }, + sort_by: proc { |distributor| distributor.name }, + summary_columns: [ + proc { |_line_items| "" }, + proc { |_line_items| I18n.t('admin.reports.total') }, + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |line_items| line_items.sum(&:amount) }, + proc { |line_items| line_items.map(&:order).uniq.sum(&:ship_total) }, + proc { |_line_items| "" } + ] + }, + { + group_by: proc { |line_item| line_item.variant.product.supplier }, + sort_by: proc { |supplier| supplier.name } + }, + { + group_by: proc { |line_item| line_item.variant.product }, + sort_by: proc { |product| product.name } + }, + { + group_by: proc { |line_item| line_item.variant.full_name }, + sort_by: proc { |full_name| full_name } + } + ] + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + + # rubocop:disable Metrics/AbcSize + def columns + [proc { |line_items| line_items.first.order.distributor.name }, + proc { |line_items| line_items.first.variant.product.supplier.name }, + proc { |line_items| line_items.first.variant.product.name }, + proc { |line_items| line_items.first.variant.full_name }, + proc { |line_items| line_items.to_a.sum(&:quantity) }, + proc { |line_items| line_items.first.price }, + proc { |line_items| line_items.sum(&:amount) }, + proc { |_line_items| "" }, + proc { |_line_items| I18n.t(:report_header_shipping_method) }] + end + # rubocop:enable Metrics/AbcSize + + def line_item_includes + [{ order: [:distributor, :adjustments, { shipments: { shipping_rates: :shipping_method } }], + variant: [{ option_values: :option_type }, { product: :supplier }] }] + end + end + end + end +end diff --git a/lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb b/lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb new file mode 100644 index 0000000000..27a5bfa1e5 --- /dev/null +++ b/lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +include Spree::ReportsHelper + +module Reporting + module Reports + module OrdersAndFulfillment + class OrdersAndFulfillmentReport + attr_reader :options, :report_type + + delegate :table_headers, :rules, :columns, to: :report + + def initialize(user, options = {}, render_table = false) + @user = user + @options = options + @report_type = options[:report_subtype] + @render_table = render_table + @variant_scopers_by_distributor_id = {} + end + + def search + report_line_items.orders + end + + def table_items + return [] unless @render_table + + report_line_items.list(report.line_item_includes) + end + + def table_rows + order_grouper = Reporting::OrderGrouper.new report.rules, report.columns, report + order_grouper.table(table_items) + end + + def line_item_name + proc { |line_item| line_item.variant.full_name } + end + + def line_items_name + proc { |line_items| line_items.first.variant.full_name } + end + + def supplier_name + proc { |line_items| line_items.first.variant.product.supplier.name } + end + + def product_name + proc { |line_items| line_items.first.variant.product.name } + end + + def total_units(line_items) + return " " if not_all_have_unit?(line_items) + + total_units = line_items.sum do |li| + product = li.variant.product + li.quantity * li.unit_value / scale_factor(product) + end + + total_units.round(3) + end + + def variant_scoper_for(distributor_id) + @variant_scopers_by_distributor_id[distributor_id] ||= + OpenFoodNetwork::ScopeVariantToHub.new( + distributor_id, + report_variant_overrides[distributor_id] || {}, + ) + end + + private + + def report + @report ||= report_klass.new(self) + end + + def report_klass + case report_type + when SupplierTotalsReport::REPORT_TYPE then SupplierTotalsReport + when SupplierTotalsByDistributorReport::REPORT_TYPE then SupplierTotalsByDistributorReport + when DistributorTotalsBySupplierReport::REPORT_TYPE then DistributorTotalsBySupplierReport + when CustomerTotalsReport::REPORT_TYPE then CustomerTotalsReport + else + DefaultReport + end + end + + def not_all_have_unit?(line_items) + line_items.map { |li| li.unit_value.nil? }.any? + end + + def scale_factor(product) + product.variant_unit == 'weight' ? 1000 : 1 + end + + def order_permissions + return @order_permissions unless @order_permissions.nil? + + @order_permissions = ::Permissions::Order.new(@user, options[:q]) + end + + def report_line_items + @report_line_items ||= Reporting::LineItems.new(order_permissions, options) + end + + def report_variant_overrides + @report_variant_overrides ||= + VariantOverridesIndexed.new( + order_permissions.visible_line_items.select('DISTINCT variant_id'), + report_line_items.orders.result.select('DISTINCT distributor_id'), + ).indexed + end + end + end + end +end diff --git a/lib/reporting/reports/orders_and_fulfillment/supplier_totals_by_distributor_report.rb b/lib/reporting/reports/orders_and_fulfillment/supplier_totals_by_distributor_report.rb new file mode 100644 index 0000000000..275ab9b0a8 --- /dev/null +++ b/lib/reporting/reports/orders_and_fulfillment/supplier_totals_by_distributor_report.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module OrdersAndFulfillment + class SupplierTotalsByDistributorReport + REPORT_TYPE = "order_cycle_supplier_totals_by_distributor" + + attr_reader :context + + delegate :supplier_name, to: :context + + def initialize(context) + @context = context + end + + def table_headers + [I18n.t(:report_header_producer), I18n.t(:report_header_product), + I18n.t(:report_header_variant), I18n.t(:report_header_to_hub), + I18n.t(:report_header_quantity), I18n.t(:report_header_curr_cost_per_unit), + I18n.t(:report_header_total_cost), I18n.t(:report_header_shipping_method)] + end + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + def rules + [ + { + group_by: proc { |line_item| line_item.variant.product.supplier }, + sort_by: proc { |supplier| supplier.name } + }, + { + group_by: proc { |line_item| line_item.variant.product }, + sort_by: proc { |product| product.name } + }, + { + group_by: proc { |line_item| line_item.variant.full_name }, + sort_by: proc { |full_name| full_name }, + summary_columns: [ + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |_line_items| I18n.t('admin.reports.total') }, + proc { |_line_items| "" }, + proc { |_line_items| "" }, + proc { |line_items| line_items.sum(&:amount) }, + proc { |_line_items| "" } + ] + }, + { + group_by: proc { |line_item| line_item.order.distributor }, + sort_by: proc { |distributor| distributor.name } + } + ] + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + + def columns + [ + supplier_name, + proc { |line_items| line_items.first.variant.product.name }, + proc { |line_items| line_items.first.variant.full_name }, + proc { |line_items| line_items.first.order.distributor.name }, + proc { |line_items| line_items.to_a.sum(&:quantity) }, + proc { |line_items| line_items.first.price }, + proc { |line_items| line_items.sum(&:amount) }, + proc { |_line_items| I18n.t(:report_header_shipping_method) } + ] + end + + def line_item_includes + [{ order: :distributor, + variant: [{ option_values: :option_type }, { product: :supplier }] }] + end + end + end + end +end diff --git a/lib/reporting/reports/orders_and_fulfillment/supplier_totals_report.rb b/lib/reporting/reports/orders_and_fulfillment/supplier_totals_report.rb new file mode 100644 index 0000000000..5e4f8c7ffe --- /dev/null +++ b/lib/reporting/reports/orders_and_fulfillment/supplier_totals_report.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module OrdersAndFulfillment + class SupplierTotalsReport + REPORT_TYPE = "order_cycle_supplier_totals" + + attr_reader :context + + delegate :supplier_name, :product_name, :line_items_name, :total_units, to: :context + + def initialize(context) + @context = context + end + + def table_headers + [I18n.t(:report_header_producer), I18n.t(:report_header_product), + I18n.t(:report_header_variant), I18n.t(:report_header_quantity), + I18n.t(:report_header_total_units), I18n.t(:report_header_curr_cost_per_unit), + I18n.t(:report_header_total_cost), I18n.t(:report_header_status), + I18n.t(:report_header_incoming_transport)] + end + + def rules + [ + { + group_by: proc { |line_item| line_item.variant.product.supplier }, + sort_by: proc { |supplier| supplier.name } + }, + { + group_by: proc { |line_item| line_item.variant.product }, + sort_by: proc { |product| product.name } + }, + { + group_by: proc { |line_item| line_item.variant.full_name }, + sort_by: proc { |full_name| full_name } + } + ] + end + + def columns + [ + supplier_name, + product_name, + line_items_name, + proc { |line_items| line_items.to_a.sum(&:quantity) }, + proc { |line_items| total_units(line_items) }, + proc { |line_items| line_items.first.price }, + proc { |line_items| line_items.sum(&:amount) }, + proc { |_line_items| "" }, + proc { |_line_items| I18n.t(:report_header_incoming_transport) } + ] + end + + def line_item_includes + [{ variant: [{ option_values: :option_type }, { product: :supplier }] }] + end + end + end + end +end diff --git a/lib/reporting/reports/payments/payments_report.rb b/lib/reporting/reports/payments/payments_report.rb new file mode 100644 index 0000000000..3d2a21a6b3 --- /dev/null +++ b/lib/reporting/reports/payments/payments_report.rb @@ -0,0 +1,150 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module Payments + class PaymentsReport + attr_reader :params + + def initialize(user, params = {}, render_table = false) + @params = params + @user = user + @render_table = render_table + end + + def table_headers + case params[:report_subtype] + when "payments_by_payment_type" + I18n.t(:report_header_payment_type) + [I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor), I18n.t(:report_header_payment_type), + I18n.t(:report_header_total_price, currency: currency_symbol)] + when "itemised_payment_totals" + [I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor), + I18n.t(:report_header_product_total_price, currency: currency_symbol), + I18n.t(:report_header_shipping_total_price, currency: currency_symbol), + I18n.t(:report_header_outstanding_balance_price, currency: currency_symbol), + I18n.t(:report_header_total_price, currency: currency_symbol)] + when "payment_totals" + [I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor), + I18n.t(:report_header_product_total_price, currency: currency_symbol), + I18n.t(:report_header_shipping_total_price, currency: currency_symbol), + I18n.t(:report_header_total_price, currency: currency_symbol), + I18n.t(:report_header_eft_price, currency: currency_symbol), + I18n.t(:report_header_paypal_price, currency: currency_symbol), + I18n.t(:report_header_outstanding_balance_price, currency: currency_symbol)] + else + [I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor), I18n.t(:report_header_payment_type), + I18n.t(:report_header_total_price, currency: currency_symbol)] + end + end + + def search + Spree::Order.complete.not_state(:canceled).managed_by(@user).ransack(params[:q]) + end + + def table_items + return [] unless @render_table + + orders = search.result + payments = orders.includes(:payments).map do |order| + order.payments.select(&:completed?) + end.flatten + + case params[:report_subtype] + when "payments_by_payment_type" + payments + when "itemised_payment_totals" + orders + when "payment_totals" + orders + else + payments + end + end + + def table_rows + order_grouper = Reporting::OrderGrouper.new rules, columns, self + order_grouper.table(table_items) + end + + def rules + case params[:report_subtype] + when "payments_by_payment_type" + [{ group_by: proc { |payment| payment.order.payment_state }, + sort_by: proc { |payment_state| payment_state } }, + { group_by: proc { |payment| payment.order.distributor }, + sort_by: proc { |distributor| distributor.name } }, + { group_by: proc { |payment| + Spree::PaymentMethod.unscoped { + payment.payment_method + } + }, + sort_by: proc { |method| method.name } }] + when "itemised_payment_totals" + [{ group_by: proc { |order| order.payment_state }, + sort_by: proc { |payment_state| payment_state } }, + { group_by: proc { |order| order.distributor }, + sort_by: proc { |distributor| distributor.name } }] + when "payment_totals" + [{ group_by: proc { |order| order.payment_state }, + sort_by: proc { |payment_state| payment_state } }, + { group_by: proc { |order| order.distributor }, + sort_by: proc { |distributor| distributor.name } }] + else + [{ group_by: proc { |payment| payment.order.payment_state }, + sort_by: proc { |payment_state| payment_state } }, + { group_by: proc { |payment| payment.order.distributor }, + sort_by: proc { |distributor| distributor.name } }, + { group_by: proc { |payment| payment.payment_method }, + sort_by: proc { |method| method.name } }] + end + end + + def columns + case params[:report_subtype] + when "payments_by_payment_type" + [proc { |payments| payments.first.order.payment_state }, + proc { |payments| payments.first.order.distributor.name }, + proc { |payments| payments.first.payment_method.name }, + proc { |payments| payments.sum(&:amount) }] + when "itemised_payment_totals" + [proc { |orders| orders.first.payment_state }, + proc { |orders| orders.first.distributor.name }, + proc { |orders| orders.to_a.sum(&:item_total) }, + proc { |orders| orders.sum(&:ship_total) }, + proc { |orders| orders.sum{ |order| order.outstanding_balance.to_f } }, + proc { |orders| orders.map(&:total).sum }] + when "payment_totals" + [proc { |orders| orders.first.payment_state }, + proc { |orders| orders.first.distributor.name }, + proc { |orders| orders.to_a.sum(&:item_total) }, + proc { |orders| orders.sum(&:ship_total) }, + proc { |orders| orders.map(&:total).sum }, + proc { |orders| + orders.sum { |o| + o.payments.select { |payment| + payment.completed? && + (payment.payment_method.name.to_s.include? "EFT") + }.sum(&:amount) + } + }, + proc { |orders| + orders.sum { |o| + o.payments.select { |payment| + payment.completed? && + (payment.payment_method.name.to_s.include? "PayPal") + }.sum(&:amount) + } + }, + proc { |orders| orders.sum{ |order| order.outstanding_balance.to_f } }] + else + [proc { |payments| payments.first.order.payment_state }, + proc { |payments| payments.first.order.distributor.name }, + proc { |payments| payments.first.payment_method.name }, + proc { |payments| payments.sum(&:amount) }] + end + end + end + end + end +end diff --git a/lib/reporting/reports/products_and_inventory/lettuce_share_report.rb b/lib/reporting/reports/products_and_inventory/lettuce_share_report.rb new file mode 100644 index 0000000000..2c63e04a8c --- /dev/null +++ b/lib/reporting/reports/products_and_inventory/lettuce_share_report.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +# require 'variant_units/option_value_namer' + +module Reporting + module Reports + module ProductsAndInventory + class LettuceShareReport + attr_reader :context + + delegate :variants, :render_table, to: :context + + def initialize(context) + @context = context + end + + def table_headers + # NOTE: These are NOT to be translated, they need to be in this exact format to work with LettucShare + [ + "PRODUCT", + "Description", + "Qty", + "Pack Size", + "Unit", + "Unit Price", + "Total", + "GST incl.", + "Grower and growing method", + "Taxon" + ] + end + + def table_rows + return [] unless render_table + + variants.select(&:in_stock?) + .map do |variant| + [ + variant.product.name, + variant.full_name, + '', + VariantUnits::OptionValueNamer.new(variant).value, + VariantUnits::OptionValueNamer.new(variant).unit, + variant.price, + '', + gst(variant), + grower_and_method(variant), + variant.product.primary_taxon.name + ] + end + end + + private + + def gst(variant) + tax_category = variant.product.tax_category + if tax_category && tax_category.tax_rates.present? + tax_rate = tax_category.tax_rates.first + line_item = mock_line_item(variant) + tax_rate.calculator.compute line_item + else + 0 + end + end + + def mock_line_item(variant) + line_item = Spree::LineItem.new quantity: 1 + line_item.define_singleton_method(:product) { variant.product } + line_item.define_singleton_method(:price) { variant.price } + line_item + end + + def grower_and_method(variant) + cert = certification(variant) + + result = producer_name(variant) + result += " (#{cert})" if cert.present? + result + end + + def producer_name(variant) + variant.product.supplier.name + end + + def certification(variant) + variant.product.properties_including_inherited.map do |p| + "#{p[:name]} - #{p[:value]}" + end.join(', ') + end + end + end + end +end diff --git a/lib/reporting/reports/products_and_inventory/products_and_inventory_default_report.rb b/lib/reporting/reports/products_and_inventory/products_and_inventory_default_report.rb new file mode 100644 index 0000000000..a733fe24ea --- /dev/null +++ b/lib/reporting/reports/products_and_inventory/products_and_inventory_default_report.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module ProductsAndInventory + class ProductsAndInventoryDefaultReport + attr_reader :context + + delegate :variants, :render_table, to: :context + + def initialize(context) + @context = context + end + + def table_headers + [ + I18n.t(:report_header_supplier), + I18n.t(:report_header_producer_suburb), + I18n.t(:report_header_product), + I18n.t(:report_header_product_properties), + I18n.t(:report_header_taxons), + I18n.t(:report_header_variant_value), + I18n.t(:report_header_price), + I18n.t(:report_header_group_buy_unit_quantity), + I18n.t(:report_header_amount), + I18n.t(:report_header_sku) + ] + end + + def table_rows + return [] unless render_table + + variants.map do |variant| + [ + variant.product.supplier.name, + variant.product.supplier.address.city, + variant.product.name, + variant.product.properties.map(&:name).join(", "), + variant.product.taxons.map(&:name).join(", "), + variant.full_name, + variant.price, + variant.product.group_buy_unit_size, + "", + sku_for(variant) + ] + end + end + + def sku_for(variant) + return variant.sku if variant.sku.present? + + variant.product.sku + end + end + end + end +end diff --git a/lib/reporting/reports/products_and_inventory/products_and_inventory_report.rb b/lib/reporting/reports/products_and_inventory/products_and_inventory_report.rb new file mode 100644 index 0000000000..b6f972f3b7 --- /dev/null +++ b/lib/reporting/reports/products_and_inventory/products_and_inventory_report.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require 'open_food_network/scope_variant_to_hub' + +module Reporting + module Reports + module ProductsAndInventory + class ProductsAndInventoryReport + attr_reader :params, :render_table + + delegate :table_rows, :table_headers, :rules, :columns, :sku_for, to: :report + + def initialize(user, params = {}, render_table = false) + @user = user + @params = params + @render_table = render_table + end + + def report + @report ||= report_klass.new(self) + end + + def report_type + params[:report_subtype] + end + + def report_klass + if report_type == 'lettuce_share' + LettuceShareReport + else + ProductsAndInventoryDefaultReport + end + end + + def permissions + @permissions ||= OpenFoodNetwork::Permissions.new(@user) + end + + def visible_products + @visible_products ||= permissions.visible_products + end + + def variants + filter(child_variants) + end + + def child_variants + Spree::Variant. + where(is_master: false). + includes(option_values: :option_type). + joins(:product). + merge(visible_products). + order('spree_products.name') + end + + def filter(variants) + filter_on_hand filter_to_distributor filter_to_order_cycle filter_to_supplier variants + end + + # Using the `in_stock?` method allows overrides by distributors. + def filter_on_hand(variants) + if report_type == 'inventory' + variants.select(&:in_stock?) + else + variants + end + end + + def filter_to_supplier(variants) + if params[:supplier_id].to_i > 0 + variants.where("spree_products.supplier_id = ?", params[:supplier_id]) + else + variants + end + end + + def filter_to_distributor(variants) + if params[:distributor_id].to_i > 0 + distributor = Enterprise.find params[:distributor_id] + scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor) + variants.in_distributor(distributor).each { |v| scoper.scope(v) } + else + variants + end + end + + def filter_to_order_cycle(variants) + if params[:order_cycle_id].to_i > 0 + order_cycle = OrderCycle.find params[:order_cycle_id] + variant_ids = Exchange.in_order_cycle(order_cycle). + joins("INNER JOIN exchange_variants ON exchanges.id = exchange_variants.exchange_id"). + select("DISTINCT exchange_variants.variant_id") + + variants.where("spree_variants.id IN (#{variant_ids.to_sql})") + else + variants + end + end + end + end + end +end diff --git a/lib/reporting/reports/sales_tax/sales_tax_report.rb b/lib/reporting/reports/sales_tax/sales_tax_report.rb new file mode 100644 index 0000000000..9bf0248415 --- /dev/null +++ b/lib/reporting/reports/sales_tax/sales_tax_report.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module SalesTax + class SalesTaxReport + include Spree::ReportsHelper + attr_accessor :user, :params + + def initialize(user, params, render_table) + @user = user + @params = params + @render_table = render_table + end + + def table_headers + case params[:report_subtype] + when "tax_rates" + [I18n.t(:report_header_order_number), + I18n.t(:report_header_total_excl_vat, currency_symbol: currency_symbol)] + + relevant_rates.map { |rate| + "%.1f%% (%s)" % [rate.amount.to_f * 100, currency_symbol] + } + + [I18n.t(:report_header_total_tax, currency_symbol: currency_symbol), + I18n.t(:report_header_total_incl_vat, currency_symbol: currency_symbol)] + else + [I18n.t(:report_header_order_number), + I18n.t(:report_header_date), + I18n.t(:report_header_items), + I18n.t(:report_header_items_total, currency_symbol: currency_symbol), + I18n.t(:report_header_taxable_items_total, currency_symbol: currency_symbol), + I18n.t(:report_header_sales_tax, currency_symbol: currency_symbol), + I18n.t(:report_header_delivery_charge, currency_symbol: currency_symbol), + I18n.t(:report_header_tax_on_delivery, currency_symbol: currency_symbol), + I18n.t(:report_header_tax_on_fees, currency_symbol: currency_symbol), + I18n.t(:report_header_total_tax, currency_symbol: currency_symbol), + I18n.t(:report_header_customer), + I18n.t(:report_header_distributor)] + end + end + + def search + permissions = ::Permissions::Order.new(user) + permissions.editable_orders.complete.not_state(:canceled).ransack(params[:q]) + end + + def orders + search.result + end + + def table_rows + return [] unless @render_table + + case params[:report_subtype] + when "tax_rates" + orders.map do |order| + [order.number, order.total - order.total_tax] + + relevant_rates.map { |rate| + OrderTaxAdjustmentsFetcher.new(order).totals.fetch(rate, 0) + } + [order.total_tax, order.total] + end + else + orders.map do |order| + totals = totals_of order.line_items + shipping_cost = shipping_cost_for order + + [order.number, order.completed_at.strftime("%F %T"), totals[:items], totals[:items_total], + totals[:taxable_total], totals[:sales_tax], shipping_cost, order.shipping_tax, order.enterprise_fee_tax, order.total_tax, + order.bill_address.full_name, order.distributor&.name] + end + end + end + + private + + def relevant_rates + return @relevant_rates unless @relevant_rates.nil? + + @relevant_rates = Spree::TaxRate.distinct + end + + def totals_of(line_items) + totals = { items: 0, items_total: 0.0, taxable_total: 0.0, sales_tax: 0.0 } + + line_items.each do |line_item| + totals[:items] += line_item.quantity + totals[:items_total] += line_item.amount + + sales_tax = tax_included_in line_item + + if sales_tax > 0 + totals[:taxable_total] += line_item.amount + totals[:sales_tax] += sales_tax + end + end + + totals.each_pair do |k, _v| + totals[k] = totals[k].round(2) + end + + totals + end + + def shipping_cost_for(order) + order.shipments.first&.cost || 0.0 + end + + def tax_included_in(line_item) + line_item.adjustments.tax.inclusive.sum(:amount) + end + end + end + end +end diff --git a/lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb b/lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb new file mode 100644 index 0000000000..34c86e97f2 --- /dev/null +++ b/lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module UsersAndEnterprises + class UsersAndEnterprisesReport + attr_reader :params + + def initialize(user, params = {}, compile_table = false) + @user = user + @params = params + @compile_table = compile_table + + # Convert arrays of ids to comma delimited strings + if @params[:enterprise_id_in].is_a? Array + @params[:enterprise_id_in] = @params[:enterprise_id_in].join(',') + end + @params[:user_id_in] = @params[:user_id_in].join(',') if @params[:user_id_in].is_a? Array + end + + def table_headers + [ + I18n.t(:report_header_user), + I18n.t(:report_header_relationship), + I18n.t(:report_header_enterprise), + I18n.t(:report_header_is_producer), + I18n.t(:report_header_sells), + I18n.t(:report_header_visible), + I18n.t(:report_header_confirmation_date), + ] + end + + def table_rows + return [] unless @compile_table + + users_and_enterprises.map do |uae| + [ + uae["user_email"], + uae["relationship_type"], + uae["name"], + to_bool(uae["is_primary_producer"]), + uae["sells"], + uae["visible"], + to_local_datetime(uae["created_at"]) + ] + end + end + + def owners_and_enterprises + query = Enterprise.joins("LEFT JOIN spree_users AS owner ON enterprises.owner_id = owner.id") + .where("enterprises.id IS NOT NULL") + + query = filter_by_int_list_if_present(query, "enterprises.id", params[:enterprise_id_in]) + query = filter_by_int_list_if_present(query, "owner.id", params[:user_id_in]) + + query_helper(query, :owner, :owns) + end + + def managers_and_enterprises + query = Enterprise + .joins("LEFT JOIN enterprise_roles ON enterprises.id = enterprise_roles.enterprise_id") + .joins("LEFT JOIN spree_users AS managers ON enterprise_roles.user_id = managers.id") + .where("enterprise_id IS NOT NULL") + .where("user_id IS NOT NULL") + + query = filter_by_int_list_if_present(query, "enterprise_id", params[:enterprise_id_in]) + query = filter_by_int_list_if_present(query, "user_id", params[:user_id_in]) + + query_helper(query, :managers, :manages) + end + + def query_helper(query, email_user, relationship_type) + query.order("enterprises.created_at DESC") + .select(["enterprises.name", + "enterprises.sells", + "enterprises.visible", + "enterprises.is_primary_producer", + "enterprises.created_at", + "#{email_user}.email AS user_email"]) + .to_a + .map { |x| + { + name: x.name, + sells: x.sells, + visible: (x.visible ? 't' : 'f'), + is_primary_producer: (x.is_primary_producer ? 't' : 'f'), + created_at: x.created_at.utc.iso8601, + relationship_type: relationship_type, + user_email: x.user_email + }.stringify_keys + } + end + + def users_and_enterprises + sort( owners_and_enterprises.concat(managers_and_enterprises) ) + end + + def filter_by_int_list_if_present(query, filtered_field_name, int_list) + if int_list.present? + query = query.where("#{filtered_field_name} IN (?)", split_int_list(int_list)) + end + query + end + + def split_int_list(int_list) + int_list.split(',').map(&:to_i) + end + + def sort(results) + results.sort do |a, b| + if a["created_at"].nil? || b["created_at"].nil? + [(a["created_at"].nil? ? 0 : 1), a["name"], b["relationship_type"], + a["user_email"]] <=> + [(b["created_at"].nil? ? 0 : 1), b["name"], a["relationship_type"], b["user_email"]] + else + [ + DateTime.parse(b["created_at"]).in_time_zone, + a["name"], + b["relationship_type"], + a["user_email"] + ] <=> [ + DateTime.parse(a["created_at"]).in_time_zone, + b["name"], + a["relationship_type"], + b["user_email"] + ] + end + end + end + + def to_bool(value) + ActiveRecord::Type::Boolean.new.cast(value) + end + + def to_local_datetime(date) + return "" if date.nil? + + date.to_datetime.in_time_zone.strftime "%Y-%m-%d %H:%M" + end + end + end + end +end diff --git a/lib/reporting/reports/xero_invoices/xero_invoices_report.rb b/lib/reporting/reports/xero_invoices/xero_invoices_report.rb new file mode 100644 index 0000000000..186dd2e830 --- /dev/null +++ b/lib/reporting/reports/xero_invoices/xero_invoices_report.rb @@ -0,0 +1,239 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module XeroInvoices + class XeroInvoicesReport + def initialize(user, opts = {}, compile_table = false) + @user = user + + @opts = opts. + symbolize_keys. + reject { |_k, v| v.blank? }. + reverse_merge( report_subtype: 'summary', + invoice_date: Time.zone.today, + due_date: Time.zone.today + 1.month, + account_code: 'food sales' ) + @compile_table = compile_table + end + + def table_headers + # NOTE: These are NOT to be translated, they need to be in this exact format to work with Xero + %w(*ContactName EmailAddress POAddressLine1 POAddressLine2 POAddressLine3 POAddressLine4 + POCity PORegion POPostalCode POCountry *InvoiceNumber Reference *InvoiceDate *DueDate InventoryItemCode *Description *Quantity *UnitAmount Discount *AccountCode *TaxType TrackingName1 TrackingOption1 TrackingName2 TrackingOption2 Currency BrandingTheme Paid?) + end + + def search + permissions = ::Permissions::Order.new(@user) + permissions.editable_orders.complete.not_state(:canceled).ransack(@opts[:q]) + end + + def orders + search.result.reorder('id DESC') + end + + def table_rows + return [] unless @compile_table + + rows = [] + + orders.each_with_index do |order, i| + invoice_number = invoice_number_for(order, i) + rows += detail_rows_for_order(order, invoice_number, @opts) if detail? + rows += summary_rows_for_order(order, invoice_number, @opts) + end + + rows.compact + end + + private + + def report_options + @opts.merge(line_item_includes: line_item_includes) + end + + def line_item_includes + [:bill_address, :adjustments, + { line_items: { variant: [{ option_values: :option_type }, { product: :supplier }] } }] + end + + def detail_rows_for_order(order, invoice_number, opts) + rows = [] + + rows += line_item_detail_rows(order, invoice_number, opts) + rows += adjustment_detail_rows(order, invoice_number, opts) + + rows + end + + def line_item_detail_rows(order, invoice_number, opts) + order.line_items.map do |line_item| + line_item_detail_row(line_item, invoice_number, opts) + end + end + + def line_item_detail_row(line_item, invoice_number, opts) + row(line_item.order, + line_item.variant.sku, + line_item.product_and_full_name, + line_item.quantity.to_s, + line_item.price.to_s, + invoice_number, + tax_type(line_item), + opts) + end + + def adjustment_detail_rows(order, invoice_number, opts) + admin_adjustments(order).map do |adjustment| + adjustment_detail_row(adjustment, invoice_number, opts) + end + end + + def adjustment_detail_row(adjustment, invoice_number, opts) + row(adjustment_order(adjustment), + '', + adjustment.label, + 1, + adjustment.amount, + invoice_number, + tax_type(adjustment), + opts) + end + + def summary_rows_for_order(order, invoice_number, opts) + rows = [] + + rows += produce_summary_rows(order, invoice_number, opts) unless detail? + rows += fee_summary_rows(order, invoice_number, opts) + rows += shipping_summary_rows(order, invoice_number, opts) + rows += payment_summary_rows(order, invoice_number, opts) + rows += admin_adjustment_summary_rows(order, invoice_number, opts) unless detail? + + rows + end + + def produce_summary_rows(order, invoice_number, opts) + [summary_row(order, I18n.t(:report_header_total_untaxable_produce), total_untaxable_products(order), invoice_number, I18n.t(:report_header_gst_free_income), opts), + summary_row(order, I18n.t(:report_header_total_taxable_produce), + total_taxable_products(order), invoice_number, I18n.t(:report_header_gst_on_income), opts)] + end + + def fee_summary_rows(order, invoice_number, opts) + [summary_row(order, I18n.t(:report_header_total_untaxable_fees), total_untaxable_fees(order), invoice_number, I18n.t(:report_header_gst_free_income), opts), + summary_row(order, I18n.t(:report_header_total_taxable_fees), total_taxable_fees(order), + invoice_number, I18n.t(:report_header_gst_on_income), opts)] + end + + def shipping_summary_rows(order, invoice_number, opts) + [summary_row(order, I18n.t(:report_header_delivery_shipping_cost), total_shipping(order), + invoice_number, tax_on_shipping_s(order), opts)] + end + + def payment_summary_rows(order, invoice_number, opts) + [summary_row(order, I18n.t(:report_header_transaction_fee), total_transaction(order), + invoice_number, I18n.t(:report_header_gst_free_income), opts)] + end + + def admin_adjustment_summary_rows(order, invoice_number, opts) + [summary_row(order, I18n.t(:report_header_total_untaxable_admin), total_untaxable_admin_adjustments(order), invoice_number, I18n.t(:report_header_gst_free_income), opts), + summary_row(order, I18n.t(:report_header_total_taxable_admin), + total_taxable_admin_adjustments(order), invoice_number, I18n.t(:report_header_gst_on_income), opts)] + end + + def summary_row(order, description, amount, invoice_number, tax_type, opts = {}) + row order, '', description, '1', amount, invoice_number, tax_type, opts + end + + def row(order, sku, description, quantity, amount, invoice_number, tax_type, opts = {}) + return nil if amount == 0 + + [order.bill_address&.full_name, + order.email, + order.bill_address&.address1, + order.bill_address&.address2, + '', + '', + order.bill_address&.city, + order.bill_address&.state, + order.bill_address&.zipcode, + order.bill_address&.country&.name, + invoice_number, + order.number, + opts[:invoice_date], + opts[:due_date], + sku, + description, + quantity, + amount, + '', + opts[:account_code], + tax_type, + '', + '', + '', + '', + Spree::Config.currency, + '', + order.paid? ? I18n.t(:y) : I18n.t(:n)] + end + + def admin_adjustments(order) + order.adjustments.admin + end + + def adjustment_order(adjustment) + adjustment.adjustable.is_a?(Spree::Order) ? adjustment.adjustable : nil + end + + def invoice_number_for(order, idx) + @opts[:initial_invoice_number] ? @opts[:initial_invoice_number].to_i + idx : order.number + end + + def total_untaxable_products(order) + order.line_items.without_tax.to_a.sum(&:amount) + end + + def total_taxable_products(order) + order.line_items.with_tax.to_a.sum(&:amount) + end + + def total_untaxable_fees(order) + order.all_adjustments.enterprise_fee.where(tax_category: nil).sum(:amount) + end + + def total_taxable_fees(order) + order.all_adjustments.enterprise_fee.where.not(tax_category: nil).sum(:amount) + end + + def total_shipping(order) + order.all_adjustments.shipping.sum(:amount) + end + + def total_transaction(order) + order.all_adjustments.payment_fee.sum(:amount) + end + + def tax_on_shipping_s(order) + tax_on_shipping = order.shipments.sum("additional_tax_total + included_tax_total").positive? + tax_on_shipping ? I18n.t(:report_header_gst_on_income) : I18n.t(:report_header_gst_free_income) + end + + def total_untaxable_admin_adjustments(order) + order.adjustments.admin.where(tax_category: nil).sum(:amount) + end + + def total_taxable_admin_adjustments(order) + order.adjustments.admin.where.not(tax_category: nil).sum(:amount) + end + + def detail? + @opts[:report_subtype] == 'detailed' + end + + def tax_type(taxable) + taxable.has_tax? ? I18n.t(:report_header_gst_on_income) : I18n.t(:report_header_gst_free_income) + end + end + end + end +end diff --git a/spec/controllers/spree/admin/reports_controller_spec.rb b/spec/controllers/spree/admin/reports_controller_spec.rb index 48af5f02fe..50bb50cac4 100644 --- a/spec/controllers/spree/admin/reports_controller_spec.rb +++ b/spec/controllers/spree/admin/reports_controller_spec.rb @@ -237,7 +237,7 @@ describe Spree::Admin::ReportsController, type: :controller do end it "creates a ProductAndInventoryReport" do - expect(OpenFoodNetwork::ProductsAndInventoryReport).to receive(:new) + expect(Reporting::Reports::ProductsAndInventory::ProductsAndInventoryReport).to receive(:new) .with(@admin_user, { "test" => "foo", "controller" => "spree/admin/reports", "report" => {}, "action" => "products_and_inventory", "use_route" => "main_app" }, false) @@ -290,7 +290,7 @@ describe Spree::Admin::ReportsController, type: :controller do end it "creates a CustomersReport" do - expect(OpenFoodNetwork::CustomersReport).to receive(:new) + expect(Reporting::Reports::Customers::CustomersReport).to receive(:new) .with(@admin_user, { "test" => "foo", "controller" => "spree/admin/reports", "action" => "customers", "use_route" => "main_app", "report" => {} }, false) diff --git a/spec/lib/open_food_network/customers_report_spec.rb b/spec/lib/open_food_network/customers_report_spec.rb deleted file mode 100644 index e59633f2f5..0000000000 --- a/spec/lib/open_food_network/customers_report_spec.rb +++ /dev/null @@ -1,163 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'open_food_network/customers_report' - -module OpenFoodNetwork - describe CustomersReport do - context "as a site admin" do - let(:user) do - user = create(:user) - user.spree_roles << Spree::Role.find_or_create_by!(name: 'admin') - user - end - subject { CustomersReport.new user, {}, true } - - describe "mailing list report" do - before do - allow(subject).to receive(:params).and_return(report_subtype: "mailing_list") - end - - it "returns headers for mailing_list" do - expect(subject.table_headers).to eq(["Email", "First Name", "Last Name", "Suburb"]) - end - - it "builds a table from a list of variants" do - order = double(:order, email: "test@test.com") - address = double(:billing_address, firstname: "Firsty", - lastname: "Lasty", city: "Suburbia") - allow(order).to receive(:billing_address).and_return address - allow(subject).to receive(:orders).and_return [order] - - expect(subject.table_rows).to eq([[ - "test@test.com", "Firsty", "Lasty", "Suburbia" - ]]) - end - end - - describe "addresses report" do - before do - allow(subject).to receive(:params).and_return(report_subtype: "addresses") - end - - it "returns headers for addresses" do - expect(subject.table_headers).to eq(["First Name", "Last Name", "Billing Address", "Email", - "Phone", "Hub", "Hub Address", "Shipping Method"]) - end - - it "builds a table from a list of variants" do - a = create(:address) - d = create(:distributor_enterprise) - o = create(:order, distributor: d, bill_address: a) - o.shipments << create(:shipment) - - allow(subject).to receive(:orders).and_return [o] - expect(subject.table_rows).to eq([[ - a.firstname, a.lastname, - [a.address1, a.address2, a.city].join(" "), - o.email, a.phone, d.name, - [d.address.address1, d.address.address2, d.address.city].join(" "), - o.shipping_method.name - ]]) - end - end - - describe "fetching orders" do - it "fetches completed orders" do - o1 = create(:order) - o2 = create(:order, completed_at: 1.day.ago) - expect(subject.orders).to eq([o2]) - end - - it "does not show cancelled orders" do - o1 = create(:order, state: "canceled", completed_at: 1.day.ago) - o2 = create(:order, completed_at: 1.day.ago) - expect(subject.orders).to eq([o2]) - end - end - end - - context "as an enterprise user" do - let(:user) do - user = create(:user) - user.spree_roles = [] - user.save! - user - end - - subject { CustomersReport.new user, {}, true } - - describe "fetching orders" do - let(:supplier) { create(:supplier_enterprise) } - let(:product) { create(:simple_product, supplier: supplier) } - let(:order) { create(:order, completed_at: 1.day.ago) } - - it "only shows orders managed by the current user" do - d1 = create(:distributor_enterprise) - d1.enterprise_roles.build(user: user).save - d2 = create(:distributor_enterprise) - d2.enterprise_roles.build(user: create(:user)).save - - o1 = create(:order, distributor: d1, completed_at: 1.day.ago) - o2 = create(:order, distributor: d2, completed_at: 1.day.ago) - - expect(subject).to receive(:filter).with([o1]).and_return([o1]) - expect(subject.orders).to eq([o1]) - end - - it "does not show orders through a hub that the current user does not manage" do - # Given a supplier enterprise with an order for one of its products - supplier.enterprise_roles.build(user: user).save - order.line_items << create(:line_item_with_shipment, product: product) - - # When I fetch orders, I should see no orders - expect(subject).to receive(:filter).with([]).and_return([]) - expect(subject.orders).to eq([]) - end - end - - describe "filtering orders" do - let(:orders) { Spree::Order.where(nil) } - let(:supplier) { create(:supplier_enterprise) } - - it "returns all orders sans-params" do - expect(subject.filter(orders)).to eq(orders) - end - - it "returns orders with a specific supplier" do - supplier = create(:supplier_enterprise) - supplier2 = create(:supplier_enterprise) - product1 = create(:simple_product, supplier: supplier) - product2 = create(:simple_product, supplier: supplier2) - order1 = create(:order) - order2 = create(:order) - order1.line_items << create(:line_item, product: product1) - order2.line_items << create(:line_item, product: product2) - - allow(subject).to receive(:params).and_return(supplier_id: supplier.id) - expect(subject.filter(orders)).to eq([order1]) - end - - it "filters to a specific distributor" do - d1 = create(:distributor_enterprise) - d2 = create(:distributor_enterprise) - order1 = create(:order, distributor: d1) - order2 = create(:order, distributor: d2) - - allow(subject).to receive(:params).and_return(distributor_id: d1.id) - expect(subject.filter(orders)).to eq([order1]) - end - - it "filters to a specific cycle" do - oc1 = create(:simple_order_cycle) - oc2 = create(:simple_order_cycle) - order1 = create(:order, order_cycle: oc1) - order2 = create(:order, order_cycle: oc2) - - allow(subject).to receive(:params).and_return(order_cycle_id: oc1.id) - expect(subject.filter(orders)).to eq([order1]) - end - end - end - end -end diff --git a/spec/lib/open_food_network/lettuce_share_report_spec.rb b/spec/lib/open_food_network/lettuce_share_report_spec.rb deleted file mode 100644 index a8e7da5b95..0000000000 --- a/spec/lib/open_food_network/lettuce_share_report_spec.rb +++ /dev/null @@ -1,90 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -require 'open_food_network/products_and_inventory_report' - -module OpenFoodNetwork - describe LettuceShareReport do - let(:user) { create(:user) } - let(:base_report) { - ProductsAndInventoryReport.new(user, { report_subtype: 'lettuce_share' }, true) - } - let(:report) { base_report.report } - let(:variant) { create(:variant) } - - describe "grower and method" do - it "shows just the producer when there is no certification" do - allow(report).to receive(:producer_name) { "Producer" } - allow(report).to receive(:certification) { "" } - - expect(report.send(:grower_and_method, variant)).to eq("Producer") - end - - it "shows producer and certification when a certification is present" do - allow(report).to receive(:producer_name) { "Producer" } - allow(report).to receive(:certification) { "Method" } - - expect(report.send(:grower_and_method, variant)).to eq("Producer (Method)") - end - end - - describe "gst" do - it "handles tax category without rates" do - expect(report.send(:gst, variant)).to eq(0) - end - end - - describe "table" do - it "handles no items" do - expect(report.table_rows).to eq [] - end - - describe "lists" do - let(:variant2) { create(:variant) } - let(:variant3) { create(:variant) } - let(:variant4) { create(:variant, on_hand: 0, on_demand: true) } - let(:hub_address) { - create(:address, address1: "distributor address", city: 'The Shire', zipcode: "1234") - } - let(:hub) { create(:distributor_enterprise, address: hub_address) } - let(:variant2_override) { create(:variant_override, hub: hub, variant: variant2) } - let(:variant3_override) { - create(:variant_override, hub: hub, variant: variant3, count_on_hand: 0) - } - - it "all items" do - allow(base_report).to receive(:child_variants) { - Spree::Variant.where(id: [variant, variant2, variant3]) - } - expect(report.table_rows.count).to eq 3 - end - - it "only available items" do - variant.on_hand = 0 - allow(base_report).to receive(:child_variants) { - Spree::Variant.where(id: [variant, variant2, variant3, variant4]) - } - expect(report.table_rows.count).to eq 3 - end - - it "only available items considering overrides" do - create(:exchange, incoming: false, receiver_id: hub.id, - variants: [variant, variant2, variant3]) - # create the overrides - variant2_override - variant3_override - allow(base_report).to receive(:child_variants) { - Spree::Variant.where(id: [variant, variant2, variant3]) - } - allow(base_report).to receive(:params) { - { distributor_id: hub.id, report_subtype: 'lettuce_share' } - } - rows = report.table_rows - expect(rows.count).to eq 2 - expect(rows.map{ |row| row[0] }).to include variant.product.name, variant2.product.name - end - end - end - end -end diff --git a/spec/lib/open_food_network/order_cycle_management_report_spec.rb b/spec/lib/open_food_network/order_cycle_management_report_spec.rb deleted file mode 100644 index 56d7781c5f..0000000000 --- a/spec/lib/open_food_network/order_cycle_management_report_spec.rb +++ /dev/null @@ -1,213 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'open_food_network/order_cycle_management_report' - -module OpenFoodNetwork - describe OrderCycleManagementReport do - context "as a site admin" do - subject { OrderCycleManagementReport.new(user, params, true) } - let(:params) { {} } - - let(:user) do - user = create(:user) - user.spree_roles << Spree::Role.find_or_create_by!(name: "admin") - user - end - - describe "fetching orders" do - let(:customers_with_balance) { instance_double(CustomersWithBalance) } - - it 'calls the OutstandingBalance query object' do - outstanding_balance = instance_double(OutstandingBalance, query: Spree::Order.none) - expect(OutstandingBalance).to receive(:new).and_return(outstanding_balance) - - subject.orders - end - - it "fetches completed orders" do - o1 = create(:order) - o2 = create(:order, completed_at: 1.day.ago, state: 'complete') - expect(subject.orders).to eq([o2]) - end - - it 'fetches resumed orders' do - order = create(:order, state: 'resumed', completed_at: 1.day.ago) - expect(subject.orders).to eq([order]) - end - - it 'orders them by id' do - order1 = create(:order, completed_at: 1.day.ago, state: 'complete') - order2 = create(:order, completed_at: 2.days.ago, state: 'complete') - - expect(subject.orders.pluck(:id)).to eq([order2.id, order1.id]) - end - - it "does not show cancelled orders" do - o1 = create(:order, state: 'canceled', completed_at: 1.day.ago) - o2 = create(:order, state: 'complete', completed_at: 1.day.ago) - expect(subject.orders).to eq([o2]) - end - - context "default date range" do - it "fetches orders completed in the past month" do - o1 = create(:order, state: 'complete', completed_at: 1.month.ago - 1.day) - o2 = create(:order, state: 'complete', completed_at: 1.month.ago + 1.day) - expect(subject.orders).to eq([o2]) - end - end - end - end - - context "as an enterprise user" do - let!(:user) { create(:user) } - - subject { OrderCycleManagementReport.new user, {}, true } - - describe "fetching orders" do - let(:supplier) { create(:supplier_enterprise) } - let(:product) { create(:simple_product, supplier: supplier) } - let(:order) { create(:order, completed_at: 1.day.ago) } - - it "only shows orders managed by the current user" do - d1 = create(:distributor_enterprise) - d1.enterprise_roles.create!(user: user) - d2 = create(:distributor_enterprise) - d2.enterprise_roles.create!(user: create(:user)) - - o1 = create(:order, distributor: d1, state: 'complete', completed_at: 1.day.ago) - o2 = create(:order, distributor: d2, state: 'complete', completed_at: 1.day.ago) - - expect(subject).to receive(:filter).with([o1]).and_return([o1]) - expect(subject.orders).to eq([o1]) - end - - it "does not show orders through a hub that the current user does not manage" do - # Given a supplier enterprise with an order for one of its products - supplier.enterprise_roles.create!(user: user) - order.line_items << create(:line_item_with_shipment, product: product) - - # When I fetch orders, I should see no orders - expect(subject).to receive(:filter).with([]).and_return([]) - expect(subject.orders).to eq([]) - end - end - - describe "filtering orders" do - let!(:orders) { Spree::Order.where(nil) } - let!(:supplier) { create(:supplier_enterprise) } - - let!(:oc1) { create(:simple_order_cycle) } - let!(:pm1) { create(:payment_method, name: "PM1") } - let!(:sm1) { create(:shipping_method, name: "ship1") } - let!(:s1) { create(:shipment_with, :shipping_method, shipping_method: sm1) } - let!(:order1) { create(:order, shipments: [s1], order_cycle: oc1) } - let!(:payment1) { create(:payment, order: order1, payment_method: pm1) } - - it "returns all orders sans-params" do - expect(subject.filter(orders)).to eq(orders) - end - - it "filters to a specific order cycle" do - oc2 = create(:simple_order_cycle) - order2 = create(:order, order_cycle: oc2) - - allow(subject).to receive(:params).and_return(order_cycle_id: oc1.id) - expect(subject.filter(orders)).to eq([order1]) - end - - it "filters to a payment method" do - pm2 = create(:payment_method, name: "PM2") - pm3 = create(:payment_method, name: "PM3") - order2 = create(:order, payments: [create(:payment, payment_method: pm2)]) - order3 = create(:order, payments: [create(:payment, payment_method: pm3)]) - - allow(subject).to receive(:params).and_return(payment_method_in: [pm1.id, pm3.id] ) - expect(subject.filter(orders)).to match_array [order1, order3] - end - - it "filters to a shipping method" do - sm2 = create(:shipping_method, name: "ship2") - sm3 = create(:shipping_method, name: "ship3") - s2 = create(:shipment_with, :shipping_method, shipping_method: sm2) - s3 = create(:shipment_with, :shipping_method, shipping_method: sm3) - order2 = create(:order, shipments: [s2]) - order3 = create(:order, shipments: [s3]) - - allow(subject).to receive(:params).and_return(shipping_method_in: [sm1.id, sm3.id]) - expect(subject.filter(orders)).to match_array [order1, order3] - end - - it "should do all the filters at once" do - allow(subject).to receive(:params).and_return(order_cycle_id: oc1.id, - shipping_method_name: sm1.name, - payment_method_name: pm1.name) - expect(subject.filter(orders)).to eq([order1]) - end - end - - describe '#table_rows' do - subject { OrderCycleManagementReport.new(user, params, true) } - - let(:distributor) { create(:distributor_enterprise) } - before { distributor.enterprise_roles.create!(user: user) } - - context 'when the report type is payment_methods' do - let(:params) { { report_subtype: 'payment_methods' } } - - let!(:order) do - create( - :completed_order_with_totals, - distributor: distributor, - completed_at: 1.day.ago - ) - end - - it 'returns rows with payment information' do - expect(subject.table_rows).to eq([[ - order.billing_address.firstname, - order.billing_address.lastname, - order.distributor.name, - '', - order.email, - order.billing_address.phone, - order.shipment.shipping_method.name, - nil, - order.total, - -order.total - ]]) - end - end - - context 'when the report type is not payment_methods' do - let(:params) { {} } - let!(:order) do - create( - :completed_order_with_totals, - distributor: distributor, - completed_at: 1.day.ago - ) - end - - it 'returns rows with delivery information' do - expect(subject.table_rows).to eq([[ - order.ship_address.firstname, - order.ship_address.lastname, - order.distributor.name, - "", - "#{order.ship_address.address1} #{order.ship_address.address2} #{order.ship_address.city}", - order.ship_address.zipcode, - order.ship_address.phone, - order.shipment.shipping_method.name, - nil, - order.total, - -order.total, - false, - order.special_instructions - ]]) - end - end - end - end - end -end diff --git a/spec/lib/open_food_network/orders_and_distributors_report_spec.rb b/spec/lib/open_food_network/orders_and_distributors_report_spec.rb deleted file mode 100644 index 4f97d9d848..0000000000 --- a/spec/lib/open_food_network/orders_and_distributors_report_spec.rb +++ /dev/null @@ -1,87 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'open_food_network/orders_and_distributors_report' - -module OpenFoodNetwork - describe OrdersAndDistributorsReport do - describe 'orders and distributors report' do - it 'should return a header row describing the report' do - subject = OrdersAndDistributorsReport.new nil - - expect(subject.table_headers).to eq( - [ - 'Order date', 'Order Id', - 'Customer Name', 'Customer Email', 'Customer Phone', 'Customer City', - 'SKU', 'Item name', 'Variant', 'Quantity', 'Max Quantity', 'Cost', 'Shipping Cost', - 'Payment Method', - 'Distributor', 'Distributor address', 'Distributor city', 'Distributor postcode', - 'Shipping Method', 'Shipping instructions' - ] - ) - end - - context 'with completed order' do - let(:bill_address) { create(:address) } - let(:distributor) { create(:distributor_enterprise) } - let(:product) { create(:product) } - let(:shipping_method) { create(:shipping_method) } - let(:shipping_instructions) { 'pick up on thursday please!' } - let(:order) { - create(:order, - state: 'complete', completed_at: Time.zone.now, - distributor: distributor, bill_address: bill_address, - special_instructions: shipping_instructions) - } - let(:payment_method) { create(:payment_method, distributors: [distributor]) } - let(:payment) { create(:payment, payment_method: payment_method, order: order) } - let(:line_item) { create(:line_item_with_shipment, product: product, order: order) } - - before do - order.select_shipping_method(shipping_method.id) - order.payments << payment - order.line_items << line_item - end - - it 'should denormalise order and distributor details for display as csv' do - subject = OrdersAndDistributorsReport.new create(:admin_user), {}, true - - table = subject.table_rows - - expect(table.size).to eq 1 - expect(table[0]).to eq([ - order.reload.completed_at.strftime("%F %T"), - order.id, - bill_address.full_name, - order.email, - bill_address.phone, - bill_address.city, - line_item.product.sku, - line_item.product.name, - line_item.options_text, - line_item.quantity, - line_item.max_quantity, - line_item.price * line_item.quantity, - line_item.distribution_fee, - payment_method.name, - distributor.name, - distributor.address.address1, - distributor.address.city, - distributor.address.zipcode, - shipping_method.name, - shipping_instructions - ]) - end - - it "prints one row per line item" do - create(:line_item_with_shipment, order: order) - - subject = OrdersAndDistributorsReport.new(create(:admin_user), {}, true) - - table = subject.table_rows - expect(table.size).to eq 2 - end - end - end - end -end diff --git a/spec/lib/open_food_network/orders_and_fulfillment_report/customer_totals_report_spec.rb b/spec/lib/open_food_network/orders_and_fulfillment_report/customer_totals_report_spec.rb deleted file mode 100644 index 966cf6e05e..0000000000 --- a/spec/lib/open_food_network/orders_and_fulfillment_report/customer_totals_report_spec.rb +++ /dev/null @@ -1,127 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" -require 'open_food_network/orders_and_fulfillment_report' -require 'open_food_network/orders_and_fulfillment_report/customer_totals_report' - -RSpec.describe OpenFoodNetwork::OrdersAndFulfillmentReport::CustomerTotalsReport do - let!(:distributor) { create(:distributor_enterprise) } - let!(:customer) { create(:customer, enterprise: distributor) } - let(:current_user) { distributor.owner } - - let(:report) do - report_options = { report_subtype: described_class::REPORT_TYPE } - OpenFoodNetwork::OrdersAndFulfillmentReport.new(current_user, report_options, true) - end - - let(:report_table) do - report.table_rows - end - - context "viewing the report" do - let!(:order) do - create(:completed_order_with_totals, line_items_count: 1, user: customer.user, - customer: customer, distributor: distributor) - end - - it "generates the report" do - expect(report_table.length).to eq(2) - end - - it "has a line item row" do - distributor_name_field = report_table.first[0] - expect(distributor_name_field).to eq distributor.name - - customer_name_field = report_table.first[1] - expect(customer_name_field).to eq order.bill_address.full_name - - total_field = report_table.last[5] - expect(total_field).to eq I18n.t("admin.reports.total") - end - - it 'includes the order number and date in item rows' do - order_number_and_date_fields = report_table.first[33..34] - expect(order_number_and_date_fields).to eq([ - order.number, - order.completed_at.strftime("%F %T"), - ]) - end - - it 'includes the order number and date in total rows' do - order_number_and_date_fields = report_table.last[33..34] - expect(order_number_and_date_fields).to eq([ - order.number, - order.completed_at.strftime("%F %T"), - ]) - end - end - - context "loading shipping methods" do - let!(:shipping_method1) { - create(:shipping_method, distributors: [distributor], name: "First") - } - let!(:shipping_method2) { - create(:shipping_method, distributors: [distributor], name: "Second") - } - let!(:shipping_method3) { - create(:shipping_method, distributors: [distributor], name: "Third") - } - let!(:order) do - create(:completed_order_with_totals, line_items_count: 1, user: customer.user, - customer: customer, distributor: distributor) - end - - before do - order.shipments.each(&:refresh_rates) - order.select_shipping_method(shipping_method2.id) - end - - it "displays the correct shipping_method" do - shipping_method_name_field = report_table.first[15] - expect(shipping_method_name_field).to eq shipping_method2.name - end - end - - context "displaying payment fees" do - context "with both failed and completed payments present" do - let!(:order) { - create(:order_ready_to_ship, user: customer.user, - customer: customer, distributor: distributor) - } - let(:completed_payment) { order.payments.completed.first } - let!(:failed_payment) { create(:payment, order: order, state: "failed") } - - before do - completed_payment.adjustment.update amount: 123.00 - failed_payment.adjustment.update amount: 456.00, eligible: false, state: "finalized" - end - - it "shows the correct payment fee amount for the order" do - payment_fee_field = report_table.last[12] - expect(payment_fee_field).to eq completed_payment.adjustment.amount - end - end - end - - context 'when a variant override applies' do - let!(:order) do - create(:completed_order_with_totals, line_items_count: 1, user: customer.user, - customer: customer, distributor: distributor) - end - let(:overidden_sku) { 'magical_sku' } - - before do - create( - :variant_override, - hub: distributor, - variant: order.line_items.first.variant, - sku: overidden_sku - ) - end - - it 'uses the sku from the variant override' do - sku_field = report_table.first[23] - expect(sku_field).to eq overidden_sku - end - end -end diff --git a/spec/lib/open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report_spec.rb b/spec/lib/open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report_spec.rb deleted file mode 100644 index 3da55893ad..0000000000 --- a/spec/lib/open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report' - -RSpec.describe OpenFoodNetwork::OrdersAndFulfillmentReport::DistributorTotalsBySupplierReport do - let!(:distributor) { create(:distributor_enterprise) } - - let!(:order) do - create(:completed_order_with_totals, line_items_count: 1, distributor: distributor) - end - - let(:current_user) { distributor.owner } - - let(:report) do - report_options = { report_subtype: described_class::REPORT_TYPE } - OpenFoodNetwork::OrdersAndFulfillmentReport.new(current_user, report_options, true) - end - - let(:report_table) do - report.table_rows - end - - it "generates the report" do - expect(report_table.length).to eq(2) - end - - it "has a variant row under the distributor" do - distributor_name_field = report_table.first[0] - expect(distributor_name_field).to eq distributor.name - - supplier = order.line_items.first.variant.product.supplier - supplier_name_field = report_table.first[1] - expect(supplier_name_field).to eq supplier.name - - total_field = report_table.last[1] - expect(total_field).to eq I18n.t("admin.reports.total") - end -end diff --git a/spec/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report_spec.rb b/spec/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report_spec.rb deleted file mode 100644 index 337f64ed45..0000000000 --- a/spec/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report' - -RSpec.describe OpenFoodNetwork::OrdersAndFulfillmentReport::SupplierTotalsByDistributorReport do - let!(:distributor) { create(:distributor_enterprise) } - - let!(:order) do - create(:completed_order_with_totals, line_items_count: 1, distributor: distributor) - end - - let(:current_user) { distributor.owner } - - let(:report) do - report_options = { report_subtype: described_class::REPORT_TYPE } - OpenFoodNetwork::OrdersAndFulfillmentReport.new(current_user, report_options, true) - end - - let(:report_table) do - report.table_rows - end - - it "generates the report" do - expect(report_table.length).to eq(2) - end - - it "has a variant row under the distributor" do - supplier = order.line_items.first.variant.product.supplier - supplier_name_field = report_table.first[0] - expect(supplier_name_field).to eq supplier.name - - distributor_name_field = report_table.first[3] - expect(distributor_name_field).to eq distributor.name - - total_field = report_table.last[3] - expect(total_field).to eq I18n.t("admin.reports.total") - end -end diff --git a/spec/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_report_spec.rb b/spec/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_report_spec.rb deleted file mode 100644 index f93c088866..0000000000 --- a/spec/lib/open_food_network/orders_and_fulfillment_report/supplier_totals_report_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'open_food_network/orders_and_fulfillment_report/supplier_totals_report' - -RSpec.describe OpenFoodNetwork::OrdersAndFulfillmentReport::SupplierTotalsReport do - let!(:distributor) { create(:distributor_enterprise) } - - let!(:order) do - create(:completed_order_with_totals, line_items_count: 1, distributor: distributor) - end - - let(:current_user) { distributor.owner } - - let(:report) do - report_options = { report_subtype: described_class::REPORT_TYPE } - OpenFoodNetwork::OrdersAndFulfillmentReport.new(current_user, report_options, true) - end - - let(:report_table) do - report.table_rows - end - - it "generates the report" do - expect(report_table.length).to eq(1) - end - - it "has a variant row" do - supplier = order.line_items.first.variant.product.supplier - supplier_name_field = report_table.first[0] - expect(supplier_name_field).to eq supplier.name - end -end diff --git a/spec/lib/open_food_network/orders_and_fulfillments_report_spec.rb b/spec/lib/open_food_network/orders_and_fulfillments_report_spec.rb deleted file mode 100644 index 249ac1c61c..0000000000 --- a/spec/lib/open_food_network/orders_and_fulfillments_report_spec.rb +++ /dev/null @@ -1,277 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'open_food_network/orders_and_fulfillment_report' -require 'open_food_network/order_grouper' - -describe OpenFoodNetwork::OrdersAndFulfillmentReport do - include AuthenticationHelper - - let(:distributor) { create(:distributor_enterprise) } - let(:order_cycle) { create(:simple_order_cycle) } - let(:address) { create(:address) } - let(:order) { - create( - :order, - completed_at: 1.day.ago, - order_cycle: order_cycle, - distributor: distributor, - bill_address: address - ) - } - let(:line_item) { build(:line_item_with_shipment) } - let(:user) { create(:user) } - let(:admin_user) { create(:admin_user) } - - describe "fetching orders" do - before { order.line_items << line_item } - - context "as a site admin" do - subject { described_class.new(admin_user, {}, true) } - - it "fetches completed orders" do - o2 = create(:order) - o2.line_items << build(:line_item) - expect(subject.table_items).to eq([line_item]) - end - - it "does not show cancelled orders" do - o2 = create(:order, state: "canceled", completed_at: 1.day.ago) - o2.line_items << build(:line_item_with_shipment) - expect(subject.table_items).to eq([line_item]) - end - end - - context "as a manager of a supplier" do - subject { described_class.new(user, {}, true) } - - let(:s1) { create(:supplier_enterprise) } - - before do - s1.enterprise_roles.create!(user: user) - end - - context "that has granted P-OC to the distributor" do - let(:o2) { - create( - :order, - distributor: distributor, - completed_at: 1.day.ago, - bill_address: create(:address), - ship_address: create(:address) - ) - } - let(:li2) { - build(:line_item_with_shipment, product: create(:simple_product, supplier: s1)) - } - - before do - o2.line_items << li2 - create( - :enterprise_relationship, - parent: s1, - child: distributor, - permissions_list: [:add_to_order_cycle] - ) - end - - it "shows line items supplied by my producers, with names hidden" do - expect(subject.table_items).to eq([li2]) - expect(subject.table_items.first.order.bill_address.firstname).to eq("HIDDEN") - end - - context "where the distributor allows suppliers to see customer names" do - before do - distributor.update_columns show_customer_names_to_suppliers: true - end - - it "shows line items supplied by my producers, with names shown" do - expect(subject.table_items).to eq([li2]) - expect(subject.table_items.first.order.bill_address.firstname). - to eq(order.bill_address.firstname) - end - end - end - - context "that has not granted P-OC to the distributor" do - let(:o2) { - create( - :order, - distributor: distributor, - completed_at: 1.day.ago, - bill_address: create(:address), - ship_address: create(:address) - ) - } - let(:li2) { - build(:line_item_with_shipment, product: create(:simple_product, supplier: s1)) - } - - before do - o2.line_items << li2 - end - - it "does not show line items supplied by my producers" do - expect(subject.table_items).to eq([]) - end - - context "where the distributor allows suppliers to see customer names" do - before do - distributor.show_customer_names_to_suppliers = true - end - - it "does not show line items supplied by my producers" do - expect(subject.table_items).to eq([]) - end - end - end - end - - context "as a manager of a distributor" do - subject { described_class.new(user, {}, true) } - - before do - distributor.enterprise_roles.create!(user: user) - end - - it "only shows line items distributed by enterprises managed by the current user" do - d2 = create(:distributor_enterprise) - d2.enterprise_roles.create!(user: create(:user)) - o2 = create(:order, distributor: d2, completed_at: 1.day.ago) - o2.line_items << build(:line_item_with_shipment) - expect(subject.table_items).to eq([line_item]) - end - - it "only shows the selected order cycle" do - oc2 = create(:simple_order_cycle) - o2 = create(:order, distributor: distributor, order_cycle: oc2) - o2.line_items << build(:line_item) - allow(subject).to receive(:params).and_return(order_cycle_id_in: order_cycle.id) - expect(subject.table_items).to eq([line_item]) - end - end - end - - describe "columns are aligned" do - it 'has aligned columsn' do - report_types = [ - "", - "order_cycle_supplier_totals", - "order_cycle_supplier_totals_by_distributor", - "order_cycle_distributor_totals_by_supplier", - "order_cycle_customer_totals" - ] - - report_types.each do |report_type| - report = described_class.new(admin_user, report_subtype: report_type) - expect(report.table_headers.size).to eq(report.columns.size) - end - end - end - - describe "order_cycle_customer_totals" do - let!(:product) { line_item.product } - let!(:fuji) do - create(:variant, product: product, display_name: "Fuji", sku: "FUJI", on_hand: 100) - end - let!(:gala) do - create(:variant, product: product, display_name: "Gala", sku: "GALA", on_hand: 100) - end - - let(:items) { - described_class.new(admin_user, { report_subtype: "order_cycle_customer_totals" }, true) - .table_rows - } - - before do - # Clear price so it will be computed based on quantity and variant price. - order.line_items << build(:line_item_with_shipment, variant: fuji, price: nil, quantity: 1) - order.line_items << build(:line_item_with_shipment, variant: gala, price: nil, quantity: 2) - end - - it "has a product row" do - product_name_field = items.first[5] - expect(product_name_field).to eq product.name - end - - it "has a summary row" do - product_name_field = items.last[5] - expect(product_name_field).to eq "TOTAL" - end - - # Expected Report for Scenario: - # - # Row 1: Armstrong Amari, Fuji Apple, price: 8 - # Row 2: SUMMARY - # Row 3: Bartoletti Brooklyn, Fuji Apple, price: 1 + 4 - # Row 4: Bartoletti Brooklyn, Gala Apple, price: 2 - # Row 5: SUMMARY - describe "grouping of line items" do - let!(:address) { create(:address, last_name: "Bartoletti", first_name: "Brooklyn") } - - let!(:second_address) { create(:address, last_name: "Armstrong", first_name: "Amari") } - let!(:second_order) do - create(:order, completed_at: 1.day.ago, order_cycle: order_cycle, distributor: distributor, - bill_address: second_address) - end - - before do - # Add a second line item for Fuji variant to the order, to test grouping in this edge case. - order.line_items << build(:line_item_with_shipment, variant: fuji, price: nil, quantity: 4) - - second_order.line_items << build(:line_item_with_shipment, variant: fuji, price: nil, - quantity: 8) - end - - it "groups line items by variant and order" do - expect(items.length).to eq(5) - - # Row 1: Armstrong Amari, Fuji Apple, price: 8 - row_data = items[0] - expect(customer_name(row_data)).to eq(second_address.full_name) - expect(amount(row_data)).to eq(fuji.price * 8) - expect(variant_sku(row_data)).to eq(fuji.sku) - - # Row 2: SUMMARY - row_data = items[1] - expect(totals_row?(row_data)).to eq(true) - expect(customer_name(row_data)).to eq(second_address.full_name) - expect(amount(row_data)).to eq(fuji.price * 8) - - # Row 3: Bartoletti Brooklyn, Fuji Apple, price: 1 + 4 - row_data = items[2] - expect(customer_name(row_data)).to eq(address.full_name) - expect(amount(row_data)).to eq(fuji.price * 5) - expect(variant_sku(row_data)).to eq(fuji.sku) - - # Row 4: Bartoletti Brooklyn, Gala Apple, price: 2 - row_data = items[3] - expect(customer_name(row_data)).to eq(address.full_name) - expect(amount(row_data)).to eq(gala.price * 2) - expect(variant_sku(row_data)).to eq(gala.sku) - - # Row 5: SUMMARY - row_data = items[4] - expect(totals_row?(row_data)).to eq(true) - expect(customer_name(row_data)).to eq(address.full_name) - expect(amount(row_data)).to eq(fuji.price * 5 + gala.price * 2) - end - end - - def totals_row?(row_data) - row_data[5] == I18n.t("admin.reports.total") - end - - def customer_name(row_data) - row_data[1] - end - - def amount(row_data) - row_data[8] - end - - def variant_sku(row_data) - row_data[23] - end - end -end diff --git a/spec/lib/open_food_network/products_and_inventory_default_report_spec.rb b/spec/lib/open_food_network/products_and_inventory_default_report_spec.rb deleted file mode 100644 index 2de38c53cc..0000000000 --- a/spec/lib/open_food_network/products_and_inventory_default_report_spec.rb +++ /dev/null @@ -1,261 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'open_food_network/products_and_inventory_report' - -describe OpenFoodNetwork::ProductsAndInventoryDefaultReport do - context "As a site admin" do - let(:user) do - user = create(:user) - user.spree_roles << Spree::Role.find_or_create_by!(name: 'admin') - user - end - subject do - OpenFoodNetwork::ProductsAndInventoryReport.new user, {}, true - end - - it "Should return headers" do - expect(subject.table_headers).to eq([ - "Supplier", - "Producer Suburb", - "Product", - "Product Properties", - "Taxons", - "Variant Value", - "Price", - "Group Buy Unit Quantity", - "Amount", - "SKU" - ]) - end - - it "should build a table from a list of variants" do - variant = double(:variant, sku: "sku", - full_name: "Variant Name", - count_on_hand: 10, - price: 100) - allow(variant).to receive_message_chain(:product, :supplier, :name).and_return("Supplier") - allow(variant).to receive_message_chain(:product, :supplier, :address, - :city).and_return("A city") - allow(variant).to receive_message_chain(:product, :name).and_return("Product Name") - allow(variant).to receive_message_chain(:product, - :properties).and_return [double(name: "property1"), - double(name: "property2")] - allow(variant).to receive_message_chain(:product, - :taxons).and_return [double(name: "taxon1"), - double(name: "taxon2")] - allow(variant).to receive_message_chain(:product, :group_buy_unit_size).and_return(21) - allow(subject).to receive(:variants).and_return [variant] - - expect(subject.table_rows).to eq([[ - "Supplier", - "A city", - "Product Name", - "property1, property2", - "taxon1, taxon2", - "Variant Name", - 100, - 21, - "", - "sku" - ]]) - end - - it "fetches variants for some params" do - expect(subject).to receive(:child_variants).and_return ["children"] - expect(subject).to receive(:filter).with(['children']).and_return ["filter_children"] - expect(subject.variants).to eq(["filter_children"]) - end - end - - context "As an enterprise user" do - let(:supplier) { create(:supplier_enterprise) } - let(:enterprise_user) do - user = create(:user) - user.enterprise_roles.create(enterprise: supplier) - user.spree_roles = [] - user.save! - user - end - - subject do - OpenFoodNetwork::ProductsAndInventoryReport.new enterprise_user, {}, true - end - - describe "fetching child variants" do - it "returns some variants" do - product1 = create(:simple_product, supplier: supplier) - variant1 = product1.variants.first - variant2 = create(:variant, product: product1) - - expect(subject.child_variants).to match_array [variant1, variant2] - end - - it "should only return variants managed by the user" do - product1 = create(:simple_product, supplier: create(:supplier_enterprise)) - product2 = create(:simple_product, supplier: supplier) - variant1 = product1.variants.first - variant2 = product2.variants.first - - expect(subject.child_variants).to eq([variant2]) - end - end - - describe "Filtering variants" do - let(:variants) { Spree::Variant.where(nil).joins(:product).where(is_master: false) } - - describe "based on report type" do - it "returns only variants on hand" do - product1 = create(:simple_product, supplier: supplier, on_hand: 99) - product2 = create(:simple_product, supplier: supplier, on_hand: 0) - - allow(subject).to receive(:params).and_return(report_subtype: 'inventory') - expect(subject.filter(variants)).to eq([product1.variants.first]) - end - end - it "filters to a specific supplier" do - supplier2 = create(:supplier_enterprise) - product1 = create(:simple_product, supplier: supplier) - product2 = create(:simple_product, supplier: supplier2) - - allow(subject).to receive(:params).and_return(supplier_id: supplier.id) - expect(subject.filter(variants)).to eq([product1.variants.first]) - end - it "filters to a specific distributor" do - distributor = create(:distributor_enterprise) - product1 = create(:simple_product, supplier: supplier) - product2 = create(:simple_product, supplier: supplier) - order_cycle = create(:simple_order_cycle, suppliers: [supplier], - distributors: [distributor], - variants: [product2.variants.first]) - - allow(subject).to receive(:params).and_return(distributor_id: distributor.id) - expect(subject.filter(variants)).to eq([product2.variants.first]) - end - - it "ignores variant overrides without filter" do - distributor = create(:distributor_enterprise) - product = create(:simple_product, supplier: supplier, price: 5) - variant = product.variants.first - order_cycle = create(:simple_order_cycle, suppliers: [supplier], - distributors: [distributor], - variants: [product.variants.first]) - create(:variant_override, hub: distributor, variant: variant, price: 2) - - result = subject.filter(variants) - - expect(result.first.price).to eq 5 - end - - it "considers variant overrides with distributor" do - distributor = create(:distributor_enterprise) - product = create(:simple_product, supplier: supplier, price: 5) - variant = product.variants.first - order_cycle = create(:simple_order_cycle, suppliers: [supplier], - distributors: [distributor], - variants: [product.variants.first]) - create(:variant_override, hub: distributor, variant: variant, price: 2) - - allow(subject).to receive(:params).and_return(distributor_id: distributor.id) - result = subject.filter(variants) - - expect(result.first.price).to eq 2 - end - - it "filters to a specific order cycle" do - distributor = create(:distributor_enterprise) - product1 = create(:simple_product, supplier: supplier) - product2 = create(:simple_product, supplier: supplier) - order_cycle = create(:simple_order_cycle, suppliers: [supplier], - distributors: [distributor], - variants: [product1.variants.first]) - - allow(subject).to receive(:params).and_return(order_cycle_id: order_cycle.id) - expect(subject.filter(variants)).to eq([product1.variants.first]) - end - - it "should do all the filters at once" do - # The following data ensures that this spec fails if any of the - # filters fail. It's testing the filters are not impacting each other. - distributor = create(:distributor_enterprise) - other_distributor = create(:distributor_enterprise) - other_supplier = create(:supplier_enterprise) - not_filtered_variant = create(:simple_product, supplier: supplier).variants.first - variant_filtered_by_order_cycle = create(:simple_product, - supplier: supplier).variants.first - variant_filtered_by_distributor = create(:simple_product, - supplier: supplier).variants.first - variant_filtered_by_supplier = create(:simple_product, - supplier: other_supplier).variants.first - variant_filtered_by_stock = create(:simple_product, supplier: supplier, - on_hand: 0).variants.first - - # This OC contains all products except the one that should be filtered - # by order cycle. We create a separate OC further down to proof that - # the product is passing all other filters. - order_cycle = create( - :simple_order_cycle, - suppliers: [supplier, other_supplier], - distributors: [distributor, other_distributor], - variants: [ - not_filtered_variant, - variant_filtered_by_distributor, - variant_filtered_by_supplier, - variant_filtered_by_stock - ] - ) - - # Remove the distribution of one product for one distributor but still - # sell it through the other distributor. - order_cycle.exchanges.outgoing.find_by(receiver_id: distributor.id). - exchange_variants. - find_by(variant_id: variant_filtered_by_distributor). - destroy - - # Make product available to be filtered later. See OC comment above. - create( - :simple_order_cycle, - suppliers: [supplier], - distributors: [distributor, other_distributor], - variants: [ - variant_filtered_by_order_cycle - ] - ) - - allow(subject).to receive(:params).and_return( - order_cycle_id: order_cycle.id, - supplier_id: supplier.id, - distributor_id: distributor.id, - report_subtype: 'inventory' - ) - - expect(subject.filter(variants)).to match_array [not_filtered_variant] - - # And it integrates with the ordering of the `variants` method. - expect(subject.variants).to match_array [not_filtered_variant] - end - end - - describe "fetching SKU for a variant" do - let(:variant) { create(:variant) } - let(:product) { variant.product } - - before { product.update_attribute(:sku, "Product SKU") } - - context "when the variant has an SKU set" do - before { variant.update_attribute(:sku, "Variant SKU") } - it "returns it" do - expect(subject.__send__(:sku_for, variant)).to eq "Variant SKU" - end - end - - context "when the variant has bo SKU set" do - before { variant.update_attribute(:sku, "") } - - it "returns the product's SKU" do - expect(subject.__send__(:sku_for, variant)).to eq "Product SKU" - end - end - end - end -end diff --git a/spec/lib/open_food_network/sales_tax_report_spec.rb b/spec/lib/open_food_network/sales_tax_report_spec.rb deleted file mode 100644 index 72c68d5c05..0000000000 --- a/spec/lib/open_food_network/sales_tax_report_spec.rb +++ /dev/null @@ -1,64 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'open_food_network/sales_tax_report' - -module OpenFoodNetwork - describe SalesTaxReport do - let(:user) { create(:user) } - let(:report) { SalesTaxReport.new(user, {}, true) } - - describe "calculating totals for line items" do - let(:li1) { double(:line_item, quantity: 1, amount: 12) } - let(:li2) { double(:line_item, quantity: 2, amount: 24) } - let(:totals) { report.send(:totals_of, [li1, li2]) } - - before do - allow(report).to receive(:tax_included_in).and_return(2, 4) - end - - it "calculates total quantity" do - expect(totals[:items]).to eq(3) - end - - it "calculates total price" do - expect(totals[:items_total]).to eq(36) - end - - context "when floating point math would result in fractional cents" do - let(:li1) { double(:line_item, quantity: 1, amount: 0.11) } - let(:li2) { double(:line_item, quantity: 2, amount: 0.12) } - - it "rounds to the nearest cent" do - expect(totals[:items_total]).to eq(0.23) - end - end - - it "calculates the taxable total price" do - expect(totals[:taxable_total]).to eq(36) - end - - it "calculates sales tax" do - expect(totals[:sales_tax]).to eq(6) - end - - context "when there is no tax on a line item" do - before do - allow(report).to receive(:tax_included_in) { 0 } - end - - it "does not appear in taxable total" do - expect(totals[:taxable_total]).to eq(0) - end - - it "still appears on items total" do - expect(totals[:items_total]).to eq(36) - end - - it "does not register sales tax" do - expect(totals[:sales_tax]).to eq(0) - end - end - end - end -end diff --git a/spec/lib/open_food_network/users_and_enterprises_report_spec.rb b/spec/lib/open_food_network/users_and_enterprises_report_spec.rb deleted file mode 100644 index ca9b9f4a23..0000000000 --- a/spec/lib/open_food_network/users_and_enterprises_report_spec.rb +++ /dev/null @@ -1,125 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'open_food_network/users_and_enterprises_report' - -module OpenFoodNetwork - describe UsersAndEnterprisesReport do - describe "users_and_enterprises" do - let!(:owners_and_enterprises) { double(:owners_and_enterprises) } - let!(:managers_and_enterprises) { double(:managers_and_enterprises) } - let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new(nil, {}, true) } - - before do - allow(subject).to receive(:owners_and_enterprises) { owners_and_enterprises } - allow(subject).to receive(:managers_and_enterprises) { managers_and_enterprises } - end - - it "should concatenate owner and manager queries" do - expect(subject).to receive(:owners_and_enterprises).once - expect(subject).to receive(:managers_and_enterprises).once - expect(owners_and_enterprises).to receive(:concat).with(managers_and_enterprises).and_return [] - expect(subject).to receive(:sort).with [] - subject.users_and_enterprises - end - end - - describe "sorting results" do - let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new(nil, {}, true) } - - it "sorts by creation date" do - uae_mock = [ - { "created_at" => "2015-01-01", "name" => "bbb" }, - { "created_at" => "2015-01-02", "name" => "aaa" } - ] - expect(subject.sort(uae_mock)).to eq [uae_mock[1], uae_mock[0]] - end - - it "then sorts by name" do - uae_mock = [ - { "name" => "aaa", "relationship_type" => "bbb", "user_email" => "bbb" }, - { "name" => "bbb", "relationship_type" => "aaa", "user_email" => "aaa" } - ] - expect(subject.sort(uae_mock)).to eq [uae_mock[0], uae_mock[1]] - end - - it "then sorts by relationship type (reveresed)" do - uae_mock = [ - { "name" => "aaa", "relationship_type" => "bbb", "user_email" => "bbb" }, - { "name" => "aaa", "relationship_type" => "aaa", "user_email" => "aaa" }, - { "name" => "aaa", "relationship_type" => "bbb", "user_email" => "aaa" } - ] - expect(subject.sort(uae_mock)).to eq [uae_mock[2], uae_mock[0], uae_mock[1]] - end - - it "then sorts by user_email" do - uae_mock = [ - { "name" => "aaa", "relationship_type" => "bbb", "user_email" => "aaa" }, - { "name" => "aaa", "relationship_type" => "aaa", "user_email" => "aaa" }, - { "name" => "aaa", "relationship_type" => "aaa", "user_email" => "bbb" } - ] - expect(subject.sort(uae_mock)).to eq [uae_mock[0], uae_mock[1], uae_mock[2]] - end - end - - describe "filtering results" do - let!(:enterprise1) { create(:enterprise, owner: create(:user) ) } - let!(:enterprise2) { create(:enterprise, owner: create(:user) ) } - - describe "for owners and enterprises" do - describe "by enterprise id" do - let!(:params) { { enterprise_id_in: [enterprise1.id.to_s] } } - let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new nil, params, true } - - it "excludes enterprises that are not explicitly requested" do - results = subject.owners_and_enterprises.to_a.map{ |oae| oae["name"] } - expect(results).to include enterprise1.name - expect(results).to_not include enterprise2.name - end - end - - describe "by user id" do - let!(:params) { { user_id_in: [enterprise1.owner.id.to_s] } } - let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new nil, params, true } - - it "excludes enterprises that are not explicitly requested" do - results = subject.owners_and_enterprises.to_a.map{ |oae| oae["name"] } - expect(results).to include enterprise1.name - expect(results).to_not include enterprise2.name - end - end - end - - describe "for managers and enterprises" do - describe "by enterprise id" do - let!(:params) { { enterprise_id_in: [enterprise1.id.to_s] } } - let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new nil, params, true } - - it "excludes enterprises that are not explicitly requested" do - results = subject.managers_and_enterprises.to_a.map{ |mae| mae["name"] } - expect(results).to include enterprise1.name - expect(results).to_not include enterprise2.name - end - end - - describe "by user id" do - let!(:manager1) { create(:user) } - let!(:manager2) { create(:user) } - let!(:params) { { user_id_in: [manager1.id.to_s] } } - let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new nil, params, true } - - before do - enterprise1.enterprise_roles.build(user: manager1).save - enterprise2.enterprise_roles.build(user: manager2).save - end - - it "excludes enterprises whose managers are not explicitly requested" do - results = subject.managers_and_enterprises.to_a.map{ |mae| mae["name"] } - expect(results).to include enterprise1.name - expect(results).to_not include enterprise2.name - end - end - end - end - end -end diff --git a/spec/lib/open_food_network/xero_invoices_report_spec.rb b/spec/lib/open_food_network/xero_invoices_report_spec.rb deleted file mode 100644 index 5235edd972..0000000000 --- a/spec/lib/open_food_network/xero_invoices_report_spec.rb +++ /dev/null @@ -1,96 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'open_food_network/xero_invoices_report' - -module OpenFoodNetwork - describe XeroInvoicesReport do - subject { XeroInvoicesReport.new user, {}, true } - - let(:user) { create(:user) } - - describe "option defaults" do - let(:report) { - XeroInvoicesReport.new user, initial_invoice_number: '', invoice_date: '', due_date: '', - account_code: '' - } - - around { |example| Timecop.travel(Time.zone.local(2015, 5, 5, 14, 0, 0)) { example.run } } - - it "uses defaults when blank params are passed" do - expect(report.instance_variable_get(:@opts)).to eq( invoice_date: Date.civil(2015, 5, 5), - due_date: Date.civil(2015, 6, 5), - account_code: 'food sales', - report_subtype: 'summary' ) - end - end - - describe "summary rows" do - let(:report) { - XeroInvoicesReport.new user, initial_invoice_number: '', invoice_date: '', due_date: '', - account_code: '' - } - let(:order) { double(:order) } - let(:summary_rows) { report.send(:summary_rows_for_order, order, 1, {}) } - - before do - allow(report).to receive(:produce_summary_rows) { ['produce'] } - allow(report).to receive(:fee_summary_rows) { ['fee'] } - allow(report).to receive(:shipping_summary_rows) { ['shipping'] } - allow(report).to receive(:payment_summary_rows) { ['payment'] } - allow(report).to receive(:admin_adjustment_summary_rows) { ['admin'] } - end - - it "displays produce summary rows when summary report" do - allow(report).to receive(:detail?) { false } - expect(summary_rows).to include 'produce' - end - - it "does not display produce summary rows when detail report" do - allow(report).to receive(:detail?) { true } - expect(summary_rows).not_to include 'produce' - end - - it "displays fee summary rows when summary report" do - allow(report).to receive(:detail?) { false } - expect(summary_rows).to include 'fee' - end - - it "displays fee summary rows when detail report" do - allow(report).to receive(:detail?) { true } - expect(summary_rows).to include 'fee' - end - - it "always displays shipping summary rows" do - expect(summary_rows).to include 'shipping' - end - - it "displays admin adjustment summary rows when summary report" do - expect(summary_rows).to include 'admin' - end - - it "does not display admin adjustment summary rows when detail report" do - allow(report).to receive(:detail?) { true } - expect(summary_rows).not_to include 'admin' - end - end - - describe "generating invoice numbers" do - let(:order) { double(:order, number: 'R731032860') } - - describe "when no initial invoice number is given" do - it "returns the order number" do - expect(subject.send(:invoice_number_for, order, 123)).to eq('R731032860') - end - end - - describe "when an initial invoice number is given" do - subject { XeroInvoicesReport.new user, initial_invoice_number: '123' } - - it "increments the number by the index" do - expect(subject.send(:invoice_number_for, order, 456)).to eq(579) - end - end - end - end -end diff --git a/spec/lib/open_food_network/bulk_coop_report_spec.rb b/spec/lib/reports/bulk_coop_report_spec.rb similarity index 88% rename from spec/lib/open_food_network/bulk_coop_report_spec.rb rename to spec/lib/reports/bulk_coop_report_spec.rb index 6fe1f36809..80f118a03a 100644 --- a/spec/lib/open_food_network/bulk_coop_report_spec.rb +++ b/spec/lib/reports/bulk_coop_report_spec.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true require 'spec_helper' -require 'open_food_network/bulk_coop_report' -describe OpenFoodNetwork::BulkCoopReport do - subject { OpenFoodNetwork::BulkCoopReport.new user, params, true } +describe Reporting::Reports::BulkCoop::BulkCoopReport do + subject { Reporting::Reports::BulkCoop::BulkCoopReport.new user, params, true } let(:user) { create(:admin_user) } describe '#table_items' do @@ -62,15 +61,15 @@ describe OpenFoodNetwork::BulkCoopReport do li2 = build(:line_item_with_shipment) o2.line_items << li2 - report = OpenFoodNetwork::BulkCoopReport.new user, {}, true + report = Reporting::Reports::BulkCoop::BulkCoopReport.new user, {}, true expect(report.table_items).to match_array [li1, li2] - report = OpenFoodNetwork::BulkCoopReport.new( + report = Reporting::Reports::BulkCoop::BulkCoopReport.new( user, { q: { completed_at_gt: 2.days.ago } }, true ) expect(report.table_items).to eq([li1]) - report = OpenFoodNetwork::BulkCoopReport.new( + report = Reporting::Reports::BulkCoop::BulkCoopReport.new( user, { q: { completed_at_lt: 2.days.ago } }, true ) expect(report.table_items).to eq([li2]) @@ -86,15 +85,15 @@ describe OpenFoodNetwork::BulkCoopReport do li2 = build(:line_item_with_shipment) o2.line_items << li2 - report = OpenFoodNetwork::BulkCoopReport.new user, {}, true + report = Reporting::Reports::BulkCoop::BulkCoopReport.new user, {}, true expect(report.table_items).to match_array [li1, li2] - report = OpenFoodNetwork::BulkCoopReport.new( + report = Reporting::Reports::BulkCoop::BulkCoopReport.new( user, { q: { distributor_id_in: [d1.id] } }, true ) expect(report.table_items).to eq([li1]) - report = OpenFoodNetwork::BulkCoopReport.new( + report = Reporting::Reports::BulkCoop::BulkCoopReport.new( user, { q: { distributor_id_in: [d2.id] } }, true ) expect(report.table_items).to eq([li2]) @@ -103,7 +102,7 @@ describe OpenFoodNetwork::BulkCoopReport do context "as a manager of a supplier" do let!(:user) { create(:user) } - subject { OpenFoodNetwork::BulkCoopReport.new user, {}, true } + subject { Reporting::Reports::BulkCoop::BulkCoopReport.new user, {}, true } let(:s1) { create(:supplier_enterprise) } @@ -171,7 +170,7 @@ describe OpenFoodNetwork::BulkCoopReport do end # Yes, I know testing a private method is bad practice but report's design, tighly coupling - # OpenFoodNetwork::OrderGrouper and OpenFoodNetwork::BulkCoopReport, makes it + # Reporting::OrderGrouper and Reporting::Reports::BulkCoop::BulkCoopReport, makes it # very hard to make things testeable without ending up in a wormwhole. This is a trade-off. describe '#customer_payments_amount_owed' do let(:params) { {} } diff --git a/spec/lib/reports/customers_report_spec.rb b/spec/lib/reports/customers_report_spec.rb new file mode 100644 index 0000000000..2ed1e950b5 --- /dev/null +++ b/spec/lib/reports/customers_report_spec.rb @@ -0,0 +1,166 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module Reporting + module Reports + module Customers + describe CustomersReport do + context "as a site admin" do + let(:user) do + user = create(:user) + user.spree_roles << Spree::Role.find_or_create_by!(name: 'admin') + user + end + subject { CustomersReport.new user, {}, true } + + describe "mailing list report" do + before do + allow(subject).to receive(:params).and_return(report_subtype: "mailing_list") + end + + it "returns headers for mailing_list" do + expect(subject.table_headers).to eq(["Email", "First Name", "Last Name", "Suburb"]) + end + + it "builds a table from a list of variants" do + order = double(:order, email: "test@test.com") + address = double(:billing_address, firstname: "Firsty", + lastname: "Lasty", city: "Suburbia") + allow(order).to receive(:billing_address).and_return address + allow(subject).to receive(:orders).and_return [order] + + expect(subject.table_rows).to eq([[ + "test@test.com", "Firsty", "Lasty", "Suburbia" + ]]) + end + end + + describe "addresses report" do + before do + allow(subject).to receive(:params).and_return(report_subtype: "addresses") + end + + it "returns headers for addresses" do + expect(subject.table_headers).to eq(["First Name", "Last Name", "Billing Address", "Email", + "Phone", "Hub", "Hub Address", "Shipping Method"]) + end + + it "builds a table from a list of variants" do + a = create(:address) + d = create(:distributor_enterprise) + o = create(:order, distributor: d, bill_address: a) + o.shipments << create(:shipment) + + allow(subject).to receive(:orders).and_return [o] + expect(subject.table_rows).to eq([[ + a.firstname, a.lastname, + [a.address1, a.address2, a.city].join(" "), + o.email, a.phone, d.name, + [d.address.address1, d.address.address2, d.address.city].join(" "), + o.shipping_method.name + ]]) + end + end + + describe "fetching orders" do + it "fetches completed orders" do + o1 = create(:order) + o2 = create(:order, completed_at: 1.day.ago) + expect(subject.orders).to eq([o2]) + end + + it "does not show cancelled orders" do + o1 = create(:order, state: "canceled", completed_at: 1.day.ago) + o2 = create(:order, completed_at: 1.day.ago) + expect(subject.orders).to eq([o2]) + end + end + end + + context "as an enterprise user" do + let(:user) do + user = create(:user) + user.spree_roles = [] + user.save! + user + end + + subject { CustomersReport.new user, {}, true } + + describe "fetching orders" do + let(:supplier) { create(:supplier_enterprise) } + let(:product) { create(:simple_product, supplier: supplier) } + let(:order) { create(:order, completed_at: 1.day.ago) } + + it "only shows orders managed by the current user" do + d1 = create(:distributor_enterprise) + d1.enterprise_roles.build(user: user).save + d2 = create(:distributor_enterprise) + d2.enterprise_roles.build(user: create(:user)).save + + o1 = create(:order, distributor: d1, completed_at: 1.day.ago) + o2 = create(:order, distributor: d2, completed_at: 1.day.ago) + + expect(subject).to receive(:filter).with([o1]).and_return([o1]) + expect(subject.orders).to eq([o1]) + end + + it "does not show orders through a hub that the current user does not manage" do + # Given a supplier enterprise with an order for one of its products + supplier.enterprise_roles.build(user: user).save + order.line_items << create(:line_item_with_shipment, product: product) + + # When I fetch orders, I should see no orders + expect(subject).to receive(:filter).with([]).and_return([]) + expect(subject.orders).to eq([]) + end + end + + describe "filtering orders" do + let(:orders) { Spree::Order.where(nil) } + let(:supplier) { create(:supplier_enterprise) } + + it "returns all orders sans-params" do + expect(subject.filter(orders)).to eq(orders) + end + + it "returns orders with a specific supplier" do + supplier = create(:supplier_enterprise) + supplier2 = create(:supplier_enterprise) + product1 = create(:simple_product, supplier: supplier) + product2 = create(:simple_product, supplier: supplier2) + order1 = create(:order) + order2 = create(:order) + order1.line_items << create(:line_item, product: product1) + order2.line_items << create(:line_item, product: product2) + + allow(subject).to receive(:params).and_return(supplier_id: supplier.id) + expect(subject.filter(orders)).to eq([order1]) + end + + it "filters to a specific distributor" do + d1 = create(:distributor_enterprise) + d2 = create(:distributor_enterprise) + order1 = create(:order, distributor: d1) + order2 = create(:order, distributor: d2) + + allow(subject).to receive(:params).and_return(distributor_id: d1.id) + expect(subject.filter(orders)).to eq([order1]) + end + + it "filters to a specific cycle" do + oc1 = create(:simple_order_cycle) + oc2 = create(:simple_order_cycle) + order1 = create(:order, order_cycle: oc1) + order2 = create(:order, order_cycle: oc2) + + allow(subject).to receive(:params).and_return(order_cycle_id: oc1.id) + expect(subject.filter(orders)).to eq([order1]) + end + end + end + end + end + end +end diff --git a/spec/lib/reports/enterprise_fee_summary/authorizer_spec.rb b/spec/lib/reports/enterprise_fee_summary/authorizer_spec.rb new file mode 100644 index 0000000000..f629c84fae --- /dev/null +++ b/spec/lib/reports/enterprise_fee_summary/authorizer_spec.rb @@ -0,0 +1,179 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Reporting + module Reports + module EnterpriseFeeSummary + describe Authorizer do + let(:user) { create(:user) } + + let(:parameters) { Parameters.new(params) } + let(:permissions) { Permissions.new(user) } + let(:authorizer) { Authorizer.new(parameters, permissions) } + + context "for distributors" do + before do + allow(permissions).to receive(:allowed_distributors) do + stub_model_collection(Enterprise, :id, ["1", "2", "3"]) + end + end + + context "when distributors are allowed" do + let(:params) { { distributor_ids: ["1", "3"] } } + + it "does not raise error" do + expect { authorizer.authorize! }.not_to raise_error + end + end + + context "when a distributor is not allowed" do + let(:params) { { distributor_ids: ["1", "4"] } } + + it "raises ParameterNotAllowedError" do + expect { authorizer.authorize! } + .to raise_error(ParameterNotAllowedError) + end + end + end + + context "for producers" do + before do + allow(permissions).to receive(:allowed_producers) do + stub_model_collection(Enterprise, :id, ["1", "2", "3"]) + end + end + + context "when producers are allowed" do + let(:params) { { producer_ids: ["1", "3"] } } + + it "does not raise error" do + expect { authorizer.authorize! }.not_to raise_error + end + end + + context "when a producer is not allowed" do + let(:params) { { producer_ids: ["1", "4"] } } + + it "raises ParameterNotAllowedError" do + expect { authorizer.authorize! } + .to raise_error(ParameterNotAllowedError) + end + end + end + + context "for order cycles" do + before do + allow(permissions).to receive(:allowed_order_cycles) do + stub_model_collection(OrderCycle, :id, ["1", "2", "3"]) + end + end + + context "when order cycles are allowed" do + let(:params) { { order_cycle_ids: ["1", "3"] } } + + it "does not raise error" do + expect { authorizer.authorize! }.not_to raise_error + end + end + + context "when an order cycle is not allowed" do + let(:params) { { order_cycle_ids: ["1", "4"] } } + + it "raises ParameterNotAllowedError" do + expect { authorizer.authorize! } + .to raise_error(ParameterNotAllowedError) + end + end + end + + context "for enterprise fees" do + before do + allow(permissions).to receive(:allowed_enterprise_fees) do + stub_model_collection(EnterpriseFee, :id, ["1", "2", "3"]) + end + end + + context "when enterprise fees are allowed" do + let(:params) { { enterprise_fee_ids: ["1", "3"] } } + + it "does not raise error" do + expect { authorizer.authorize! }.not_to raise_error + end + end + + context "when an enterprise fee is not allowed" do + let(:params) { { enterprise_fee_ids: ["1", "4"] } } + + it "raises ParameterNotAllowedError" do + expect { authorizer.authorize! } + .to raise_error(ParameterNotAllowedError) + end + end + end + + context "for shipping methods" do + before do + allow(permissions).to receive(:allowed_shipping_methods) do + stub_model_collection(Spree::ShippingMethod, :id, ["1", "2", "3"]) + end + end + + context "when shipping methods are allowed" do + let(:params) { { shipping_method_ids: ["1", "3"] } } + + it "does not raise error" do + expect { authorizer.authorize! }.not_to raise_error + end + end + + context "when a shipping method is not allowed" do + let(:params) { { shipping_method_ids: ["1", "4"] } } + + it "raises ParameterNotAllowedError" do + expect { authorizer.authorize! } + .to raise_error(ParameterNotAllowedError) + end + end + end + + context "for payment methods" do + before do + allow(permissions).to receive(:allowed_payment_methods) do + stub_model_collection(Spree::PaymentMethod, :id, ["1", "2", "3"]) + end + end + + context "when payment methods are allowed" do + let(:params) { { payment_method_ids: ["1", "3"] } } + + it "does not raise error" do + expect { authorizer.authorize! }.not_to raise_error + end + end + + context "when a payment method is not allowed" do + let(:params) { { payment_method_ids: ["1", "4"] } } + + it "raises ParameterNotAllowedError" do + expect { authorizer.authorize! } + .to raise_error(ParameterNotAllowedError) + end + end + end + + def stub_model_collection(model, attribute_name, attribute_list) + attribute_list.map do |attribute_value| + stub_model(model, attribute_name => attribute_value) + end + end + + def stub_model(model, params) + model.new.tap do |instance| + instance.stub(params) + end + end + end + end + end +end diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report_spec.rb b/spec/lib/reports/enterprise_fee_summary/enterprise_fee_summary_report_spec.rb similarity index 99% rename from engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report_spec.rb rename to spec/lib/reports/enterprise_fee_summary/enterprise_fee_summary_report_spec.rb index d18a21b83e..c5db21860a 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report_spec.rb +++ b/spec/lib/reports/enterprise_fee_summary/enterprise_fee_summary_report_spec.rb @@ -3,8 +3,8 @@ require "spec_helper" # rubocop:disable Layout/LineLength -# describe OrderManagement::Reports::EnterpriseFeeSummary::EnterpriseFeeSummaryReport do -# let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } +# describe Reporting::Reports::EnterpriseFeeSummary::EnterpriseFeeSummaryReport do +# let(:report_klass) { Reporting::Reports::EnterpriseFeeSummary } # # Basic data. # let!(:shipping_method) do diff --git a/spec/lib/reports/enterprise_fee_summary/parameters_spec.rb b/spec/lib/reports/enterprise_fee_summary/parameters_spec.rb new file mode 100644 index 0000000000..85689cab90 --- /dev/null +++ b/spec/lib/reports/enterprise_fee_summary/parameters_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require "spec_helper" + +require "date_time_string_validator" + +module Reporting + module Reports + module EnterpriseFeeSummary + describe Parameters do + describe "validation" do + let(:parameters) { described_class.new } + + it "allows all parameters to be blank" do + expect(parameters).to be_valid + end + + context "for type of parameters" do + it { is_expected.to validate_date_time_format_of(:start_at) } + it { is_expected.to validate_date_time_format_of(:end_at) } + it { is_expected.to validate_integer_array(:distributor_ids) } + it { is_expected.to validate_integer_array(:producer_ids) } + it { is_expected.to validate_integer_array(:order_cycle_ids) } + it { is_expected.to validate_integer_array(:enterprise_fee_ids) } + it { is_expected.to validate_integer_array(:shipping_method_ids) } + it { is_expected.to validate_integer_array(:payment_method_ids) } + + it "allows integer arrays to include blank string and cleans it up" do + subject.distributor_ids = ["", "1"] + subject.producer_ids = ["", "1"] + subject.order_cycle_ids = ["", "1"] + subject.enterprise_fee_ids = ["", "1"] + subject.shipping_method_ids = ["", "1"] + subject.payment_method_ids = ["", "1"] + + expect(subject).to be_valid + + expect(subject.distributor_ids).to eq(["1"]) + expect(subject.producer_ids).to eq(["1"]) + expect(subject.order_cycle_ids).to eq(["1"]) + expect(subject.enterprise_fee_ids).to eq(["1"]) + expect(subject.shipping_method_ids).to eq(["1"]) + expect(subject.payment_method_ids).to eq(["1"]) + end + + describe "requiring start_at to be before end_at" do + let(:now) { Time.zone.now.utc } + + it "adds error when start_at is after end_at" do + allow(subject).to receive(:start_at) { now.to_s } + allow(subject).to receive(:end_at) { (now - 1.hour).to_s } + + expect(subject).not_to be_valid + error_message = described_class.date_end_before_start_error_message + expect(subject.errors[:end_at]).to eq([error_message]) + end + + it "does not add error when start_at is before end_at" do + allow(subject).to receive(:start_at) { now.to_s } + allow(subject).to receive(:end_at) { (now + 1.hour).to_s } + + expect(subject).to be_valid + end + end + end + end + + describe "smoke authorization" do + let!(:order_cycle) { create(:order_cycle) } + let!(:user) { create(:user) } + + let(:permissions) do + Permissions.new(nil).tap do |instance| + instance.stub(allowed_order_cycles: [order_cycle]) + end + end + + it "does not raise error when the parameters are allowed" do + parameters = described_class.new(order_cycle_ids: [order_cycle.id.to_s]) + expect { parameters.authorize!(permissions) }.not_to raise_error + end + + it "raises error when the parameters are not allowed" do + parameters = described_class.new(order_cycle_ids: [(order_cycle.id + 1).to_s]) + expect { parameters.authorize!(permissions) } + .to raise_error(ParameterNotAllowedError) + end + end + end + end + end +end diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/permissions_spec.rb b/spec/lib/reports/enterprise_fee_summary/permissions_spec.rb similarity index 99% rename from engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/permissions_spec.rb rename to spec/lib/reports/enterprise_fee_summary/permissions_spec.rb index 39334f6dc0..11530314d5 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/permissions_spec.rb +++ b/spec/lib/reports/enterprise_fee_summary/permissions_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -describe OrderManagement::Reports::EnterpriseFeeSummary::Permissions do +describe Reporting::Reports::EnterpriseFeeSummary::Permissions do let!(:order_cycle) { create(:simple_order_cycle) } let!(:incoming_exchange) { create(:exchange, incoming: true, order_cycle: order_cycle) } let!(:outgoing_exchange) { create(:exchange, incoming: false, order_cycle: order_cycle) } diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb b/spec/lib/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb similarity index 95% rename from engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb rename to spec/lib/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb index f21f44c472..91046372fd 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb +++ b/spec/lib/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb @@ -2,8 +2,8 @@ require "spec_helper" -# describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::CsvRenderer do -# let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } +# describe Reporting::Reports::EnterpriseFeeSummary::Renderers::CsvRenderer do +# let(:report_klass) { Reporting::Reports::EnterpriseFeeSummary } # let!(:permissions) { report_klass::Permissions.new(current_user) } # let!(:parameters) { report_klass::Parameters.new } diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb b/spec/lib/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb similarity index 89% rename from engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb rename to spec/lib/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb index dd5a4a301c..b396458e0f 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb +++ b/spec/lib/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb @@ -2,12 +2,12 @@ require "spec_helper" -# describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::HtmlRenderer do -# let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary } +# describe Reporting::Reports::EnterpriseFeeSummary::Renderers::HtmlRenderer do +# let(:report_klass) { Reporting::Reports::EnterpriseFeeSummary } # let!(:permissions) { report_klass::Permissions.new(current_user) } # let!(:parameters) { report_klass::Parameters.new } -# let!(:controller) { OrderManagement::Reports::EnterpriseFeeSummariesController.new } +# let!(:controller) { Reporting::Reports::EnterpriseFeeSummariesController.new } # let!(:service) { report_klass::ReportService.new(permissions, parameters) } # let!(:renderer) { described_class.new(service) } diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb b/spec/lib/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb similarity index 92% rename from engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb rename to spec/lib/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb index a091896a3d..5f26970c00 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb +++ b/spec/lib/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -describe OrderManagement::Reports::EnterpriseFeeSummary::ReportData::EnterpriseFeeTypeTotal do +describe Reporting::Reports::EnterpriseFeeSummary::ReportData::EnterpriseFeeTypeTotal do it "sorts instances according to their attributes" do instance_a = described_class.new( fee_type: "sales", diff --git a/spec/lib/reports/lettuce_share_report_spec.rb b/spec/lib/reports/lettuce_share_report_spec.rb new file mode 100644 index 0000000000..6fd5b8fca5 --- /dev/null +++ b/spec/lib/reports/lettuce_share_report_spec.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module Reporting + module Reports + module ProductsAndInventory + describe LettuceShareReport do + let(:user) { create(:user) } + let(:base_report) { + ProductsAndInventoryReport.new(user, { report_subtype: 'lettuce_share' }, true) + } + let(:report) { base_report.report } + let(:variant) { create(:variant) } + + describe "grower and method" do + it "shows just the producer when there is no certification" do + allow(report).to receive(:producer_name) { "Producer" } + allow(report).to receive(:certification) { "" } + + expect(report.__send__(:grower_and_method, variant)).to eq("Producer") + end + + it "shows producer and certification when a certification is present" do + allow(report).to receive(:producer_name) { "Producer" } + allow(report).to receive(:certification) { "Method" } + + expect(report.__send__(:grower_and_method, variant)).to eq("Producer (Method)") + end + end + + describe "gst" do + it "handles tax category without rates" do + expect(report.__send__(:gst, variant)).to eq(0) + end + end + + describe "table" do + it "handles no items" do + expect(report.table_rows).to eq [] + end + + describe "lists" do + let(:variant2) { create(:variant) } + let(:variant3) { create(:variant) } + let(:variant4) { create(:variant, on_hand: 0, on_demand: true) } + let(:hub_address) { + create(:address, address1: "distributor address", city: 'The Shire', zipcode: "1234") + } + let(:hub) { create(:distributor_enterprise, address: hub_address) } + let(:variant2_override) { create(:variant_override, hub: hub, variant: variant2) } + let(:variant3_override) { + create(:variant_override, hub: hub, variant: variant3, count_on_hand: 0) + } + + it "all items" do + allow(base_report).to receive(:child_variants) { + Spree::Variant.where(id: [variant, variant2, variant3]) + } + expect(report.table_rows.count).to eq 3 + end + + it "only available items" do + variant.on_hand = 0 + allow(base_report).to receive(:child_variants) { + Spree::Variant.where(id: [variant, variant2, variant3, + variant4]) + } + expect(report.table_rows.count).to eq 3 + end + + it "only available items considering overrides" do + create(:exchange, incoming: false, receiver_id: hub.id, + variants: [variant, variant2, variant3]) + # create the overrides + variant2_override + variant3_override + allow(base_report).to receive(:child_variants) { + Spree::Variant.where(id: [variant, variant2, variant3]) + } + allow(base_report).to receive(:params) { + { distributor_id: hub.id, report_subtype: 'lettuce_share' } + } + rows = report.table_rows + expect(rows.count).to eq 2 + expect(rows.map{ |row| + row[0] + } ).to include variant.product.name, variant2.product.name + end + end + end + end + end + end +end diff --git a/spec/lib/open_food_network/reports/line_items_spec.rb b/spec/lib/reports/line_items_spec.rb similarity index 93% rename from spec/lib/open_food_network/reports/line_items_spec.rb rename to spec/lib/reports/line_items_spec.rb index 34f8f359bf..523d2234bc 100644 --- a/spec/lib/open_food_network/reports/line_items_spec.rb +++ b/spec/lib/reports/line_items_spec.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true require 'spec_helper' -require 'open_food_network/reports/line_items' -describe OpenFoodNetwork::Reports::LineItems do +describe Reporting::LineItems do subject(:reports_line_items) { described_class.new(order_permissions, params) } # This object lets us add some test coverage despite the very deep coupling between the class diff --git a/spec/lib/reports/order_cycle_management_report_spec.rb b/spec/lib/reports/order_cycle_management_report_spec.rb new file mode 100644 index 0000000000..7cd8160ab0 --- /dev/null +++ b/spec/lib/reports/order_cycle_management_report_spec.rb @@ -0,0 +1,216 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module Reporting + module Reports + module OrderCycleManagement + describe OrderCycleManagementReport do + context "as a site admin" do + subject { OrderCycleManagementReport.new(user, params, true) } + let(:params) { {} } + + let(:user) do + user = create(:user) + user.spree_roles << Spree::Role.find_or_create_by!(name: "admin") + user + end + + describe "fetching orders" do + let(:customers_with_balance) { instance_double(CustomersWithBalance) } + + it 'calls the OutstandingBalance query object' do + outstanding_balance = instance_double(OutstandingBalance, query: Spree::Order.none) + expect(OutstandingBalance).to receive(:new).and_return(outstanding_balance) + + subject.orders + end + + it "fetches completed orders" do + o1 = create(:order) + o2 = create(:order, completed_at: 1.day.ago, state: 'complete') + expect(subject.orders).to eq([o2]) + end + + it 'fetches resumed orders' do + order = create(:order, state: 'resumed', completed_at: 1.day.ago) + expect(subject.orders).to eq([order]) + end + + it 'orders them by id' do + order1 = create(:order, completed_at: 1.day.ago, state: 'complete') + order2 = create(:order, completed_at: 2.days.ago, state: 'complete') + + expect(subject.orders.pluck(:id)).to eq([order2.id, order1.id]) + end + + it "does not show cancelled orders" do + o1 = create(:order, state: 'canceled', completed_at: 1.day.ago) + o2 = create(:order, state: 'complete', completed_at: 1.day.ago) + expect(subject.orders).to eq([o2]) + end + + context "default date range" do + it "fetches orders completed in the past month" do + o1 = create(:order, state: 'complete', completed_at: 1.month.ago - 1.day) + o2 = create(:order, state: 'complete', completed_at: 1.month.ago + 1.day) + expect(subject.orders).to eq([o2]) + end + end + end + end + + context "as an enterprise user" do + let!(:user) { create(:user) } + + subject { OrderCycleManagementReport.new user, {}, true } + + describe "fetching orders" do + let(:supplier) { create(:supplier_enterprise) } + let(:product) { create(:simple_product, supplier: supplier) } + let(:order) { create(:order, completed_at: 1.day.ago) } + + it "only shows orders managed by the current user" do + d1 = create(:distributor_enterprise) + d1.enterprise_roles.create!(user: user) + d2 = create(:distributor_enterprise) + d2.enterprise_roles.create!(user: create(:user)) + + o1 = create(:order, distributor: d1, state: 'complete', completed_at: 1.day.ago) + o2 = create(:order, distributor: d2, state: 'complete', completed_at: 1.day.ago) + + expect(subject).to receive(:filter).with([o1]).and_return([o1]) + expect(subject.orders).to eq([o1]) + end + + it "does not show orders through a hub that the current user does not manage" do + # Given a supplier enterprise with an order for one of its products + supplier.enterprise_roles.create!(user: user) + order.line_items << create(:line_item_with_shipment, product: product) + + # When I fetch orders, I should see no orders + expect(subject).to receive(:filter).with([]).and_return([]) + expect(subject.orders).to eq([]) + end + end + + describe "filtering orders" do + let!(:orders) { Spree::Order.where(nil) } + let!(:supplier) { create(:supplier_enterprise) } + + let!(:oc1) { create(:simple_order_cycle) } + let!(:pm1) { create(:payment_method, name: "PM1") } + let!(:sm1) { create(:shipping_method, name: "ship1") } + let!(:s1) { create(:shipment_with, :shipping_method, shipping_method: sm1) } + let!(:order1) { create(:order, shipments: [s1], order_cycle: oc1) } + let!(:payment1) { create(:payment, order: order1, payment_method: pm1) } + + it "returns all orders sans-params" do + expect(subject.filter(orders)).to eq(orders) + end + + it "filters to a specific order cycle" do + oc2 = create(:simple_order_cycle) + order2 = create(:order, order_cycle: oc2) + + allow(subject).to receive(:params).and_return(order_cycle_id: oc1.id) + expect(subject.filter(orders)).to eq([order1]) + end + + it "filters to a payment method" do + pm2 = create(:payment_method, name: "PM2") + pm3 = create(:payment_method, name: "PM3") + order2 = create(:order, payments: [create(:payment, payment_method: pm2)]) + order3 = create(:order, payments: [create(:payment, payment_method: pm3)]) + + allow(subject).to receive(:params).and_return(payment_method_in: [pm1.id, pm3.id] ) + expect(subject.filter(orders)).to match_array [order1, order3] + end + + it "filters to a shipping method" do + sm2 = create(:shipping_method, name: "ship2") + sm3 = create(:shipping_method, name: "ship3") + s2 = create(:shipment_with, :shipping_method, shipping_method: sm2) + s3 = create(:shipment_with, :shipping_method, shipping_method: sm3) + order2 = create(:order, shipments: [s2]) + order3 = create(:order, shipments: [s3]) + + allow(subject).to receive(:params).and_return(shipping_method_in: [sm1.id, sm3.id]) + expect(subject.filter(orders)).to match_array [order1, order3] + end + + it "should do all the filters at once" do + allow(subject).to receive(:params).and_return(order_cycle_id: oc1.id, + shipping_method_name: sm1.name, + payment_method_name: pm1.name) + expect(subject.filter(orders)).to eq([order1]) + end + end + + describe '#table_rows' do + subject { OrderCycleManagementReport.new(user, params, true) } + + let(:distributor) { create(:distributor_enterprise) } + before { distributor.enterprise_roles.create!(user: user) } + + context 'when the report type is payment_methods' do + let(:params) { { report_subtype: 'payment_methods' } } + + let!(:order) do + create( + :completed_order_with_totals, + distributor: distributor, + completed_at: 1.day.ago + ) + end + + it 'returns rows with payment information' do + expect(subject.table_rows).to eq([[ + order.billing_address.firstname, + order.billing_address.lastname, + order.distributor.name, + '', + order.email, + order.billing_address.phone, + order.shipment.shipping_method.name, + nil, + order.total, + -order.total + ]]) + end + end + + context 'when the report type is not payment_methods' do + let(:params) { {} } + let!(:order) do + create( + :completed_order_with_totals, + distributor: distributor, + completed_at: 1.day.ago + ) + end + + it 'returns rows with delivery information' do + expect(subject.table_rows).to eq([[ + order.ship_address.firstname, + order.ship_address.lastname, + order.distributor.name, + "", + "#{order.ship_address.address1} #{order.ship_address.address2} #{order.ship_address.city}", + order.ship_address.zipcode, + order.ship_address.phone, + order.shipment.shipping_method.name, + nil, + order.total, + -order.total, + false, + order.special_instructions + ]]) + end + end + end + end + end + end + end +end diff --git a/spec/lib/open_food_network/order_grouper_spec.rb b/spec/lib/reports/order_grouper_spec.rb similarity index 97% rename from spec/lib/open_food_network/order_grouper_spec.rb rename to spec/lib/reports/order_grouper_spec.rb index db6538efec..630b5de196 100644 --- a/spec/lib/open_food_network/order_grouper_spec.rb +++ b/spec/lib/reports/order_grouper_spec.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true require 'spec_helper' -require 'open_food_network/order_grouper' -module OpenFoodNetwork +module Reporting describe OrderGrouper do before(:each) do @items = [1, 2, 3, 4] @@ -84,7 +83,7 @@ module OpenFoodNetwork subject = OrderGrouper.new @rules, @columns grouped_tree = double(:grouped_tree) - expect(subject).to receive(:group_and_sort).with(@rule1, @rules[1..-1], + expect(subject).to receive(:group_and_sort).with(@rule1, @rules[1..], @items).and_return(grouped_tree) expect(subject.build_tree(@items, @rules)).to eq(grouped_tree) @@ -101,7 +100,7 @@ module OpenFoodNetwork expect(@items).to receive(:group_by).and_return(groups) sorted_groups = {} 1.upto(number_of_categories) { |i| - sorted_groups[i] = double(:group, name: "Group " + i.to_s ) + sorted_groups[i] = double(:group, name: "Group #{i}" ) } expect(groups).to receive(:sort_by).and_return(sorted_groups) group = { group1: 1, group2: 2, group3: 3 } diff --git a/spec/lib/reports/orders_and_distributors_report_spec.rb b/spec/lib/reports/orders_and_distributors_report_spec.rb new file mode 100644 index 0000000000..ca9a8eaaa6 --- /dev/null +++ b/spec/lib/reports/orders_and_distributors_report_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module Reporting + module Reports + module OrdersAndDistributors + describe OrdersAndDistributorsReport do + describe 'orders and distributors report' do + it 'should return a header row describing the report' do + subject = OrdersAndDistributorsReport.new nil + + expect(subject.table_headers).to eq( + [ + 'Order date', 'Order Id', + 'Customer Name', 'Customer Email', 'Customer Phone', 'Customer City', + 'SKU', 'Item name', 'Variant', 'Quantity', 'Max Quantity', 'Cost', 'Shipping Cost', + 'Payment Method', + 'Distributor', 'Distributor address', 'Distributor city', 'Distributor postcode', + 'Shipping Method', 'Shipping instructions' + ] + ) + end + + context 'with completed order' do + let(:bill_address) { create(:address) } + let(:distributor) { create(:distributor_enterprise) } + let(:product) { create(:product) } + let(:shipping_method) { create(:shipping_method) } + let(:shipping_instructions) { 'pick up on thursday please!' } + let(:order) { + create(:order, + state: 'complete', completed_at: Time.zone.now, + distributor: distributor, bill_address: bill_address, + special_instructions: shipping_instructions) + } + let(:payment_method) { create(:payment_method, distributors: [distributor]) } + let(:payment) { create(:payment, payment_method: payment_method, order: order) } + let(:line_item) { create(:line_item_with_shipment, product: product, order: order) } + + before do + order.select_shipping_method(shipping_method.id) + order.payments << payment + order.line_items << line_item + end + + it 'should denormalise order and distributor details for display as csv' do + subject = OrdersAndDistributorsReport.new create(:admin_user), {}, true + + table = subject.table_rows + + expect(table.size).to eq 1 + expect(table[0]).to eq([ + order.reload.completed_at.strftime("%F %T"), + order.id, + bill_address.full_name, + order.email, + bill_address.phone, + bill_address.city, + line_item.product.sku, + line_item.product.name, + line_item.options_text, + line_item.quantity, + line_item.max_quantity, + line_item.price * line_item.quantity, + line_item.distribution_fee, + payment_method.name, + distributor.name, + distributor.address.address1, + distributor.address.city, + distributor.address.zipcode, + shipping_method.name, + shipping_instructions + ]) + end + + it "prints one row per line item" do + create(:line_item_with_shipment, order: order) + + subject = OrdersAndDistributorsReport.new(create(:admin_user), {}, true) + + table = subject.table_rows + expect(table.size).to eq 2 + end + end + end + end + end + end +end diff --git a/spec/lib/reports/orders_and_fulfillment/customer_totals_report_spec.rb b/spec/lib/reports/orders_and_fulfillment/customer_totals_report_spec.rb new file mode 100644 index 0000000000..6c990d4bc0 --- /dev/null +++ b/spec/lib/reports/orders_and_fulfillment/customer_totals_report_spec.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Reporting + module Reports + module OrdersAndFulfillment + describe CustomerTotalsReport do + let!(:distributor) { create(:distributor_enterprise) } + let!(:customer) { create(:customer, enterprise: distributor) } + let(:current_user) { distributor.owner } + + let(:report) do + report_options = { report_subtype: described_class::REPORT_TYPE } + OrdersAndFulfillmentReport.new(current_user, report_options, true) + end + + let(:report_table) do + report.table_rows + end + + context "viewing the report" do + let!(:order) do + create(:completed_order_with_totals, line_items_count: 1, user: customer.user, + customer: customer, distributor: distributor) + end + + it "generates the report" do + expect(report_table.length).to eq(2) + end + + it "has a line item row" do + distributor_name_field = report_table.first[0] + expect(distributor_name_field).to eq distributor.name + + customer_name_field = report_table.first[1] + expect(customer_name_field).to eq order.bill_address.full_name + + total_field = report_table.last[5] + expect(total_field).to eq I18n.t("admin.reports.total") + end + + it 'includes the order number and date in item rows' do + order_number_and_date_fields = report_table.first[33..34] + expect(order_number_and_date_fields).to eq([ + order.number, + order.completed_at.strftime("%F %T"), + ]) + end + + it 'includes the order number and date in total rows' do + order_number_and_date_fields = report_table.last[33..34] + expect(order_number_and_date_fields).to eq([ + order.number, + order.completed_at.strftime("%F %T"), + ]) + end + end + + context "loading shipping methods" do + let!(:shipping_method1) { + create(:shipping_method, distributors: [distributor], name: "First") + } + let!(:shipping_method2) { + create(:shipping_method, distributors: [distributor], name: "Second") + } + let!(:shipping_method3) { + create(:shipping_method, distributors: [distributor], name: "Third") + } + let!(:order) do + create(:completed_order_with_totals, line_items_count: 1, user: customer.user, + customer: customer, distributor: distributor) + end + + before do + order.shipments.each(&:refresh_rates) + order.select_shipping_method(shipping_method2.id) + end + + it "displays the correct shipping_method" do + shipping_method_name_field = report_table.first[15] + expect(shipping_method_name_field).to eq shipping_method2.name + end + end + + context "displaying payment fees" do + context "with both failed and completed payments present" do + let!(:order) { + create(:order_ready_to_ship, user: customer.user, + customer: customer, distributor: distributor) + } + let(:completed_payment) { order.payments.completed.first } + let!(:failed_payment) { create(:payment, order: order, state: "failed") } + + before do + completed_payment.adjustment.update amount: 123.00 + failed_payment.adjustment.update amount: 456.00, eligible: false, state: "finalized" + end + + it "shows the correct payment fee amount for the order" do + payment_fee_field = report_table.last[12] + expect(payment_fee_field).to eq completed_payment.adjustment.amount + end + end + end + + context 'when a variant override applies' do + let!(:order) do + create(:completed_order_with_totals, line_items_count: 1, user: customer.user, + customer: customer, distributor: distributor) + end + let(:overidden_sku) { 'magical_sku' } + + before do + create( + :variant_override, + hub: distributor, + variant: order.line_items.first.variant, + sku: overidden_sku + ) + end + + it 'uses the sku from the variant override' do + sku_field = report_table.first[23] + expect(sku_field).to eq overidden_sku + end + end + end + end + end +end diff --git a/spec/lib/reports/orders_and_fulfillment/distributor_totals_by_supplier_report_spec.rb b/spec/lib/reports/orders_and_fulfillment/distributor_totals_by_supplier_report_spec.rb new file mode 100644 index 0000000000..665cce0c67 --- /dev/null +++ b/spec/lib/reports/orders_and_fulfillment/distributor_totals_by_supplier_report_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module Reporting + module Reports + module OrdersAndFulfillment + describe DistributorTotalsBySupplierReport do + let!(:distributor) { create(:distributor_enterprise) } + + let!(:order) do + create(:completed_order_with_totals, line_items_count: 1, distributor: distributor) + end + + let(:current_user) { distributor.owner } + + let(:report) do + report_options = { report_subtype: described_class::REPORT_TYPE } + OrdersAndFulfillmentReport.new(current_user, report_options, true) + end + + let(:report_table) do + report.table_rows + end + + it "generates the report" do + expect(report_table.length).to eq(2) + end + + it "has a variant row under the distributor" do + distributor_name_field = report_table.first[0] + expect(distributor_name_field).to eq distributor.name + + supplier = order.line_items.first.variant.product.supplier + supplier_name_field = report_table.first[1] + expect(supplier_name_field).to eq supplier.name + + total_field = report_table.last[1] + expect(total_field).to eq I18n.t("admin.reports.total") + end + end + end + end +end diff --git a/spec/lib/reports/orders_and_fulfillment/orders_and_fulfillment_report_spec.rb b/spec/lib/reports/orders_and_fulfillment/orders_and_fulfillment_report_spec.rb new file mode 100644 index 0000000000..7d20f85638 --- /dev/null +++ b/spec/lib/reports/orders_and_fulfillment/orders_and_fulfillment_report_spec.rb @@ -0,0 +1,284 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module Reporting + module Reports + module OrdersAndFulfillment + describe OrdersAndFulfillmentReport do + include AuthenticationHelper + + let(:distributor) { create(:distributor_enterprise) } + let(:order_cycle) { create(:simple_order_cycle) } + let(:address) { create(:address) } + let(:order) { + create( + :order, + completed_at: 1.day.ago, + order_cycle: order_cycle, + distributor: distributor, + bill_address: address + ) + } + let(:line_item) { build(:line_item_with_shipment) } + let(:user) { create(:user) } + let(:admin_user) { create(:admin_user) } + + describe "fetching orders" do + before { order.line_items << line_item } + + context "as a site admin" do + subject { described_class.new(admin_user, {}, true) } + + it "fetches completed orders" do + o2 = create(:order) + o2.line_items << build(:line_item) + expect(subject.table_items).to eq([line_item]) + end + + it "does not show cancelled orders" do + o2 = create(:order, state: "canceled", completed_at: 1.day.ago) + o2.line_items << build(:line_item_with_shipment) + expect(subject.table_items).to eq([line_item]) + end + end + + context "as a manager of a supplier" do + subject { described_class.new(user, {}, true) } + + let(:s1) { create(:supplier_enterprise) } + + before do + s1.enterprise_roles.create!(user: user) + end + + context "that has granted P-OC to the distributor" do + let(:o2) { + create( + :order, + distributor: distributor, + completed_at: 1.day.ago, + bill_address: create(:address), + ship_address: create(:address) + ) + } + let(:li2) { + build(:line_item_with_shipment, product: create(:simple_product, supplier: s1)) + } + + before do + o2.line_items << li2 + create( + :enterprise_relationship, + parent: s1, + child: distributor, + permissions_list: [:add_to_order_cycle] + ) + end + + it "shows line items supplied by my producers, with names hidden" do + expect(subject.table_items).to eq([li2]) + expect(subject.table_items.first.order.bill_address.firstname).to eq("HIDDEN") + end + + context "where the distributor allows suppliers to see customer names" do + before do + distributor.update_columns show_customer_names_to_suppliers: true + end + + it "shows line items supplied by my producers, with names shown" do + expect(subject.table_items).to eq([li2]) + expect(subject.table_items.first.order.bill_address.firstname). + to eq(order.bill_address.firstname) + end + end + end + + context "that has not granted P-OC to the distributor" do + let(:o2) { + create( + :order, + distributor: distributor, + completed_at: 1.day.ago, + bill_address: create(:address), + ship_address: create(:address) + ) + } + let(:li2) { + build(:line_item_with_shipment, product: create(:simple_product, supplier: s1)) + } + + before do + o2.line_items << li2 + end + + it "does not show line items supplied by my producers" do + expect(subject.table_items).to eq([]) + end + + context "where the distributor allows suppliers to see customer names" do + before do + distributor.show_customer_names_to_suppliers = true + end + + it "does not show line items supplied by my producers" do + expect(subject.table_items).to eq([]) + end + end + end + end + + context "as a manager of a distributor" do + subject { described_class.new(user, {}, true) } + + before do + distributor.enterprise_roles.create!(user: user) + end + + it "only shows line items distributed by enterprises managed by the current user" do + d2 = create(:distributor_enterprise) + d2.enterprise_roles.create!(user: create(:user)) + o2 = create(:order, distributor: d2, completed_at: 1.day.ago) + o2.line_items << build(:line_item_with_shipment) + expect(subject.table_items).to eq([line_item]) + end + + it "only shows the selected order cycle" do + oc2 = create(:simple_order_cycle) + o2 = create(:order, distributor: distributor, order_cycle: oc2) + o2.line_items << build(:line_item) + allow(subject).to receive(:params).and_return(order_cycle_id_in: order_cycle.id) + expect(subject.table_items).to eq([line_item]) + end + end + end + + describe "columns are aligned" do + it 'has aligned columsn' do + report_types = [ + "", + "order_cycle_supplier_totals", + "order_cycle_supplier_totals_by_distributor", + "order_cycle_distributor_totals_by_supplier", + "order_cycle_customer_totals" + ] + + report_types.each do |report_type| + report = described_class.new(admin_user, report_subtype: report_type) + expect(report.table_headers.size).to eq(report.columns.size) + end + end + end + + describe "order_cycle_customer_totals" do + let!(:product) { line_item.product } + let!(:fuji) do + create(:variant, product: product, display_name: "Fuji", sku: "FUJI", on_hand: 100) + end + let!(:gala) do + create(:variant, product: product, display_name: "Gala", sku: "GALA", on_hand: 100) + end + + let(:items) { + described_class.new(admin_user, { report_subtype: "order_cycle_customer_totals" }, true) + .table_rows + } + + before do + # Clear price so it will be computed based on quantity and variant price. + order.line_items << build(:line_item_with_shipment, variant: fuji, price: nil, + quantity: 1) + order.line_items << build(:line_item_with_shipment, variant: gala, price: nil, + quantity: 2) + end + + it "has a product row" do + product_name_field = items.first[5] + expect(product_name_field).to eq product.name + end + + it "has a summary row" do + product_name_field = items.last[5] + expect(product_name_field).to eq "TOTAL" + end + + # Expected Report for Scenario: + # + # Row 1: Armstrong Amari, Fuji Apple, price: 8 + # Row 2: SUMMARY + # Row 3: Bartoletti Brooklyn, Fuji Apple, price: 1 + 4 + # Row 4: Bartoletti Brooklyn, Gala Apple, price: 2 + # Row 5: SUMMARY + describe "grouping of line items" do + let!(:address) { create(:address, last_name: "Bartoletti", first_name: "Brooklyn") } + + let!(:second_address) { create(:address, last_name: "Armstrong", first_name: "Amari") } + let!(:second_order) do + create(:order, completed_at: 1.day.ago, order_cycle: order_cycle, distributor: distributor, + bill_address: second_address) + end + + before do + # Add a second line item for Fuji variant to the order, to test grouping in this edge case. + order.line_items << build(:line_item_with_shipment, variant: fuji, price: nil, + quantity: 4) + + second_order.line_items << build(:line_item_with_shipment, variant: fuji, price: nil, + quantity: 8) + end + + it "groups line items by variant and order" do + expect(items.length).to eq(5) + + # Row 1: Armstrong Amari, Fuji Apple, price: 8 + row_data = items[0] + expect(customer_name(row_data)).to eq(second_address.full_name) + expect(amount(row_data)).to eq(fuji.price * 8) + expect(variant_sku(row_data)).to eq(fuji.sku) + + # Row 2: SUMMARY + row_data = items[1] + expect(totals_row?(row_data)).to eq(true) + expect(customer_name(row_data)).to eq(second_address.full_name) + expect(amount(row_data)).to eq(fuji.price * 8) + + # Row 3: Bartoletti Brooklyn, Fuji Apple, price: 1 + 4 + row_data = items[2] + expect(customer_name(row_data)).to eq(address.full_name) + expect(amount(row_data)).to eq(fuji.price * 5) + expect(variant_sku(row_data)).to eq(fuji.sku) + + # Row 4: Bartoletti Brooklyn, Gala Apple, price: 2 + row_data = items[3] + expect(customer_name(row_data)).to eq(address.full_name) + expect(amount(row_data)).to eq(gala.price * 2) + expect(variant_sku(row_data)).to eq(gala.sku) + + # Row 5: SUMMARY + row_data = items[4] + expect(totals_row?(row_data)).to eq(true) + expect(customer_name(row_data)).to eq(address.full_name) + expect(amount(row_data)).to eq(fuji.price * 5 + gala.price * 2) + end + end + + def totals_row?(row_data) + row_data[5] == I18n.t("admin.reports.total") + end + + def customer_name(row_data) + row_data[1] + end + + def amount(row_data) + row_data[8] + end + + def variant_sku(row_data) + row_data[23] + end + end + end + end + end +end diff --git a/spec/lib/reports/orders_and_fulfillment/supplier_totals_by_distributor_report_spec.rb b/spec/lib/reports/orders_and_fulfillment/supplier_totals_by_distributor_report_spec.rb new file mode 100644 index 0000000000..26fd0b7d31 --- /dev/null +++ b/spec/lib/reports/orders_and_fulfillment/supplier_totals_by_distributor_report_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module Reporting + module Reports + module OrdersAndFulfillment + describe Reporting::Reports::OrdersAndFulfillment::SupplierTotalsByDistributorReport do + let!(:distributor) { create(:distributor_enterprise) } + + let!(:order) do + create(:completed_order_with_totals, line_items_count: 1, distributor: distributor) + end + + let(:current_user) { distributor.owner } + + let(:report) do + report_options = { report_subtype: described_class::REPORT_TYPE } + OrdersAndFulfillmentReport.new(current_user, report_options, true) + end + + let(:report_table) do + report.table_rows + end + + it "generates the report" do + expect(report_table.length).to eq(2) + end + + it "has a variant row under the distributor" do + supplier = order.line_items.first.variant.product.supplier + supplier_name_field = report_table.first[0] + expect(supplier_name_field).to eq supplier.name + + distributor_name_field = report_table.first[3] + expect(distributor_name_field).to eq distributor.name + + total_field = report_table.last[3] + expect(total_field).to eq I18n.t("admin.reports.total") + end + end + end + end +end diff --git a/spec/lib/reports/orders_and_fulfillment/supplier_totals_report_spec.rb b/spec/lib/reports/orders_and_fulfillment/supplier_totals_report_spec.rb new file mode 100644 index 0000000000..d21839bed7 --- /dev/null +++ b/spec/lib/reports/orders_and_fulfillment/supplier_totals_report_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module Reporting + module Reports + module OrdersAndFulfillment + describe SupplierTotalsReport do + let!(:distributor) { create(:distributor_enterprise) } + + let!(:order) do + create(:completed_order_with_totals, line_items_count: 1, distributor: distributor) + end + + let(:current_user) { distributor.owner } + + let(:report) do + report_options = { report_subtype: described_class::REPORT_TYPE } + OrdersAndFulfillmentReport.new(current_user, report_options, true) + end + + let(:report_table) do + report.table_rows + end + + it "generates the report" do + expect(report_table.length).to eq(1) + end + + it "has a variant row" do + supplier = order.line_items.first.variant.product.supplier + supplier_name_field = report_table.first[0] + expect(supplier_name_field).to eq supplier.name + end + end + end + end +end diff --git a/spec/lib/reports/products_and_inventory_default_report_spec.rb b/spec/lib/reports/products_and_inventory_default_report_spec.rb new file mode 100644 index 0000000000..32f42f0c8d --- /dev/null +++ b/spec/lib/reports/products_and_inventory_default_report_spec.rb @@ -0,0 +1,267 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module Reporting + module Reports + module ProductsAndInventory + describe ProductsAndInventoryDefaultReport do + context "As a site admin" do + let(:user) do + user = create(:user) + user.spree_roles << Spree::Role.find_or_create_by!(name: 'admin') + user + end + subject do + ProductsAndInventoryReport.new user, {}, true + end + + it "Should return headers" do + expect(subject.table_headers).to eq([ + "Supplier", + "Producer Suburb", + "Product", + "Product Properties", + "Taxons", + "Variant Value", + "Price", + "Group Buy Unit Quantity", + "Amount", + "SKU" + ]) + end + + it "should build a table from a list of variants" do + variant = double(:variant, sku: "sku", + full_name: "Variant Name", + count_on_hand: 10, + price: 100) + allow(variant).to receive_message_chain(:product, :supplier, + :name).and_return("Supplier") + allow(variant).to receive_message_chain(:product, :supplier, :address, + :city).and_return("A city") + allow(variant).to receive_message_chain(:product, :name).and_return("Product Name") + allow(variant).to receive_message_chain(:product, + :properties).and_return [double(name: "property1"), + double(name: "property2")] + allow(variant).to receive_message_chain(:product, + :taxons).and_return [double(name: "taxon1"), + double(name: "taxon2")] + allow(variant).to receive_message_chain(:product, :group_buy_unit_size).and_return(21) + allow(subject).to receive(:variants).and_return [variant] + + expect(subject.table_rows).to eq([[ + "Supplier", + "A city", + "Product Name", + "property1, property2", + "taxon1, taxon2", + "Variant Name", + 100, + 21, + "", + "sku" + ]]) + end + + it "fetches variants for some params" do + expect(subject).to receive(:child_variants).and_return ["children"] + expect(subject).to receive(:filter).with(['children']).and_return ["filter_children"] + expect(subject.variants).to eq(["filter_children"]) + end + end + + context "As an enterprise user" do + let(:supplier) { create(:supplier_enterprise) } + let(:enterprise_user) do + user = create(:user) + user.enterprise_roles.create(enterprise: supplier) + user.spree_roles = [] + user.save! + user + end + + subject do + ProductsAndInventoryReport.new enterprise_user, {}, true + end + + describe "fetching child variants" do + it "returns some variants" do + product1 = create(:simple_product, supplier: supplier) + variant1 = product1.variants.first + variant2 = create(:variant, product: product1) + + expect(subject.child_variants).to match_array [variant1, variant2] + end + + it "should only return variants managed by the user" do + product1 = create(:simple_product, supplier: create(:supplier_enterprise)) + product2 = create(:simple_product, supplier: supplier) + variant1 = product1.variants.first + variant2 = product2.variants.first + + expect(subject.child_variants).to eq([variant2]) + end + end + + describe "Filtering variants" do + let(:variants) { Spree::Variant.where(nil).joins(:product).where(is_master: false) } + + describe "based on report type" do + it "returns only variants on hand" do + product1 = create(:simple_product, supplier: supplier, on_hand: 99) + product2 = create(:simple_product, supplier: supplier, on_hand: 0) + + allow(subject).to receive(:params).and_return(report_subtype: 'inventory') + expect(subject.filter(variants)).to eq([product1.variants.first]) + end + end + it "filters to a specific supplier" do + supplier2 = create(:supplier_enterprise) + product1 = create(:simple_product, supplier: supplier) + product2 = create(:simple_product, supplier: supplier2) + + allow(subject).to receive(:params).and_return(supplier_id: supplier.id) + expect(subject.filter(variants)).to eq([product1.variants.first]) + end + it "filters to a specific distributor" do + distributor = create(:distributor_enterprise) + product1 = create(:simple_product, supplier: supplier) + product2 = create(:simple_product, supplier: supplier) + order_cycle = create(:simple_order_cycle, suppliers: [supplier], + distributors: [distributor], + variants: [product2.variants.first]) + + allow(subject).to receive(:params).and_return(distributor_id: distributor.id) + expect(subject.filter(variants)).to eq([product2.variants.first]) + end + + it "ignores variant overrides without filter" do + distributor = create(:distributor_enterprise) + product = create(:simple_product, supplier: supplier, price: 5) + variant = product.variants.first + order_cycle = create(:simple_order_cycle, suppliers: [supplier], + distributors: [distributor], + variants: [product.variants.first]) + create(:variant_override, hub: distributor, variant: variant, price: 2) + + result = subject.filter(variants) + + expect(result.first.price).to eq 5 + end + + it "considers variant overrides with distributor" do + distributor = create(:distributor_enterprise) + product = create(:simple_product, supplier: supplier, price: 5) + variant = product.variants.first + order_cycle = create(:simple_order_cycle, suppliers: [supplier], + distributors: [distributor], + variants: [product.variants.first]) + create(:variant_override, hub: distributor, variant: variant, price: 2) + + allow(subject).to receive(:params).and_return(distributor_id: distributor.id) + result = subject.filter(variants) + + expect(result.first.price).to eq 2 + end + + it "filters to a specific order cycle" do + distributor = create(:distributor_enterprise) + product1 = create(:simple_product, supplier: supplier) + product2 = create(:simple_product, supplier: supplier) + order_cycle = create(:simple_order_cycle, suppliers: [supplier], + distributors: [distributor], + variants: [product1.variants.first]) + + allow(subject).to receive(:params).and_return(order_cycle_id: order_cycle.id) + expect(subject.filter(variants)).to eq([product1.variants.first]) + end + + it "should do all the filters at once" do + # The following data ensures that this spec fails if any of the + # filters fail. It's testing the filters are not impacting each other. + distributor = create(:distributor_enterprise) + other_distributor = create(:distributor_enterprise) + other_supplier = create(:supplier_enterprise) + not_filtered_variant = create(:simple_product, supplier: supplier).variants.first + variant_filtered_by_order_cycle = create(:simple_product, + supplier: supplier).variants.first + variant_filtered_by_distributor = create(:simple_product, + supplier: supplier).variants.first + variant_filtered_by_supplier = create(:simple_product, + supplier: other_supplier).variants.first + variant_filtered_by_stock = create(:simple_product, supplier: supplier, + on_hand: 0).variants.first + + # This OC contains all products except the one that should be filtered + # by order cycle. We create a separate OC further down to proof that + # the product is passing all other filters. + order_cycle = create( + :simple_order_cycle, + suppliers: [supplier, other_supplier], + distributors: [distributor, other_distributor], + variants: [ + not_filtered_variant, + variant_filtered_by_distributor, + variant_filtered_by_supplier, + variant_filtered_by_stock + ] + ) + + # Remove the distribution of one product for one distributor but still + # sell it through the other distributor. + order_cycle.exchanges.outgoing.find_by(receiver_id: distributor.id). + exchange_variants. + find_by(variant_id: variant_filtered_by_distributor). + destroy + + # Make product available to be filtered later. See OC comment above. + create( + :simple_order_cycle, + suppliers: [supplier], + distributors: [distributor, other_distributor], + variants: [ + variant_filtered_by_order_cycle + ] + ) + + allow(subject).to receive(:params).and_return( + order_cycle_id: order_cycle.id, + supplier_id: supplier.id, + distributor_id: distributor.id, + report_subtype: 'inventory' + ) + + expect(subject.filter(variants)).to match_array [not_filtered_variant] + + # And it integrates with the ordering of the `variants` method. + expect(subject.variants).to match_array [not_filtered_variant] + end + end + + describe "fetching SKU for a variant" do + let(:variant) { create(:variant) } + let(:product) { variant.product } + + before { product.update_attribute(:sku, "Product SKU") } + + context "when the variant has an SKU set" do + before { variant.update_attribute(:sku, "Variant SKU") } + it "returns it" do + expect(subject.__send__(:sku_for, variant)).to eq "Variant SKU" + end + end + + context "when the variant has bo SKU set" do + before { variant.update_attribute(:sku, "") } + + it "returns the product's SKU" do + expect(subject.__send__(:sku_for, variant)).to eq "Product SKU" + end + end + end + end + end + end + end +end diff --git a/spec/lib/reports/sales_tax_report_spec.rb b/spec/lib/reports/sales_tax_report_spec.rb new file mode 100644 index 0000000000..f1695c2d4a --- /dev/null +++ b/spec/lib/reports/sales_tax_report_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module Reporting + module Reports + module SalesTax + describe SalesTaxReport do + let(:user) { create(:user) } + let(:report) { SalesTaxReport.new(user, {}, true) } + + describe "calculating totals for line items" do + let(:li1) { double(:line_item, quantity: 1, amount: 12) } + let(:li2) { double(:line_item, quantity: 2, amount: 24) } + let(:totals) { report.__send__(:totals_of, [li1, li2]) } + + before do + allow(report).to receive(:tax_included_in).and_return(2, 4) + end + + it "calculates total quantity" do + expect(totals[:items]).to eq(3) + end + + it "calculates total price" do + expect(totals[:items_total]).to eq(36) + end + + context "when floating point math would result in fractional cents" do + let(:li1) { double(:line_item, quantity: 1, amount: 0.11) } + let(:li2) { double(:line_item, quantity: 2, amount: 0.12) } + + it "rounds to the nearest cent" do + expect(totals[:items_total]).to eq(0.23) + end + end + + it "calculates the taxable total price" do + expect(totals[:taxable_total]).to eq(36) + end + + it "calculates sales tax" do + expect(totals[:sales_tax]).to eq(6) + end + + context "when there is no tax on a line item" do + before do + allow(report).to receive(:tax_included_in) { 0 } + end + + it "does not appear in taxable total" do + expect(totals[:taxable_total]).to eq(0) + end + + it "still appears on items total" do + expect(totals[:items_total]).to eq(36) + end + + it "does not register sales tax" do + expect(totals[:sales_tax]).to eq(0) + end + end + end + end + end + end +end diff --git a/spec/lib/reports/users_and_enterprises_report_spec.rb b/spec/lib/reports/users_and_enterprises_report_spec.rb new file mode 100644 index 0000000000..b967ed3d5c --- /dev/null +++ b/spec/lib/reports/users_and_enterprises_report_spec.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module Reporting + module Reports + module UsersAndEnterprises + describe UsersAndEnterprisesReport do + describe "users_and_enterprises" do + let!(:owners_and_enterprises) { double(:owners_and_enterprises) } + let!(:managers_and_enterprises) { double(:managers_and_enterprises) } + let!(:subject) { UsersAndEnterprisesReport.new(nil, {}, true) } + + before do + allow(subject).to receive(:owners_and_enterprises) { owners_and_enterprises } + allow(subject).to receive(:managers_and_enterprises) { managers_and_enterprises } + end + + it "should concatenate owner and manager queries" do + expect(subject).to receive(:owners_and_enterprises).once + expect(subject).to receive(:managers_and_enterprises).once + expect(owners_and_enterprises).to receive(:concat).with(managers_and_enterprises).and_return [] + expect(subject).to receive(:sort).with [] + subject.users_and_enterprises + end + end + + describe "sorting results" do + let!(:subject) { UsersAndEnterprisesReport.new(nil, {}, true) } + + it "sorts by creation date" do + uae_mock = [ + { "created_at" => "2015-01-01", "name" => "bbb" }, + { "created_at" => "2015-01-02", "name" => "aaa" } + ] + expect(subject.sort(uae_mock)).to eq [uae_mock[1], uae_mock[0]] + end + + it "then sorts by name" do + uae_mock = [ + { "name" => "aaa", "relationship_type" => "bbb", "user_email" => "bbb" }, + { "name" => "bbb", "relationship_type" => "aaa", "user_email" => "aaa" } + ] + expect(subject.sort(uae_mock)).to eq [uae_mock[0], uae_mock[1]] + end + + it "then sorts by relationship type (reveresed)" do + uae_mock = [ + { "name" => "aaa", "relationship_type" => "bbb", "user_email" => "bbb" }, + { "name" => "aaa", "relationship_type" => "aaa", "user_email" => "aaa" }, + { "name" => "aaa", "relationship_type" => "bbb", "user_email" => "aaa" } + ] + expect(subject.sort(uae_mock)).to eq [uae_mock[2], uae_mock[0], uae_mock[1]] + end + + it "then sorts by user_email" do + uae_mock = [ + { "name" => "aaa", "relationship_type" => "bbb", "user_email" => "aaa" }, + { "name" => "aaa", "relationship_type" => "aaa", "user_email" => "aaa" }, + { "name" => "aaa", "relationship_type" => "aaa", "user_email" => "bbb" } + ] + expect(subject.sort(uae_mock)).to eq [uae_mock[0], uae_mock[1], uae_mock[2]] + end + end + + describe "filtering results" do + let!(:enterprise1) { create(:enterprise, owner: create(:user) ) } + let!(:enterprise2) { create(:enterprise, owner: create(:user) ) } + + describe "for owners and enterprises" do + describe "by enterprise id" do + let!(:params) { { enterprise_id_in: [enterprise1.id.to_s] } } + let!(:subject) { UsersAndEnterprisesReport.new nil, params, true } + + it "excludes enterprises that are not explicitly requested" do + results = subject.owners_and_enterprises.to_a.map{ |oae| oae["name"] } + expect(results).to include enterprise1.name + expect(results).to_not include enterprise2.name + end + end + + describe "by user id" do + let!(:params) { { user_id_in: [enterprise1.owner.id.to_s] } } + let!(:subject) { UsersAndEnterprisesReport.new nil, params, true } + + it "excludes enterprises that are not explicitly requested" do + results = subject.owners_and_enterprises.to_a.map{ |oae| oae["name"] } + expect(results).to include enterprise1.name + expect(results).to_not include enterprise2.name + end + end + end + + describe "for managers and enterprises" do + describe "by enterprise id" do + let!(:params) { { enterprise_id_in: [enterprise1.id.to_s] } } + let!(:subject) { UsersAndEnterprisesReport.new nil, params, true } + + it "excludes enterprises that are not explicitly requested" do + results = subject.managers_and_enterprises.to_a.map{ |mae| mae["name"] } + expect(results).to include enterprise1.name + expect(results).to_not include enterprise2.name + end + end + + describe "by user id" do + let!(:manager1) { create(:user) } + let!(:manager2) { create(:user) } + let!(:params) { { user_id_in: [manager1.id.to_s] } } + let!(:subject) { UsersAndEnterprisesReport.new nil, params, true } + + before do + enterprise1.enterprise_roles.build(user: manager1).save + enterprise2.enterprise_roles.build(user: manager2).save + end + + it "excludes enterprises whose managers are not explicitly requested" do + results = subject.managers_and_enterprises.to_a.map{ |mae| mae["name"] } + expect(results).to include enterprise1.name + expect(results).to_not include enterprise2.name + end + end + end + end + end + end + end +end diff --git a/spec/lib/reports/xero_invoices_report_spec.rb b/spec/lib/reports/xero_invoices_report_spec.rb new file mode 100644 index 0000000000..a5ad21e0cc --- /dev/null +++ b/spec/lib/reports/xero_invoices_report_spec.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module Reporting + module Reports + module XeroInvoices + describe XeroInvoicesReport do + subject { XeroInvoicesReport.new user, {}, true } + + let(:user) { create(:user) } + + describe "option defaults" do + let(:report) { + XeroInvoicesReport.new user, initial_invoice_number: '', invoice_date: '', due_date: '', + account_code: '' + } + + around { |example| Timecop.travel(Time.zone.local(2015, 5, 5, 14, 0, 0)) { example.run } } + + it "uses defaults when blank params are passed" do + expect(report.instance_variable_get(:@opts)).to eq( invoice_date: Date.civil(2015, 5, 5), + due_date: Date.civil(2015, 6, 5), + account_code: 'food sales', + report_subtype: 'summary' ) + end + end + + describe "summary rows" do + let(:report) { + XeroInvoicesReport.new user, initial_invoice_number: '', invoice_date: '', due_date: '', + account_code: '' + } + let(:order) { double(:order) } + let(:summary_rows) { report.__send__(:summary_rows_for_order, order, 1, {}) } + + before do + allow(report).to receive(:produce_summary_rows) { ['produce'] } + allow(report).to receive(:fee_summary_rows) { ['fee'] } + allow(report).to receive(:shipping_summary_rows) { ['shipping'] } + allow(report).to receive(:payment_summary_rows) { ['payment'] } + allow(report).to receive(:admin_adjustment_summary_rows) { ['admin'] } + end + + it "displays produce summary rows when summary report" do + allow(report).to receive(:detail?) { false } + expect(summary_rows).to include 'produce' + end + + it "does not display produce summary rows when detail report" do + allow(report).to receive(:detail?) { true } + expect(summary_rows).not_to include 'produce' + end + + it "displays fee summary rows when summary report" do + allow(report).to receive(:detail?) { false } + expect(summary_rows).to include 'fee' + end + + it "displays fee summary rows when detail report" do + allow(report).to receive(:detail?) { true } + expect(summary_rows).to include 'fee' + end + + it "always displays shipping summary rows" do + expect(summary_rows).to include 'shipping' + end + + it "displays admin adjustment summary rows when summary report" do + expect(summary_rows).to include 'admin' + end + + it "does not display admin adjustment summary rows when detail report" do + allow(report).to receive(:detail?) { true } + expect(summary_rows).not_to include 'admin' + end + end + + describe "generating invoice numbers" do + let(:order) { double(:order, number: 'R731032860') } + + describe "when no initial invoice number is given" do + it "returns the order number" do + expect(subject.send(:invoice_number_for, order, 123)).to eq('R731032860') + end + end + + describe "when an initial invoice number is given" do + subject { XeroInvoicesReport.new user, initial_invoice_number: '123' } + + it "increments the number by the index" do + expect(subject.send(:invoice_number_for, order, 456)).to eq(579) + end + end + end + end + end + end +end diff --git a/spec/validators/date_time_string_validator_spec.rb b/spec/validators/date_time_string_validator_spec.rb index 3ee3143eb8..5476b0f6a0 100644 --- a/spec/validators/date_time_string_validator_spec.rb +++ b/spec/validators/date_time_string_validator_spec.rb @@ -13,11 +13,11 @@ describe DateTimeStringValidator do describe "internationalization" do it "has translation for NOT_STRING_ERROR" do - expect(described_class::NOT_STRING_ERROR).not_to be_blank + expect(described_class.not_string_error).not_to be_blank end it "has translation for INVALID_FORMAT_ERROR" do - expect(described_class::INVALID_FORMAT_ERROR).not_to be_blank + expect(described_class.invalid_format_error).not_to be_blank end end @@ -37,13 +37,13 @@ describe DateTimeStringValidator do it "adds error NOT_STRING_ERROR when blank but neither nil nor a string" do instance.timestamp = [] expect(instance).not_to be_valid - expect(instance.errors[:timestamp]).to eq([described_class::NOT_STRING_ERROR]) + expect(instance.errors[:timestamp]).to eq([described_class.not_string_error]) end it "adds error NOT_STRING_ERROR when not a string" do instance.timestamp = 1 expect(instance).not_to be_valid - expect(instance.errors[:timestamp]).to eq([described_class::NOT_STRING_ERROR]) + expect(instance.errors[:timestamp]).to eq([described_class.not_string_error]) end it "does not add error when value can be parsed" do @@ -54,7 +54,7 @@ describe DateTimeStringValidator do it "adds error INVALID_FORMAT_ERROR when value cannot be parsed" do instance.timestamp = "Not Valid" expect(instance).not_to be_valid - expect(instance.errors[:timestamp]).to eq([described_class::INVALID_FORMAT_ERROR]) + expect(instance.errors[:timestamp]).to eq([described_class.invalid_format_error]) end end end diff --git a/spec/validators/integer_array_validator_spec.rb b/spec/validators/integer_array_validator_spec.rb index 57fb3d6b3b..0fbb08fd20 100644 --- a/spec/validators/integer_array_validator_spec.rb +++ b/spec/validators/integer_array_validator_spec.rb @@ -13,11 +13,11 @@ describe IntegerArrayValidator do describe "internationalization" do it "has translation for NOT_ARRAY_ERROR" do - expect(described_class::NOT_ARRAY_ERROR).not_to be_blank + expect(described_class.not_array_error).not_to be_blank end it "has translation for INVALID_ELEMENT_ERROR" do - expect(described_class::INVALID_ELEMENT_ERROR).not_to be_blank + expect(described_class.invalid_element_error).not_to be_blank end end @@ -37,7 +37,7 @@ describe IntegerArrayValidator do it "adds error NOT_ARRAY_ERROR when neither nil nor an array" do instance.ids = 1 expect(instance).not_to be_valid - expect(instance.errors[:ids]).to include(described_class::NOT_ARRAY_ERROR) + expect(instance.errors[:ids]).to include(described_class.not_array_error) end it "does not add error when array of integers" do @@ -53,7 +53,7 @@ describe IntegerArrayValidator do it "adds error INVALID_ELEMENT_ERROR when an element cannot be parsed as Integer" do instance.ids = [1, "2", "Not Integer", 3] expect(instance).not_to be_valid - expect(instance.errors[:ids]).to include(described_class::INVALID_ELEMENT_ERROR) + expect(instance.errors[:ids]).to include(described_class.invalid_element_error) end end end From 288a35f06224363ee73a76af3906aeddaac6e225 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 6 Apr 2022 10:40:06 +0200 Subject: [PATCH 20/54] Reports Refactor 2: New templates abstract classes --- .rubocop_todo.yml | 10 --- app/controllers/admin/reports_controller.rb | 2 +- .../spree/admin/reports_controller.rb | 14 +--- .../spree/admin/reports/_table.html.haml | 8 +-- app/views/spree/admin/reports/show.html.haml | 5 +- lib/reporting/report_object_template.rb | 34 ++++++++++ lib/reporting/report_query_template.rb | 68 +++++++++++++++++++ lib/reporting/report_renderer.rb | 22 +++--- lib/reporting/report_template.rb | 52 ++------------ .../reports/bulk_coop/bulk_coop_report.rb | 11 +-- .../reports/customers/customers_report.rb | 12 +--- .../enterprise_fee_summary_report.rb | 18 ++--- lib/reporting/reports/list.rb | 11 +++ .../order_cycle_management_report.rb | 22 ++---- .../orders_and_distributors_report.rb | 11 +-- .../orders_and_fulfillment_report.rb | 23 ++++--- lib/reporting/reports/packing/base.rb | 2 +- .../reports/payments/payments_report.rb | 12 +--- .../lettuce_share_report.rb | 2 - .../products_and_inventory_default_report.rb | 2 - .../products_and_inventory_report.rb | 10 +-- .../reports/sales_tax/sales_tax_report.rb | 11 +-- .../users_and_enterprises_report.rb | 11 +-- .../xero_invoices/xero_invoices_report.rb | 23 +++---- spec/lib/reports/bulk_coop_report_spec.rb | 16 ++--- spec/lib/reports/customers_report_spec.rb | 4 +- spec/lib/reports/lettuce_share_report_spec.rb | 2 +- .../order_cycle_management_report_spec.rb | 6 +- .../orders_and_distributors_report_spec.rb | 4 +- .../customer_totals_report_spec.rb | 2 +- ...tributor_totals_by_supplier_report_spec.rb | 2 +- .../orders_and_fulfillment_report_spec.rb | 8 +-- ...plier_totals_by_distributor_report_spec.rb | 2 +- .../supplier_totals_report_spec.rb | 2 +- ...ducts_and_inventory_default_report_spec.rb | 4 +- spec/lib/reports/report_renderer_spec.rb | 4 +- spec/lib/reports/sales_tax_report_spec.rb | 2 +- .../users_and_enterprises_report_spec.rb | 12 ++-- spec/lib/reports/xero_invoices_report_spec.rb | 4 +- 39 files changed, 224 insertions(+), 246 deletions(-) create mode 100644 lib/reporting/report_object_template.rb create mode 100644 lib/reporting/report_query_template.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ae5ba7ddd7..65b2fa5266 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1145,16 +1145,6 @@ Style/OptionalBooleanParameter: - 'app/models/spree/preferences/file_configuration.rb' - 'app/models/spree/shipment.rb' - 'engines/order_management/app/services/order_management/stock/estimator.rb' - - 'lib/reporting/reports/bulk_coop/bulk_coop_report.rb' - - 'lib/reporting/reports/customers/customers_report.rb' - - 'lib/reporting/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb' - - 'lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb' - - 'lib/reporting/reports/orders_and_distributors/orders_and_distributors_report.rb' - - 'lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb' - - 'lib/reporting/reports/payments/payments_report.rb' - - 'lib/reporting/reports/products_and_inventory/products_and_inventory_report.rb' - - 'lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb' - - 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb' - 'lib/spree/core/controller_helpers/order.rb' - 'lib/spree/core/delegate_belongs_to.rb' - 'spec/support/request/web_helper.rb' diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index 31393a79a6..d097f6527b 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -22,7 +22,7 @@ module Admin private def export_report - send_data @report.public_send("to_#{report_format}"), filename: report_filename + send_data @report.public_send("to_#{report_format}", self), filename: report_filename end def render_report diff --git a/app/controllers/spree/admin/reports_controller.rb b/app/controllers/spree/admin/reports_controller.rb index 253ebbf45a..3f1be8c33f 100644 --- a/app/controllers/spree/admin/reports_controller.rb +++ b/app/controllers/spree/admin/reports_controller.rb @@ -59,12 +59,6 @@ module Spree end def orders_and_fulfillment - now = Time.zone.now - raw_params[:q] ||= { - completed_at_gt: (now - 1.month).beginning_of_day, - completed_at_lt: (now + 1.day).beginning_of_day - } - form_options = Reporting::FrontendData.new(spree_current_user) @distributors = form_options.distributors @@ -118,14 +112,10 @@ module Spree @report_subtypes = report_types[action_name.to_sym] @report_subtype = params[:report_subtype] klass = "Reporting::Reports::#{action_name.camelize}::#{action_name.camelize}Report".constantize - @report = klass.new spree_current_user, raw_params, render_content? + @report = klass.new spree_current_user, raw_params if report_format.present? - data = Reporting::ReportRenderer.new(@report).public_send("to_#{report_format}") - send_data data, filename: report_filename + send_data @report.public_send("to_#{report_format}"), filename: report_filename else - @header = @report.table_headers - @table = @report.table_rows - render "show" end end diff --git a/app/views/spree/admin/reports/_table.html.haml b/app/views/spree/admin/reports/_table.html.haml index 81d7dd524f..b6b932b4e7 100644 --- a/app/views/spree/admin/reports/_table.html.haml +++ b/app/views/spree/admin/reports/_table.html.haml @@ -2,10 +2,10 @@ %table.report__table %thead %tr - - @header.each do |heading| + - @report.table_headers.each do |heading| %th= heading %tbody - - @table.each do |row| + - @report.table_rows.each do |row| %tr - row.each_with_index do |cell_value, column_index| %td @@ -14,6 +14,6 @@ = render partial, value: cell_value - else = cell_value - - if @table.empty? + - if @report.table_rows.empty? %tr - %td{colspan: @header.count}= t(:none) + %td{colspan: @report.table_headers.count}= t(:none) diff --git a/app/views/spree/admin/reports/show.html.haml b/app/views/spree/admin/reports/show.html.haml index 70e0424593..5ea2ed207e 100644 --- a/app/views/spree/admin/reports/show.html.haml +++ b/app/views/spree/admin/reports/show.html.haml @@ -1,6 +1,5 @@ -/ If the report object do not use ransack search, create a fake one just for the form_for -- ransack_search = @report.respond_to?(:search) ? @report.search : Ransack::Search.new(Spree::Order) -= form_for ransack_search, :url => url_for(only_path: false) do |f| + += form_for @report.search, :url => url_for(only_path: false) do |f| %fieldset.no-border-bottom.print-hidden %legend{ align: 'center'}= t(:report_filters) diff --git a/lib/reporting/report_object_template.rb b/lib/reporting/report_object_template.rb new file mode 100644 index 0000000000..1eba55a0f9 --- /dev/null +++ b/lib/reporting/report_object_template.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# This is the old way of managing report, by loading Models from the DB and building +# The result from those models +module Reporting + class ReportObjectTemplate < ReportTemplate + + attr_accessor :user, :params + + def initialize(user, params = {}) + @user = user + @params = params + end + + def table_headers + raise NotImplementedError + end + + def table_rows + raise NotImplementedError + end + + # If the report object do not use ransack search, create a fake one just for the form_for + # in reports/show.haml + def search + Ransack::Search.new(Spree::Order) + end + + # Rules for grouping, ordering, and summary rows + def rules + [] + end + end +end diff --git a/lib/reporting/report_query_template.rb b/lib/reporting/report_query_template.rb new file mode 100644 index 0000000000..f4b79d22aa --- /dev/null +++ b/lib/reporting/report_query_template.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +# This is the new report template that use QueryBuilder to directly get the data from the DB +module Reporting + class ReportQueryTemplate < ReportTemplate + attr_reader :options + + SUBTYPES = [].freeze + + 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 + + def report_query + raise NotImplementedError + end + + def table_headers + report_data.columns + end + + def table_rows + report_data.rows + end + + private + + attr_reader :current_user, :ransack_params + + def ransacked_orders_relation + visible_orders_relation.ransack(ransack_params).result + end + + def ransacked_line_items_relation + visible_line_items_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 visible_line_items_relation + ::Permissions::Order.new(current_user). + visible_line_items. + 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 diff --git a/lib/reporting/report_renderer.rb b/lib/reporting/report_renderer.rb index a4cfcc1d6d..998b5529d1 100644 --- a/lib/reporting/report_renderer.rb +++ b/lib/reporting/report_renderer.rb @@ -9,39 +9,43 @@ module Reporting end def table_headers - @report.respond_to?(:report_data) ? @report.report_data.columns : @report.table_headers + @report.table_headers || [] end def table_rows - @report.respond_to?(:report_data) ? @report.report_data.rows : @report.table_rows + @report.table_rows || [] end def as_json - @report.report_data.as_json + table_rows.map do |row| + result = {} + table_headers.zip(row) { |a,b| result[a.to_sym] = b } + result + end.as_json end def as_arrays @as_arrays ||= [table_headers] + table_rows end - def to_csv + def to_csv(_context_controller = nil) SpreadsheetArchitect.to_csv(headers: table_headers, data: table_rows) end - def to_ods + def to_ods(_context_controller = nil) SpreadsheetArchitect.to_ods(headers: table_headers, data: table_rows) end - def to_xlsx + def to_xlsx(_context_controller = nil) SpreadsheetArchitect.to_xlsx(headers: table_headers, data: table_rows) end - def to_pdf + def to_pdf(context_controller) WickedPdf.new.pdf_from_string( - ActionController::Base.new.render_to_string( + context_controller.render_to_string( template: 'admin/reports/_table', layout: 'pdf', - locals: { report: self } + locals: { report: @report } ) ) end diff --git a/lib/reporting/report_template.rb b/lib/reporting/report_template.rb index 3192a56ae4..91c3fb73b2 100644 --- a/lib/reporting/report_template.rb +++ b/lib/reporting/report_template.rb @@ -2,61 +2,23 @@ module Reporting class ReportTemplate - delegate :as_json, :as_arrays, :table_headers, :table_rows, - :to_csv, :to_xlsx, :to_ods, :to_pdf, :to_json, to: :renderer + include ReportsHelper + attr_accessor :user, :params, :ransack_params - attr_reader :options + delegate :as_json, :as_arrays, :to_csv, :to_xlsx, :to_ods, :to_pdf, :to_json, to: :renderer - SUBTYPES = [] - - def self.report_subtypes - self::SUBTYPES + def table_headers + raise NotImplementedError 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 + def table_rows + raise NotImplementedError end private - attr_reader :current_user, :ransack_params - def renderer @renderer ||= ReportRenderer.new(self) end - - def ransacked_orders_relation - visible_orders_relation.ransack(ransack_params).result - end - - def ransacked_line_items_relation - visible_line_items_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 visible_line_items_relation - ::Permissions::Order.new(current_user). - visible_line_items. - 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 diff --git a/lib/reporting/reports/bulk_coop/bulk_coop_report.rb b/lib/reporting/reports/bulk_coop/bulk_coop_report.rb index d30641f5df..be15a9bdd4 100644 --- a/lib/reporting/reports/bulk_coop/bulk_coop_report.rb +++ b/lib/reporting/reports/bulk_coop/bulk_coop_report.rb @@ -3,13 +3,10 @@ module Reporting module Reports module BulkCoop - class BulkCoopReport - attr_reader :params + class BulkCoopReport < ReportObjectTemplate - def initialize(user, params = {}, render_table = false) - @params = params - @user = user - @render_table = render_table + def initialize(user, params = {}) + super(user, params) @supplier_report = BulkCoopSupplierReport.new @allocation_report = BulkCoopAllocationReport.new @@ -52,8 +49,6 @@ module Reporting end def table_items - return [] unless @render_table - report_line_items.list(line_item_includes) end diff --git a/lib/reporting/reports/customers/customers_report.rb b/lib/reporting/reports/customers/customers_report.rb index 0fa040646b..323bf97551 100644 --- a/lib/reporting/reports/customers/customers_report.rb +++ b/lib/reporting/reports/customers/customers_report.rb @@ -3,15 +3,7 @@ module Reporting module Reports module Customers - class CustomersReport - attr_reader :params - - def initialize(user, params = {}, compile_table = false) - @params = params - @user = user - @compile_table = compile_table - end - + class CustomersReport < ReportObjectTemplate def table_headers if is_mailing_list? [I18n.t(:report_header_email), @@ -31,8 +23,6 @@ module Reporting end def table_rows - return [] unless @compile_table - orders.map do |order| if is_mailing_list? [order.email, diff --git a/lib/reporting/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb b/lib/reporting/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb index c8bdd7895a..4298533d8e 100644 --- a/lib/reporting/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb +++ b/lib/reporting/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb @@ -3,10 +3,11 @@ module Reporting module Reports module EnterpriseFeeSummary - class EnterpriseFeeSummaryReport - attr_accessor :permissions, :parameters, :user + class EnterpriseFeeSummaryReport < ReportObjectTemplate + attr_accessor :permissions, :parameters - def initialize(user, params = {}, render_table = false) + def initialize(user, params = {}) + super(user, params) p = params[:q] if p.present? p['start_at'] = p.delete('completed_at_gt') @@ -14,8 +15,6 @@ module Reporting end @parameters = Reporting::Reports::EnterpriseFeeSummary::Parameters.new(p || {}) @parameters.validate! - @user = user - @render_table = render_table @permissions = Permissions.new(user) @parameters.authorize!(@permissions) end @@ -27,8 +26,6 @@ module Reporting end def table_rows - return [] unless @render_table - enterprise_fee_type_total_list.sort.map do |data| data_row_attributes.map do |attribute| data.public_send(attribute) @@ -36,13 +33,6 @@ module Reporting end end - # This report does not use ransack search, but all other are, so creating a fake - # Ransack search at least for the view to display correctly the selected - # ransack params like completed_at_gt and completed_at_lt - def search - Spree::Order.where('1=2').ransack(parameters) - end - private def data_row_attributes diff --git a/lib/reporting/reports/list.rb b/lib/reporting/reports/list.rb index d9ab57b945..917282b7db 100644 --- a/lib/reporting/reports/list.rb +++ b/lib/reporting/reports/list.rb @@ -9,6 +9,9 @@ module Reporting def all { + orders_and_distributors: [], + bulk_coop: bulk_coop_report_types, + payments: payments_report_types, orders_and_fulfillment: orders_and_fulfillment_report_types, products_and_inventory: products_and_inventory_report_types, customers: customers_report_types, @@ -41,6 +44,14 @@ module Reporting ] end + def payments_report_types + [ + [I18n.t(:report_payment_by), :payments_by_payment_type], + [I18n.t(:report_itemised_payment), :itemised_payment_totals], + [I18n.t(:report_payment_totals), :payment_totals] + ] + end + def customers_report_types [ [i18n_translate("mailing_list"), :mailing_list], diff --git a/lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb b/lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb index 1f095f4982..9236725549 100644 --- a/lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb +++ b/lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb @@ -3,15 +3,14 @@ module Reporting module Reports module OrderCycleManagement - class OrderCycleManagementReport + class OrderCycleManagementReport < ReportObjectTemplate DEFAULT_DATE_INTERVAL = { from: -1.month, to: 1.day }.freeze - attr_reader :params - - def initialize(user, params = {}, render_table = false) - @params = sanitize_params(params) - @user = user - @render_table = render_table + def initialize(user, params = {}) + super(user, params) + params[:q] ||= {} + params[:q][:completed_at_gt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:from] + params[:q][:completed_at_lt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:to] end def table_headers @@ -66,8 +65,6 @@ module Reporting end def table_rows - return [] unless @render_table - if is_payment_methods? orders.map { |o| payment_method_row o } else @@ -157,13 +154,6 @@ module Reporting customer = Customer.where(email: email).first customer.nil? ? "" : customer.code end - - def sanitize_params(params) - params[:q] ||= {} - params[:q][:completed_at_gt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:from] - params[:q][:completed_at_lt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:to] - params - end end end end diff --git a/lib/reporting/reports/orders_and_distributors/orders_and_distributors_report.rb b/lib/reporting/reports/orders_and_distributors/orders_and_distributors_report.rb index 5f5ef599c1..afd1120412 100644 --- a/lib/reporting/reports/orders_and_distributors/orders_and_distributors_report.rb +++ b/lib/reporting/reports/orders_and_distributors/orders_and_distributors_report.rb @@ -3,12 +3,9 @@ module Reporting module Reports module OrdersAndDistributors - class OrdersAndDistributorsReport - def initialize(user, params = {}, render_table = false) - @params = params - @user = user - @render_table = render_table - + class OrdersAndDistributorsReport < ReportObjectTemplate + def initialize(user, params = {}) + super(user, params) @permissions = ::Permissions::Order.new(user, @params[:q]) end @@ -44,8 +41,6 @@ module Reporting end def table_rows - return [] unless @render_table - orders = search.result orders.select{ |order| orders_with_hidden_details(orders).include? order }.each do |order| diff --git a/lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb b/lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb index 27a5bfa1e5..d99767f474 100644 --- a/lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb +++ b/lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb @@ -5,17 +5,20 @@ include Spree::ReportsHelper module Reporting module Reports module OrdersAndFulfillment - class OrdersAndFulfillmentReport - attr_reader :options, :report_type + class OrdersAndFulfillmentReport < ReportObjectTemplate + attr_reader :report_type delegate :table_headers, :rules, :columns, to: :report - def initialize(user, options = {}, render_table = false) - @user = user - @options = options - @report_type = options[:report_subtype] - @render_table = render_table + def initialize(user, params = {}) + super(user, params) + @report_type = params[:report_subtype] @variant_scopers_by_distributor_id = {} + now = Time.zone.now + params[:q] ||= { + completed_at_gt: (now - 1.month).beginning_of_day, + completed_at_lt: (now + 1.day).beginning_of_day + } end def search @@ -23,8 +26,6 @@ module Reporting end def table_items - return [] unless @render_table - report_line_items.list(report.line_item_includes) end @@ -96,11 +97,11 @@ module Reporting def order_permissions return @order_permissions unless @order_permissions.nil? - @order_permissions = ::Permissions::Order.new(@user, options[:q]) + @order_permissions = ::Permissions::Order.new(@user, params[:q]) end def report_line_items - @report_line_items ||= Reporting::LineItems.new(order_permissions, options) + @report_line_items ||= Reporting::LineItems.new(order_permissions, params) end def report_variant_overrides diff --git a/lib/reporting/reports/packing/base.rb b/lib/reporting/reports/packing/base.rb index 7102d9d553..5518906120 100644 --- a/lib/reporting/reports/packing/base.rb +++ b/lib/reporting/reports/packing/base.rb @@ -3,7 +3,7 @@ module Reporting module Reports module Packing - class Base < ReportTemplate + class Base < ReportQueryTemplate SUBTYPES = ["customer", "supplier"] def primary_model diff --git a/lib/reporting/reports/payments/payments_report.rb b/lib/reporting/reports/payments/payments_report.rb index 3d2a21a6b3..c7f02b6b12 100644 --- a/lib/reporting/reports/payments/payments_report.rb +++ b/lib/reporting/reports/payments/payments_report.rb @@ -3,15 +3,7 @@ module Reporting module Reports module Payments - class PaymentsReport - attr_reader :params - - def initialize(user, params = {}, render_table = false) - @params = params - @user = user - @render_table = render_table - end - + class PaymentsReport < ReportObjectTemplate def table_headers case params[:report_subtype] when "payments_by_payment_type" @@ -43,8 +35,6 @@ module Reporting end def table_items - return [] unless @render_table - orders = search.result payments = orders.includes(:payments).map do |order| order.payments.select(&:completed?) diff --git a/lib/reporting/reports/products_and_inventory/lettuce_share_report.rb b/lib/reporting/reports/products_and_inventory/lettuce_share_report.rb index 2c63e04a8c..8dd9ba1797 100644 --- a/lib/reporting/reports/products_and_inventory/lettuce_share_report.rb +++ b/lib/reporting/reports/products_and_inventory/lettuce_share_report.rb @@ -31,8 +31,6 @@ module Reporting end def table_rows - return [] unless render_table - variants.select(&:in_stock?) .map do |variant| [ diff --git a/lib/reporting/reports/products_and_inventory/products_and_inventory_default_report.rb b/lib/reporting/reports/products_and_inventory/products_and_inventory_default_report.rb index a733fe24ea..be54824b9c 100644 --- a/lib/reporting/reports/products_and_inventory/products_and_inventory_default_report.rb +++ b/lib/reporting/reports/products_and_inventory/products_and_inventory_default_report.rb @@ -28,8 +28,6 @@ module Reporting end def table_rows - return [] unless render_table - variants.map do |variant| [ variant.product.supplier.name, diff --git a/lib/reporting/reports/products_and_inventory/products_and_inventory_report.rb b/lib/reporting/reports/products_and_inventory/products_and_inventory_report.rb index b6f972f3b7..b8dc78977b 100644 --- a/lib/reporting/reports/products_and_inventory/products_and_inventory_report.rb +++ b/lib/reporting/reports/products_and_inventory/products_and_inventory_report.rb @@ -5,17 +5,9 @@ require 'open_food_network/scope_variant_to_hub' module Reporting module Reports module ProductsAndInventory - class ProductsAndInventoryReport - attr_reader :params, :render_table - + class ProductsAndInventoryReport < ReportObjectTemplate delegate :table_rows, :table_headers, :rules, :columns, :sku_for, to: :report - def initialize(user, params = {}, render_table = false) - @user = user - @params = params - @render_table = render_table - end - def report @report ||= report_klass.new(self) end diff --git a/lib/reporting/reports/sales_tax/sales_tax_report.rb b/lib/reporting/reports/sales_tax/sales_tax_report.rb index 9bf0248415..ad07846920 100644 --- a/lib/reporting/reports/sales_tax/sales_tax_report.rb +++ b/lib/reporting/reports/sales_tax/sales_tax_report.rb @@ -3,15 +3,8 @@ module Reporting module Reports module SalesTax - class SalesTaxReport + class SalesTaxReport < ReportObjectTemplate include Spree::ReportsHelper - attr_accessor :user, :params - - def initialize(user, params, render_table) - @user = user - @params = params - @render_table = render_table - end def table_headers case params[:report_subtype] @@ -49,8 +42,6 @@ module Reporting end def table_rows - return [] unless @render_table - case params[:report_subtype] when "tax_rates" orders.map do |order| diff --git a/lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb b/lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb index 34c86e97f2..dade08a64b 100644 --- a/lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb +++ b/lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb @@ -3,13 +3,10 @@ module Reporting module Reports module UsersAndEnterprises - class UsersAndEnterprisesReport - attr_reader :params + class UsersAndEnterprisesReport < ReportObjectTemplate - def initialize(user, params = {}, compile_table = false) - @user = user - @params = params - @compile_table = compile_table + def initialize(user, params = {}) + super(user, params) # Convert arrays of ids to comma delimited strings if @params[:enterprise_id_in].is_a? Array @@ -31,8 +28,6 @@ module Reporting end def table_rows - return [] unless @compile_table - users_and_enterprises.map do |uae| [ uae["user_email"], diff --git a/lib/reporting/reports/xero_invoices/xero_invoices_report.rb b/lib/reporting/reports/xero_invoices/xero_invoices_report.rb index 186dd2e830..2da61f0f78 100644 --- a/lib/reporting/reports/xero_invoices/xero_invoices_report.rb +++ b/lib/reporting/reports/xero_invoices/xero_invoices_report.rb @@ -3,18 +3,17 @@ module Reporting module Reports module XeroInvoices - class XeroInvoicesReport - def initialize(user, opts = {}, compile_table = false) - @user = user + class XeroInvoicesReport < ReportObjectTemplate + def initialize(user, params = {}) + super(user, params) - @opts = opts. + @params = @params. symbolize_keys. reject { |_k, v| v.blank? }. reverse_merge( report_subtype: 'summary', invoice_date: Time.zone.today, due_date: Time.zone.today + 1.month, account_code: 'food sales' ) - @compile_table = compile_table end def table_headers @@ -25,7 +24,7 @@ module Reporting def search permissions = ::Permissions::Order.new(@user) - permissions.editable_orders.complete.not_state(:canceled).ransack(@opts[:q]) + permissions.editable_orders.complete.not_state(:canceled).ransack(params[:q]) end def orders @@ -33,14 +32,12 @@ module Reporting end def table_rows - return [] unless @compile_table - rows = [] orders.each_with_index do |order, i| invoice_number = invoice_number_for(order, i) - rows += detail_rows_for_order(order, invoice_number, @opts) if detail? - rows += summary_rows_for_order(order, invoice_number, @opts) + rows += detail_rows_for_order(order, invoice_number, params) if detail? + rows += summary_rows_for_order(order, invoice_number, params) end rows.compact @@ -49,7 +46,7 @@ module Reporting private def report_options - @opts.merge(line_item_includes: line_item_includes) + params.merge(line_item_includes: line_item_includes) end def line_item_includes @@ -186,7 +183,7 @@ module Reporting end def invoice_number_for(order, idx) - @opts[:initial_invoice_number] ? @opts[:initial_invoice_number].to_i + idx : order.number + params[:initial_invoice_number] ? params[:initial_invoice_number].to_i + idx : order.number end def total_untaxable_products(order) @@ -227,7 +224,7 @@ module Reporting end def detail? - @opts[:report_subtype] == 'detailed' + params[:report_subtype] == 'detailed' end def tax_type(taxable) diff --git a/spec/lib/reports/bulk_coop_report_spec.rb b/spec/lib/reports/bulk_coop_report_spec.rb index 80f118a03a..49ed7d861d 100644 --- a/spec/lib/reports/bulk_coop_report_spec.rb +++ b/spec/lib/reports/bulk_coop_report_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Reporting::Reports::BulkCoop::BulkCoopReport do - subject { Reporting::Reports::BulkCoop::BulkCoopReport.new user, params, true } + subject { Reporting::Reports::BulkCoop::BulkCoopReport.new user, params } let(:user) { create(:admin_user) } describe '#table_items' do @@ -61,16 +61,16 @@ describe Reporting::Reports::BulkCoop::BulkCoopReport do li2 = build(:line_item_with_shipment) o2.line_items << li2 - report = Reporting::Reports::BulkCoop::BulkCoopReport.new user, {}, true + report = Reporting::Reports::BulkCoop::BulkCoopReport.new user, {} expect(report.table_items).to match_array [li1, li2] report = Reporting::Reports::BulkCoop::BulkCoopReport.new( - user, { q: { completed_at_gt: 2.days.ago } }, true + user, { q: { completed_at_gt: 2.days.ago } } ) expect(report.table_items).to eq([li1]) report = Reporting::Reports::BulkCoop::BulkCoopReport.new( - user, { q: { completed_at_lt: 2.days.ago } }, true + user, { q: { completed_at_lt: 2.days.ago } } ) expect(report.table_items).to eq([li2]) end @@ -85,16 +85,16 @@ describe Reporting::Reports::BulkCoop::BulkCoopReport do li2 = build(:line_item_with_shipment) o2.line_items << li2 - report = Reporting::Reports::BulkCoop::BulkCoopReport.new user, {}, true + report = Reporting::Reports::BulkCoop::BulkCoopReport.new user, {} expect(report.table_items).to match_array [li1, li2] report = Reporting::Reports::BulkCoop::BulkCoopReport.new( - user, { q: { distributor_id_in: [d1.id] } }, true + user, { q: { distributor_id_in: [d1.id] } } ) expect(report.table_items).to eq([li1]) report = Reporting::Reports::BulkCoop::BulkCoopReport.new( - user, { q: { distributor_id_in: [d2.id] } }, true + user, { q: { distributor_id_in: [d2.id] } } ) expect(report.table_items).to eq([li2]) end @@ -102,7 +102,7 @@ describe Reporting::Reports::BulkCoop::BulkCoopReport do context "as a manager of a supplier" do let!(:user) { create(:user) } - subject { Reporting::Reports::BulkCoop::BulkCoopReport.new user, {}, true } + subject { Reporting::Reports::BulkCoop::BulkCoopReport.new user, {} } let(:s1) { create(:supplier_enterprise) } diff --git a/spec/lib/reports/customers_report_spec.rb b/spec/lib/reports/customers_report_spec.rb index 2ed1e950b5..dc73ac7617 100644 --- a/spec/lib/reports/customers_report_spec.rb +++ b/spec/lib/reports/customers_report_spec.rb @@ -12,7 +12,7 @@ module Reporting user.spree_roles << Spree::Role.find_or_create_by!(name: 'admin') user end - subject { CustomersReport.new user, {}, true } + subject { CustomersReport.new user, {} } describe "mailing list report" do before do @@ -86,7 +86,7 @@ module Reporting user end - subject { CustomersReport.new user, {}, true } + subject { CustomersReport.new user, {} } describe "fetching orders" do let(:supplier) { create(:supplier_enterprise) } diff --git a/spec/lib/reports/lettuce_share_report_spec.rb b/spec/lib/reports/lettuce_share_report_spec.rb index 6fd5b8fca5..f0c1e3d554 100644 --- a/spec/lib/reports/lettuce_share_report_spec.rb +++ b/spec/lib/reports/lettuce_share_report_spec.rb @@ -8,7 +8,7 @@ module Reporting describe LettuceShareReport do let(:user) { create(:user) } let(:base_report) { - ProductsAndInventoryReport.new(user, { report_subtype: 'lettuce_share' }, true) + ProductsAndInventoryReport.new(user, { report_subtype: 'lettuce_share' }) } let(:report) { base_report.report } let(:variant) { create(:variant) } diff --git a/spec/lib/reports/order_cycle_management_report_spec.rb b/spec/lib/reports/order_cycle_management_report_spec.rb index 7cd8160ab0..9cdb4846e8 100644 --- a/spec/lib/reports/order_cycle_management_report_spec.rb +++ b/spec/lib/reports/order_cycle_management_report_spec.rb @@ -7,7 +7,7 @@ module Reporting module OrderCycleManagement describe OrderCycleManagementReport do context "as a site admin" do - subject { OrderCycleManagementReport.new(user, params, true) } + subject { OrderCycleManagementReport.new(user, params) } let(:params) { {} } let(:user) do @@ -63,7 +63,7 @@ module Reporting context "as an enterprise user" do let!(:user) { create(:user) } - subject { OrderCycleManagementReport.new user, {}, true } + subject { OrderCycleManagementReport.new user, {} } describe "fetching orders" do let(:supplier) { create(:supplier_enterprise) } @@ -148,7 +148,7 @@ module Reporting end describe '#table_rows' do - subject { OrderCycleManagementReport.new(user, params, true) } + subject { OrderCycleManagementReport.new(user, params) } let(:distributor) { create(:distributor_enterprise) } before { distributor.enterprise_roles.create!(user: user) } diff --git a/spec/lib/reports/orders_and_distributors_report_spec.rb b/spec/lib/reports/orders_and_distributors_report_spec.rb index ca9a8eaaa6..a1a6eb196a 100644 --- a/spec/lib/reports/orders_and_distributors_report_spec.rb +++ b/spec/lib/reports/orders_and_distributors_report_spec.rb @@ -45,7 +45,7 @@ module Reporting end it 'should denormalise order and distributor details for display as csv' do - subject = OrdersAndDistributorsReport.new create(:admin_user), {}, true + subject = OrdersAndDistributorsReport.new create(:admin_user), {} table = subject.table_rows @@ -77,7 +77,7 @@ module Reporting it "prints one row per line item" do create(:line_item_with_shipment, order: order) - subject = OrdersAndDistributorsReport.new(create(:admin_user), {}, true) + subject = OrdersAndDistributorsReport.new(create(:admin_user)) table = subject.table_rows expect(table.size).to eq 2 diff --git a/spec/lib/reports/orders_and_fulfillment/customer_totals_report_spec.rb b/spec/lib/reports/orders_and_fulfillment/customer_totals_report_spec.rb index 6c990d4bc0..a0a1ff5184 100644 --- a/spec/lib/reports/orders_and_fulfillment/customer_totals_report_spec.rb +++ b/spec/lib/reports/orders_and_fulfillment/customer_totals_report_spec.rb @@ -12,7 +12,7 @@ module Reporting let(:report) do report_options = { report_subtype: described_class::REPORT_TYPE } - OrdersAndFulfillmentReport.new(current_user, report_options, true) + OrdersAndFulfillmentReport.new(current_user, report_options) end let(:report_table) do diff --git a/spec/lib/reports/orders_and_fulfillment/distributor_totals_by_supplier_report_spec.rb b/spec/lib/reports/orders_and_fulfillment/distributor_totals_by_supplier_report_spec.rb index 665cce0c67..e8a0fa3864 100644 --- a/spec/lib/reports/orders_and_fulfillment/distributor_totals_by_supplier_report_spec.rb +++ b/spec/lib/reports/orders_and_fulfillment/distributor_totals_by_supplier_report_spec.rb @@ -16,7 +16,7 @@ module Reporting let(:report) do report_options = { report_subtype: described_class::REPORT_TYPE } - OrdersAndFulfillmentReport.new(current_user, report_options, true) + OrdersAndFulfillmentReport.new(current_user, report_options) end let(:report_table) do diff --git a/spec/lib/reports/orders_and_fulfillment/orders_and_fulfillment_report_spec.rb b/spec/lib/reports/orders_and_fulfillment/orders_and_fulfillment_report_spec.rb index 7d20f85638..5d6bc377ee 100644 --- a/spec/lib/reports/orders_and_fulfillment/orders_and_fulfillment_report_spec.rb +++ b/spec/lib/reports/orders_and_fulfillment/orders_and_fulfillment_report_spec.rb @@ -28,7 +28,7 @@ module Reporting before { order.line_items << line_item } context "as a site admin" do - subject { described_class.new(admin_user, {}, true) } + subject { described_class.new(admin_user, {}) } it "fetches completed orders" do o2 = create(:order) @@ -44,7 +44,7 @@ module Reporting end context "as a manager of a supplier" do - subject { described_class.new(user, {}, true) } + subject { described_class.new(user, {}) } let(:s1) { create(:supplier_enterprise) } @@ -129,7 +129,7 @@ module Reporting end context "as a manager of a distributor" do - subject { described_class.new(user, {}, true) } + subject { described_class.new(user, {}) } before do distributor.enterprise_roles.create!(user: user) @@ -180,7 +180,7 @@ module Reporting end let(:items) { - described_class.new(admin_user, { report_subtype: "order_cycle_customer_totals" }, true) + described_class.new(admin_user, { report_subtype: "order_cycle_customer_totals" }) .table_rows } diff --git a/spec/lib/reports/orders_and_fulfillment/supplier_totals_by_distributor_report_spec.rb b/spec/lib/reports/orders_and_fulfillment/supplier_totals_by_distributor_report_spec.rb index 26fd0b7d31..b265e6c338 100644 --- a/spec/lib/reports/orders_and_fulfillment/supplier_totals_by_distributor_report_spec.rb +++ b/spec/lib/reports/orders_and_fulfillment/supplier_totals_by_distributor_report_spec.rb @@ -16,7 +16,7 @@ module Reporting let(:report) do report_options = { report_subtype: described_class::REPORT_TYPE } - OrdersAndFulfillmentReport.new(current_user, report_options, true) + OrdersAndFulfillmentReport.new(current_user, report_options) end let(:report_table) do diff --git a/spec/lib/reports/orders_and_fulfillment/supplier_totals_report_spec.rb b/spec/lib/reports/orders_and_fulfillment/supplier_totals_report_spec.rb index d21839bed7..bbf2ad2ec4 100644 --- a/spec/lib/reports/orders_and_fulfillment/supplier_totals_report_spec.rb +++ b/spec/lib/reports/orders_and_fulfillment/supplier_totals_report_spec.rb @@ -16,7 +16,7 @@ module Reporting let(:report) do report_options = { report_subtype: described_class::REPORT_TYPE } - OrdersAndFulfillmentReport.new(current_user, report_options, true) + OrdersAndFulfillmentReport.new(current_user, report_options) end let(:report_table) do diff --git a/spec/lib/reports/products_and_inventory_default_report_spec.rb b/spec/lib/reports/products_and_inventory_default_report_spec.rb index 32f42f0c8d..f57a775844 100644 --- a/spec/lib/reports/products_and_inventory_default_report_spec.rb +++ b/spec/lib/reports/products_and_inventory_default_report_spec.rb @@ -13,7 +13,7 @@ module Reporting user end subject do - ProductsAndInventoryReport.new user, {}, true + ProductsAndInventoryReport.new user, {} end it "Should return headers" do @@ -82,7 +82,7 @@ module Reporting end subject do - ProductsAndInventoryReport.new enterprise_user, {}, true + ProductsAndInventoryReport.new enterprise_user, {} end describe "fetching child variants" do diff --git a/spec/lib/reports/report_renderer_spec.rb b/spec/lib/reports/report_renderer_spec.rb index 2377c96950..b7ad4c0565 100644 --- a/spec/lib/reports/report_renderer_spec.rb +++ b/spec/lib/reports/report_renderer_spec.rb @@ -9,9 +9,7 @@ describe Reporting::ReportRenderer do { "id" => 2, "name" => "onions", "quantity" => 6 } ] } - let(:report_data) { ActiveRecord::Result.new(data.first.keys, data.map(&:values)) } - let(:report) { OpenStruct.new(report_data: report_data) - } + let(:report) { OpenStruct.new(table_headers: data.first.keys, table_rows: data.map(&:values)) } let(:service) { described_class.new(report) } describe "#table_headers" do diff --git a/spec/lib/reports/sales_tax_report_spec.rb b/spec/lib/reports/sales_tax_report_spec.rb index f1695c2d4a..f316da1f48 100644 --- a/spec/lib/reports/sales_tax_report_spec.rb +++ b/spec/lib/reports/sales_tax_report_spec.rb @@ -7,7 +7,7 @@ module Reporting module SalesTax describe SalesTaxReport do let(:user) { create(:user) } - let(:report) { SalesTaxReport.new(user, {}, true) } + let(:report) { SalesTaxReport.new(user, {}) } describe "calculating totals for line items" do let(:li1) { double(:line_item, quantity: 1, amount: 12) } diff --git a/spec/lib/reports/users_and_enterprises_report_spec.rb b/spec/lib/reports/users_and_enterprises_report_spec.rb index b967ed3d5c..9929a4cb04 100644 --- a/spec/lib/reports/users_and_enterprises_report_spec.rb +++ b/spec/lib/reports/users_and_enterprises_report_spec.rb @@ -9,7 +9,7 @@ module Reporting describe "users_and_enterprises" do let!(:owners_and_enterprises) { double(:owners_and_enterprises) } let!(:managers_and_enterprises) { double(:managers_and_enterprises) } - let!(:subject) { UsersAndEnterprisesReport.new(nil, {}, true) } + let!(:subject) { UsersAndEnterprisesReport.new(nil, {}) } before do allow(subject).to receive(:owners_and_enterprises) { owners_and_enterprises } @@ -26,7 +26,7 @@ module Reporting end describe "sorting results" do - let!(:subject) { UsersAndEnterprisesReport.new(nil, {}, true) } + let!(:subject) { UsersAndEnterprisesReport.new(nil, {}) } it "sorts by creation date" do uae_mock = [ @@ -70,7 +70,7 @@ module Reporting describe "for owners and enterprises" do describe "by enterprise id" do let!(:params) { { enterprise_id_in: [enterprise1.id.to_s] } } - let!(:subject) { UsersAndEnterprisesReport.new nil, params, true } + let!(:subject) { UsersAndEnterprisesReport.new nil, params } it "excludes enterprises that are not explicitly requested" do results = subject.owners_and_enterprises.to_a.map{ |oae| oae["name"] } @@ -81,7 +81,7 @@ module Reporting describe "by user id" do let!(:params) { { user_id_in: [enterprise1.owner.id.to_s] } } - let!(:subject) { UsersAndEnterprisesReport.new nil, params, true } + let!(:subject) { UsersAndEnterprisesReport.new nil, params } it "excludes enterprises that are not explicitly requested" do results = subject.owners_and_enterprises.to_a.map{ |oae| oae["name"] } @@ -94,7 +94,7 @@ module Reporting describe "for managers and enterprises" do describe "by enterprise id" do let!(:params) { { enterprise_id_in: [enterprise1.id.to_s] } } - let!(:subject) { UsersAndEnterprisesReport.new nil, params, true } + let!(:subject) { UsersAndEnterprisesReport.new nil, params } it "excludes enterprises that are not explicitly requested" do results = subject.managers_and_enterprises.to_a.map{ |mae| mae["name"] } @@ -107,7 +107,7 @@ module Reporting let!(:manager1) { create(:user) } let!(:manager2) { create(:user) } let!(:params) { { user_id_in: [manager1.id.to_s] } } - let!(:subject) { UsersAndEnterprisesReport.new nil, params, true } + let!(:subject) { UsersAndEnterprisesReport.new nil, params } before do enterprise1.enterprise_roles.build(user: manager1).save diff --git a/spec/lib/reports/xero_invoices_report_spec.rb b/spec/lib/reports/xero_invoices_report_spec.rb index a5ad21e0cc..4d63acdad4 100644 --- a/spec/lib/reports/xero_invoices_report_spec.rb +++ b/spec/lib/reports/xero_invoices_report_spec.rb @@ -6,7 +6,7 @@ module Reporting module Reports module XeroInvoices describe XeroInvoicesReport do - subject { XeroInvoicesReport.new user, {}, true } + subject { XeroInvoicesReport.new user, {} } let(:user) { create(:user) } @@ -19,7 +19,7 @@ module Reporting around { |example| Timecop.travel(Time.zone.local(2015, 5, 5, 14, 0, 0)) { example.run } } it "uses defaults when blank params are passed" do - expect(report.instance_variable_get(:@opts)).to eq( invoice_date: Date.civil(2015, 5, 5), + expect(report.instance_variable_get(:@params)).to eq( invoice_date: Date.civil(2015, 5, 5), due_date: Date.civil(2015, 6, 5), account_code: 'food sales', report_subtype: 'summary' ) From 452a3fa9332a6732d0084d4a0db7f023c9b79f0e Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Thu, 31 Mar 2022 09:49:08 +0200 Subject: [PATCH 21/54] Reports Refactor 2: Merge Spree::reports into Admin::Reports --- app/controllers/admin/reports_controller.rb | 37 ++-- app/controllers/api/v0/reports_controller.rb | 2 +- app/controllers/concerns/reports_actions.rb | 25 ++- .../spree/admin/reports_controller.rb | 198 ------------------ app/helpers/reports_helper.rb | 21 +- app/helpers/spree/admin/navigation_helper.rb | 2 +- app/helpers/spree/reports_helper.rb | 28 --- app/models/spree/ability.rb | 31 +-- .../admin/reports/_date_range_form.html.haml | 11 +- app/views/admin/reports/_table.html.haml | 1 + .../reports/filters/_bulk_coop.html.haml | 6 + .../reports/filters/_customers.html.haml | 6 +- .../filters/_enterprise_fee_summary.html.haml | 2 +- .../filters/_order_cycle_management.html.haml | 6 +- .../_orders_and_distributors.html.haml | 1 + .../filters/_orders_and_fulfillment.html.haml | 14 ++ .../admin/reports/filters/_packing.html.haml | 8 +- .../admin/reports/filters/_payments.html.haml | 6 + .../filters/_products_and_inventory.html.haml | 6 +- .../reports/filters/_sales_tax.html.haml | 6 + .../filters/_users_and_enterprises.html.haml | 0 .../reports/filters/_xero_invoices.html.haml | 6 +- app/views/admin/reports/index.html.haml | 25 +++ app/views/admin/reports/show.html.haml | 12 +- .../reports/_customer_names_message.html.haml | 2 - .../reports/_customers_description.html.haml | 4 - .../admin/reports/_date_range_form.html.haml | 7 - .../spree/admin/reports/_link_order.html.haml | 2 - ...der_cycle_management_description.html.haml | 4 - ...ders_and_fulfillment_description.html.haml | 4 - .../reports/_packing_description.html.haml | 5 - ...oducts_and_inventory_description.html.haml | 4 - .../reports/_sales_tax_description.html.haml | 4 - .../spree/admin/reports/_table.html.haml | 19 -- .../reports/filters/_bulk_coop.html.haml | 6 - .../_orders_and_distributors.html.haml | 1 - .../filters/_orders_and_fulfillment.html.haml | 14 -- .../admin/reports/filters/_payments.html.haml | 6 - .../reports/filters/_sales_tax.html.haml | 6 - app/views/spree/admin/reports/index.html.haml | 14 -- app/views/spree/admin/reports/show.html.haml | 19 -- app/views/spree/admin/shared/_tabs.html.haml | 2 +- config/locales/en.yml | 3 - config/routes/admin.rb | 3 +- config/routes/spree.rb | 15 -- lib/reporting/frontend_data.rb | 31 ++- lib/reporting/report_loader.rb | 29 +-- lib/reporting/report_object_template.rb | 13 -- lib/reporting/report_query_template.rb | 32 +-- lib/reporting/report_template.rb | 21 ++ .../reports/bulk_coop/bulk_coop_report.rb | 5 +- .../enterprise_fee_summary_report.rb | 4 + lib/reporting/reports/list.rb | 24 +-- .../customer_totals_report.rb | 2 + .../orders_and_fulfillment_report.rb | 8 +- lib/reporting/reports/packing/base.rb | 4 +- .../reports/sales_tax/sales_tax_report.rb | 2 +- .../xero_invoices/xero_invoices_report.rb | 4 - .../admin/reports_controller_spec.rb | 107 +++++----- .../api/v0/reports_controller_spec.rb | 1 - .../{spree => }/admin/reports_helper_spec.rb | 2 +- spec/helpers/navigation_helper_spec.rb | 4 +- .../reports/packing/packing_report_spec.rb | 2 +- spec/lib/reports/report_loader_spec.rb | 69 +----- spec/models/spree/ability_spec.rb | 21 +- spec/support/ability_helper.rb | 18 -- spec/support/ability_helpers.rb | 15 ++ spec/system/admin/reports_spec.rb | 22 +- 68 files changed, 352 insertions(+), 692 deletions(-) delete mode 100644 app/controllers/spree/admin/reports_controller.rb delete mode 100644 app/helpers/spree/reports_helper.rb create mode 100644 app/views/admin/reports/filters/_bulk_coop.html.haml rename app/views/{spree => }/admin/reports/filters/_customers.html.haml (66%) rename app/views/{spree => }/admin/reports/filters/_enterprise_fee_summary.html.haml (97%) rename app/views/{spree => }/admin/reports/filters/_order_cycle_management.html.haml (70%) create mode 100644 app/views/admin/reports/filters/_orders_and_distributors.html.haml create mode 100755 app/views/admin/reports/filters/_orders_and_fulfillment.html.haml create mode 100644 app/views/admin/reports/filters/_payments.html.haml rename app/views/{spree => }/admin/reports/filters/_products_and_inventory.html.haml (66%) create mode 100644 app/views/admin/reports/filters/_sales_tax.html.haml rename app/views/{spree => }/admin/reports/filters/_users_and_enterprises.html.haml (100%) rename app/views/{spree => }/admin/reports/filters/_xero_invoices.html.haml (79%) create mode 100644 app/views/admin/reports/index.html.haml delete mode 100644 app/views/spree/admin/reports/_customer_names_message.html.haml delete mode 100644 app/views/spree/admin/reports/_customers_description.html.haml delete mode 100644 app/views/spree/admin/reports/_date_range_form.html.haml delete mode 100644 app/views/spree/admin/reports/_link_order.html.haml delete mode 100644 app/views/spree/admin/reports/_order_cycle_management_description.html.haml delete mode 100644 app/views/spree/admin/reports/_orders_and_fulfillment_description.html.haml delete mode 100644 app/views/spree/admin/reports/_packing_description.html.haml delete mode 100644 app/views/spree/admin/reports/_products_and_inventory_description.html.haml delete mode 100644 app/views/spree/admin/reports/_sales_tax_description.html.haml delete mode 100644 app/views/spree/admin/reports/_table.html.haml delete mode 100644 app/views/spree/admin/reports/filters/_bulk_coop.html.haml delete mode 100644 app/views/spree/admin/reports/filters/_orders_and_distributors.html.haml delete mode 100755 app/views/spree/admin/reports/filters/_orders_and_fulfillment.html.haml delete mode 100644 app/views/spree/admin/reports/filters/_payments.html.haml delete mode 100644 app/views/spree/admin/reports/filters/_sales_tax.html.haml delete mode 100644 app/views/spree/admin/reports/index.html.haml delete mode 100644 app/views/spree/admin/reports/show.html.haml rename spec/controllers/{spree => }/admin/reports_controller_spec.rb (75%) rename spec/helpers/{spree => }/admin/reports_helper_spec.rb (94%) delete mode 100644 spec/support/ability_helper.rb diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index d097f6527b..b5c400a33e 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -5,12 +5,21 @@ module Admin include ReportsActions helper ReportsHelper - before_action :authorize_report + before_action :authorize_report, only: [:show] + + # Define custom model class for Cancan permissions + def model_class + Admin::ReportsController + end + + def index + @reports = reports.select do |report_type, _description| + can? report_type, :report + end + end def show - render_report && return if ransack_params.blank? - - @report = report_class.new(spree_current_user, ransack_params, report_options) + @report = report_class.new(spree_current_user, params) if report_format.present? export_report @@ -27,29 +36,15 @@ module Admin def render_report assign_view_data - load_form_options render "show" end def assign_view_data @report_type = report_type - @report_subtype = report_subtype || report_loader.default_report_subtype - @report_subtypes = report_class.report_subtypes.map do |subtype| - [t("packing.#{subtype}_report", scope: i18n_scope), subtype] - end - if @report_type == "packing" - @report_message = I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") - end - end + @report_subtypes = report_subtypes + @report_subtype = report_subtype - def load_form_options - return unless form_options_required? - - form_options = Reporting::FrontendData.new(spree_current_user) - - @distributors = form_options.distributors.to_a - @suppliers = form_options.suppliers.to_a - @order_cycles = form_options.order_cycles.to_a + @data = Reporting::FrontendData.new(spree_current_user) end end end diff --git a/app/controllers/api/v0/reports_controller.rb b/app/controllers/api/v0/reports_controller.rb index b6cc0facc9..1e4462df85 100644 --- a/app/controllers/api/v0/reports_controller.rb +++ b/app/controllers/api/v0/reports_controller.rb @@ -10,7 +10,7 @@ module Api before_action :validate_report, :authorize_report, :validate_query def show - @report = report_class.new(current_api_user, ransack_params, report_options) + @report = report_class.new(current_api_user, params) render_report end diff --git a/app/controllers/concerns/reports_actions.rb b/app/controllers/concerns/reports_actions.rb index 8a4cd10afa..35a508b154 100644 --- a/app/controllers/concerns/reports_actions.rb +++ b/app/controllers/concerns/reports_actions.rb @@ -3,10 +3,14 @@ module ReportsActions extend ActiveSupport::Concern + def reports + Reporting::Reports::List.all + end + private def authorize_report - authorize! report_type&.to_sym, :report + authorize! report_type.to_sym, :report end def report_class @@ -23,27 +27,26 @@ module ReportsActions params[:report_type] end + def report_subtypes + reports[report_type.to_sym] || [] + end + + def report_subtypes_codes + report_subtypes.map(&:second).map(&:to_s) + end + def report_subtype - params[:report_subtype] + params[:report_subtype] || report_subtypes_codes.first end def ransack_params raw_params[:q] end - def report_options - raw_params[:options] - end - def report_format params[:report_format] end - def form_options_required? - [:packing, :customers, :products_and_inventory, :order_cycle_management]. - include? report_type.to_sym - end - def report_filename "#{report_type || action_name}_#{file_timestamp}.#{report_format}" end diff --git a/app/controllers/spree/admin/reports_controller.rb b/app/controllers/spree/admin/reports_controller.rb deleted file mode 100644 index 3f1be8c33f..0000000000 --- a/app/controllers/spree/admin/reports_controller.rb +++ /dev/null @@ -1,198 +0,0 @@ -# frozen_string_literal: true - -# require 'open_food_network/orders_and_distributors_report' -# require 'open_food_network/products_and_inventory_report' -# require 'open_food_network/lettuce_share_report' -# require 'open_food_network/order_grouper' -# require 'open_food_network/customers_report' -# require 'open_food_network/users_and_enterprises_report' -# require 'open_food_network/order_cycle_management_report' -# require 'open_food_network/sales_tax_report' -# require 'open_food_network/xero_invoices_report' -# require 'open_food_network/payments_report' -# require 'open_food_network/orders_and_fulfillment_report' -# require 'open_food_network/bulk_coop_report' - -module Spree - module Admin - class ReportsController < Spree::Admin::BaseController - include Spree::ReportsHelper - include ReportsActions - helper ::ReportsHelper - - helper_method :render_content? - - # Fetches user's distributors, suppliers and order_cycles - before_action :load_basic_data, only: [:customers, :products_and_inventory, :order_cycle_management] - - respond_to :html - - def report_types - Reporting::Reports::List.all - end - - def index - @reports = authorized_reports - respond_with(@reports) - end - - def customers - render_report - end - - def order_cycle_management - render_report - end - - def orders_and_distributors - render_report - end - - def sales_tax - @distributors = my_distributors - render_report - end - - def payments - @distributors = my_distributors - render_report - end - - def orders_and_fulfillment - form_options = Reporting::FrontendData.new(spree_current_user) - - @distributors = form_options.distributors - @suppliers = form_options.suppliers - @order_cycles = form_options.order_cycles - - @report_message = I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") - - render_report - end - - def products_and_inventory - render_report - end - - def users_and_enterprises - render_report - end - - def xero_invoices - @distributors = my_distributors - @order_cycles = my_order_cycles - - render_report - end - - def bulk_coop - @distributors = my_distributors - @report_message = I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") - render_report - end - - def enterprise_fee_summary - @report_message = I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") - render_report - end - - private - - def model_class - Spree::Admin::ReportsController - end - - # We don't want to render data unless search params are supplied. - # Compiling data can take a long time. - def render_content? - request.post? - end - - def render_report - @report_subtypes = report_types[action_name.to_sym] - @report_subtype = params[:report_subtype] - klass = "Reporting::Reports::#{action_name.camelize}::#{action_name.camelize}Report".constantize - @report = klass.new spree_current_user, raw_params - if report_format.present? - send_data @report.public_send("to_#{report_format}"), filename: report_filename - else - render "show" - end - end - - def load_basic_data - @distributors = my_distributors - @suppliers = my_suppliers | suppliers_of_products_distributed_by(@distributors) - @order_cycles = my_order_cycles - end - - # Load managed distributor enterprises of current user - def my_distributors - Enterprise.is_distributor.managed_by(spree_current_user) - end - - # Load managed producer enterprises of current user - def my_suppliers - Enterprise.is_primary_producer.managed_by(spree_current_user) - end - - def suppliers_of_products_distributed_by(distributors) - supplier_ids = Spree::Product.in_distributors(distributors.select('enterprises.id')). - select('spree_products.supplier_id') - - Enterprise.where(id: supplier_ids) - end - - # Load order cycles the current user has access to - def my_order_cycles - OrderCycle. - active_or_complete. - visible_by(spree_current_user). - order('orders_close_at DESC') - end - - def authorized_reports - all_reports = [ - :orders_and_distributors, - :bulk_coop, - :payments, - :orders_and_fulfillment, - :customers, - :products_and_inventory, - :users_and_enterprises, - :enterprise_fee_summary, - :order_cycle_management, - :sales_tax, - :xero_invoices, - :packing - ] - reports = all_reports.select { |action| can? action, Spree::Admin::ReportsController } - reports.map { |report| [report, describe_report(report)] }.to_h - end - - def describe_report(report) - name = I18n.t(:name, scope: [:admin, :reports, report]) - description = begin - I18n.t!(:description, scope: [:admin, :reports, report]) - rescue I18n::MissingTranslationData - render_to_string( - partial: "#{report}_description", - layout: false, - locals: { report_types: report_types[report] } - ).html_safe - end - { name: name, url: url_for_report(report), description: description } - end - - def url_for_report(report) - spree.public_send("#{report}_admin_reports_url".to_sym) - rescue NoMethodError - main_app.admin_reports_url(report_type: report) - end - - def timestamp - Time.zone.now.strftime("%Y%m%d") - end - end - end -end diff --git a/app/helpers/reports_helper.rb b/app/helpers/reports_helper.rb index bfcc4232db..953a4bbffa 100644 --- a/app/helpers/reports_helper.rb +++ b/app/helpers/reports_helper.rb @@ -9,7 +9,24 @@ module ReportsHelper end end - def report_subtypes(report) - Reporting::ReportLoader.new(report).report_subtypes + def report_payment_method_options(orders) + orders.map do |order| + payment_method = order.payments.last&.payment_method + + next unless payment_method + + [payment_method.name, payment_method.id] + end.compact.uniq + end + + def report_shipping_method_options(orders) + orders.map do |o| + sm = o.shipping_method + [sm&.name, sm&.id] + end.uniq + end + + def currency_symbol + Spree::Money.currency_symbol end end diff --git a/app/helpers/spree/admin/navigation_helper.rb b/app/helpers/spree/admin/navigation_helper.rb index 324e056330..56fc759660 100644 --- a/app/helpers/spree/admin/navigation_helper.rb +++ b/app/helpers/spree/admin/navigation_helper.rb @@ -77,7 +77,7 @@ module Spree klass = EnterpriseGroup if klass == :group klass = VariantOverride if klass == :Inventory klass = ProductImport::ProductImporter if klass == :import - klass = Spree::Admin::ReportsController if klass == :report + klass = ::Admin::ReportsController if klass == :report klass end diff --git a/app/helpers/spree/reports_helper.rb b/app/helpers/spree/reports_helper.rb deleted file mode 100644 index 05b3fa7af6..0000000000 --- a/app/helpers/spree/reports_helper.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -require 'spree/money' - -module Spree - module ReportsHelper - def report_payment_method_options(orders) - orders.map do |order| - payment_method = order.payments.last&.payment_method - - next unless payment_method - - [payment_method.name, payment_method.id] - end.compact.uniq - end - - def report_shipping_method_options(orders) - orders.map do |o| - sm = o.shipping_method - [sm&.name, sm&.id] - end.uniq - end - - def currency_symbol - Spree::Money.currency_symbol - end - end -end diff --git a/app/models/spree/ability.rb b/app/models/spree/ability.rb index fcb1ffc37a..2e9aec849f 100644 --- a/app/models/spree/ability.rb +++ b/app/models/spree/ability.rb @@ -236,12 +236,10 @@ module Spree :validate_data, :reset_absent_products], ProductImport::ProductImporter # Reports page - can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :payments, - :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing], - Spree::Admin::ReportsController - can [:admin, :show, :packing], :report - add_bulk_coop_abilities - add_enterprise_fee_summary_abilities + can [:admin, :index, :show], ::Admin::ReportsController + can [:admin, :show, :customers, :orders_and_distributors, :group_buys, :payments, + :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, + :packing, :enterprise_fee_summary, :bulk_coop], :report end def add_order_cycle_management_abilities(user) @@ -317,11 +315,10 @@ module Spree end # Reports page - can [:admin, :index, :customers, :group_buys, :sales_tax, :payments, + can [:admin, :index, :show], ::Admin::ReportsController + can [:admin, :customers, :group_buys, :sales_tax, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, - :order_cycle_management, :xero_invoices], Spree::Admin::ReportsController - add_bulk_coop_abilities - add_enterprise_fee_summary_abilities + :order_cycle_management, :xero_invoices, :enterprise_fee_summary, :bulk_coop], :report can [:create], Customer can [:admin, :index, :update, @@ -346,19 +343,5 @@ module Spree user.enterprises.include?(enterprise_relationship.child) end end - - def add_bulk_coop_abilities - # Reveal the report link in spree/admin/reports#index - can [:bulk_coop], Spree::Admin::ReportsController - # Allow direct access to the report resource - can [:admin, :new, :create], :bulk_coop - end - - def add_enterprise_fee_summary_abilities - # Reveal the report link in spree/admin/reports#index - can [:enterprise_fee_summary], Spree::Admin::ReportsController - # Allow direct access to the report resource - can [:admin, :new, :create], :enterprise_fee_summary - end end end diff --git a/app/views/admin/reports/_date_range_form.html.haml b/app/views/admin/reports/_date_range_form.html.haml index 134e7d967f..34bd30ec84 100644 --- a/app/views/admin/reports/_date_range_form.html.haml +++ b/app/views/admin/reports/_date_range_form.html.haml @@ -1,9 +1,10 @@ +-# Field used for ransack search. This date range is mostly used for Spree::Order +-# so default field is 'completed_at' +- field ||= 'completed_at' .row.date-range-filter - .alpha.two.columns - = label_tag nil, t(:date_range) + .alpha.two.columns= label_tag nil, t(:date_range) .omega.fourteen.columns - = text_field_tag "q[order_completed_at_gt]", params.dig(:q, :order_completed_at_gt), :class => 'datetimepicker datepicker-from', :placeholder => t(:start) + = f.text_field "#{field}_gt", :class => 'datetimepicker datepicker-from', :placeholder => t(:start) %span.range-divider %i.icon-arrow-right - = text_field_tag "q[order_completed_at_lt]", params.dig(:q, :order_completed_at_lt), :class => 'datetimepicker datepicker-to', :placeholder => t(:stop) - + = f.text_field "#{field}_lt", :class => 'datetimepicker datepicker-to', :placeholder => t(:stop) diff --git a/app/views/admin/reports/_table.html.haml b/app/views/admin/reports/_table.html.haml index b5dd0586a5..1d83358fd1 100644 --- a/app/views/admin/reports/_table.html.haml +++ b/app/views/admin/reports/_table.html.haml @@ -6,6 +6,7 @@ - report.table_headers.each do |heading| %th = t("admin.reports.table.headings.#{heading}") + -# = heading %tbody - report.table_rows.each do |row| - if row diff --git a/app/views/admin/reports/filters/_bulk_coop.html.haml b/app/views/admin/reports/filters/_bulk_coop.html.haml new file mode 100644 index 0000000000..264facbf9f --- /dev/null +++ b/app/views/admin/reports/filters/_bulk_coop.html.haml @@ -0,0 +1,6 @@ += render 'admin/reports/date_range_form', f: f + +.row + .alpha.two.columns= label_tag nil, t(:report_hubs) + .omega.fourteen.columns + = f.collection_select(:distributor_id_in, @data.distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) diff --git a/app/views/spree/admin/reports/filters/_customers.html.haml b/app/views/admin/reports/filters/_customers.html.haml similarity index 66% rename from app/views/spree/admin/reports/filters/_customers.html.haml rename to app/views/admin/reports/filters/_customers.html.haml index 83fb9f87b5..caa88f4e5d 100644 --- a/app/views/spree/admin/reports/filters/_customers.html.haml +++ b/app/views/admin/reports/filters/_customers.html.haml @@ -2,17 +2,17 @@ .alpha.two.columns= label_tag nil, t(:report_customers_distributor) .omega.fourteen.columns = select_tag(:distributor_id, - options_from_collection_for_select(@distributors, :id, :name, params[:distributor_id]), + options_from_collection_for_select(@data.distributors, :id, :name, params[:distributor_id]), {:include_blank => true, :class => "select2 fullwidth light"}) .row .alpha.two.columns= label_tag nil, t(:report_customers_supplier) .omega.fourteen.columns = select_tag(:supplier_id, - options_from_collection_for_select(@suppliers, :id, :name, params[:supplier_id]), + options_from_collection_for_select(@data.suppliers, :id, :name, params[:supplier_id]), {:include_blank => true, :class => "select2 fullwidth light"}) .row .alpha.two.columns= label_tag nil, t(:report_customers_cycle) .omega.fourteen.columns = select_tag(:order_cycle_id, - options_for_select(report_order_cycle_options(@order_cycles), params[:order_cycle_id]), + options_for_select(report_order_cycle_options(@data.order_cycles), params[:order_cycle_id]), {:include_blank => true, :class => "select2 fullwidth light"}) \ No newline at end of file diff --git a/app/views/spree/admin/reports/filters/_enterprise_fee_summary.html.haml b/app/views/admin/reports/filters/_enterprise_fee_summary.html.haml similarity index 97% rename from app/views/spree/admin/reports/filters/_enterprise_fee_summary.html.haml rename to app/views/admin/reports/filters/_enterprise_fee_summary.html.haml index c64a55823c..79998980cd 100644 --- a/app/views/spree/admin/reports/filters/_enterprise_fee_summary.html.haml +++ b/app/views/admin/reports/filters/_enterprise_fee_summary.html.haml @@ -1,4 +1,4 @@ -= render 'spree/admin/reports/date_range_form', f: f += render 'admin/reports/date_range_form', f: f .row .alpha.two.columns= label_tag nil, t(:report_hubs) diff --git a/app/views/spree/admin/reports/filters/_order_cycle_management.html.haml b/app/views/admin/reports/filters/_order_cycle_management.html.haml similarity index 70% rename from app/views/spree/admin/reports/filters/_order_cycle_management.html.haml rename to app/views/admin/reports/filters/_order_cycle_management.html.haml index 7de9126e14..dc3977266c 100644 --- a/app/views/spree/admin/reports/filters/_order_cycle_management.html.haml +++ b/app/views/admin/reports/filters/_order_cycle_management.html.haml @@ -1,13 +1,13 @@ -= render 'spree/admin/reports/date_range_form', f: f += render 'admin/reports/date_range_form', f: f .row .alpha.two.columns= label_tag nil, t(:report_hubs) - .omega.fourteen.columns= f.collection_select(:distributor_id_in, @distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + .omega.fourteen.columns= f.collection_select(:distributor_id_in, @data.distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) .row .alpha.two.columns= label_tag nil, t(:report_customers_cycle) .omega.fourteen.columns - = f.select(:order_cycle_id_in, report_order_cycle_options(@order_cycles), {selected: params.dig(:q, :order_cycle_id_in)}, {class: "select2 fullwidth", multiple: true}) + = f.select(:order_cycle_id_in, report_order_cycle_options(@data.order_cycles), {selected: params.dig(:q, :order_cycle_id_in)}, {class: "select2 fullwidth", multiple: true}) .row .alpha.two.columns= label_tag nil, t(:report_payment) diff --git a/app/views/admin/reports/filters/_orders_and_distributors.html.haml b/app/views/admin/reports/filters/_orders_and_distributors.html.haml new file mode 100644 index 0000000000..35c5a74266 --- /dev/null +++ b/app/views/admin/reports/filters/_orders_and_distributors.html.haml @@ -0,0 +1 @@ += render 'admin/reports/date_range_form', f: f diff --git a/app/views/admin/reports/filters/_orders_and_fulfillment.html.haml b/app/views/admin/reports/filters/_orders_and_fulfillment.html.haml new file mode 100755 index 0000000000..0a9963544f --- /dev/null +++ b/app/views/admin/reports/filters/_orders_and_fulfillment.html.haml @@ -0,0 +1,14 @@ += render 'admin/reports/date_range_form', f: f + +.row + .alpha.two.columns= label_tag nil, t(:report_hubs) + .omega.fourteen.columns= f.collection_select(:distributor_id_in, @data.orders_distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + +.row + .alpha.two.columns= label_tag nil, t(:report_producers) + .omega.fourteen.columns= select_tag(:supplier_id_in, options_from_collection_for_select(@data.orders_suppliers, :id, :name, params[:supplier_id_in]), {class: "select2 fullwidth", multiple: true}) + +.row + .alpha.two.columns= label_tag nil, t(:report_customers_cycle) + .omega.fourteen.columns + = f.select(:order_cycle_id_in, report_order_cycle_options(@data.order_cycles), {selected: params.dig(:q, :order_cycle_id_in)}, {class: "select2 fullwidth", multiple: true}) diff --git a/app/views/admin/reports/filters/_packing.html.haml b/app/views/admin/reports/filters/_packing.html.haml index c7f54750ee..b15d3a5085 100755 --- a/app/views/admin/reports/filters/_packing.html.haml +++ b/app/views/admin/reports/filters/_packing.html.haml @@ -1,16 +1,16 @@ -= render partial: 'admin/reports/date_range_form' += render partial: 'admin/reports/date_range_form', locals: { f: f, field: 'order_completed_at' } .row .alpha.two.columns= label_tag nil, t(:report_hubs) .omega.fourteen.columns - = collection_select("q", "order_distributor_id_in", @distributors, :id, :name, {selected: params.dig(:q, :order_distributor_id_in)}, {class: "select2 fullwidth", multiple: true}) + = collection_select("q", "order_distributor_id_in", @data.orders_distributors, :id, :name, {selected: params.dig(:q, :order_distributor_id_in)}, {class: "select2 fullwidth", multiple: true}) .row .alpha.two.columns= label_tag nil, t(:report_producers) .omega.fourteen.columns - = select_tag("q[supplier_id_in]", options_from_collection_for_select(@suppliers, :id, :name, params.dig(:q, :supplier_id_in)), {class: "select2 fullwidth", multiple: true}) + = select_tag("q[supplier_id_in]", options_from_collection_for_select(@data.orders_suppliers, :id, :name, params.dig(:q, :supplier_id_in)), {class: "select2 fullwidth", multiple: true}) .row .alpha.two.columns= label_tag nil, t(:report_customers_cycle) .omega.fourteen.columns - = select_tag("q[order_cycle_id_in]", options_for_select(report_order_cycle_options(@order_cycles), params.dig(:q, :order_cycle_id_in)), {class: "select2 fullwidth", multiple: true}) \ No newline at end of file + = select_tag("q[order_cycle_id_in]", options_for_select(report_order_cycle_options(@data.order_cycles), params.dig(:q, :order_cycle_id_in)), {class: "select2 fullwidth", multiple: true}) \ No newline at end of file diff --git a/app/views/admin/reports/filters/_payments.html.haml b/app/views/admin/reports/filters/_payments.html.haml new file mode 100644 index 0000000000..91ec0c9e71 --- /dev/null +++ b/app/views/admin/reports/filters/_payments.html.haml @@ -0,0 +1,6 @@ += render 'admin/reports/date_range_form', f: f + +.row + .alpha.two.columns= label_tag nil, t(:report_distributor) + .omega.fourteen.columns + = f.collection_select(:distributor_id_eq, @data.distributors, :id, :name, {:include_blank => t(:report_all)}, {:class => "select2 fullwidth light"}) diff --git a/app/views/spree/admin/reports/filters/_products_and_inventory.html.haml b/app/views/admin/reports/filters/_products_and_inventory.html.haml similarity index 66% rename from app/views/spree/admin/reports/filters/_products_and_inventory.html.haml rename to app/views/admin/reports/filters/_products_and_inventory.html.haml index dbb56d76b7..921e55be0f 100644 --- a/app/views/spree/admin/reports/filters/_products_and_inventory.html.haml +++ b/app/views/admin/reports/filters/_products_and_inventory.html.haml @@ -2,19 +2,19 @@ .alpha.two.columns= label_tag nil, t(:report_distributor) .omega.fourteen.columns = select_tag(:distributor_id, - options_from_collection_for_select(@distributors, :id, :name, params[:distributor_id]), + options_from_collection_for_select(@data.distributors, :id, :name, params[:distributor_id]), {:include_blank => true, :class => "select2 fullwidth light"}) .row .alpha.two.columns= label_tag nil, t(:report_customers_supplier) .omega.fourteen.columns = select_tag(:supplier_id, - options_from_collection_for_select(@suppliers, :id, :name, params[:supplier_id]), + options_from_collection_for_select(@data.suppliers, :id, :name, params[:supplier_id]), {:include_blank => true, :class => "select2 fullwidth light"}) .row .alpha.two.columns= label_tag nil, t(:report_order_cycle) .omega.fourteen.columns = select_tag(:order_cycle_id, - options_for_select(report_order_cycle_options(@order_cycles), params[:order_cycle_id]), + options_for_select(report_order_cycle_options(@data.order_cycles), params[:order_cycle_id]), {:include_blank => true, :class => "select2 fullwidth light"}) diff --git a/app/views/admin/reports/filters/_sales_tax.html.haml b/app/views/admin/reports/filters/_sales_tax.html.haml new file mode 100644 index 0000000000..bdedb904c2 --- /dev/null +++ b/app/views/admin/reports/filters/_sales_tax.html.haml @@ -0,0 +1,6 @@ += render 'admin/reports/date_range_form', f: f + +.row + .alpha.two.columns= label_tag nil, t(:report_distributor) + .omega.fourteen.columns + = f.collection_select(:distributor_id_eq, @data.distributors, :id, :name, {:include_blank => t(:all)}, {:class => "select2 fullwidth light"}) diff --git a/app/views/spree/admin/reports/filters/_users_and_enterprises.html.haml b/app/views/admin/reports/filters/_users_and_enterprises.html.haml similarity index 100% rename from app/views/spree/admin/reports/filters/_users_and_enterprises.html.haml rename to app/views/admin/reports/filters/_users_and_enterprises.html.haml diff --git a/app/views/spree/admin/reports/filters/_xero_invoices.html.haml b/app/views/admin/reports/filters/_xero_invoices.html.haml similarity index 79% rename from app/views/spree/admin/reports/filters/_xero_invoices.html.haml rename to app/views/admin/reports/filters/_xero_invoices.html.haml index d6425ed0bd..c3c7f765cb 100644 --- a/app/views/spree/admin/reports/filters/_xero_invoices.html.haml +++ b/app/views/admin/reports/filters/_xero_invoices.html.haml @@ -1,12 +1,12 @@ -= render 'spree/admin/reports/date_range_form', f: f += render 'admin/reports/date_range_form', f: f .row .two.columns.alpha= label_tag nil, t(:report_hubs) - .fourteen.columns.omega= f.collection_select(:distributor_id_eq, @distributors, :id, :name, {:include_blank => 'All'}, {:class => "select2 fullwidth light"}) + .fourteen.columns.omega= f.collection_select(:distributor_id_eq, @data.distributors, :id, :name, {:include_blank => 'All'}, {:class => "select2 fullwidth light"}) .row .two.columns.alpha= label_tag nil, t(:report_order_cycle) .fourteen.columns.omega= f.select(:order_cycle_id_eq, - options_for_select(report_order_cycle_options(@order_cycles), params.dig(:q, :order_cycle_id_eq)), + options_for_select(report_order_cycle_options(@data.order_cycles), params.dig(:q, :order_cycle_id_eq)), {:include_blank => true}, {:class => "select2 fullwidth light"}) .row diff --git a/app/views/admin/reports/index.html.haml b/app/views/admin/reports/index.html.haml new file mode 100644 index 0000000000..4465695cd6 --- /dev/null +++ b/app/views/admin/reports/index.html.haml @@ -0,0 +1,25 @@ +- content_for :page_title do + = t(:listing_reports) + +.columns.twelve + %table.index + %thead + %tr + %th= t(:name) + %th= t(:description) + %tbody + - @reports.each do |report_type, report_subtypes| + %tr + %td + - name = I18n.t(:name, scope: [:admin, :reports, report_type]) + - url = main_app.admin_report_url(report_type: report_type) + = link_to name, url + %td + - begin + = I18n.t!(:description, scope: [:admin, :reports, report_type]) + - rescue I18n::MissingTranslationData + %ul{style: "margin-left: 12pt"} + - report_subtypes.each do |report_subtype| + %li + - url = main_app.admin_report_url(report_type: report_type, report_subtype: report_subtype[1]) + = link_to report_subtype[0], url \ No newline at end of file diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index 714001f325..716edfab41 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -1,7 +1,7 @@ -= form_tag main_app.admin_reports_path, report_type: @report_type do += form_for @report.search, :url => url_for(only_path: false) do |f| %fieldset.no-border-bottom.print-hidden %legend{ align: 'center'}= t(:report_filters) - = render partial: "admin/reports/filters/#{@report_type}" + = render partial: "admin/reports/filters/#{@report_type}", locals: { f: f } %fieldset.print-hidden %legend{ align: 'center'}= t(:report_render_options) @@ -10,8 +10,10 @@ .actions.filter-actions = button t(:go), "report__submit-btn" -- if @report_message.present? - %p.report__message.print-hidden= @report_message +- if @report.message.present? + %p.report__message.print-hidden= @report.message -- if params[:q].present? +/ We don't want to render data unless search params are supplied. +/ Compiling data can take a long time. +- if request.post? = render "table" diff --git a/app/views/spree/admin/reports/_customer_names_message.html.haml b/app/views/spree/admin/reports/_customer_names_message.html.haml deleted file mode 100644 index 27f18cc93c..0000000000 --- a/app/views/spree/admin/reports/_customer_names_message.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -%p.report__message - = t(".customer_names_tip") diff --git a/app/views/spree/admin/reports/_customers_description.html.haml b/app/views/spree/admin/reports/_customers_description.html.haml deleted file mode 100644 index 22d440725e..0000000000 --- a/app/views/spree/admin/reports/_customers_description.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -%ul{style: "margin-left: 12pt"} - - report_types.each do |report_type| - %li - = link_to report_type[0], "#{customers_admin_reports_url}?report_subtype=#{report_type[1]}" diff --git a/app/views/spree/admin/reports/_date_range_form.html.haml b/app/views/spree/admin/reports/_date_range_form.html.haml deleted file mode 100644 index 28d1385c51..0000000000 --- a/app/views/spree/admin/reports/_date_range_form.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -.row.date-range-filter - .alpha.two.columns= label_tag nil, t(:date_range) - .omega.fourteen.columns - = f.text_field :completed_at_gt, :class => 'datetimepicker datepicker-from', :placeholder => t(:start) - %span.range-divider - %i.icon-arrow-right - = f.text_field :completed_at_lt, :class => 'datetimepicker datepicker-to', :placeholder => t(:stop) diff --git a/app/views/spree/admin/reports/_link_order.html.haml b/app/views/spree/admin/reports/_link_order.html.haml deleted file mode 100644 index 1efecf6d25..0000000000 --- a/app/views/spree/admin/reports/_link_order.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -- order_number = value -= link_to order_number, edit_admin_order_url(order_number), class: 'edit-order' diff --git a/app/views/spree/admin/reports/_order_cycle_management_description.html.haml b/app/views/spree/admin/reports/_order_cycle_management_description.html.haml deleted file mode 100644 index 7c3b39a69e..0000000000 --- a/app/views/spree/admin/reports/_order_cycle_management_description.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -%ul{style: "margin-left: 12pt"} - - report_types.each do |report_type| - %li - = link_to report_type[0], "#{order_cycle_management_admin_reports_url}?report_subtype=#{report_type[1]}" diff --git a/app/views/spree/admin/reports/_orders_and_fulfillment_description.html.haml b/app/views/spree/admin/reports/_orders_and_fulfillment_description.html.haml deleted file mode 100644 index d17874cd38..0000000000 --- a/app/views/spree/admin/reports/_orders_and_fulfillment_description.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -%ul{style: "margin-left: 12pt"} - - report_types.each do |report_type| - %li - = link_to report_type[0], "#{orders_and_fulfillment_admin_reports_url}?report_subtype=#{report_type[1]}" diff --git a/app/views/spree/admin/reports/_packing_description.html.haml b/app/views/spree/admin/reports/_packing_description.html.haml deleted file mode 100644 index d91e291909..0000000000 --- a/app/views/spree/admin/reports/_packing_description.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -%ul{style: "margin-left: 12pt"} - - report_subtypes("packing").each do |report_subtype| - %li - = link_to t("admin.reports.packing.#{report_subtype}_report"), - main_app.admin_reports_url(report_type: 'packing', report_subtype: report_subtype) diff --git a/app/views/spree/admin/reports/_products_and_inventory_description.html.haml b/app/views/spree/admin/reports/_products_and_inventory_description.html.haml deleted file mode 100644 index f65d13273e..0000000000 --- a/app/views/spree/admin/reports/_products_and_inventory_description.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -%ul{style: "margin-left: 12pt"} - - report_types.each do |report_type| - %li - = link_to report_type[0], "#{products_and_inventory_admin_reports_url}?report_subtype=#{report_type[1]}" diff --git a/app/views/spree/admin/reports/_sales_tax_description.html.haml b/app/views/spree/admin/reports/_sales_tax_description.html.haml deleted file mode 100644 index 169be2591e..0000000000 --- a/app/views/spree/admin/reports/_sales_tax_description.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -%ul{style: "margin-left: 12pt"} - - report_types.each do |report_type| - %li - = link_to report_type[0], "#{sales_tax_admin_reports_url}?report_subtype=#{report_type[1]}" diff --git a/app/views/spree/admin/reports/_table.html.haml b/app/views/spree/admin/reports/_table.html.haml deleted file mode 100644 index b6b932b4e7..0000000000 --- a/app/views/spree/admin/reports/_table.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -- column_partials ||= {} -%table.report__table - %thead - %tr - - @report.table_headers.each do |heading| - %th= heading - %tbody - - @report.table_rows.each do |row| - %tr - - row.each_with_index do |cell_value, column_index| - %td - - partial = column_partials[column_index] - - if partial - = render partial, value: cell_value - - else - = cell_value - - if @report.table_rows.empty? - %tr - %td{colspan: @report.table_headers.count}= t(:none) diff --git a/app/views/spree/admin/reports/filters/_bulk_coop.html.haml b/app/views/spree/admin/reports/filters/_bulk_coop.html.haml deleted file mode 100644 index f9ba0eb4f9..0000000000 --- a/app/views/spree/admin/reports/filters/_bulk_coop.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -= render 'spree/admin/reports/date_range_form', f: f - -.row - .alpha.two.columns= label_tag nil, t(:report_hubs) - .omega.fourteen.columns - = f.collection_select(:distributor_id_in, @distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) diff --git a/app/views/spree/admin/reports/filters/_orders_and_distributors.html.haml b/app/views/spree/admin/reports/filters/_orders_and_distributors.html.haml deleted file mode 100644 index 6d7296bbab..0000000000 --- a/app/views/spree/admin/reports/filters/_orders_and_distributors.html.haml +++ /dev/null @@ -1 +0,0 @@ -= render 'spree/admin/reports/date_range_form', f: f diff --git a/app/views/spree/admin/reports/filters/_orders_and_fulfillment.html.haml b/app/views/spree/admin/reports/filters/_orders_and_fulfillment.html.haml deleted file mode 100755 index e43204e60d..0000000000 --- a/app/views/spree/admin/reports/filters/_orders_and_fulfillment.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -= render 'spree/admin/reports/date_range_form', f: f - -.row - .alpha.two.columns= label_tag nil, t(:report_hubs) - .omega.fourteen.columns= f.collection_select(:distributor_id_in, @distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) - -.row - .alpha.two.columns= label_tag nil, t(:report_producers) - .omega.fourteen.columns= select_tag(:supplier_id_in, options_from_collection_for_select(@suppliers, :id, :name, params[:supplier_id_in]), {class: "select2 fullwidth", multiple: true}) - -.row - .alpha.two.columns= label_tag nil, t(:report_customers_cycle) - .omega.fourteen.columns - = f.select(:order_cycle_id_in, report_order_cycle_options(@order_cycles), {selected: params.dig(:q, :order_cycle_id_in)}, {class: "select2 fullwidth", multiple: true}) diff --git a/app/views/spree/admin/reports/filters/_payments.html.haml b/app/views/spree/admin/reports/filters/_payments.html.haml deleted file mode 100644 index 5a208a876a..0000000000 --- a/app/views/spree/admin/reports/filters/_payments.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -= render 'spree/admin/reports/date_range_form', f: f - -.row - .alpha.two.columns= label_tag nil, t(:report_distributor) - .omega.fourteen.columns - = f.collection_select(:distributor_id_eq, @distributors, :id, :name, {:include_blank => t(:report_all)}, {:class => "select2 fullwidth light"}) diff --git a/app/views/spree/admin/reports/filters/_sales_tax.html.haml b/app/views/spree/admin/reports/filters/_sales_tax.html.haml deleted file mode 100644 index bdd7a00f97..0000000000 --- a/app/views/spree/admin/reports/filters/_sales_tax.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -= render 'spree/admin/reports/date_range_form', f: f - -.row - .alpha.two.columns= label_tag nil, t(:report_distributor) - .omega.fourteen.columns - = f.collection_select(:distributor_id_eq, @distributors, :id, :name, {:include_blank => t(:all)}, {:class => "select2 fullwidth light"}) diff --git a/app/views/spree/admin/reports/index.html.haml b/app/views/spree/admin/reports/index.html.haml deleted file mode 100644 index 533eab2de1..0000000000 --- a/app/views/spree/admin/reports/index.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -- content_for :page_title do - = t(:listing_reports) - -.columns.twelve - %table.index - %thead - %tr - %th= t(:name) - %th= t(:description) - %tbody - - @reports.each do |key, value| - %tr - %td= link_to value[:name], value[:url] - %td= value[:description] diff --git a/app/views/spree/admin/reports/show.html.haml b/app/views/spree/admin/reports/show.html.haml deleted file mode 100644 index 5ea2ed207e..0000000000 --- a/app/views/spree/admin/reports/show.html.haml +++ /dev/null @@ -1,19 +0,0 @@ - -= form_for @report.search, :url => url_for(only_path: false) do |f| - - %fieldset.no-border-bottom.print-hidden - %legend{ align: 'center'}= t(:report_filters) - = render partial: "spree/admin/reports/filters/#{action_name}", locals: { f: f } - - %fieldset.print-hidden - %legend{ align: 'center'}= t(:report_render_options) - = render partial: "admin/reports/rendering_options" - - .actions.filter-actions - = button t(:go), "report__submit-btn" - -- if @report_message.present? - %p.report__message.print-hidden= @report_message - -- if render_content? - = render "table" diff --git a/app/views/spree/admin/shared/_tabs.html.haml b/app/views/spree/admin/shared/_tabs.html.haml index eb653c2500..696a3d29a6 100644 --- a/app/views/spree/admin/shared/_tabs.html.haml +++ b/app/views/spree/admin/shared/_tabs.html.haml @@ -2,7 +2,7 @@ = tab :products, :properties, :inventory, :product_import, :images, :variants, :product_properties, :group_buy_options, :seo, url: admin_products_path, icon: 'icon-th-large' = tab :order_cycles, url: main_app.admin_order_cycles_path, icon: 'icon-refresh' = tab :orders, :subscriptions, :customer_details, :adjustments, :payments, :return_authorizations, url: admin_orders_path('q[s]' => 'completed_at desc'), icon: 'icon-shopping-cart' -= tab :reports, icon: 'icon-file' += tab :reports, url: main_app.admin_reports_path, icon: 'icon-file' = tab :general_settings, :mail_methods, :tax_categories, :tax_rates, :tax_settings, :zones, :countries, :states, :payment_methods, :taxonomies, :shipping_methods, :shipping_categories, :enterprise_fees, :contents, :invoice_settings, :matomo_settings, :stripe_connect_settings, label: 'configuration', icon: 'icon-wrench', url: edit_admin_general_settings_path = tab :enterprises, :enterprise_relationships, url: main_app.admin_enterprises_path = tab :customers, url: main_app.admin_customers_path diff --git a/config/locales/en.yml b/config/locales/en.yml index cfa48a650c..bdd2bebcc3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1285,7 +1285,6 @@ en: name: Customers products_and_inventory: name: Products & Inventory - description: users_and_enterprises: name: Users & Enterprises description: Enterprise Ownership & Status @@ -1330,8 +1329,6 @@ en: hide_summary_rows: "Hide summary Rows" packing: name: "Packing Reports" - customer_report: "Pack By Customer" - supplier_report: "Pack By Supplier" subscriptions: index: title: "Subscriptions" diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 954b2861d4..7fc4afcd8f 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -115,6 +115,7 @@ Openfoodnetwork::Application.routes.draw do put :resume, on: :member, format: :json end - match '/reports/:report_type(/:report_subtype)', to: 'reports#show', via: [:get, :post], as: :reports + get '/reports', to: 'reports#index', as: :reports + match '/reports/:report_type(/:report_subtype)', to: 'reports#show', via: [:get, :post], as: :report end end diff --git a/config/routes/spree.rb b/config/routes/spree.rb index 952b3a423f..eed705f5d6 100644 --- a/config/routes/spree.rb +++ b/config/routes/spree.rb @@ -31,19 +31,6 @@ Spree::Core::Engine.routes.draw do resource :account, :controller => 'users' - match '/admin/reports/orders_and_distributors' => 'admin/reports#orders_and_distributors', :as => "orders_and_distributors_admin_reports", :via => [:get, :post] - match '/admin/reports/order_cycle_management' => 'admin/reports#order_cycle_management', :as => "order_cycle_management_admin_reports", :via => [:get, :post] - match '/admin/reports/group_buys' => 'admin/reports#group_buys', :as => "group_buys_admin_reports", :via => [:get, :post] - match '/admin/reports/bulk_coop' => 'admin/reports#bulk_coop', :as => "bulk_coop_admin_reports", :via => [:get, :post] - match '/admin/reports/payments' => 'admin/reports#payments', :as => "payments_admin_reports", :via => [:get, :post] - match '/admin/reports/orders_and_fulfillment' => 'admin/reports#orders_and_fulfillment', :as => "orders_and_fulfillment_admin_reports", :via => [:get, :post] - match '/admin/reports/users_and_enterprises' => 'admin/reports#users_and_enterprises', :as => "users_and_enterprises_admin_reports", :via => [:get, :post] - match '/admin/reports/sales_tax' => 'admin/reports#sales_tax', :as => "sales_tax_admin_reports", :via => [:get, :post] - match '/admin/reports/products_and_inventory' => 'admin/reports#products_and_inventory', :as => "products_and_inventory_admin_reports", :via => [:get, :post] - match '/admin/reports/customers' => 'admin/reports#customers', :as => "customers_admin_reports", :via => [:get, :post] - match '/admin/reports/xero_invoices' => 'admin/reports#xero_invoices', :as => "xero_invoices_admin_reports", :via => [:get, :post] - match '/admin/reports/enterprise_fee_summary' => 'admin/reports#enterprise_fee_summary', :as => "enterprise_fee_summary_admin_reports", :via => [:get, :post] - match '/admin/orders/bulk_management' => 'admin/orders#bulk_management', :as => "admin_bulk_order_management", via: :get match '/admin/payment_methods/show_provider_preferences' => 'admin/payment_methods#show_provider_preferences', :via => :get put 'credit_cards/new_from_token', to: 'credit_cards#new_from_token' @@ -127,8 +114,6 @@ Spree::Core::Engine.routes.draw do end end - resources :reports, only: :index - resources :users do member do put :generate_api_key diff --git a/lib/reporting/frontend_data.rb b/lib/reporting/frontend_data.rb index aad748b1ff..a702a91de0 100644 --- a/lib/reporting/frontend_data.rb +++ b/lib/reporting/frontend_data.rb @@ -6,18 +6,39 @@ module Reporting @current_user = current_user end + # Load managed distributor enterprises of current user def distributors - permissions.visible_enterprises_for_order_reports.is_distributor. - select("enterprises.id, enterprises.name") + @distributors ||= Enterprise.is_distributor.managed_by(current_user) end def suppliers - permissions.visible_enterprises_for_order_reports.is_primary_producer. - select("enterprises.id, enterprises.name") + @suppliers ||= my_suppliers | suppliers_of_products_distributed_by(distributors) + end + + # Load managed producer enterprises of current user + def my_suppliers + Enterprise.is_primary_producer.managed_by(current_user) + end + + def suppliers_of_products_distributed_by(distributors) + supplier_ids = Spree::Product.in_distributors(distributors.select('enterprises.id')). + select('spree_products.supplier_id') + + Enterprise.where(id: supplier_ids) + end + + def orders_distributors + @orders_distributors ||= permissions.visible_enterprises_for_order_reports + .is_distributor.select("enterprises.id, enterprises.name") + end + + def orders_suppliers + @orders_suppliers ||= permissions.visible_enterprises_for_order_reports + .is_primary_producer.select("enterprises.id, enterprises.name") end def order_cycles - OrderCycle. + @order_cycles ||= OrderCycle. active_or_complete. visible_by(current_user). order('order_cycles.orders_close_at DESC') diff --git a/lib/reporting/report_loader.rb b/lib/reporting/report_loader.rb index b40f03b4ca..29dee2f794 100644 --- a/lib/reporting/report_loader.rb +++ b/lib/reporting/report_loader.rb @@ -2,21 +2,22 @@ module Reporting class ReportLoader - delegate :report_subtypes, to: :base_class - def initialize(report_type, report_subtype = nil) @report_type = report_type - @report_subtype = report_subtype + @report_subtype = report_subtype || "base" end + # We currently use two types of report : old report inheriting from ReportObjectReport + # And new ones inheriting gtom ReportQueryReport + # They use different namespace and we try to load them both def report_class - "#{report_module}::#{report_subtype_class}".constantize + "#{report_module}::#{report_type.camelize}Report".constantize rescue NameError - raise Reporting::Errors::ReportNotFound - end - - def default_report_subtype - report_subtypes.first || "base" + begin + "#{report_module}::#{report_subtype.camelize}".constantize + rescue NameError + raise Reporting::Errors::ReportNotFound + end end private @@ -26,15 +27,5 @@ module Reporting def report_module "Reporting::Reports::#{report_type.camelize}" end - - def report_subtype_class - (report_subtype || default_report_subtype).camelize - end - - def base_class - "#{report_module}::Base".constantize - rescue NameError - raise Reporting::Errors::ReportNotFound - end end end diff --git a/lib/reporting/report_object_template.rb b/lib/reporting/report_object_template.rb index 1eba55a0f9..e9d6e2bbb5 100644 --- a/lib/reporting/report_object_template.rb +++ b/lib/reporting/report_object_template.rb @@ -5,13 +5,6 @@ module Reporting class ReportObjectTemplate < ReportTemplate - attr_accessor :user, :params - - def initialize(user, params = {}) - @user = user - @params = params - end - def table_headers raise NotImplementedError end @@ -20,12 +13,6 @@ module Reporting raise NotImplementedError end - # If the report object do not use ransack search, create a fake one just for the form_for - # in reports/show.haml - def search - Ransack::Search.new(Spree::Order) - end - # Rules for grouping, ordering, and summary rows def rules [] diff --git a/lib/reporting/report_query_template.rb b/lib/reporting/report_query_template.rb index f4b79d22aa..79cea83f86 100644 --- a/lib/reporting/report_query_template.rb +++ b/lib/reporting/report_query_template.rb @@ -3,20 +3,6 @@ # This is the new report template that use QueryBuilder to directly get the data from the DB module Reporting class ReportQueryTemplate < ReportTemplate - attr_reader :options - - SUBTYPES = [].freeze - - 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 @@ -33,32 +19,30 @@ module Reporting report_data.rows end - private - - attr_reader :current_user, :ransack_params - - def ransacked_orders_relation - visible_orders_relation.ransack(ransack_params).result + def search + visible_line_items_relation.ransack(ransack_params) end + private + def ransacked_line_items_relation - visible_line_items_relation.ransack(ransack_params).result + search.result end def visible_orders_relation - ::Permissions::Order.new(current_user). + ::Permissions::Order.new(user). visible_orders.complete.not_state(:canceled). select(:id).distinct end def visible_line_items_relation - ::Permissions::Order.new(current_user). + ::Permissions::Order.new(user). visible_line_items. select(:id).distinct end def managed_orders_relation - ::Enterprise.managed_by(current_user).select(:id).distinct + ::Enterprise.managed_by(user).select(:id).distinct end def i18n_scope diff --git a/lib/reporting/report_template.rb b/lib/reporting/report_template.rb index 91c3fb73b2..2e961331f4 100644 --- a/lib/reporting/report_template.rb +++ b/lib/reporting/report_template.rb @@ -7,6 +7,15 @@ module Reporting delegate :as_json, :as_arrays, :to_csv, :to_xlsx, :to_ods, :to_pdf, :to_json, to: :renderer + OPTIONAL_HEADERS = [].freeze + + def initialize(user, params) + @user = user + @params = params || {} + @params = @params.permit!.to_h unless @params.is_a? Hash + @ransack_params = @params[:q] || {} + end + def table_headers raise NotImplementedError end @@ -15,6 +24,18 @@ module Reporting raise NotImplementedError end + # Message to be displayed at the top of rendered table + def message + "" + end + + # Ransack search to get base ActiveRelation + # If the report object do not use ransack search, create a fake one just for the form_for + # in reports/show.haml + def search + Ransack::Search.new(Spree::Order) + end + private def renderer diff --git a/lib/reporting/reports/bulk_coop/bulk_coop_report.rb b/lib/reporting/reports/bulk_coop/bulk_coop_report.rb index be15a9bdd4..62cb436868 100644 --- a/lib/reporting/reports/bulk_coop/bulk_coop_report.rb +++ b/lib/reporting/reports/bulk_coop/bulk_coop_report.rb @@ -4,7 +4,6 @@ module Reporting module Reports module BulkCoop class BulkCoopReport < ReportObjectTemplate - def initialize(user, params = {}) super(user, params) @@ -13,6 +12,10 @@ module Reporting @filter_canceled = false end + def message + I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") + end + def table_headers case params[:report_subtype] when "bulk_coop_supplier_report" diff --git a/lib/reporting/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb b/lib/reporting/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb index 4298533d8e..69f443aca8 100644 --- a/lib/reporting/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb +++ b/lib/reporting/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb @@ -19,6 +19,10 @@ module Reporting @parameters.authorize!(@permissions) end + def message + I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") + end + def table_headers data_row_attributes.map do |attribute| header_label(attribute) diff --git a/lib/reporting/reports/list.rb b/lib/reporting/reports/list.rb index 917282b7db..e8055ca9d8 100644 --- a/lib/reporting/reports/list.rb +++ b/lib/reporting/reports/list.rb @@ -13,14 +13,14 @@ module Reporting bulk_coop: bulk_coop_report_types, payments: payments_report_types, orders_and_fulfillment: orders_and_fulfillment_report_types, - products_and_inventory: products_and_inventory_report_types, customers: customers_report_types, - enterprise_fee_summary: enterprise_fee_summary_report_types, + products_and_inventory: products_and_inventory_report_types, + users_and_enterprises: [], + enterprise_fee_summary: [], order_cycle_management: order_cycle_management_report_types, sales_tax: sales_tax_report_types, - packing: packing_report_types, xero_invoices: xero_report_types, - bulk_coop: bulk_coop_report_types + packing: packing_report_types } end @@ -59,12 +59,6 @@ module Reporting ] end - def enterprise_fee_summary_report_types - [ - [i18n_translate("enterprise_fee_summary"), :enterprise_fee_summary] - ] - end - def order_cycle_management_report_types [ [i18n_translate("payment_methods"), :payment_methods], @@ -81,14 +75,16 @@ module Reporting def packing_report_types [ - [i18n_translate("pack_by_customer"), :pack_by_customer], - [i18n_translate("pack_by_supplier"), :pack_by_supplier] + [i18n_translate("pack_by_customer"), :customer], + [i18n_translate("pack_by_supplier"), :supplier] ] end def xero_report_types - [[I18n.t(:summary), 'summary'], - [I18n.t(:detailed), 'detailed']] + [ + [I18n.t(:summary), 'summary'], + [I18n.t(:detailed), 'detailed'] + ] end def bulk_coop_report_types diff --git a/lib/reporting/reports/orders_and_fulfillment/customer_totals_report.rb b/lib/reporting/reports/orders_and_fulfillment/customer_totals_report.rb index ada0f6d533..b352fd338e 100644 --- a/lib/reporting/reports/orders_and_fulfillment/customer_totals_report.rb +++ b/lib/reporting/reports/orders_and_fulfillment/customer_totals_report.rb @@ -5,6 +5,8 @@ module Reporting module Reports module OrdersAndFulfillment class CustomerTotalsReport + include ReportsHelper + REPORT_TYPE = "order_cycle_customer_totals" attr_reader :context diff --git a/lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb b/lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb index d99767f474..7089dd911a 100644 --- a/lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb +++ b/lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -include Spree::ReportsHelper - module Reporting module Reports module OrdersAndFulfillment class OrdersAndFulfillmentReport < ReportObjectTemplate + include ReportsHelper + attr_reader :report_type delegate :table_headers, :rules, :columns, to: :report @@ -21,6 +21,10 @@ module Reporting } end + def message + I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") + end + def search report_line_items.orders end diff --git a/lib/reporting/reports/packing/base.rb b/lib/reporting/reports/packing/base.rb index 5518906120..f5fb7515f3 100644 --- a/lib/reporting/reports/packing/base.rb +++ b/lib/reporting/reports/packing/base.rb @@ -4,7 +4,9 @@ module Reporting module Reports module Packing class Base < ReportQueryTemplate - SUBTYPES = ["customer", "supplier"] + def message + I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") + end def primary_model Spree::LineItem diff --git a/lib/reporting/reports/sales_tax/sales_tax_report.rb b/lib/reporting/reports/sales_tax/sales_tax_report.rb index ad07846920..e2c44eb147 100644 --- a/lib/reporting/reports/sales_tax/sales_tax_report.rb +++ b/lib/reporting/reports/sales_tax/sales_tax_report.rb @@ -4,7 +4,7 @@ module Reporting module Reports module SalesTax class SalesTaxReport < ReportObjectTemplate - include Spree::ReportsHelper + include ReportsHelper def table_headers case params[:report_subtype] diff --git a/lib/reporting/reports/xero_invoices/xero_invoices_report.rb b/lib/reporting/reports/xero_invoices/xero_invoices_report.rb index 2da61f0f78..696857e918 100644 --- a/lib/reporting/reports/xero_invoices/xero_invoices_report.rb +++ b/lib/reporting/reports/xero_invoices/xero_invoices_report.rb @@ -45,10 +45,6 @@ module Reporting private - def report_options - params.merge(line_item_includes: line_item_includes) - end - def line_item_includes [:bill_address, :adjustments, { line_items: { variant: [{ option_values: :option_type }, { product: :supplier }] } }] diff --git a/spec/controllers/spree/admin/reports_controller_spec.rb b/spec/controllers/admin/reports_controller_spec.rb similarity index 75% rename from spec/controllers/spree/admin/reports_controller_spec.rb rename to spec/controllers/admin/reports_controller_spec.rb index 50bb50cac4..3ad47e99d7 100644 --- a/spec/controllers/spree/admin/reports_controller_spec.rb +++ b/spec/controllers/admin/reports_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Spree::Admin::ReportsController, type: :controller do +describe Admin::ReportsController, type: :controller do # Given two distributors and two suppliers let(:bill_address) { create(:address) } let(:ship_address) { create(:address) } @@ -22,17 +22,20 @@ describe Spree::Admin::ReportsController, type: :controller do # Given two order cycles with both distributors let(:ocA) { create(:simple_order_cycle, coordinator: coordinator1, distributors: [distributor1, distributor2], - suppliers: [supplier1, supplier2, supplier3], variants: [product1.master, product3.master]) + suppliers: [supplier1, supplier2, supplier3], + variants: [product1.master, product3.master]) } let(:ocB) { create(:simple_order_cycle, coordinator: coordinator2, distributors: [distributor1, distributor2], - suppliers: [supplier1, supplier2, supplier3], variants: [product2.master]) + suppliers: [supplier1, supplier2, supplier3], + variants: [product2.master]) } # orderA1 can only be accessed by supplier1, supplier3 and distributor1 let(:orderA1) do order = create(:order, distributor: distributor1, bill_address: bill_address, - ship_address: ship_address, special_instructions: instructions, order_cycle: ocA) + ship_address: ship_address, special_instructions: instructions, + order_cycle: ocA) order.line_items << create(:line_item, variant: product1.master) order.line_items << create(:line_item, variant: product3.master) order.finalize! @@ -42,7 +45,8 @@ describe Spree::Admin::ReportsController, type: :controller do # orderA2 can only be accessed by supplier2 and distributor2 let(:orderA2) do order = create(:order, distributor: distributor2, bill_address: bill_address, - ship_address: ship_address, special_instructions: instructions, order_cycle: ocA) + ship_address: ship_address, special_instructions: instructions, + order_cycle: ocA) order.line_items << create(:line_item, variant: product2.master) order.finalize! order.save @@ -51,7 +55,8 @@ describe Spree::Admin::ReportsController, type: :controller do # orderB1 can only be accessed by supplier1, supplier3 and distributor1 let(:orderB1) do order = create(:order, distributor: distributor1, bill_address: bill_address, - ship_address: ship_address, special_instructions: instructions, order_cycle: ocB) + ship_address: ship_address, special_instructions: instructions, + order_cycle: ocB) order.line_items << create(:line_item, variant: product1.master) order.line_items << create(:line_item, variant: product3.master) order.finalize! @@ -61,7 +66,8 @@ describe Spree::Admin::ReportsController, type: :controller do # orderB2 can only be accessed by supplier2 and distributor2 let(:orderB2) do order = create(:order, distributor: distributor2, bill_address: bill_address, - ship_address: ship_address, special_instructions: instructions, order_cycle: ocB) + ship_address: ship_address, special_instructions: instructions, + order_cycle: ocB) order.line_items << create(:line_item, variant: product2.master) order.finalize! order.save @@ -81,8 +87,7 @@ describe Spree::Admin::ReportsController, type: :controller do describe 'Orders & Fulfillment' do it "shows all orders in order cycles I coordinate" do - spree_post :orders_and_fulfillment, q: {} - + spree_post :show, report_type: :orders_and_fulfillment, q: {} expect(resulting_orders).to include orderA1, orderA2 expect(resulting_orders).not_to include orderB1, orderB2 end @@ -97,7 +102,7 @@ describe Spree::Admin::ReportsController, type: :controller do let!(:present_objects) { [orderA1, orderA2, orderB1, orderB2] } it "only shows orders that I have access to" do - spree_post :orders_and_distributors + spree_post :show, report_type: :orders_and_distributors expect(assigns(:report).search.result).to include(orderA1, orderB1) expect(assigns(:report).search.result).not_to include(orderA2) @@ -109,7 +114,7 @@ describe Spree::Admin::ReportsController, type: :controller do let!(:present_objects) { [orderA1, orderA2, orderB1, orderB2] } it "only shows orders that I have access to" do - spree_post :payments + spree_post :show, report_type: :payments expect(resulting_orders_prelim).to include(orderA1, orderB1) expect(resulting_orders_prelim).not_to include(orderA2) @@ -122,7 +127,7 @@ describe Spree::Admin::ReportsController, type: :controller do let!(:present_objects) { [orderA1, orderA2, orderB1, orderB2] } it "only shows orders that I distribute" do - spree_post :orders_and_fulfillment, q: {} + spree_post :show, report_type: :orders_and_fulfillment, q: {} expect(resulting_orders).to include orderA1, orderB1 expect(resulting_orders).not_to include orderA2, orderB2 @@ -133,7 +138,8 @@ describe Spree::Admin::ReportsController, type: :controller do let!(:present_objects) { [orderA1, orderB1] } it "only shows the selected order cycle" do - spree_post :orders_and_fulfillment, q: { order_cycle_id_in: [ocA.id.to_s] } + spree_post :show, report_type: :orders_and_fulfillment, + q: { order_cycle_id_in: [ocA.id.to_s] } expect(resulting_orders).to include(orderA1) expect(resulting_orders).not_to include(orderB1) @@ -166,14 +172,14 @@ describe Spree::Admin::ReportsController, type: :controller do end it "only shows product line items that I am supplying" do - spree_post :orders_and_fulfillment, q: {} + spree_post :show, report_type: :orders_and_fulfillment, q: {} expect(resulting_products).to include product1 expect(resulting_products).not_to include product2, product3 end it "only shows the selected order cycle" do - spree_post :orders_and_fulfillment, q: { order_cycle_id_eq: ocA.id } + spree_post :show, report_type: :orders_and_fulfillment, q: { order_cycle_id_eq: ocA.id } expect(resulting_orders_prelim).to include(orderA1) expect(resulting_orders_prelim).not_to include(orderB1) @@ -183,7 +189,7 @@ describe Spree::Admin::ReportsController, type: :controller do before { orderA1.line_items.first.product.destroy } it "only shows product line items that I am supplying" do - spree_post :orders_and_fulfillment, q: {} + spree_post :show, report_type: :orders_and_fulfillment, q: {} table_items = assigns(:report).table_items variant = Spree::Variant.unscoped.find(table_items.first.variant_id) @@ -195,7 +201,7 @@ describe Spree::Admin::ReportsController, type: :controller do context "where I have not granted P-OC to the distributor" do it "does not show me line_items I supply" do - spree_post :orders_and_fulfillment + spree_post :show, report_type: :orders_and_fulfillment expect(resulting_products).not_to include product1, product2, product3 end @@ -212,13 +218,13 @@ describe Spree::Admin::ReportsController, type: :controller do let!(:present_objects) { [distributors, suppliers] } it "should build distributors for the current user" do - spree_get :products_and_inventory - expect(assigns(:distributors)).to match_array distributors + spree_get :show, report_type: :products_and_inventory + expect(assigns(:data).distributors).to match_array distributors end it "builds suppliers for the current user" do - spree_get :products_and_inventory - expect(assigns(:suppliers)).to match_array suppliers + spree_get :show, report_type: :products_and_inventory + expect(assigns(:data).suppliers).to match_array suppliers end end @@ -226,25 +232,22 @@ describe Spree::Admin::ReportsController, type: :controller do let!(:order_cycles) { [ocA, ocB] } it "builds order cycles for the current user" do - spree_get :products_and_inventory - expect(assigns(:order_cycles)).to match_array order_cycles + spree_get :show, report_type: :products_and_inventory + expect(assigns(:data).order_cycles).to match_array order_cycles end end it "assigns report types" do - spree_get :products_and_inventory - expect(assigns(:report_subtypes)).to eq(subject.report_types[:products_and_inventory]) + spree_get :show, report_type: :products_and_inventory + expect(assigns(:report_subtypes)).to eq(subject.reports[:products_and_inventory]) end it "creates a ProductAndInventoryReport" do - expect(Reporting::Reports::ProductsAndInventory::ProductsAndInventoryReport).to receive(:new) - .with(@admin_user, - { "test" => "foo", "controller" => "spree/admin/reports", "report" => {}, - "action" => "products_and_inventory", "use_route" => "main_app" }, false) + allow(Reporting::Reports::ProductsAndInventory::ProductsAndInventoryReport).to receive(:new) .and_return(report = double(:report)) allow(report).to receive(:table_headers).and_return [] allow(report).to receive(:table_rows).and_return [] - spree_get :products_and_inventory, test: "foo" + spree_get :show, report_type: :products_and_inventory, test: "foo" expect(assigns(:report)).to eq(report) end end @@ -253,10 +256,10 @@ describe Spree::Admin::ReportsController, type: :controller do before { controller_login_as_admin } it "should have report types for customers" do - expect(subject.report_types[:customers]).to eq([ - ["Mailing List", :mailing_list], - ["Addresses", :addresses] - ]) + expect(subject.reports[:customers]).to eq([ + ["Mailing List", :mailing_list], + ["Addresses", :addresses] + ]) end context "with distributors and suppliers" do @@ -265,13 +268,13 @@ describe Spree::Admin::ReportsController, type: :controller do let!(:present_objects) { [distributors, suppliers] } it "should build distributors for the current user" do - spree_get :customers - expect(assigns(:distributors)).to match_array distributors + spree_get :show, report_type: :customers + expect(assigns(:data).distributors).to match_array distributors end it "builds suppliers for the current user" do - spree_get :customers - expect(assigns(:suppliers)).to match_array suppliers + spree_get :show, report_type: :customers + expect(assigns(:data).suppliers).to match_array suppliers end end @@ -279,25 +282,22 @@ describe Spree::Admin::ReportsController, type: :controller do let!(:order_cycles) { [ocA, ocB] } it "builds order cycles for the current user" do - spree_get :customers - expect(assigns(:order_cycles)).to match_array order_cycles + spree_get :show, report_type: :customers + expect(assigns(:data).order_cycles).to match_array order_cycles end end it "assigns report types" do - spree_get :customers - expect(assigns(:report_subtypes)).to eq(subject.report_types[:customers]) + spree_get :show, report_type: :customers + expect(assigns(:report_subtypes)).to eq(subject.reports[:customers]) end it "creates a CustomersReport" do - expect(Reporting::Reports::Customers::CustomersReport).to receive(:new) - .with(@admin_user, { "test" => "foo", "controller" => "spree/admin/reports", - "action" => "customers", "use_route" => "main_app", - "report" => {} }, false) + allow(Reporting::Reports::Customers::CustomersReport).to receive(:new) .and_return(report = double(:report)) allow(report).to receive(:table_headers).and_return [] allow(report).to receive(:table_rows).and_return [] - spree_get :customers, test: "foo" + spree_get :show, report_type: :customers, test: "foo" expect(assigns(:report)).to eq(report) end end @@ -310,9 +310,10 @@ describe Spree::Admin::ReportsController, type: :controller do end it 'renders the delivery report' do - spree_post :order_cycle_management, { + spree_post :show, { q: { completed_at_lt: 1.day.ago }, shipping_method_in: ["123"], # We just need to search for shipping methods + report_type: :order_cycle_management, report_subtype: "delivery", } @@ -327,20 +328,20 @@ describe Spree::Admin::ReportsController, type: :controller do let!(:present_objects) { [coordinator1] } it "shows report search forms" do - spree_get :users_and_enterprises - expect(assigns(:report).table_rows).to eq [] + spree_get :show, report_type: :users_and_enterprises + expect(response).to have_http_status(:ok) end it "shows report data" do - spree_post :users_and_enterprises, q: {} + spree_post :show, report_type: :users_and_enterprises, q: {} expect(assigns(:report).table_rows.empty?).to be false end end describe "sales_tax" do it "shows report search forms" do - spree_get :sales_tax - expect(assigns(:report).table_rows).to eq [] + spree_get :show, report_type: :sales_tax + expect(response).to have_http_status(:ok) end end end diff --git a/spec/controllers/api/v0/reports_controller_spec.rb b/spec/controllers/api/v0/reports_controller_spec.rb index 23ddfabf21..09ba607f4b 100644 --- a/spec/controllers/api/v0/reports_controller_spec.rb +++ b/spec/controllers/api/v0/reports_controller_spec.rb @@ -63,7 +63,6 @@ describe Api::V0::ReportsController, type: :controller do it "returns an error" do api_get :show, report_type: "packing" - expect(response.status).to eq 422 expect(json_response["error"]).to eq( I18n.t('errors.missing_ransack_params', scope: i18n_scope) diff --git a/spec/helpers/spree/admin/reports_helper_spec.rb b/spec/helpers/admin/reports_helper_spec.rb similarity index 94% rename from spec/helpers/spree/admin/reports_helper_spec.rb rename to spec/helpers/admin/reports_helper_spec.rb index a4460e2b4a..9ea424bc48 100644 --- a/spec/helpers/spree/admin/reports_helper_spec.rb +++ b/spec/helpers/admin/reports_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Spree::ReportsHelper, type: :helper do +describe ReportsHelper, type: :helper do describe "#report_payment_method_options" do let(:order_with_payments) { create(:order_ready_to_ship) } let(:order_without_payments) { create(:order_with_line_items) } diff --git a/spec/helpers/navigation_helper_spec.rb b/spec/helpers/navigation_helper_spec.rb index 90199a1fe6..ee01b3204a 100644 --- a/spec/helpers/navigation_helper_spec.rb +++ b/spec/helpers/navigation_helper_spec.rb @@ -14,8 +14,8 @@ module Spree expect(helper.klass_for('lions')).to eq(:lion) end - it "returns Spree::Admin::ReportsController for reports" do - expect(helper.klass_for('reports')).to eq(Spree::Admin::ReportsController) + it "returns Admin::ReportsController for reports" do + expect(helper.klass_for('reports')).to eq(::Admin::ReportsController) end it "returns :overview for the dashboard" do diff --git a/spec/lib/reports/packing/packing_report_spec.rb b/spec/lib/reports/packing/packing_report_spec.rb index d661fbb586..9902d3ebe2 100644 --- a/spec/lib/reports/packing/packing_report_spec.rb +++ b/spec/lib/reports/packing/packing_report_spec.rb @@ -20,7 +20,7 @@ describe "Packing Reports" do let(:report_contents) { subject.report_data.rows.flatten } let(:row_count) { subject.report_data.rows.count } - subject { Reporting::Reports::Packing::Customer.new user, params } + subject { Reporting::Reports::Packing::Customer.new user, { q: params } } before do order.line_items << line_item diff --git a/spec/lib/reports/report_loader_spec.rb b/spec/lib/reports/report_loader_spec.rb index 5341ae641f..1bb8ff00ad 100644 --- a/spec/lib/reports/report_loader_spec.rb +++ b/spec/lib/reports/report_loader_spec.rb @@ -9,6 +9,10 @@ module Reporting class Green; end class Yellow; end end + + module Orange + class OrangeReport; end + end end end @@ -17,10 +21,6 @@ describe Reporting::ReportLoader do let(:report_base_class) { Reporting::Reports::Bananas::Base } let(:report_subtypes) { ["green", "yellow"] } - before do - allow(report_base_class).to receive(:report_subtypes).and_return(report_subtypes) - end - describe "#report_class" do describe "given report type and subtype" do let(:arguments) { ["bananas", "yellow"] } @@ -30,18 +30,17 @@ describe Reporting::ReportLoader do end end - describe "given report type only" do - context "when the report has multiple subtypes" do - let(:arguments) { ["bananas"] } + describe "given report type and subtype for old reports" do + let(:arguments) { ["orange", "subtype"] } - it "returns first listed report type" do - expect(service.report_class).to eq Reporting::Reports::Bananas::Green - end + it "returns a report class when given type and subtype" do + expect(service.report_class).to eq Reporting::Reports::Orange::OrangeReport end + end + describe "given report type only" do context "when the report has no subtypes" do let(:arguments) { ["bananas"] } - let(:report_subtypes) { [] } it "returns base class" do expect(service.report_class).to eq Reporting::Reports::Bananas::Base @@ -50,7 +49,6 @@ describe Reporting::ReportLoader do context "given a report type that does not exist" do let(:arguments) { ["apples"] } - let(:report_subtypes) { [] } it "raises an error" do expect{ service.report_class }.to raise_error(Reporting::Errors::ReportNotFound) @@ -58,51 +56,4 @@ describe Reporting::ReportLoader do end end end - - describe "#default_report_subtype" do - context "when the report has multiple subtypes" do - let(:arguments) { ["bananas"] } - - it "returns the first report type" do - expect(service.default_report_subtype).to eq report_base_class.report_subtypes.first - end - end - - context "when the report has no subtypes" do - let(:arguments) { ["bananas"] } - let(:report_subtypes) { [] } - - it "returns base" do - expect(service.default_report_subtype).to eq "base" - end - end - - context "given a report type that does not exist" do - let(:arguments) { ["apples"] } - let(:report_subtypes) { [] } - - it "raises an error" do - expect{ service.report_class }.to raise_error(Reporting::Errors::ReportNotFound) - end - end - end - - describe "#report_subtypes" do - context "when the report has multiple subtypes" do - let(:arguments) { ["bananas"] } - - it "returns a list of report subtypes for a given report" do - expect(service.report_subtypes).to eq report_subtypes - end - end - - context "when the report has no subtypes" do - let(:arguments) { ["bananas"] } - let(:report_subtypes) { [] } - - it "returns an empty array" do - expect(service.report_subtypes).to eq [] - end - end - end end diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index 07805b6f9a..c4f754f6d9 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -5,8 +5,6 @@ require 'cancan/matchers' require 'support/ability_helpers' describe Spree::Ability do - include ::AbilityHelper - let(:user) { create(:user) } let(:subject) { Spree::Ability.new(user) } let(:token) { nil } @@ -441,8 +439,11 @@ describe Spree::Ability do it "should be able to read some reports" do is_expected.to have_ability( - [:admin, :index, :customers, :bulk_coop, :orders_and_fulfillment, :products_and_inventory, - :order_cycle_management], for: Spree::Admin::ReportsController + [:admin, :index, :show], for: Admin::ReportsController + ) + is_expected.to have_ability( + [:customers, :bulk_coop, :orders_and_fulfillment, :products_and_inventory, + :order_cycle_management], for: :report ) end @@ -451,7 +452,7 @@ describe Spree::Ability do it "should not be able to read other reports" do is_expected.not_to have_ability( [:group_buys, :payments, :orders_and_distributors, :users_and_enterprises, - :xero_invoices], for: Spree::Admin::ReportsController + :xero_invoices], for: :report ) end @@ -671,8 +672,12 @@ describe Spree::Ability do it "should be able to read some reports" do is_expected.to have_ability( - [:admin, :index, :customers, :sales_tax, :group_buys, :bulk_coop, :payments, - :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :xero_invoices], for: Spree::Admin::ReportsController + [:admin, :index, :show], for: Admin::ReportsController + ) + is_expected.to have_ability( + [:customers, :sales_tax, :group_buys, :bulk_coop, :payments, + :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, + :order_cycle_management, :xero_invoices], for: :report ) end @@ -680,7 +685,7 @@ describe Spree::Ability do it "should not be able to read other reports" do is_expected.not_to have_ability([:users_and_enterprises], - for: Spree::Admin::ReportsController) + for: :report) end it "should be able to access customer actions" do diff --git a/spec/support/ability_helper.rb b/spec/support/ability_helper.rb deleted file mode 100644 index aa1d17fd14..0000000000 --- a/spec/support/ability_helper.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module AbilityHelper - shared_examples "allows access to Enterprise Fee Summary" do - it "should be able to see link and read report" do - is_expected.to have_link_to_enterprise_fee_summary - is_expected.to have_direct_access_to_enterprise_fee_summary - end - - def have_link_to_enterprise_fee_summary - have_ability([:enterprise_fee_summary], for: Spree::Admin::ReportsController) - end - - def have_direct_access_to_enterprise_fee_summary - have_ability([:admin, :new, :create], for: :enterprise_fee_summary) - end - end -end diff --git a/spec/support/ability_helpers.rb b/spec/support/ability_helpers.rb index 4dcb840fe7..c474c8363d 100644 --- a/spec/support/ability_helpers.rb +++ b/spec/support/ability_helpers.rb @@ -105,3 +105,18 @@ shared_examples_for 'update only' do expect(subject).to_not be_able_to(:index, resource) end end + +shared_examples "allows access to Enterprise Fee Summary" do + it "should be able to see link and read report" do + is_expected.to have_link_to_enterprise_fee_summary + is_expected.to have_direct_access_to_enterprise_fee_summary + end + + def have_link_to_enterprise_fee_summary + have_ability([:enterprise_fee_summary], for: :report) + end + + def have_direct_access_to_enterprise_fee_summary + have_ability([:show, :index], for: Admin::ReportsController) + end +end diff --git a/spec/system/admin/reports_spec.rb b/spec/system/admin/reports_spec.rb index f2c372d129..9ffcccaa97 100644 --- a/spec/system/admin/reports_spec.rb +++ b/spec/system/admin/reports_spec.rb @@ -33,7 +33,7 @@ describe ' describe "Customers report" do before do - login_as_admin_and_visit spree.admin_reports_path + login_as_admin_and_visit admin_reports_path end it "customers report" do @@ -64,7 +64,7 @@ describe ' describe "Order cycle management report" do before do - login_as_admin_and_visit spree.admin_reports_path + login_as_admin_and_visit admin_reports_path end it "payment method report" do @@ -92,7 +92,7 @@ describe ' end it "orders and distributors report" do - login_as_admin_and_visit spree.admin_reports_path + login_as_admin_and_visit admin_reports_path click_link 'Orders And Distributors' click_button 'Go' @@ -100,7 +100,7 @@ describe ' end it "payments reports" do - login_as_admin_and_visit spree.admin_reports_path + login_as_admin_and_visit admin_reports_path click_link 'Payment Reports' click_button 'Go' @@ -162,7 +162,7 @@ describe ' payment_method: create(:payment_method, distributors: [distributor1])) break unless order1.next! until order1.complete? - login_as_admin_and_visit spree.admin_reports_path + login_as_admin_and_visit admin_reports_path click_link "Sales Tax" select("Tax Types", from: "report_subtype") end @@ -198,7 +198,7 @@ describe ' describe "orders & fulfilment reports" do it "loads the report page" do - login_as_admin_and_visit spree.admin_reports_path + login_as_admin_and_visit admin_reports_path click_link 'Orders & Fulfillment Reports' expect(page).to have_content 'Supplier' @@ -293,7 +293,7 @@ describe ' end it "shows products and inventory report" do - login_as_admin_and_visit spree.admin_reports_path + login_as_admin_and_visit admin_reports_path expect(page).to have_content "All products" expect(page).to have_content "Inventory (on hand)" @@ -321,7 +321,7 @@ describe ' end it "shows the LettuceShare report" do - login_as_admin_and_visit spree.admin_reports_path + login_as_admin_and_visit admin_reports_path click_link 'LettuceShare' click_button "Go" @@ -341,7 +341,7 @@ describe ' before do enterprise3.enterprise_roles.build( user: enterprise1.owner ).save - login_as_admin_and_visit spree.admin_reports_path + login_as_admin_and_visit admin_reports_path click_link 'Users & Enterprises' end @@ -382,7 +382,7 @@ describe ' describe 'bulk coop report' do before do - login_as_admin_and_visit spree.admin_reports_path + login_as_admin_and_visit admin_reports_path click_link 'Bulk Co-Op' end @@ -539,7 +539,7 @@ describe ' order1.reload order1.create_tax_charge! - login_as_admin_and_visit spree.admin_reports_path + login_as_admin_and_visit admin_reports_path click_link 'Xero Invoices' end From caccbb698b5fd1588b780308a90811d810f127a4 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 30 Mar 2022 17:46:19 +0200 Subject: [PATCH 22/54] Reports Refactor 2: Fix linting --- lib/open_food_network/order_cycle_permissions.rb | 8 ++++---- lib/reporting/report_object_template.rb | 1 - lib/reporting/report_renderer.rb | 2 +- .../users_and_enterprises_report.rb | 1 - spec/controllers/admin/reports_controller_spec.rb | 9 ++++++--- spec/lib/reports/xero_invoices_report_spec.rb | 8 ++++---- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/open_food_network/order_cycle_permissions.rb b/lib/open_food_network/order_cycle_permissions.rb index 8f3ede515d..4635322482 100644 --- a/lib/open_food_network/order_cycle_permissions.rb +++ b/lib/open_food_network/order_cycle_permissions.rb @@ -25,7 +25,7 @@ module OpenFoodNetwork if @coordinator.sells == "any" # If the coordinator sells any, relationships come into play related_enterprises_granting(:add_to_order_cycle, - to: [@coordinator.id]).each do |enterprise_id| + to: [@coordinator.id]).each do |enterprise_id| coordinator_permitted_ids << enterprise_id end @@ -125,7 +125,7 @@ module OpenFoodNetwork # Find the variants that a user can POTENTIALLY see within incoming exchanges def visible_variants_for_incoming_exchanges_from(producer) if @order_cycle && - (user_manages_coordinator_or(producer) || user_is_permitted_add_to_oc_by(producer)) + (user_manages_coordinator_or(producer) || user_is_permitted_add_to_oc_by(producer)) all_variants_supplied_by(producer) else no_variants @@ -212,8 +212,8 @@ module OpenFoodNetwork # Variants produced by MY PRODUCERS that are in this OC, # where my producer has granted P-OC to the hub granting_producer_ids = related_enterprises_granting(:add_to_order_cycle, - to: [hub.id], - scope: granted_producers) + to: [hub.id], + scope: granted_producers) permitted_variants = variants_from_suppliers(granting_producer_ids) Spree::Variant.where(id: permitted_variants) diff --git a/lib/reporting/report_object_template.rb b/lib/reporting/report_object_template.rb index e9d6e2bbb5..fff2559934 100644 --- a/lib/reporting/report_object_template.rb +++ b/lib/reporting/report_object_template.rb @@ -4,7 +4,6 @@ # The result from those models module Reporting class ReportObjectTemplate < ReportTemplate - def table_headers raise NotImplementedError end diff --git a/lib/reporting/report_renderer.rb b/lib/reporting/report_renderer.rb index 998b5529d1..84e19254ae 100644 --- a/lib/reporting/report_renderer.rb +++ b/lib/reporting/report_renderer.rb @@ -19,7 +19,7 @@ module Reporting def as_json table_rows.map do |row| result = {} - table_headers.zip(row) { |a,b| result[a.to_sym] = b } + table_headers.zip(row) { |a, b| result[a.to_sym] = b } result end.as_json end diff --git a/lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb b/lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb index dade08a64b..26b4d37822 100644 --- a/lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb +++ b/lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb @@ -4,7 +4,6 @@ module Reporting module Reports module UsersAndEnterprises class UsersAndEnterprisesReport < ReportObjectTemplate - def initialize(user, params = {}) super(user, params) diff --git a/spec/controllers/admin/reports_controller_spec.rb b/spec/controllers/admin/reports_controller_spec.rb index 3ad47e99d7..00d1031da6 100644 --- a/spec/controllers/admin/reports_controller_spec.rb +++ b/spec/controllers/admin/reports_controller_spec.rb @@ -21,12 +21,14 @@ describe Admin::ReportsController, type: :controller do # Given two order cycles with both distributors let(:ocA) { - create(:simple_order_cycle, coordinator: coordinator1, distributors: [distributor1, distributor2], + create(:simple_order_cycle, coordinator: coordinator1, + distributors: [distributor1, distributor2], suppliers: [supplier1, supplier2, supplier3], variants: [product1.master, product3.master]) } let(:ocB) { - create(:simple_order_cycle, coordinator: coordinator2, distributors: [distributor1, distributor2], + create(:simple_order_cycle, coordinator: coordinator2, + distributors: [distributor1, distributor2], suppliers: [supplier1, supplier2, supplier3], variants: [product2.master]) } @@ -157,7 +159,8 @@ describe Admin::ReportsController, type: :controller do spree_get :index report_types = assigns(:reports).keys - expect(report_types).to include :orders_and_fulfillment, :products_and_inventory, :packing # and others + expect(report_types).to include :orders_and_fulfillment, + :products_and_inventory, :packing # and others expect(report_types).to_not include :sales_tax end end diff --git a/spec/lib/reports/xero_invoices_report_spec.rb b/spec/lib/reports/xero_invoices_report_spec.rb index 4d63acdad4..3348abb510 100644 --- a/spec/lib/reports/xero_invoices_report_spec.rb +++ b/spec/lib/reports/xero_invoices_report_spec.rb @@ -19,10 +19,10 @@ module Reporting around { |example| Timecop.travel(Time.zone.local(2015, 5, 5, 14, 0, 0)) { example.run } } it "uses defaults when blank params are passed" do - expect(report.instance_variable_get(:@params)).to eq( invoice_date: Date.civil(2015, 5, 5), - due_date: Date.civil(2015, 6, 5), - account_code: 'food sales', - report_subtype: 'summary' ) + expect(report.instance_variable_get(:@params)).to eq(invoice_date: Date.civil(2015, 5, 5), + due_date: Date.civil(2015, 6, 5), + account_code: 'food sales', + report_subtype: 'summary' ) end end From 287e8f5845030fe97777513c95fdf83dfad10853 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 6 Apr 2022 16:34:34 +0200 Subject: [PATCH 23/54] Reports Refactor 3 Easily group and create header and summary row Auto format cells when appropriate type (boolean, dates) and render_format (neither csv nor json) --- .rubocop_todo.yml | 1 + app/controllers/admin/reports_controller.rb | 5 +- app/controllers/api/v0/reports_controller.rb | 1 + app/helpers/reports_helper.rb | 12 ++ .../reports/_rendering_options.html.haml | 17 +- app/views/admin/reports/_row_group.haml | 22 +++ app/views/admin/reports/_table.html.haml | 35 ++-- app/webpacker/css/admin/openfoodnetwork.scss | 4 + app/webpacker/css/admin/reports.scss | 42 +++-- app/webpacker/css/admin/shared/forms.scss | 14 ++ config/locales/en.yml | 8 +- lib/reporting/report_grouper.rb | 158 ++++++++++++++++++ lib/reporting/report_object_template.rb | 24 ++- lib/reporting/report_query_template.rb | 8 +- lib/reporting/report_renderer.rb | 9 +- lib/reporting/report_ruler.rb | 62 +++++++ lib/reporting/report_template.rb | 83 ++++++++- .../orders_and_fulfillment_report.rb | 2 - .../lettuce_share_report.rb | 8 + .../products_and_inventory_default_report.rb | 8 + spec/system/admin/reports_spec.rb | 8 +- 21 files changed, 472 insertions(+), 59 deletions(-) create mode 100644 app/views/admin/reports/_row_group.haml create mode 100644 lib/reporting/report_grouper.rb create mode 100644 lib/reporting/report_ruler.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 65b2fa5266..e3ccc8f5fa 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -539,6 +539,7 @@ Metrics/ClassLength: - 'lib/reporting/reports/payments/payments_report.rb' - 'lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb' - 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb' + - 'lib/reporting/report_grouper.rb' # Offense count: 39 # Configuration parameters: IgnoredMethods, Max. diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index b5c400a33e..04d55e1fc9 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -7,7 +7,7 @@ module Admin before_action :authorize_report, only: [:show] - # Define custom model class for Cancan permissions + # Define model class for Can? permissions def model_class Admin::ReportsController end @@ -44,6 +44,9 @@ module Admin @report_subtypes = report_subtypes @report_subtype = report_subtype + # Initialize data + params[:display_summary_row] = true if request.get? + @data = Reporting::FrontendData.new(spree_current_user) end end diff --git a/app/controllers/api/v0/reports_controller.rb b/app/controllers/api/v0/reports_controller.rb index 1e4462df85..48c5077f8b 100644 --- a/app/controllers/api/v0/reports_controller.rb +++ b/app/controllers/api/v0/reports_controller.rb @@ -10,6 +10,7 @@ module Api before_action :validate_report, :authorize_report, :validate_query def show + params[:report_format] = 'json' @report = report_class.new(current_api_user, params) render_report diff --git a/app/helpers/reports_helper.rb b/app/helpers/reports_helper.rb index 953a4bbffa..6cf6cb8c16 100644 --- a/app/helpers/reports_helper.rb +++ b/app/helpers/reports_helper.rb @@ -29,4 +29,16 @@ module ReportsHelper def currency_symbol Spree::Money.currency_symbol end + + def format_cell(value) + return "" if value.nil? + + if value.in? [true, false] # Boolean + value ? I18n.t(:yes) : I18n.t(:no) + elsif value.respond_to?(:strftime) # Date + value.to_datetime.in_time_zone.strftime "%Y-%m-%d %H:%M" + else + value + end + end end diff --git a/app/views/admin/reports/_rendering_options.html.haml b/app/views/admin/reports/_rendering_options.html.haml index b6b20fd0cf..dc8395f584 100644 --- a/app/views/admin/reports/_rendering_options.html.haml +++ b/app/views/admin/reports/_rendering_options.html.haml @@ -4,6 +4,19 @@ .omega.fourteen.columns = select_tag(:report_subtype, options_for_select(@report_subtypes, @report_subtype)) +- if @report.header_option? || @report.summary_row_option? + .row + .alpha.two.columns= label_tag nil, t(".display") + .omega.fourteen.columns + - if @report.header_option? + %span.inline-checkbox{ style: "margin-right: 1rem;" } + = check_box_tag :display_header_row, true, params[:display_header_row] + = label_tag :display_header_row, t(".header_row") + - if @report.summary_row_option? + %span.inline-checkbox + = check_box_tag :display_summary_row, true, params[:display_summary_row] + = label_tag :display_summary_row, t(".summary_row") + .row.rendering-options .alpha.two.columns = label_tag :report_format, t(".generate_report") @@ -15,6 +28,4 @@ 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") + diff --git a/app/views/admin/reports/_row_group.haml b/app/views/admin/reports/_row_group.haml new file mode 100644 index 0000000000..fcf8e01052 --- /dev/null +++ b/app/views/admin/reports/_row_group.haml @@ -0,0 +1,22 @@ +-# Locals : +-# - data +-# - report + +- data.each do |group_or_row| + - if group_or_row[:is_group].present? + / Header Row + - if group_or_row[:header].present? && params[:display_header_row].present? + %tr + %td.header-row{ colspan: report.table_headers.count, class: group_or_row[:header_class] } + = group_or_row[:header].html_safe + / Rows + = render partial: 'admin/reports/row_group', locals: { report: report, data: group_or_row[:data] } + / Summary Row + - if group_or_row[:summary_row].present? && params[:display_summary_row].present? + %tr.summary_row{ class: group_or_row[:summary_row_class] } + - group_or_row[:summary_row].to_h.each do |key, value| + %td= format_cell(value) + - else + %tr + - group_or_row.row.to_h.each do |key, value| + %td= format_cell(value) \ No newline at end of file diff --git a/app/views/admin/reports/_table.html.haml b/app/views/admin/reports/_table.html.haml index 1d83358fd1..42f7ec67cf 100644 --- a/app/views/admin/reports/_table.html.haml +++ b/app/views/admin/reports/_table.html.haml @@ -1,19 +1,22 @@ - report ||= @report -%table.report__table - %thead - %tr - - report.table_headers.each do |heading| - %th - = t("admin.reports.table.headings.#{heading}") - -# = heading - %tbody - - report.table_rows.each do |row| - - if row - %tr - - row.each do |cell| - %td - = cell - - if report.table_rows.empty? +.report__table-container + %table.report__table + %thead %tr - %td{colspan: report.table_headers.count}= t(:none) + - report.table_headers.each do |heading| + %th + = heading + %tbody + - if report.grouped_data.present? + = render partial: 'admin/reports/row_group', locals: { report: report, data: report.grouped_data } + - else + - report.table_rows.each do |row| + - if row + %tr + - row.each do |cell| + %td + = cell + - if report.table_rows.empty? + %tr + %td{colspan: report.table_headers.count}= t(:none) diff --git a/app/webpacker/css/admin/openfoodnetwork.scss b/app/webpacker/css/admin/openfoodnetwork.scss index a573de4126..636b938d6d 100644 --- a/app/webpacker/css/admin/openfoodnetwork.scss +++ b/app/webpacker/css/admin/openfoodnetwork.scss @@ -6,6 +6,10 @@ text-align: right; } +.text-bold{ + font-weight: bold; +} + .underline { text-decoration: underline; } diff --git a/app/webpacker/css/admin/reports.scss b/app/webpacker/css/admin/reports.scss index 2a6d3036d5..a8faa18ae0 100644 --- a/app/webpacker/css/admin/reports.scss +++ b/app/webpacker/css/admin/reports.scss @@ -1,8 +1,34 @@ -.report__table { +table.report__table { margin-top: 2em; @media print { margin: 0; } + thead th { + text-align: left; + padding: 10px 6px; + } + + .header-row { + &.h1, &.h2, &.h3 { + font-weight: bold; + margin-top: 8px; + } + &.h1 { + font-size: 2em; + } + &.h2 { + font-size: 1.5em; + } + &.h3 { + font-size: 1.25em; + } + &.h4 { + font-size: 1; + } + &.with-background { + background-color: #eef5fc; + } + } } .report__message { @@ -17,20 +43,6 @@ margin-top: 1em; } -.rendering-options { - select { - display: block; - float: left; - } - - .inline-checkbox { - line-height: 2.5em; - margin-left: 1em; - display: block; - float: left; - } -} - .report__submit-btn { margin: 0 !important; width: 120px; diff --git a/app/webpacker/css/admin/shared/forms.scss b/app/webpacker/css/admin/shared/forms.scss index 7e4dc7c3bb..e1c92e38ae 100644 --- a/app/webpacker/css/admin/shared/forms.scss +++ b/app/webpacker/css/admin/shared/forms.scss @@ -242,3 +242,17 @@ select { @extend input, [type="text"]; background-color: white; } + +.inline-checkbox { + display: inline-flex; + align-items: center; + margin-top: 3px; + + input, label { + cursor: pointer; + } + label { + margin: 0; + padding-left: .4rem; + } +} diff --git a/config/locales/en.yml b/config/locales/en.yml index bdd2bebcc3..7209c01ac6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1255,6 +1255,8 @@ en: unitsize: UNITSIZE total: TOTAL total_items: TOTAL ITEMS + total_by_customer: Total By Customer + total_by_supplier: Total By Supplier supplier_totals: Order Cycle Supplier Totals supplier_totals_by_distributor: Order Cycle Supplier Totals by Distributor totals_by_supplier: Order Cycle Distributor Totals by Supplier @@ -1326,7 +1328,9 @@ en: csv_spreadsheet: "CSV Spreadsheet" excel_spreadsheet: "Excel Spreadsheet" openoffice_spreadsheet: "OpenOffice Spreadsheet" - hide_summary_rows: "Hide summary Rows" + display: Display + summary_row: Summary Row + header_row: Header Row packing: name: "Packing Reports" subscriptions: @@ -2627,7 +2631,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using report_tax_rates: Tax rates report_tax_types: Tax types report_filters: Report Filters - report_render_options: Render Options + report_render_options: Rendering Options report_header_order_cycle: Order Cycle report_header_user: User report_header_email: Email diff --git a/lib/reporting/report_grouper.rb b/lib/reporting/report_grouper.rb new file mode 100644 index 0000000000..9b369e4036 --- /dev/null +++ b/lib/reporting/report_grouper.rb @@ -0,0 +1,158 @@ +# frozen_string_literal: true + +module Reporting + class ReportGrouper + 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? + extract_rows(group_or_row[:data], result) + if group_or_row[:summary_row].present? && report.params[:display_summary_row].present? + 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 + 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| + row[previous_key] = label and break if value.present? + + previous_key = key + end + row + end + + def blank_row + report.columns.transform_values { |_v| "" } + end + + def slice_row_fields(row) + result = row.clone + report.fields_to_hide.each { |field| result.delete_field!(field) } + result + 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 diff --git a/lib/reporting/report_object_template.rb b/lib/reporting/report_object_template.rb index fff2559934..e4e45899ed 100644 --- a/lib/reporting/report_object_template.rb +++ b/lib/reporting/report_object_template.rb @@ -4,17 +4,27 @@ # The result from those models module Reporting class ReportObjectTemplate < ReportTemplate - def table_headers - raise NotImplementedError - end - + # rubocop:disable Rails/Delegate + # Not delegating for now cause not all subclasses are ready to use reportGrouper + # so they can implement this method themseves def table_rows + grouper.table_rows + end + # rubocop:enable Rails/Delegate + + # The search result, an ActiveRecord Array + def query_result raise NotImplementedError end - # Rules for grouping, ordering, and summary rows - def rules - [] + # Convert the query_result into expected row result (which will be displayed) + # Example + # { + # name: proc { |model| model.display_name }, + # best_friend: proc { |model| model.friends.first.first_name } + # } + def columns + raise NotImplementedError end end end diff --git a/lib/reporting/report_query_template.rb b/lib/reporting/report_query_template.rb index 79cea83f86..8dfa488b12 100644 --- a/lib/reporting/report_query_template.rb +++ b/lib/reporting/report_query_template.rb @@ -6,13 +6,17 @@ module Reporting def report_data @report_data ||= report_query.raw_result end + alias_method :query_result, :report_data def report_query raise NotImplementedError end - def table_headers - report_data.columns + # ReportQueryTemplate work differently than ReportObjectTemplate + # Here the query_result is already the expected result, so we just create + # a fake columns method to copy the sql result into the row result + def columns + report_data.columns.map { |field| [field.to_sym, proc { |data| data[field] }] }.to_h end def table_rows diff --git a/lib/reporting/report_renderer.rb b/lib/reporting/report_renderer.rb index 84e19254ae..d910f5f42e 100644 --- a/lib/reporting/report_renderer.rb +++ b/lib/reporting/report_renderer.rb @@ -17,9 +17,16 @@ module Reporting end def as_json + # columns methods give the headers code, but as not reports are implementing it + # we fallback with the translated headers with table_headers + headers = begin + @report.columns.keys + rescue NotImplementedError, NoMethodError + table_headers + end table_rows.map do |row| result = {} - table_headers.zip(row) { |a, b| result[a.to_sym] = b } + headers.zip(row) { |a, b| result[a.to_sym] = b } result end.as_json end diff --git a/lib/reporting/report_ruler.rb b/lib/reporting/report_ruler.rb new file mode 100644 index 0000000000..448d600698 --- /dev/null +++ b/lib/reporting/report_ruler.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module Reporting + class ReportRuler + def initialize(report) + @report = report + end + + def formatted_rules + @formatted_rules ||= @report.rules.map { |rule| format_rule(rule) } + end + + def header_option? + formatted_rules.find { |rule| rule[:header].present? } + end + + def summary_row_option? + formatted_rules.find { |rule| rule[:summary_row].present? } + end + + private + + def format_rule(rule) + handle_header_shortcuts(rule) + default_values = { + header_class: "h2", + summary_row_class: "text-bold", + summary_row_label: I18n.t('admin.reports.total') + } + rule.reverse_merge(default_values) + end + + def handle_header_shortcuts(rule) + # Handles shortcut header: :supplier + rule[:header] = Array(rule[:header]) if rule[:header].is_a?(Symbol) + # Handles shortcut header: [:last_name, :first_name] + case rule[:header] + when true + handle_shortcut_header_true(rule) + when proc { |r| r.is_a?(Array) } + handle_shortcut_header_array(rule) + end + rule[:fields_used_in_header] ||= [rule[:group_by]] if rule[:group_by].is_a?(Symbol) + end + + # header: true + # Use the grouping key for header + def handle_shortcut_header_true(rule) + rule[:header] = proc { |key, _items, _rows| key } + end + + # header: [:first_name, :last_name] + # Use the list of properties ot build the header + def handle_shortcut_header_array(rule) + rule[:fields_used_in_header] ||= rule[:header] + fields = rule[:header] + rule[:header] = proc do |_key, _items, rows| + fields.map { |field| rows.first[field] }.reject(&:blank?).join(' ') + end + end + end +end diff --git a/lib/reporting/report_template.rb b/lib/reporting/report_template.rb index 2e961331f4..ca771fc269 100644 --- a/lib/reporting/report_template.rb +++ b/lib/reporting/report_template.rb @@ -6,6 +6,8 @@ module Reporting attr_accessor :user, :params, :ransack_params delegate :as_json, :as_arrays, :to_csv, :to_xlsx, :to_ods, :to_pdf, :to_json, to: :renderer + delegate :formatted_rules, :header_option?, :summary_row_option?, to: :ruler + delegate :grouped_data, :rows, to: :grouper OPTIONAL_HEADERS = [].freeze @@ -16,14 +18,6 @@ module Reporting @ransack_params = @params[:q] || {} end - def table_headers - raise NotImplementedError - end - - def table_rows - raise NotImplementedError - end - # Message to be displayed at the top of rendered table def message "" @@ -36,10 +30,83 @@ module Reporting Ransack::Search.new(Spree::Order) end + # Can be re implemented in subclasses if they not use yet the new syntax + # with columns method + def table_headers + columns.keys.filter{ |key| !key.in?(fields_to_hide) }.map do |key| + custom_headers[key] || I18n.t("report_header_#{key}") + end + end + + def translate_header(key) + # Quite some headers use currency interpolation, so providing it by default + default_params = { currency: currency_symbol, currency_symbol: currency_symbol } + custom_headers[key] || I18n.t("report_header_#{key}", **default_params) + end + + # Headers are automatically translated with table_headers method + # You can customize some header name if needed + def custom_headers + {} + end + + def table_rows + raise NotImplementedError + end + + def fields_to_hide + if params[:display_header_row] + formatted_rules.map { |rule| rule[:fields_used_in_header] }.flatten.reject(&:blank?) + else + [] + end + end + + # Rules for grouping, ordering, and summary rows + # Rule Full Example. In the following item reference the query_result item and + # row the transformation of this item into the expected result + # { + # group_by: proc { |item, row| row.last_name }, + # group_by: :last_name, # same that previous line, + # group_by: proc { |line_item, row| line_item.product }, + # sort_by: proc { |product| product.name }, + # header: proc { |group_key, items, rows| items.first.display_name }, + # header: true, # shortcut to use group_key as header + # header: :supplier, # shortcut to use supplier column as header + # header: [:last_name, :first_name], # shortcut to use last_name & first_name as header + # header_class: "h1 h2 h3 h4 text-center background", # class applies to the header row + # # Those fields will be hidden when the header_row is activated + # fields_used_in_header: [:first_name, :last_name], + # summary_row: proc do |group_key, items, rows| + # { + # quantity: rows.sum(&:quantity), + # price: "#{rows.sum(&:price)} #{currency_symbol}" + # } + # end, + # summary_row_class: "", # by default 'text-bold' + # summary_row_label: "Total by Customer" # by default 'TOTAL' + # summary_row_label: proc { |group_key, items, rows| "Total for #{group_key}" } + # } + def rules + [] + end + private + def raw_render? + params[:report_format].in?(['json', 'csv']) + end + def renderer @renderer ||= ReportRenderer.new(self) end + + def grouper + @grouper ||= ReportGrouper.new(self) + end + + def ruler + @ruler ||= ReportRuler.new(self) + end end end diff --git a/lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb b/lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb index 7089dd911a..cea7120a8d 100644 --- a/lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb +++ b/lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb @@ -4,8 +4,6 @@ module Reporting module Reports module OrdersAndFulfillment class OrdersAndFulfillmentReport < ReportObjectTemplate - include ReportsHelper - attr_reader :report_type delegate :table_headers, :rules, :columns, to: :report diff --git a/lib/reporting/reports/products_and_inventory/lettuce_share_report.rb b/lib/reporting/reports/products_and_inventory/lettuce_share_report.rb index 8dd9ba1797..7f3ec04a93 100644 --- a/lib/reporting/reports/products_and_inventory/lettuce_share_report.rb +++ b/lib/reporting/reports/products_and_inventory/lettuce_share_report.rb @@ -48,6 +48,14 @@ module Reporting end end + def rules + [] + end + + def columns + {} + end + private def gst(variant) diff --git a/lib/reporting/reports/products_and_inventory/products_and_inventory_default_report.rb b/lib/reporting/reports/products_and_inventory/products_and_inventory_default_report.rb index be54824b9c..6d8307a611 100644 --- a/lib/reporting/reports/products_and_inventory/products_and_inventory_default_report.rb +++ b/lib/reporting/reports/products_and_inventory/products_and_inventory_default_report.rb @@ -44,6 +44,14 @@ module Reporting end end + def rules + [] + end + + def columns + {} + end + def sku_for(variant) return variant.sku if variant.sku.present? diff --git a/spec/system/admin/reports_spec.rb b/spec/system/admin/reports_spec.rb index 9ffcccaa97..f6f1c776db 100644 --- a/spec/system/admin/reports_spec.rb +++ b/spec/system/admin/reports_spec.rb @@ -237,7 +237,9 @@ describe ' it "is precise to time of day, not just date" do # When I generate a customer report # with a timeframe that includes one order but not the other - login_as_admin_and_visit spree.orders_and_fulfillment_admin_reports_path + login_as_admin_and_visit admin_reports_path + click_link 'Orders & Fulfillment Reports' + click_button 'Go' pick_datetime "#q_completed_at_gt", datetime_start pick_datetime "#q_completed_at_lt", datetime_end @@ -255,7 +257,9 @@ describe ' orders_open_at: Time.zone.now, orders_close_at: nil) o = create(:order, order_cycle: oc, distributor: distributor) - login_as_admin_and_visit spree.orders_and_fulfillment_admin_reports_path + login_as_admin_and_visit admin_reports_path + click_link 'Orders & Fulfillment Reports' + click_button 'Go' expect(page).to have_content "My Order Cycle" end From 88bc4178689b5394c33e19c2d09bc558a9a6e5c7 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 6 Apr 2022 11:01:28 +0200 Subject: [PATCH 24/54] Report Refactor 3: User & Entreprises --- .rubocop_todo.yml | 2 - .../reports/users_and_enterprises/base.rb | 81 +++++++++++ .../users_and_enterprises_report.rb | 137 ------------------ .../users_and_enterprises_report_spec.rb | 46 +++--- 4 files changed, 108 insertions(+), 158 deletions(-) create mode 100644 lib/reporting/reports/users_and_enterprises/base.rb delete mode 100644 lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index e3ccc8f5fa..13d9769853 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -128,7 +128,6 @@ Layout/LineLength: - 'lib/reporting/reports/payments/payments_report.rb' - 'lib/reporting/reports/products_and_inventory/lettuce_share_report.rb' - 'lib/reporting/reports/sales_tax/sales_tax_report.rb' - - 'lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb' - 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb' - 'lib/spree/localized_number.rb' - 'lib/tasks/data.rake' @@ -537,7 +536,6 @@ Metrics/ClassLength: - 'lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb' - 'lib/open_food_network/order_cycle_permissions.rb' - 'lib/reporting/reports/payments/payments_report.rb' - - 'lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb' - 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb' - 'lib/reporting/report_grouper.rb' diff --git a/lib/reporting/reports/users_and_enterprises/base.rb b/lib/reporting/reports/users_and_enterprises/base.rb new file mode 100644 index 0000000000..f7d7c52610 --- /dev/null +++ b/lib/reporting/reports/users_and_enterprises/base.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module UsersAndEnterprises + class Base < ReportObjectTemplate + def initialize(user, params = {}) + super(user, params) + end + + def query_result + sort(owners_and_enterprises.concat(managers_and_enterprises)) + end + + def columns + { + user: proc { |x| x.user_email }, + relationship: proc { |x| x.relationship_type }, + enterprise: proc { |x| x.name }, + is_producer: proc { |x| x.is_primary_producer }, + sells: proc { |x| x.sells }, + visible: proc { |x| x.visible }, + confirmation_date: proc { |x| x.created_at } + } + end + + def owners_and_enterprises + query = Enterprise + .joins("LEFT JOIN spree_users AS owner ON enterprises.owner_id = owner.id") + .where("enterprises.id IS NOT NULL") + + query = filter_by_int_list_if_present(query, "enterprises.id", params[:enterprise_id_in]) + query = filter_by_int_list_if_present(query, "owner.id", params[:user_id_in]) + + query_helper(query, :owner, :owns) + end + + def managers_and_enterprises + query = Enterprise + .joins("LEFT JOIN enterprise_roles ON enterprises.id = enterprise_roles.enterprise_id") + .joins("LEFT JOIN spree_users AS managers ON enterprise_roles.user_id = managers.id") + .where("enterprise_id IS NOT NULL") + .where("user_id IS NOT NULL") + + query = filter_by_int_list_if_present(query, "enterprise_id", params[:enterprise_id_in]) + query = filter_by_int_list_if_present(query, "user_id", params[:user_id_in]) + + query_helper(query, :managers, :manages) + end + + def query_helper(query, email_user, relationship_type) + query.order("enterprises.created_at DESC") + .select(["enterprises.name", + "enterprises.sells", + "enterprises.visible", + "enterprises.is_primary_producer", + "enterprises.created_at", + "#{email_user}.email AS user_email", + "'#{relationship_type}' AS relationship_type"]) + .to_a + end + + def filter_by_int_list_if_present(query, filtered_field_name, int_list) + if int_list.present? + query = query.where("#{filtered_field_name} IN (?)", int_list.map(&:to_i)) + end + query + end + + def sort(results) + results.sort do |a, b| + a_date = (a.created_at || Date.new(1970, 1, 1)).in_time_zone + b_date = (b.created_at || Date.new(1970, 1, 1)).in_time_zone + [b_date, a.name, b.relationship_type, a.user_email] <=> + [a_date, b.name, a.relationship_type, b.user_email] + end + end + end + end + end +end diff --git a/lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb b/lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb deleted file mode 100644 index 26b4d37822..0000000000 --- a/lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb +++ /dev/null @@ -1,137 +0,0 @@ -# frozen_string_literal: true - -module Reporting - module Reports - module UsersAndEnterprises - class UsersAndEnterprisesReport < ReportObjectTemplate - def initialize(user, params = {}) - super(user, params) - - # Convert arrays of ids to comma delimited strings - if @params[:enterprise_id_in].is_a? Array - @params[:enterprise_id_in] = @params[:enterprise_id_in].join(',') - end - @params[:user_id_in] = @params[:user_id_in].join(',') if @params[:user_id_in].is_a? Array - end - - def table_headers - [ - I18n.t(:report_header_user), - I18n.t(:report_header_relationship), - I18n.t(:report_header_enterprise), - I18n.t(:report_header_is_producer), - I18n.t(:report_header_sells), - I18n.t(:report_header_visible), - I18n.t(:report_header_confirmation_date), - ] - end - - def table_rows - users_and_enterprises.map do |uae| - [ - uae["user_email"], - uae["relationship_type"], - uae["name"], - to_bool(uae["is_primary_producer"]), - uae["sells"], - uae["visible"], - to_local_datetime(uae["created_at"]) - ] - end - end - - def owners_and_enterprises - query = Enterprise.joins("LEFT JOIN spree_users AS owner ON enterprises.owner_id = owner.id") - .where("enterprises.id IS NOT NULL") - - query = filter_by_int_list_if_present(query, "enterprises.id", params[:enterprise_id_in]) - query = filter_by_int_list_if_present(query, "owner.id", params[:user_id_in]) - - query_helper(query, :owner, :owns) - end - - def managers_and_enterprises - query = Enterprise - .joins("LEFT JOIN enterprise_roles ON enterprises.id = enterprise_roles.enterprise_id") - .joins("LEFT JOIN spree_users AS managers ON enterprise_roles.user_id = managers.id") - .where("enterprise_id IS NOT NULL") - .where("user_id IS NOT NULL") - - query = filter_by_int_list_if_present(query, "enterprise_id", params[:enterprise_id_in]) - query = filter_by_int_list_if_present(query, "user_id", params[:user_id_in]) - - query_helper(query, :managers, :manages) - end - - def query_helper(query, email_user, relationship_type) - query.order("enterprises.created_at DESC") - .select(["enterprises.name", - "enterprises.sells", - "enterprises.visible", - "enterprises.is_primary_producer", - "enterprises.created_at", - "#{email_user}.email AS user_email"]) - .to_a - .map { |x| - { - name: x.name, - sells: x.sells, - visible: (x.visible ? 't' : 'f'), - is_primary_producer: (x.is_primary_producer ? 't' : 'f'), - created_at: x.created_at.utc.iso8601, - relationship_type: relationship_type, - user_email: x.user_email - }.stringify_keys - } - end - - def users_and_enterprises - sort( owners_and_enterprises.concat(managers_and_enterprises) ) - end - - def filter_by_int_list_if_present(query, filtered_field_name, int_list) - if int_list.present? - query = query.where("#{filtered_field_name} IN (?)", split_int_list(int_list)) - end - query - end - - def split_int_list(int_list) - int_list.split(',').map(&:to_i) - end - - def sort(results) - results.sort do |a, b| - if a["created_at"].nil? || b["created_at"].nil? - [(a["created_at"].nil? ? 0 : 1), a["name"], b["relationship_type"], - a["user_email"]] <=> - [(b["created_at"].nil? ? 0 : 1), b["name"], a["relationship_type"], b["user_email"]] - else - [ - DateTime.parse(b["created_at"]).in_time_zone, - a["name"], - b["relationship_type"], - a["user_email"] - ] <=> [ - DateTime.parse(a["created_at"]).in_time_zone, - b["name"], - a["relationship_type"], - b["user_email"] - ] - end - end - end - - def to_bool(value) - ActiveRecord::Type::Boolean.new.cast(value) - end - - def to_local_datetime(date) - return "" if date.nil? - - date.to_datetime.in_time_zone.strftime "%Y-%m-%d %H:%M" - end - end - end - end -end diff --git a/spec/lib/reports/users_and_enterprises_report_spec.rb b/spec/lib/reports/users_and_enterprises_report_spec.rb index 9929a4cb04..0bcc77dc93 100644 --- a/spec/lib/reports/users_and_enterprises_report_spec.rb +++ b/spec/lib/reports/users_and_enterprises_report_spec.rb @@ -5,11 +5,11 @@ require 'spec_helper' module Reporting module Reports module UsersAndEnterprises - describe UsersAndEnterprisesReport do - describe "users_and_enterprises" do + describe Base do + describe "query_result" do let!(:owners_and_enterprises) { double(:owners_and_enterprises) } let!(:managers_and_enterprises) { double(:managers_and_enterprises) } - let!(:subject) { UsersAndEnterprisesReport.new(nil, {}) } + let!(:subject) { Base.new(nil, {}) } before do allow(subject).to receive(:owners_and_enterprises) { owners_and_enterprises } @@ -21,43 +21,51 @@ module Reporting expect(subject).to receive(:managers_and_enterprises).once expect(owners_and_enterprises).to receive(:concat).with(managers_and_enterprises).and_return [] expect(subject).to receive(:sort).with [] - subject.users_and_enterprises + subject.query_result end end describe "sorting results" do - let!(:subject) { UsersAndEnterprisesReport.new(nil, {}) } + let!(:subject) { Base.new(nil, {}) } it "sorts by creation date" do uae_mock = [ - { "created_at" => "2015-01-01", "name" => "bbb" }, - { "created_at" => "2015-01-02", "name" => "aaa" } + OpenStruct.new({ created_at: Date.new(2015, 1, 1), name: "aaa" }), + OpenStruct.new({ created_at: Date.new(2015, 1, 2), name: "bbb" }) + ] + expect(subject.sort(uae_mock)).to eq [uae_mock[1], uae_mock[0]] + end + + it "sorts by creation date when nil date" do + uae_mock = [ + OpenStruct.new({ created_at: nil, name: "aaa" }), + OpenStruct.new({ created_at: Date.new(2015, 1, 2), name: "bbb" }) ] expect(subject.sort(uae_mock)).to eq [uae_mock[1], uae_mock[0]] end it "then sorts by name" do uae_mock = [ - { "name" => "aaa", "relationship_type" => "bbb", "user_email" => "bbb" }, - { "name" => "bbb", "relationship_type" => "aaa", "user_email" => "aaa" } + OpenStruct.new({ name: "aaa", relationship_type: "bbb", user_email: "bbb" }), + OpenStruct.new({ name: "bbb", relationship_type: "aaa", user_email: "aaa" }) ] expect(subject.sort(uae_mock)).to eq [uae_mock[0], uae_mock[1]] end it "then sorts by relationship type (reveresed)" do uae_mock = [ - { "name" => "aaa", "relationship_type" => "bbb", "user_email" => "bbb" }, - { "name" => "aaa", "relationship_type" => "aaa", "user_email" => "aaa" }, - { "name" => "aaa", "relationship_type" => "bbb", "user_email" => "aaa" } + OpenStruct.new({ name: "aaa", relationship_type: "bbb", user_email: "bbb" }), + OpenStruct.new({ name: "aaa", relationship_type: "aaa", user_email: "aaa" }), + OpenStruct.new({ name: "aaa", relationship_type: "bbb", user_email: "aaa" }) ] expect(subject.sort(uae_mock)).to eq [uae_mock[2], uae_mock[0], uae_mock[1]] end it "then sorts by user_email" do uae_mock = [ - { "name" => "aaa", "relationship_type" => "bbb", "user_email" => "aaa" }, - { "name" => "aaa", "relationship_type" => "aaa", "user_email" => "aaa" }, - { "name" => "aaa", "relationship_type" => "aaa", "user_email" => "bbb" } + OpenStruct.new({ name: "aaa", relationship_type: "bbb", user_email: "aaa" }), + OpenStruct.new({ name: "aaa", relationship_type: "aaa", user_email: "aaa" }), + OpenStruct.new({ name: "aaa", relationship_type: "aaa", user_email: "bbb" }) ] expect(subject.sort(uae_mock)).to eq [uae_mock[0], uae_mock[1], uae_mock[2]] end @@ -70,7 +78,7 @@ module Reporting describe "for owners and enterprises" do describe "by enterprise id" do let!(:params) { { enterprise_id_in: [enterprise1.id.to_s] } } - let!(:subject) { UsersAndEnterprisesReport.new nil, params } + let!(:subject) { Base.new nil, params } it "excludes enterprises that are not explicitly requested" do results = subject.owners_and_enterprises.to_a.map{ |oae| oae["name"] } @@ -81,7 +89,7 @@ module Reporting describe "by user id" do let!(:params) { { user_id_in: [enterprise1.owner.id.to_s] } } - let!(:subject) { UsersAndEnterprisesReport.new nil, params } + let!(:subject) { Base.new nil, params } it "excludes enterprises that are not explicitly requested" do results = subject.owners_and_enterprises.to_a.map{ |oae| oae["name"] } @@ -94,7 +102,7 @@ module Reporting describe "for managers and enterprises" do describe "by enterprise id" do let!(:params) { { enterprise_id_in: [enterprise1.id.to_s] } } - let!(:subject) { UsersAndEnterprisesReport.new nil, params } + let!(:subject) { Base.new nil, params } it "excludes enterprises that are not explicitly requested" do results = subject.managers_and_enterprises.to_a.map{ |mae| mae["name"] } @@ -107,7 +115,7 @@ module Reporting let!(:manager1) { create(:user) } let!(:manager2) { create(:user) } let!(:params) { { user_id_in: [manager1.id.to_s] } } - let!(:subject) { UsersAndEnterprisesReport.new nil, params } + let!(:subject) { Base.new nil, params } before do enterprise1.enterprise_roles.build(user: manager1).save From 529858946fc7dbad95e2088890c2713cebdeb430 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Thu, 7 Apr 2022 11:14:19 +0200 Subject: [PATCH 25/54] Report Refactor 3: Packing Stop using SQL for grouping, so we can use same grouping code than for other reports Fix query builder default grouping_fields --- lib/reporting/queries/query_builder.rb | 2 +- lib/reporting/reports/packing/base.rb | 40 +++++++++++--- lib/reporting/reports/packing/customer.rb | 42 +++++++-------- lib/reporting/reports/packing/supplier.rb | 53 ++++++++++--------- .../api/v0/reports/packing_report_spec.rb | 25 ++------- .../reports/packing/packing_report_spec.rb | 2 +- 6 files changed, 85 insertions(+), 79 deletions(-) diff --git a/lib/reporting/queries/query_builder.rb b/lib/reporting/queries/query_builder.rb index e146699567..4def4b6c5d 100644 --- a/lib/reporting/queries/query_builder.rb +++ b/lib/reporting/queries/query_builder.rb @@ -8,7 +8,7 @@ module Reporting attr_reader :grouping_fields - def initialize(model, grouping_fields = []) + def initialize(model, grouping_fields = proc { [] }) @grouping_fields = instance_exec(&grouping_fields) super model.arel_table diff --git a/lib/reporting/reports/packing/base.rb b/lib/reporting/reports/packing/base.rb index f5fb7515f3..132b0d1f18 100644 --- a/lib/reporting/reports/packing/base.rb +++ b/lib/reporting/reports/packing/base.rb @@ -13,7 +13,7 @@ module Reporting end def report_query - Queries::QueryBuilder.new(primary_model, grouping_fields). + Queries::QueryBuilder.new(primary_model). scoped_to_orders(visible_orders_relation). scoped_to_line_items(ransacked_line_items_relation). with_managed_orders(managed_orders_relation). @@ -26,16 +26,42 @@ module Reporting 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 + def select_fields lambda do - [ - order_table[:id], - line_item_table[:id] - ] + { + hub: distributor_alias[:name], + customer_code: masked(customer_table[:code]), + last_name: masked(bill_address_alias[:lastname]), + first_name: masked(bill_address_alias[:firstname]), + phone: masked(bill_address_alias[:phone]), + supplier: supplier_alias[:name], + product: product_table[:name], + variant: variant_full_name, + quantity: line_item_table[:quantity], + price: (line_item_table[:quantity] * line_item_table[:price]), + temp_controlled: shipping_category_table[:temperature_controlled], + } + end + end + + private + + def row_header(row) + result = "#{row.last_name} #{row.first_name}" + result += " (#{row.customer_code})" if row.customer_code + result += " - #{row.phone}" if row.phone + result + end + + def summary_row + proc do |_key, _items, rows| + { + quantity: rows.sum(&:quantity), + price: rows.sum(&:price) + } end end end diff --git a/lib/reporting/reports/packing/customer.rb b/lib/reporting/reports/packing/customer.rb index 5c3cfcae09..ad660ddbd1 100644 --- a/lib/reporting/reports/packing/customer.rb +++ b/lib/reporting/reports/packing/customer.rb @@ -4,21 +4,26 @@ module Reporting module Reports module Packing class Customer < Base - def select_fields - lambda do + def columns + # Reorder default columns + super.slice(:hub, :customer_code, :last_name, :first_name, :phone, + :supplier, :product, :variant, :quantity, :price, :temp_controlled) + end + + def rules + [ { - hub: default_blank(distributor_alias[:name]), - customer_code: default_blank(masked(customer_table[:code])), - last_name: default_blank(masked(bill_address_alias[:lastname])), - first_name: default_blank(masked(bill_address_alias[:firstname])), - 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]), - price: sum_values(line_item_table[:quantity] * line_item_table[:price]), - temp_controlled: boolean_blank(shipping_category_table[:temperature_controlled]), + group_by: :hub, + header: true, + header_class: "h1 with-background text-center", + }, + { + group_by: proc { |_item, row| row_header(row) }, + header: true, + fields_used_in_header: [:first_name, :last_name, :customer_code, :phone], + summary_row: summary_row, } - end + ] end def ordering_fields @@ -27,23 +32,12 @@ module Reporting 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 diff --git a/lib/reporting/reports/packing/supplier.rb b/lib/reporting/reports/packing/supplier.rb index fbeb96cb07..cd0acbb6ab 100644 --- a/lib/reporting/reports/packing/supplier.rb +++ b/lib/reporting/reports/packing/supplier.rb @@ -4,33 +4,35 @@ module Reporting module Reports module Packing class Supplier < Base - # rubocop:disable Metrics/AbcSize - def select_fields - lambda do - { - hub: default_blank(distributor_alias[:name]), - supplier: default_blank(supplier_alias[:name]), - customer_code: default_blank(customer_table[:code]), - last_name: default_blank(masked(bill_address_alias[:lastname])), - first_name: default_blank(masked(bill_address_alias[:firstname])), - product: default_string(product_table[:name], summary_row_title), - variant: default_blank(variant_full_name), - quantity: sum_values(line_item_table[:quantity]), - price: sum_values(line_item_table[:quantity] * line_item_table[:price]), - temp_controlled: boolean_blank(shipping_category_table[:temperature_controlled]), - } - end + def columns + # Reorder default columns + super.slice(:hub, :supplier, :customer_code, :last_name, :first_name, :phone, + :product, :variant, :quantity, :price, :temp_controlled) end - # rubocop:enable Metrics/AbcSize - def group_sets - lambda do - [ - distributor_alias[:name], - supplier_alias[:name], - grouping_sets([parenthesise(supplier_alias[:name]), parenthesise(grouping_fields)]) - ] - end + def rules + [ + { + group_by: :hub, + header: true, + header_class: "h1 with-background text-center", + }, + { + group_by: :supplier, + header: true, + summary_row: summary_row, + summary_row_label: I18n.t('admin.reports.total_by_supplier').upcase + }, + { + group_by: proc { |_item, row| row_header(row) }, + header: true, + header_class: 'h4', + fields_used_in_header: [:first_name, :last_name, :customer_code, :phone], + summary_row: summary_row, + summary_row_class: "", + summary_row_label: I18n.t('admin.reports.total_by_customer') + } + ] end def ordering_fields @@ -38,7 +40,6 @@ module Reporting [ distributor_alias[:name], supplier_alias[:name], - sql_grouping(grouping_fields), Arel.sql("product"), Arel.sql("variant"), Arel.sql("last_name") diff --git a/spec/controllers/api/v0/reports/packing_report_spec.rb b/spec/controllers/api/v0/reports/packing_report_spec.rb index 92d14f112f..8600462701 100644 --- a/spec/controllers/api/v0/reports/packing_report_spec.rb +++ b/spec/controllers/api/v0/reports/packing_report_spec.rb @@ -56,7 +56,7 @@ describe Api::V0::ReportsController, type: :controller do results << __send__("#{user_type}_report_row", line_item) end - results << summary_row(order) + results end def distributor_report_row(line_item) @@ -70,8 +70,8 @@ describe Api::V0::ReportsController, type: :controller do "variant" => line_item.full_name, "quantity" => line_item.quantity, "price" => (line_item.quantity * line_item.price).to_s, - "temp_controlled" => - line_item.product.shipping_category&.temperature_controlled ? I18n.t(:yes) : I18n.t(:no) + "phone" => line_item.order.bill_address.phone, + "temp_controlled" => line_item.product.shipping_category&.temperature_controlled } end @@ -81,28 +81,13 @@ describe Api::V0::ReportsController, type: :controller do "customer_code" => I18n.t("hidden_field", scope: i18n_scope), "first_name" => I18n.t("hidden_field", scope: i18n_scope), "last_name" => I18n.t("hidden_field", scope: i18n_scope), + "phone" => I18n.t("hidden_field", scope: i18n_scope), "supplier" => line_item.product.supplier.name, "product" => line_item.product.name, "variant" => line_item.full_name, "quantity" => line_item.quantity, "price" => (line_item.quantity * line_item.price).to_s, - "temp_controlled" => - line_item.product.shipping_category&.temperature_controlled ? I18n.t(:yes) : I18n.t(:no) - } - end - - def summary_row(order) - { - "hub" => "", - "customer_code" => "", - "first_name" => "", - "last_name" => "", - "supplier" => "", - "product" => I18n.t("total", scope: i18n_scope), - "variant" => "", - "quantity" => order.line_items.sum(&:quantity), - "price" => order.line_items.sum(&:price).to_s, - "temp_controlled" => "", + "temp_controlled" => line_item.product.shipping_category&.temperature_controlled } end diff --git a/spec/lib/reports/packing/packing_report_spec.rb b/spec/lib/reports/packing/packing_report_spec.rb index 9902d3ebe2..959c2c5546 100644 --- a/spec/lib/reports/packing/packing_report_spec.rb +++ b/spec/lib/reports/packing/packing_report_spec.rb @@ -174,7 +174,7 @@ describe "Packing Reports" do it "groups and orders by distributor and order" do expect(subject.report_data.rows.map(&:first)).to eq( - [order.distributor.name, "", order2.distributor.name, order2.distributor.name, ""] + [order.distributor.name, order2.distributor.name, order2.distributor.name] ) end end From b25192c31db85761fe6295ee76870919227d5df4 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 6 Apr 2022 11:21:47 +0200 Subject: [PATCH 26/54] Report Refactor 3: Sales Tax --- lib/reporting/reports/sales_tax/base.rb | 35 ++++++ .../reports/sales_tax/sales_tax_report.rb | 105 ------------------ lib/reporting/reports/sales_tax/tax_rates.rb | 42 +++++++ lib/reporting/reports/sales_tax/tax_types.rb | 61 ++++++++++ spec/lib/reports/sales_tax_report_spec.rb | 7 +- 5 files changed, 142 insertions(+), 108 deletions(-) create mode 100644 lib/reporting/reports/sales_tax/base.rb delete mode 100644 lib/reporting/reports/sales_tax/sales_tax_report.rb create mode 100644 lib/reporting/reports/sales_tax/tax_rates.rb create mode 100644 lib/reporting/reports/sales_tax/tax_types.rb diff --git a/lib/reporting/reports/sales_tax/base.rb b/lib/reporting/reports/sales_tax/base.rb new file mode 100644 index 0000000000..3ac4f0e2da --- /dev/null +++ b/lib/reporting/reports/sales_tax/base.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module SalesTax + class Base < ReportObjectTemplate + def search + permissions = ::Permissions::Order.new(user) + permissions.editable_orders.complete.not_state(:canceled).ransack(params[:q]) + end + + def query_result + search.result + end + + private + + def relevant_rates + @relevant_rates ||= Spree::TaxRate.distinct + end + + def order_number_column(order) + if raw_render? + order.number + else + url = Spree::Core::Engine.routes.url_helpers.edit_admin_order_path(order.number) + <<-HTML + #{order.number} + HTML + end + end + end + end + end +end diff --git a/lib/reporting/reports/sales_tax/sales_tax_report.rb b/lib/reporting/reports/sales_tax/sales_tax_report.rb deleted file mode 100644 index e2c44eb147..0000000000 --- a/lib/reporting/reports/sales_tax/sales_tax_report.rb +++ /dev/null @@ -1,105 +0,0 @@ -# frozen_string_literal: true - -module Reporting - module Reports - module SalesTax - class SalesTaxReport < ReportObjectTemplate - include ReportsHelper - - def table_headers - case params[:report_subtype] - when "tax_rates" - [I18n.t(:report_header_order_number), - I18n.t(:report_header_total_excl_vat, currency_symbol: currency_symbol)] + - relevant_rates.map { |rate| - "%.1f%% (%s)" % [rate.amount.to_f * 100, currency_symbol] - } + - [I18n.t(:report_header_total_tax, currency_symbol: currency_symbol), - I18n.t(:report_header_total_incl_vat, currency_symbol: currency_symbol)] - else - [I18n.t(:report_header_order_number), - I18n.t(:report_header_date), - I18n.t(:report_header_items), - I18n.t(:report_header_items_total, currency_symbol: currency_symbol), - I18n.t(:report_header_taxable_items_total, currency_symbol: currency_symbol), - I18n.t(:report_header_sales_tax, currency_symbol: currency_symbol), - I18n.t(:report_header_delivery_charge, currency_symbol: currency_symbol), - I18n.t(:report_header_tax_on_delivery, currency_symbol: currency_symbol), - I18n.t(:report_header_tax_on_fees, currency_symbol: currency_symbol), - I18n.t(:report_header_total_tax, currency_symbol: currency_symbol), - I18n.t(:report_header_customer), - I18n.t(:report_header_distributor)] - end - end - - def search - permissions = ::Permissions::Order.new(user) - permissions.editable_orders.complete.not_state(:canceled).ransack(params[:q]) - end - - def orders - search.result - end - - def table_rows - case params[:report_subtype] - when "tax_rates" - orders.map do |order| - [order.number, order.total - order.total_tax] + - relevant_rates.map { |rate| - OrderTaxAdjustmentsFetcher.new(order).totals.fetch(rate, 0) - } + [order.total_tax, order.total] - end - else - orders.map do |order| - totals = totals_of order.line_items - shipping_cost = shipping_cost_for order - - [order.number, order.completed_at.strftime("%F %T"), totals[:items], totals[:items_total], - totals[:taxable_total], totals[:sales_tax], shipping_cost, order.shipping_tax, order.enterprise_fee_tax, order.total_tax, - order.bill_address.full_name, order.distributor&.name] - end - end - end - - private - - def relevant_rates - return @relevant_rates unless @relevant_rates.nil? - - @relevant_rates = Spree::TaxRate.distinct - end - - def totals_of(line_items) - totals = { items: 0, items_total: 0.0, taxable_total: 0.0, sales_tax: 0.0 } - - line_items.each do |line_item| - totals[:items] += line_item.quantity - totals[:items_total] += line_item.amount - - sales_tax = tax_included_in line_item - - if sales_tax > 0 - totals[:taxable_total] += line_item.amount - totals[:sales_tax] += sales_tax - end - end - - totals.each_pair do |k, _v| - totals[k] = totals[k].round(2) - end - - totals - end - - def shipping_cost_for(order) - order.shipments.first&.cost || 0.0 - end - - def tax_included_in(line_item) - line_item.adjustments.tax.inclusive.sum(:amount) - end - end - end - end -end diff --git a/lib/reporting/reports/sales_tax/tax_rates.rb b/lib/reporting/reports/sales_tax/tax_rates.rb new file mode 100644 index 0000000000..afc62ac3c8 --- /dev/null +++ b/lib/reporting/reports/sales_tax/tax_rates.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module SalesTax + class TaxRates < Base + # rubocop:disable Rails/OutputSafety + def columns + result = { + order_number: proc { |order| order_number_column(order).html_safe }, + total_excl_vat: proc { |order| order.total - order.total_tax } + } + add_key_for_each_rate(result, proc { |rate| + OrderTaxAdjustmentsFetcher.new(order).totals.fetch(rate, 0) + }) + other = { + total_tax: proc { |order| order.total_tax }, + total_incl_vat: proc { |order| order.total } + } + result.merge(other) + end + # rubocop:enable Rails/OutputSafety + + def custom_headers + result = {} + add_key_for_each_rate(result, proc { |rate| + "%.1f%% (%s)" % [rate.amount.to_f * 100, currency_symbol] + }) + result + end + + private + + def add_key_for_each_rate(result, proc) + relevant_rates.each { |rate| + result["rate_#{rate.id}"] = proc.call(rate) + } + end + end + end + end +end diff --git a/lib/reporting/reports/sales_tax/tax_types.rb b/lib/reporting/reports/sales_tax/tax_types.rb new file mode 100644 index 0000000000..7c9eda9f02 --- /dev/null +++ b/lib/reporting/reports/sales_tax/tax_types.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module SalesTax + class TaxTypes < Base + # rubocop:disable Metrics/AbcSize + # rubocop:disable Rails/OutputSafety + def columns + { + order_number: proc { |order| order_number_column(order).html_safe }, + date: proc { |order| order.completed_at }, + items: proc { |order| totals_of(order)[:items] }, + items_total: proc { |order| totals_of(order)[:items_total] }, + taxable_items_total: proc { |order| totals_of(order)[:taxable_total] }, + sales_tax: proc { |order| totals_of(order)[:sales_tax] }, + delivery_charge: proc { |order| order.shipments.first&.cost || 0.0 }, + tax_on_delivery: proc { |order| order.shipping_tax }, + tax_on_fees: proc { |order| order.enterprise_fee_tax }, + total_tax: proc { |order| order.total_tax }, + customer: proc { |order| order.bill_address.full_name }, + distributor: proc { |order| order.distributor&.name }, + } + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Rails/OutputSafety + + private + + def totals_of(order) + @totals ||= {} + return @totals[order.id] if @totals[order.id].present? + + totals = { items: 0, items_total: 0.0, taxable_total: 0.0, sales_tax: 0.0 } + + order.line_items.each do |line_item| + totals[:items] += line_item.quantity + totals[:items_total] += line_item.amount + + sales_tax = tax_included_in line_item + + if sales_tax > 0 + totals[:taxable_total] += line_item.amount + totals[:sales_tax] += sales_tax + end + end + + totals.each_pair do |k, _v| + totals[k] = totals[k].round(2) + end + + @totals[order.id] = totals + end + + def tax_included_in(line_item) + line_item.adjustments.tax.inclusive.sum(:amount) + end + end + end + end +end diff --git a/spec/lib/reports/sales_tax_report_spec.rb b/spec/lib/reports/sales_tax_report_spec.rb index f316da1f48..aa8b6b7885 100644 --- a/spec/lib/reports/sales_tax_report_spec.rb +++ b/spec/lib/reports/sales_tax_report_spec.rb @@ -5,14 +5,15 @@ require 'spec_helper' module Reporting module Reports module SalesTax - describe SalesTaxReport do + describe TaxTypes do let(:user) { create(:user) } - let(:report) { SalesTaxReport.new(user, {}) } + let(:report) { TaxTypes.new(user, {}) } describe "calculating totals for line items" do let(:li1) { double(:line_item, quantity: 1, amount: 12) } let(:li2) { double(:line_item, quantity: 2, amount: 24) } - let(:totals) { report.__send__(:totals_of, [li1, li2]) } + let(:order) { double(:order, id: 1, line_items: [li1, li2]) } + let(:totals) { report.__send__(:totals_of, order) } before do allow(report).to receive(:tax_included_in).and_return(2, 4) From 7a9ed7a73c6da9d8e497b4b605721f47a84762de Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 6 Apr 2022 11:21:52 +0200 Subject: [PATCH 27/54] Report Refactor 3: Order & Fulfilment --- lib/reporting/report_grouper.rb | 4 +- ...ders_and_fulfillment_report.rb => base.rb} | 60 ++-- .../customer_totals_report.rb | 225 -------------- .../orders_and_fulfillment/default_report.rb | 66 ---- .../distributor_totals_by_supplier_report.rb | 80 ----- .../order_cycle_customer_totals.rb | 155 ++++++++++ ...er_cycle_distributor_totals_by_supplier.rb | 50 +++ .../order_cycle_supplier_totals.rb | 41 +++ ...er_cycle_supplier_totals_by_distributor.rb | 48 +++ .../supplier_totals_by_distributor_report.rb | 79 ----- .../supplier_totals_report.rb | 62 ---- .../admin/reports_controller_spec.rb | 8 +- ...rder_cycle_customer_totals_report_spec.rb} | 36 +-- ...ributor_totals_by_supplier_report_spec.rb} | 10 +- ...lier_totals_by_distributor_report_spec.rb} | 17 +- .../orders_and_fulfillment_report_spec.rb | 284 ------------------ ...ders_cycle_supplier_totals_report_spec.rb} | 7 +- 17 files changed, 338 insertions(+), 894 deletions(-) rename lib/reporting/reports/orders_and_fulfillment/{orders_and_fulfillment_report.rb => base.rb} (66%) delete mode 100644 lib/reporting/reports/orders_and_fulfillment/customer_totals_report.rb delete mode 100644 lib/reporting/reports/orders_and_fulfillment/default_report.rb delete mode 100644 lib/reporting/reports/orders_and_fulfillment/distributor_totals_by_supplier_report.rb create mode 100644 lib/reporting/reports/orders_and_fulfillment/order_cycle_customer_totals.rb create mode 100644 lib/reporting/reports/orders_and_fulfillment/order_cycle_distributor_totals_by_supplier.rb create mode 100644 lib/reporting/reports/orders_and_fulfillment/order_cycle_supplier_totals.rb create mode 100644 lib/reporting/reports/orders_and_fulfillment/order_cycle_supplier_totals_by_distributor.rb delete mode 100644 lib/reporting/reports/orders_and_fulfillment/supplier_totals_by_distributor_report.rb delete mode 100644 lib/reporting/reports/orders_and_fulfillment/supplier_totals_report.rb rename spec/lib/reports/orders_and_fulfillment/{customer_totals_report_spec.rb => order_cycle_customer_totals_report_spec.rb} (70%) rename spec/lib/reports/orders_and_fulfillment/{distributor_totals_by_supplier_report_spec.rb => order_cycle_distributor_totals_by_supplier_report_spec.rb} (75%) rename spec/lib/reports/orders_and_fulfillment/{supplier_totals_by_distributor_report_spec.rb => order_cycle_supplier_totals_by_distributor_report_spec.rb} (55%) delete mode 100644 spec/lib/reports/orders_and_fulfillment/orders_and_fulfillment_report_spec.rb rename spec/lib/reports/orders_and_fulfillment/{supplier_totals_report_spec.rb => orders_cycle_supplier_totals_report_spec.rb} (81%) diff --git a/lib/reporting/report_grouper.rb b/lib/reporting/report_grouper.rb index 9b369e4036..21cddf3108 100644 --- a/lib/reporting/report_grouper.rb +++ b/lib/reporting/report_grouper.rb @@ -125,7 +125,9 @@ module Reporting label = label.call(*proc_args) if label.respond_to?(:call) # Adds Total before first non empty column row.each_pair do |key, value| - row[previous_key] = label and break if value.present? + if value.present? && previous_key.present? && row[previous_key].blank? + row[previous_key] = label and break + end previous_key = key end diff --git a/lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb b/lib/reporting/reports/orders_and_fulfillment/base.rb similarity index 66% rename from lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb rename to lib/reporting/reports/orders_and_fulfillment/base.rb index cea7120a8d..fbd9725f79 100644 --- a/lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb +++ b/lib/reporting/reports/orders_and_fulfillment/base.rb @@ -3,15 +3,10 @@ module Reporting module Reports module OrdersAndFulfillment - class OrdersAndFulfillmentReport < ReportObjectTemplate - attr_reader :report_type - - delegate :table_headers, :rules, :columns, to: :report - + class Base < ReportObjectTemplate def initialize(user, params = {}) super(user, params) - @report_type = params[:report_subtype] - @variant_scopers_by_distributor_id = {} + now = Time.zone.now params[:q] ||= { completed_at_gt: (now - 1.month).beginning_of_day, @@ -27,20 +22,23 @@ module Reporting report_line_items.orders end - def table_items - report_line_items.list(report.line_item_includes) + def query_result + report_line_items.list(line_item_includes).group_by(&:variant).values end - def table_rows - order_grouper = Reporting::OrderGrouper.new report.rules, report.columns, report - order_grouper.table(table_items) + private + + def order_permissions + return @order_permissions unless @order_permissions.nil? + + @order_permissions = ::Permissions::Order.new(@user, params[:q]) end - def line_item_name - proc { |line_item| line_item.variant.full_name } + def report_line_items + @report_line_items ||= Reporting::LineItems.new(order_permissions, params) end - def line_items_name + def variant_name proc { |line_items| line_items.first.variant.full_name } end @@ -52,6 +50,10 @@ module Reporting proc { |line_items| line_items.first.variant.product.name } end + def hub_name + proc { |line_items| line_items.first.order.distributor.name } + end + def total_units(line_items) return " " if not_all_have_unit?(line_items) @@ -64,6 +66,7 @@ module Reporting end def variant_scoper_for(distributor_id) + @variant_scopers_by_distributor_id ||= {} @variant_scopers_by_distributor_id[distributor_id] ||= OpenFoodNetwork::ScopeVariantToHub.new( distributor_id, @@ -71,23 +74,6 @@ module Reporting ) end - private - - def report - @report ||= report_klass.new(self) - end - - def report_klass - case report_type - when SupplierTotalsReport::REPORT_TYPE then SupplierTotalsReport - when SupplierTotalsByDistributorReport::REPORT_TYPE then SupplierTotalsByDistributorReport - when DistributorTotalsBySupplierReport::REPORT_TYPE then DistributorTotalsBySupplierReport - when CustomerTotalsReport::REPORT_TYPE then CustomerTotalsReport - else - DefaultReport - end - end - def not_all_have_unit?(line_items) line_items.map { |li| li.unit_value.nil? }.any? end @@ -96,16 +82,6 @@ module Reporting product.variant_unit == 'weight' ? 1000 : 1 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 ||= Reporting::LineItems.new(order_permissions, params) - end - def report_variant_overrides @report_variant_overrides ||= VariantOverridesIndexed.new( diff --git a/lib/reporting/reports/orders_and_fulfillment/customer_totals_report.rb b/lib/reporting/reports/orders_and_fulfillment/customer_totals_report.rb deleted file mode 100644 index b352fd338e..0000000000 --- a/lib/reporting/reports/orders_and_fulfillment/customer_totals_report.rb +++ /dev/null @@ -1,225 +0,0 @@ -# frozen_string_literal: true - -# rubocop:disable Metrics/ClassLength -module Reporting - module Reports - module OrdersAndFulfillment - class CustomerTotalsReport - include ReportsHelper - - REPORT_TYPE = "order_cycle_customer_totals" - - attr_reader :context - - delegate :line_item_name, to: :context - delegate :variant_scoper_for, to: :context - - def initialize(context) - @context = context - @scopers_by_distributor_id = {} - end - - # rubocop:disable Metrics/AbcSize - def table_headers - [I18n.t(:report_header_hub), I18n.t(:report_header_customer), I18n.t(:report_header_email), - I18n.t(:report_header_phone), I18n.t(:report_header_producer), - I18n.t(:report_header_product), I18n.t(:report_header_variant), - I18n.t(:report_header_quantity), - I18n.t(:report_header_item_price, currency: currency_symbol), - I18n.t(:report_header_item_fees_price, currency: currency_symbol), - I18n.t(:report_header_admin_handling_fees, currency: currency_symbol), - I18n.t(:report_header_ship_price, currency: currency_symbol), - I18n.t(:report_header_pay_fee_price, currency: currency_symbol), - I18n.t(:report_header_total_price, currency: currency_symbol), - I18n.t(:report_header_paid), I18n.t(:report_header_shipping), - I18n.t(:report_header_delivery), I18n.t(:report_header_ship_street), - I18n.t(:report_header_ship_street_2), I18n.t(:report_header_ship_city), - I18n.t(:report_header_ship_postcode), I18n.t(:report_header_ship_state), - I18n.t(:report_header_comments), I18n.t(:report_header_sku), - I18n.t(:report_header_order_cycle), I18n.t(:report_header_payment_method), - I18n.t(:report_header_customer_code), I18n.t(:report_header_tags), - I18n.t(:report_header_billing_street), I18n.t(:report_header_billing_street_2), - I18n.t(:report_header_billing_city), I18n.t(:report_header_billing_postcode), - I18n.t(:report_header_billing_state), - I18n.t(:report_header_order_number), - I18n.t(:report_header_date)] - end - - # rubocop:enable Metrics/AbcSize - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/MethodLength - def rules - [ - { - group_by: proc { |line_item| line_item.order.distributor }, - sort_by: proc { |distributor| distributor.name } - }, - { - group_by: proc { |line_item| line_item.order }, - sort_by: proc { |order| order.bill_address.full_name_reverse }, - summary_columns: [ - proc { |line_items| line_items.first.order.distributor.name }, - proc { |line_items| line_items.first.order.bill_address.full_name }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| I18n.t('admin.reports.total') }, - proc { |_line_items| "" }, - - proc { |_line_items| "" }, - proc { |line_items| line_items.sum(&:amount) }, - proc { |line_items| line_items.sum(&:amount_with_adjustments) }, - proc { |line_items| line_items.first.order.admin_and_handling_total }, - proc { |line_items| line_items.first.order.ship_total }, - proc { |line_items| line_items.first.order.payment_fee }, - proc { |line_items| line_items.first.order.total }, - proc { |line_items| line_items.first.order.paid? ? I18n.t(:yes) : I18n.t(:no) }, - - proc { |_line_items| "" }, - proc { |_line_items| "" }, - - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - - proc { |line_items| line_items.first.order.special_instructions }, - proc { |_line_items| "" }, - - proc { |line_items| line_items.first.order.order_cycle&.name }, - proc { |line_items| - line_items.first.order.payments.first&.payment_method&.name - }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |line_items| line_items.first.order.number }, - proc { |line_items| line_items.first.order.completed_at.strftime("%F %T") }, - ] - }, - { - group_by: proc { |line_item| line_item.variant.product }, - sort_by: proc { |product| product.name } - }, - { - group_by: proc { |line_item| line_item.variant }, - sort_by: proc { |variant| variant.full_name } - }, - { - group_by: line_item_name, - sort_by: proc { |full_name| full_name } - } - ] - end - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/MethodLength - - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/MethodLength - # rubocop:disable Metrics/PerceivedComplexity - def columns - rsa = proc { |line_items| shipping_method(line_items)&.delivery? } - [ - proc { |line_items| line_items.first.order.distributor.name }, - proc { |line_items| - bill_address = line_items.first.order.bill_address - "#{bill_address.firstname} #{bill_address.lastname}" - }, - proc { |line_items| line_items.first.order.email }, - proc { |line_items| line_items.first.order.bill_address.phone }, - proc { |line_items| line_items.first.variant.product.supplier.name }, - proc { |line_items| line_items.first.variant.product.name }, - proc { |line_items| line_items.first.variant.full_name }, - - proc { |line_items| line_items.to_a.sum(&:quantity) }, - proc { |line_items| line_items.sum(&:amount) }, - proc { |line_items| line_items.sum(&:amount_with_adjustments) }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |line_items| - line_items.all? { |li| li.order.paid? } ? I18n.t(:yes) : I18n.t(:no) - }, - - proc { |line_items| shipping_method(line_items)&.name }, - proc { |line_items| rsa.call(line_items) ? I18n.t(:yes) : I18n.t(:no) }, - - proc { |line_items| - line_items.first.order.ship_address&.address1 if rsa.call(line_items) - }, - proc { |line_items| - line_items.first.order.ship_address&.address2 if rsa.call(line_items) - }, - proc { |line_items| - line_items.first.order.ship_address&.city if rsa.call(line_items) - }, - proc { |line_items| - line_items.first.order.ship_address&.zipcode if rsa.call(line_items) - }, - proc { |line_items| - line_items.first.order.ship_address&.state if rsa.call(line_items) - }, - - proc { |_line_items| "" }, - proc do |line_items| - line_item = line_items.first - variant_scoper_for(line_item.order.distributor_id).scope(line_item.variant) - line_item.variant.sku - end, - - proc { |line_items| line_items.first.order.order_cycle&.name }, - proc { |line_items| - payment = line_items.first.order.payments.first - payment&.payment_method&.name - }, - proc { |line_items| - distributor = line_items.first.order.distributor - user = line_items.first.order.user - user&.customer_of(distributor)&.code - }, - proc { |line_items| - distributor = line_items.first.order.distributor - user = line_items.first.order.user - user&.customer_of(distributor)&.tags&.join(', ') - }, - - proc { |line_items| line_items.first.order.bill_address&.address1 }, - proc { |line_items| line_items.first.order.bill_address&.address2 }, - proc { |line_items| line_items.first.order.bill_address&.city }, - proc { |line_items| line_items.first.order.bill_address&.zipcode }, - proc { |line_items| line_items.first.order.bill_address&.state }, - proc { |line_items| line_items.first.order.number }, - proc { |line_items| line_items.first.order.completed_at.strftime("%F %T") }, - ] - end - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/MethodLength - # rubocop:enable Metrics/PerceivedComplexity - - def line_item_includes - [{ variant: [{ option_values: :option_type }, { product: :supplier }], - order: [:bill_address, :ship_address, :order_cycle, :adjustments, :payments, - :user, :distributor, :shipments] }] - end - - private - - def shipping_method(line_items) - shipping_rates = line_items.first.order.shipments.first&.shipping_rates - - return unless shipping_rates - - shipping_rate = shipping_rates.find(&:selected) || shipping_rates.first - shipping_rate.try(:shipping_method) - end - end - end - end -end -# rubocop:enable Metrics/ClassLength diff --git a/lib/reporting/reports/orders_and_fulfillment/default_report.rb b/lib/reporting/reports/orders_and_fulfillment/default_report.rb deleted file mode 100644 index 3bde81eca1..0000000000 --- a/lib/reporting/reports/orders_and_fulfillment/default_report.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -module Reporting - module Reports - module OrdersAndFulfillment - class DefaultReport - delegate :line_item_name, :supplier_name, :product_name, :line_items_name, to: :context - - def initialize(context) - @context = context - end - - def table_headers - [ - I18n.t(:report_header_producer), - I18n.t(:report_header_product), - I18n.t(:report_header_variant), - I18n.t(:report_header_quantity), - I18n.t(:report_header_curr_cost_per_unit), - I18n.t(:report_header_total_cost), - I18n.t(:report_header_status), - I18n.t(:report_header_incoming_transport) - ] - end - - def rules - [ - { - group_by: proc { |line_item| line_item.variant.product.supplier }, - sort_by: proc { |supplier| supplier.name } - }, - { - group_by: proc { |line_item| line_item.variant.product }, - sort_by: proc { |product| product.name } - }, - { - group_by: line_item_name, - sort_by: proc { |full_name| full_name } - } - ] - end - - def columns - [ - supplier_name, - product_name, - line_items_name, - proc { |line_items| line_items.to_a.sum(&:quantity) }, - proc { |line_items| line_items.first.price }, - proc { |line_items| line_items.sum { |li| li.quantity * li.price } }, - proc { |_line_items| "" }, - proc { |_line_items| I18n.t(:report_header_incoming_transport) } - ] - end - - def line_item_includes - [] - end - - private - - attr_reader :context - end - end - end -end diff --git a/lib/reporting/reports/orders_and_fulfillment/distributor_totals_by_supplier_report.rb b/lib/reporting/reports/orders_and_fulfillment/distributor_totals_by_supplier_report.rb deleted file mode 100644 index 88bf7ebee5..0000000000 --- a/lib/reporting/reports/orders_and_fulfillment/distributor_totals_by_supplier_report.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true - -module Reporting - module Reports - module OrdersAndFulfillment - class DistributorTotalsBySupplierReport - REPORT_TYPE = "order_cycle_distributor_totals_by_supplier" - - attr_reader :context - - def initialize(context) - @context = context - end - - def table_headers - [I18n.t(:report_header_hub), I18n.t(:report_header_producer), - I18n.t(:report_header_product), I18n.t(:report_header_variant), - I18n.t(:report_header_quantity), I18n.t(:report_header_curr_cost_per_unit), - I18n.t(:report_header_total_cost), I18n.t(:report_header_total_shipping_cost), - I18n.t(:report_header_shipping_method)] - end - - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/MethodLength - def rules - [ - { - group_by: proc { |line_item| line_item.order.distributor }, - sort_by: proc { |distributor| distributor.name }, - summary_columns: [ - proc { |_line_items| "" }, - proc { |_line_items| I18n.t('admin.reports.total') }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |line_items| line_items.sum(&:amount) }, - proc { |line_items| line_items.map(&:order).uniq.sum(&:ship_total) }, - proc { |_line_items| "" } - ] - }, - { - group_by: proc { |line_item| line_item.variant.product.supplier }, - sort_by: proc { |supplier| supplier.name } - }, - { - group_by: proc { |line_item| line_item.variant.product }, - sort_by: proc { |product| product.name } - }, - { - group_by: proc { |line_item| line_item.variant.full_name }, - sort_by: proc { |full_name| full_name } - } - ] - end - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/MethodLength - - # rubocop:disable Metrics/AbcSize - def columns - [proc { |line_items| line_items.first.order.distributor.name }, - proc { |line_items| line_items.first.variant.product.supplier.name }, - proc { |line_items| line_items.first.variant.product.name }, - proc { |line_items| line_items.first.variant.full_name }, - proc { |line_items| line_items.to_a.sum(&:quantity) }, - proc { |line_items| line_items.first.price }, - proc { |line_items| line_items.sum(&:amount) }, - proc { |_line_items| "" }, - proc { |_line_items| I18n.t(:report_header_shipping_method) }] - end - # rubocop:enable Metrics/AbcSize - - def line_item_includes - [{ order: [:distributor, :adjustments, { shipments: { shipping_rates: :shipping_method } }], - variant: [{ option_values: :option_type }, { product: :supplier }] }] - end - end - end - end -end diff --git a/lib/reporting/reports/orders_and_fulfillment/order_cycle_customer_totals.rb b/lib/reporting/reports/orders_and_fulfillment/order_cycle_customer_totals.rb new file mode 100644 index 0000000000..1c3739aed6 --- /dev/null +++ b/lib/reporting/reports/orders_and_fulfillment/order_cycle_customer_totals.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +# rubocop:disable Metrics/ClassLength +module Reporting + module Reports + module OrdersAndFulfillment + class OrderCycleCustomerTotals < Base + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/PerceivedComplexity + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Naming/VariableNumber + def columns + { + hub: hub_name, + customer: proc { |line_items| + bill_address = bill_address(line_items) + "#{bill_address&.firstname} #{bill_address&.lastname}" + }, + email: proc { |line_items| line_items.first.order.email }, + phone: proc { |line_items| bill_address(line_items)&.phone }, + producer: supplier_name, + product: product_name, + variant: variant_name, + + quantity: proc { |line_items| line_items.to_a.sum(&:quantity) }, + item_price: proc { |line_items| line_items.sum(&:amount) }, + item_fees_price: proc { |line_items| line_items.sum(&:amount_with_adjustments) }, + admin_handling_fees: proc { |_line_items| "" }, + ship_price: proc { |_line_items| "" }, + pay_fee_price: proc { |_line_items| "" }, + total_price: proc { |_line_items| "" }, + paid: proc { |line_items| line_items.all? { |li| li.order.paid? } }, + + shipping: proc { |line_items| shipping_method(line_items)&.name }, + delivery: proc { |line_items| delivery?(line_items) }, + + ship_street: proc { |line_items| ship_address(line_items)&.address1 }, + ship_street_2: proc { |line_items| ship_address(line_items)&.address2 }, + ship_city: proc { |line_items| ship_address(line_items)&.city }, + ship_postcode: proc { |line_items| ship_address(line_items)&.zipcode }, + ship_state: proc { |line_items| ship_address(line_items)&.state }, + + comments: proc { |_line_items| "" }, + sku: proc do |line_items| + line_item = line_items.first + variant_scoper_for(line_item.order.distributor_id).scope(line_item.variant) + line_item.variant.sku + end, + + order_cycle: proc { |line_items| line_items.first.order.order_cycle&.name }, + payment_method: proc { |line_items| + payment = line_items.first.order.payments.first + payment&.payment_method&.name + }, + customer_code: proc { |line_items| distributor_customer(line_items)&.code }, + tags: proc { |line_items| distributor_customer(line_items)&.tags&.join(', ') }, + + billing_street: proc { |line_items| bill_address(line_items)&.address1 }, + billing_street_2: proc { |line_items| bill_address(line_items)&.address2 }, + billing_city: proc { |line_items| bill_address(line_items)&.city }, + billing_postcode: proc { |line_items| bill_address(line_items)&.zipcode }, + billing_state: proc { |line_items| bill_address(line_items)&.state }, + + order_number: proc { |line_items| line_items.first.order.number }, + date: proc { |line_items| line_items.first.order.completed_at.strftime("%F %T") }, + } + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/PerceivedComplexity + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Naming/VariableNumber + + def rules + [ + { + group_by: :hub, + header: proc { |key, _items, _rows| "#{I18n.t(:report_header_hub)} #{key}" }, + header_class: "h1", + }, + { + group_by: proc { |line_items, _row| line_items.first.order }, + sort_by: proc { |order| order.bill_address.full_name_reverse }, + header: proc { |_order, _items, rows| row_header(rows.first) }, + fields_used_in_header: [:customer, :email, :phone, :order_cycle, :order_number], + summary_row: proc { |order, _grouped_line_items, rows| summary_row(order, rows) } + }, + ] + end + + def line_item_includes + [{ variant: [{ option_values: :option_type }, { product: :supplier }], + order: [:bill_address, :ship_address, :order_cycle, :adjustments, :payments, + :user, :distributor, :shipments] }] + end + + private + + def row_header(row) + result = row.customer + result += " - #{row.email}" if row.email + result += " - #{row.phone}" if row.phone + result += " | #{row.order_cycle} (#{row.order_number})" + result + end + + def summary_row(order, rows) + { + hub: rows.last.hub, + customer: rows.last.customer, + item_price: rows.sum(&:item_price), + item_fees_price: rows.sum(&:item_fees_price), + admin_handling_fees: order.admin_and_handling_total, + ship_price: order.ship_total, + pay_fee_price: order.payment_fee, + total_price: order.total, + paid: order.paid?, + comments: order.special_instructions, + order_cycle: order.order_cycle&.name, + payment_method: order.payments.first&.payment_method&.name, + order_number: order.number, + date: order.completed_at, + } + end + + def shipping_method(line_items) + return unless shipping_rates = line_items.first.order.shipments.first&.shipping_rates + + shipping_rate = shipping_rates.find(&:selected) || shipping_rates.first + shipping_rate.try(:shipping_method) + end + + def delivery?(line_items) + shipping_method(line_items)&.delivery? + end + + def ship_address(line_items) + line_items.first.order.ship_address if delivery?(line_items) + end + + def bill_address(line_items) + line_items.first.order.bill_address + end + + def distributor_customer(line_items) + distributor = line_items.first.order.distributor + user = line_items.first.order.user + user&.customer_of(distributor) + end + end + end + end +end +# rubocop:enable Metrics/ClassLength diff --git a/lib/reporting/reports/orders_and_fulfillment/order_cycle_distributor_totals_by_supplier.rb b/lib/reporting/reports/orders_and_fulfillment/order_cycle_distributor_totals_by_supplier.rb new file mode 100644 index 0000000000..be4c1ecdf1 --- /dev/null +++ b/lib/reporting/reports/orders_and_fulfillment/order_cycle_distributor_totals_by_supplier.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module OrdersAndFulfillment + class OrderCycleDistributorTotalsBySupplier < Base + def columns + { + hub: hub_name, + producer: supplier_name, + product: product_name, + variant: variant_name, + quantity: proc { |line_items| line_items.to_a.sum(&:quantity) }, + curr_cost_per_unit: proc { |line_items| line_items.first.price }, + total_cost: proc { |line_items| line_items.sum(&:amount) }, + shipping_method: proc { |line_items| line_items.first.order.shipping_method&.name }, + total_shipping_cost: proc { |_line_items| "" } + } + end + + def rules + [ + { + group_by: :hub, + header: proc { |key, _items, _rows| "#{I18n.t(:report_header_hub)} #{key}" }, + summary_row: proc do |_key, line_items, rows| + { + total_cost: rows.sum(&:total_cost), + total_shipping_cost: line_items.map(&:first).map(&:order).uniq.sum(&:ship_total), + shipping_method: rows.first.shipping_method + } + end + } + ] + end + + def line_item_includes + [{ + order: [ + :distributor, + :adjustments, + { shipments: { shipping_rates: :shipping_method } } + ], + variant: [{ option_values: :option_type }, { product: :supplier }] + }] + end + end + end + end +end diff --git a/lib/reporting/reports/orders_and_fulfillment/order_cycle_supplier_totals.rb b/lib/reporting/reports/orders_and_fulfillment/order_cycle_supplier_totals.rb new file mode 100644 index 0000000000..abaf4ad8e4 --- /dev/null +++ b/lib/reporting/reports/orders_and_fulfillment/order_cycle_supplier_totals.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module OrdersAndFulfillment + class OrderCycleSupplierTotals < Base + def columns + { + producer: supplier_name, + product: product_name, + variant: variant_name, + curr_cost_per_unit: proc { |line_items| line_items.first.price }, + quantity: proc { |line_items| line_items.sum(&:quantity) }, + total_units: proc { |line_items| total_units(line_items) }, + total_cost: proc { |line_items| line_items.sum(&:amount) }, + } + end + + def rules + [ + { + group_by: :producer, + header: true, + summary_row: proc do |_key, _items, rows| + { + quantity: rows.sum(&:quantity), + total_units: rows.sum(&:total_units), + total_cost: rows.sum(&:total_cost) + } + end + } + ] + end + + def line_item_includes + [{ variant: [{ option_values: :option_type }, { product: :supplier }] }] + end + end + end + end +end diff --git a/lib/reporting/reports/orders_and_fulfillment/order_cycle_supplier_totals_by_distributor.rb b/lib/reporting/reports/orders_and_fulfillment/order_cycle_supplier_totals_by_distributor.rb new file mode 100644 index 0000000000..2a64349125 --- /dev/null +++ b/lib/reporting/reports/orders_and_fulfillment/order_cycle_supplier_totals_by_distributor.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module OrdersAndFulfillment + class OrderCycleSupplierTotalsByDistributor < Base + def columns + { + producer: supplier_name, + product: product_name, + variant: variant_name, + curr_cost_per_unit: proc { |line_items| line_items.first.price }, + hub: hub_name, + quantity: proc { |line_items| line_items.to_a.sum(&:quantity) }, + total_cost: proc { |line_items| line_items.sum(&:amount) }, + shipping_method: proc { |line_items| line_items.first.order.shipping_method&.name } + } + end + + def rules + [ + { + group_by: :producer, + header: true, + }, + { + group_by: proc { |line_items, _row| line_items.first.variant } + }, + { + group_by: :hub, + summary_row: proc do |_key, _items, rows| + { + quantity: rows.sum(&:quantity), + total_cost: rows.sum(&:total_cost) + } + end, + } + ] + end + + def line_item_includes + [{ order: :distributor, + variant: [{ option_values: :option_type }, { product: :supplier }] }] + end + end + end + end +end diff --git a/lib/reporting/reports/orders_and_fulfillment/supplier_totals_by_distributor_report.rb b/lib/reporting/reports/orders_and_fulfillment/supplier_totals_by_distributor_report.rb deleted file mode 100644 index 275ab9b0a8..0000000000 --- a/lib/reporting/reports/orders_and_fulfillment/supplier_totals_by_distributor_report.rb +++ /dev/null @@ -1,79 +0,0 @@ -# frozen_string_literal: true - -module Reporting - module Reports - module OrdersAndFulfillment - class SupplierTotalsByDistributorReport - REPORT_TYPE = "order_cycle_supplier_totals_by_distributor" - - attr_reader :context - - delegate :supplier_name, to: :context - - def initialize(context) - @context = context - end - - def table_headers - [I18n.t(:report_header_producer), I18n.t(:report_header_product), - I18n.t(:report_header_variant), I18n.t(:report_header_to_hub), - I18n.t(:report_header_quantity), I18n.t(:report_header_curr_cost_per_unit), - I18n.t(:report_header_total_cost), I18n.t(:report_header_shipping_method)] - end - - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/MethodLength - def rules - [ - { - group_by: proc { |line_item| line_item.variant.product.supplier }, - sort_by: proc { |supplier| supplier.name } - }, - { - group_by: proc { |line_item| line_item.variant.product }, - sort_by: proc { |product| product.name } - }, - { - group_by: proc { |line_item| line_item.variant.full_name }, - sort_by: proc { |full_name| full_name }, - summary_columns: [ - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |_line_items| I18n.t('admin.reports.total') }, - proc { |_line_items| "" }, - proc { |_line_items| "" }, - proc { |line_items| line_items.sum(&:amount) }, - proc { |_line_items| "" } - ] - }, - { - group_by: proc { |line_item| line_item.order.distributor }, - sort_by: proc { |distributor| distributor.name } - } - ] - end - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/MethodLength - - def columns - [ - supplier_name, - proc { |line_items| line_items.first.variant.product.name }, - proc { |line_items| line_items.first.variant.full_name }, - proc { |line_items| line_items.first.order.distributor.name }, - proc { |line_items| line_items.to_a.sum(&:quantity) }, - proc { |line_items| line_items.first.price }, - proc { |line_items| line_items.sum(&:amount) }, - proc { |_line_items| I18n.t(:report_header_shipping_method) } - ] - end - - def line_item_includes - [{ order: :distributor, - variant: [{ option_values: :option_type }, { product: :supplier }] }] - end - end - end - end -end diff --git a/lib/reporting/reports/orders_and_fulfillment/supplier_totals_report.rb b/lib/reporting/reports/orders_and_fulfillment/supplier_totals_report.rb deleted file mode 100644 index 5e4f8c7ffe..0000000000 --- a/lib/reporting/reports/orders_and_fulfillment/supplier_totals_report.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -module Reporting - module Reports - module OrdersAndFulfillment - class SupplierTotalsReport - REPORT_TYPE = "order_cycle_supplier_totals" - - attr_reader :context - - delegate :supplier_name, :product_name, :line_items_name, :total_units, to: :context - - def initialize(context) - @context = context - end - - def table_headers - [I18n.t(:report_header_producer), I18n.t(:report_header_product), - I18n.t(:report_header_variant), I18n.t(:report_header_quantity), - I18n.t(:report_header_total_units), I18n.t(:report_header_curr_cost_per_unit), - I18n.t(:report_header_total_cost), I18n.t(:report_header_status), - I18n.t(:report_header_incoming_transport)] - end - - def rules - [ - { - group_by: proc { |line_item| line_item.variant.product.supplier }, - sort_by: proc { |supplier| supplier.name } - }, - { - group_by: proc { |line_item| line_item.variant.product }, - sort_by: proc { |product| product.name } - }, - { - group_by: proc { |line_item| line_item.variant.full_name }, - sort_by: proc { |full_name| full_name } - } - ] - end - - def columns - [ - supplier_name, - product_name, - line_items_name, - proc { |line_items| line_items.to_a.sum(&:quantity) }, - proc { |line_items| total_units(line_items) }, - proc { |line_items| line_items.first.price }, - proc { |line_items| line_items.sum(&:amount) }, - proc { |_line_items| "" }, - proc { |_line_items| I18n.t(:report_header_incoming_transport) } - ] - end - - def line_item_includes - [{ variant: [{ option_values: :option_type }, { product: :supplier }] }] - end - end - end - end -end diff --git a/spec/controllers/admin/reports_controller_spec.rb b/spec/controllers/admin/reports_controller_spec.rb index 00d1031da6..586efbe66e 100644 --- a/spec/controllers/admin/reports_controller_spec.rb +++ b/spec/controllers/admin/reports_controller_spec.rb @@ -78,8 +78,9 @@ describe Admin::ReportsController, type: :controller do # Results let(:resulting_orders_prelim) { assigns(:report).search.result } - let(:resulting_orders) { assigns(:report).table_items.map(&:order) } - let(:resulting_products) { assigns(:report).table_items.map(&:product) } + let(:resulting_line_items) { assigns(:report).query_result.flatten } + let(:resulting_orders) { resulting_line_items.map(&:order).uniq } + let(:resulting_products) { resulting_line_items.map(&:product).uniq } # As manager of a coordinator (coordinator1) context "Coordinator Enterprise User" do @@ -194,8 +195,7 @@ describe Admin::ReportsController, type: :controller do it "only shows product line items that I am supplying" do spree_post :show, report_type: :orders_and_fulfillment, q: {} - table_items = assigns(:report).table_items - variant = Spree::Variant.unscoped.find(table_items.first.variant_id) + variant = Spree::Variant.unscoped.find(resulting_line_items.first.variant_id) expect(variant.product).to eq(product1) end diff --git a/spec/lib/reports/orders_and_fulfillment/customer_totals_report_spec.rb b/spec/lib/reports/orders_and_fulfillment/order_cycle_customer_totals_report_spec.rb similarity index 70% rename from spec/lib/reports/orders_and_fulfillment/customer_totals_report_spec.rb rename to spec/lib/reports/orders_and_fulfillment/order_cycle_customer_totals_report_spec.rb index a0a1ff5184..8d39e883a4 100644 --- a/spec/lib/reports/orders_and_fulfillment/customer_totals_report_spec.rb +++ b/spec/lib/reports/orders_and_fulfillment/order_cycle_customer_totals_report_spec.rb @@ -5,15 +5,12 @@ require "spec_helper" module Reporting module Reports module OrdersAndFulfillment - describe CustomerTotalsReport do + describe OrderCycleCustomerTotals do let!(:distributor) { create(:distributor_enterprise) } let!(:customer) { create(:customer, enterprise: distributor) } let(:current_user) { distributor.owner } - - let(:report) do - report_options = { report_subtype: described_class::REPORT_TYPE } - OrdersAndFulfillmentReport.new(current_user, report_options) - end + let(:params) { { display_summary_row: true } } + let(:report) { OrderCycleCustomerTotals.new(current_user, params) } let(:report_table) do report.table_rows @@ -35,25 +32,11 @@ module Reporting customer_name_field = report_table.first[1] expect(customer_name_field).to eq order.bill_address.full_name - - total_field = report_table.last[5] - expect(total_field).to eq I18n.t("admin.reports.total") end it 'includes the order number and date in item rows' do - order_number_and_date_fields = report_table.first[33..34] - expect(order_number_and_date_fields).to eq([ - order.number, - order.completed_at.strftime("%F %T"), - ]) - end - - it 'includes the order number and date in total rows' do - order_number_and_date_fields = report_table.last[33..34] - expect(order_number_and_date_fields).to eq([ - order.number, - order.completed_at.strftime("%F %T"), - ]) + expect(report.rows.first.order_number).to eq order.number + expect(report.rows.first.date).to eq order.completed_at.strftime("%F %T") end end @@ -78,8 +61,7 @@ module Reporting end it "displays the correct shipping_method" do - shipping_method_name_field = report_table.first[15] - expect(shipping_method_name_field).to eq shipping_method2.name + expect(report.rows.first.shipping).to eq shipping_method2.name end end @@ -98,8 +80,7 @@ module Reporting end it "shows the correct payment fee amount for the order" do - payment_fee_field = report_table.last[12] - expect(payment_fee_field).to eq completed_payment.adjustment.amount + expect(report.rows.last.pay_fee_price).to eq completed_payment.adjustment.amount end end end @@ -121,8 +102,7 @@ module Reporting end it 'uses the sku from the variant override' do - sku_field = report_table.first[23] - expect(sku_field).to eq overidden_sku + expect(report.rows.first.sku).to eq overidden_sku end end end diff --git a/spec/lib/reports/orders_and_fulfillment/distributor_totals_by_supplier_report_spec.rb b/spec/lib/reports/orders_and_fulfillment/order_cycle_distributor_totals_by_supplier_report_spec.rb similarity index 75% rename from spec/lib/reports/orders_and_fulfillment/distributor_totals_by_supplier_report_spec.rb rename to spec/lib/reports/orders_and_fulfillment/order_cycle_distributor_totals_by_supplier_report_spec.rb index e8a0fa3864..39283c6fd3 100644 --- a/spec/lib/reports/orders_and_fulfillment/distributor_totals_by_supplier_report_spec.rb +++ b/spec/lib/reports/orders_and_fulfillment/order_cycle_distributor_totals_by_supplier_report_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' module Reporting module Reports module OrdersAndFulfillment - describe DistributorTotalsBySupplierReport do + describe OrderCycleDistributorTotalsBySupplier do let!(:distributor) { create(:distributor_enterprise) } let!(:order) do @@ -13,10 +13,9 @@ module Reporting end let(:current_user) { distributor.owner } - + let(:params) { { display_summary_row: true } } let(:report) do - report_options = { report_subtype: described_class::REPORT_TYPE } - OrdersAndFulfillmentReport.new(current_user, report_options) + OrderCycleDistributorTotalsBySupplier.new(current_user, params) end let(:report_table) do @@ -34,9 +33,6 @@ module Reporting supplier = order.line_items.first.variant.product.supplier supplier_name_field = report_table.first[1] expect(supplier_name_field).to eq supplier.name - - total_field = report_table.last[1] - expect(total_field).to eq I18n.t("admin.reports.total") end end end diff --git a/spec/lib/reports/orders_and_fulfillment/supplier_totals_by_distributor_report_spec.rb b/spec/lib/reports/orders_and_fulfillment/order_cycle_supplier_totals_by_distributor_report_spec.rb similarity index 55% rename from spec/lib/reports/orders_and_fulfillment/supplier_totals_by_distributor_report_spec.rb rename to spec/lib/reports/orders_and_fulfillment/order_cycle_supplier_totals_by_distributor_report_spec.rb index b265e6c338..d99bd2b28f 100644 --- a/spec/lib/reports/orders_and_fulfillment/supplier_totals_by_distributor_report_spec.rb +++ b/spec/lib/reports/orders_and_fulfillment/order_cycle_supplier_totals_by_distributor_report_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' module Reporting module Reports module OrdersAndFulfillment - describe Reporting::Reports::OrdersAndFulfillment::SupplierTotalsByDistributorReport do + describe OrderCycleSupplierTotalsByDistributor do let!(:distributor) { create(:distributor_enterprise) } let!(:order) do @@ -13,10 +13,9 @@ module Reporting end let(:current_user) { distributor.owner } - + let(:params) { { display_summary_row: true } } let(:report) do - report_options = { report_subtype: described_class::REPORT_TYPE } - OrdersAndFulfillmentReport.new(current_user, report_options) + OrderCycleSupplierTotalsByDistributor.new(current_user, params) end let(:report_table) do @@ -29,14 +28,8 @@ module Reporting it "has a variant row under the distributor" do supplier = order.line_items.first.variant.product.supplier - supplier_name_field = report_table.first[0] - expect(supplier_name_field).to eq supplier.name - - distributor_name_field = report_table.first[3] - expect(distributor_name_field).to eq distributor.name - - total_field = report_table.last[3] - expect(total_field).to eq I18n.t("admin.reports.total") + expect(report.rows.first.producer).to eq supplier.name + expect(report.rows.first.hub).to eq distributor.name end end end diff --git a/spec/lib/reports/orders_and_fulfillment/orders_and_fulfillment_report_spec.rb b/spec/lib/reports/orders_and_fulfillment/orders_and_fulfillment_report_spec.rb deleted file mode 100644 index 5d6bc377ee..0000000000 --- a/spec/lib/reports/orders_and_fulfillment/orders_and_fulfillment_report_spec.rb +++ /dev/null @@ -1,284 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Reporting - module Reports - module OrdersAndFulfillment - describe OrdersAndFulfillmentReport do - include AuthenticationHelper - - let(:distributor) { create(:distributor_enterprise) } - let(:order_cycle) { create(:simple_order_cycle) } - let(:address) { create(:address) } - let(:order) { - create( - :order, - completed_at: 1.day.ago, - order_cycle: order_cycle, - distributor: distributor, - bill_address: address - ) - } - let(:line_item) { build(:line_item_with_shipment) } - let(:user) { create(:user) } - let(:admin_user) { create(:admin_user) } - - describe "fetching orders" do - before { order.line_items << line_item } - - context "as a site admin" do - subject { described_class.new(admin_user, {}) } - - it "fetches completed orders" do - o2 = create(:order) - o2.line_items << build(:line_item) - expect(subject.table_items).to eq([line_item]) - end - - it "does not show cancelled orders" do - o2 = create(:order, state: "canceled", completed_at: 1.day.ago) - o2.line_items << build(:line_item_with_shipment) - expect(subject.table_items).to eq([line_item]) - end - end - - context "as a manager of a supplier" do - subject { described_class.new(user, {}) } - - let(:s1) { create(:supplier_enterprise) } - - before do - s1.enterprise_roles.create!(user: user) - end - - context "that has granted P-OC to the distributor" do - let(:o2) { - create( - :order, - distributor: distributor, - completed_at: 1.day.ago, - bill_address: create(:address), - ship_address: create(:address) - ) - } - let(:li2) { - build(:line_item_with_shipment, product: create(:simple_product, supplier: s1)) - } - - before do - o2.line_items << li2 - create( - :enterprise_relationship, - parent: s1, - child: distributor, - permissions_list: [:add_to_order_cycle] - ) - end - - it "shows line items supplied by my producers, with names hidden" do - expect(subject.table_items).to eq([li2]) - expect(subject.table_items.first.order.bill_address.firstname).to eq("HIDDEN") - end - - context "where the distributor allows suppliers to see customer names" do - before do - distributor.update_columns show_customer_names_to_suppliers: true - end - - it "shows line items supplied by my producers, with names shown" do - expect(subject.table_items).to eq([li2]) - expect(subject.table_items.first.order.bill_address.firstname). - to eq(order.bill_address.firstname) - end - end - end - - context "that has not granted P-OC to the distributor" do - let(:o2) { - create( - :order, - distributor: distributor, - completed_at: 1.day.ago, - bill_address: create(:address), - ship_address: create(:address) - ) - } - let(:li2) { - build(:line_item_with_shipment, product: create(:simple_product, supplier: s1)) - } - - before do - o2.line_items << li2 - end - - it "does not show line items supplied by my producers" do - expect(subject.table_items).to eq([]) - end - - context "where the distributor allows suppliers to see customer names" do - before do - distributor.show_customer_names_to_suppliers = true - end - - it "does not show line items supplied by my producers" do - expect(subject.table_items).to eq([]) - end - end - end - end - - context "as a manager of a distributor" do - subject { described_class.new(user, {}) } - - before do - distributor.enterprise_roles.create!(user: user) - end - - it "only shows line items distributed by enterprises managed by the current user" do - d2 = create(:distributor_enterprise) - d2.enterprise_roles.create!(user: create(:user)) - o2 = create(:order, distributor: d2, completed_at: 1.day.ago) - o2.line_items << build(:line_item_with_shipment) - expect(subject.table_items).to eq([line_item]) - end - - it "only shows the selected order cycle" do - oc2 = create(:simple_order_cycle) - o2 = create(:order, distributor: distributor, order_cycle: oc2) - o2.line_items << build(:line_item) - allow(subject).to receive(:params).and_return(order_cycle_id_in: order_cycle.id) - expect(subject.table_items).to eq([line_item]) - end - end - end - - describe "columns are aligned" do - it 'has aligned columsn' do - report_types = [ - "", - "order_cycle_supplier_totals", - "order_cycle_supplier_totals_by_distributor", - "order_cycle_distributor_totals_by_supplier", - "order_cycle_customer_totals" - ] - - report_types.each do |report_type| - report = described_class.new(admin_user, report_subtype: report_type) - expect(report.table_headers.size).to eq(report.columns.size) - end - end - end - - describe "order_cycle_customer_totals" do - let!(:product) { line_item.product } - let!(:fuji) do - create(:variant, product: product, display_name: "Fuji", sku: "FUJI", on_hand: 100) - end - let!(:gala) do - create(:variant, product: product, display_name: "Gala", sku: "GALA", on_hand: 100) - end - - let(:items) { - described_class.new(admin_user, { report_subtype: "order_cycle_customer_totals" }) - .table_rows - } - - before do - # Clear price so it will be computed based on quantity and variant price. - order.line_items << build(:line_item_with_shipment, variant: fuji, price: nil, - quantity: 1) - order.line_items << build(:line_item_with_shipment, variant: gala, price: nil, - quantity: 2) - end - - it "has a product row" do - product_name_field = items.first[5] - expect(product_name_field).to eq product.name - end - - it "has a summary row" do - product_name_field = items.last[5] - expect(product_name_field).to eq "TOTAL" - end - - # Expected Report for Scenario: - # - # Row 1: Armstrong Amari, Fuji Apple, price: 8 - # Row 2: SUMMARY - # Row 3: Bartoletti Brooklyn, Fuji Apple, price: 1 + 4 - # Row 4: Bartoletti Brooklyn, Gala Apple, price: 2 - # Row 5: SUMMARY - describe "grouping of line items" do - let!(:address) { create(:address, last_name: "Bartoletti", first_name: "Brooklyn") } - - let!(:second_address) { create(:address, last_name: "Armstrong", first_name: "Amari") } - let!(:second_order) do - create(:order, completed_at: 1.day.ago, order_cycle: order_cycle, distributor: distributor, - bill_address: second_address) - end - - before do - # Add a second line item for Fuji variant to the order, to test grouping in this edge case. - order.line_items << build(:line_item_with_shipment, variant: fuji, price: nil, - quantity: 4) - - second_order.line_items << build(:line_item_with_shipment, variant: fuji, price: nil, - quantity: 8) - end - - it "groups line items by variant and order" do - expect(items.length).to eq(5) - - # Row 1: Armstrong Amari, Fuji Apple, price: 8 - row_data = items[0] - expect(customer_name(row_data)).to eq(second_address.full_name) - expect(amount(row_data)).to eq(fuji.price * 8) - expect(variant_sku(row_data)).to eq(fuji.sku) - - # Row 2: SUMMARY - row_data = items[1] - expect(totals_row?(row_data)).to eq(true) - expect(customer_name(row_data)).to eq(second_address.full_name) - expect(amount(row_data)).to eq(fuji.price * 8) - - # Row 3: Bartoletti Brooklyn, Fuji Apple, price: 1 + 4 - row_data = items[2] - expect(customer_name(row_data)).to eq(address.full_name) - expect(amount(row_data)).to eq(fuji.price * 5) - expect(variant_sku(row_data)).to eq(fuji.sku) - - # Row 4: Bartoletti Brooklyn, Gala Apple, price: 2 - row_data = items[3] - expect(customer_name(row_data)).to eq(address.full_name) - expect(amount(row_data)).to eq(gala.price * 2) - expect(variant_sku(row_data)).to eq(gala.sku) - - # Row 5: SUMMARY - row_data = items[4] - expect(totals_row?(row_data)).to eq(true) - expect(customer_name(row_data)).to eq(address.full_name) - expect(amount(row_data)).to eq(fuji.price * 5 + gala.price * 2) - end - end - - def totals_row?(row_data) - row_data[5] == I18n.t("admin.reports.total") - end - - def customer_name(row_data) - row_data[1] - end - - def amount(row_data) - row_data[8] - end - - def variant_sku(row_data) - row_data[23] - end - end - end - end - end -end diff --git a/spec/lib/reports/orders_and_fulfillment/supplier_totals_report_spec.rb b/spec/lib/reports/orders_and_fulfillment/orders_cycle_supplier_totals_report_spec.rb similarity index 81% rename from spec/lib/reports/orders_and_fulfillment/supplier_totals_report_spec.rb rename to spec/lib/reports/orders_and_fulfillment/orders_cycle_supplier_totals_report_spec.rb index bbf2ad2ec4..2c2c7047d7 100644 --- a/spec/lib/reports/orders_and_fulfillment/supplier_totals_report_spec.rb +++ b/spec/lib/reports/orders_and_fulfillment/orders_cycle_supplier_totals_report_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' module Reporting module Reports module OrdersAndFulfillment - describe SupplierTotalsReport do + describe OrderCycleSupplierTotals do let!(:distributor) { create(:distributor_enterprise) } let!(:order) do @@ -13,10 +13,9 @@ module Reporting end let(:current_user) { distributor.owner } - + let(:params) { { display_summary_row: false } } let(:report) do - report_options = { report_subtype: described_class::REPORT_TYPE } - OrdersAndFulfillmentReport.new(current_user, report_options) + OrderCycleSupplierTotals.new(current_user, params) end let(:report_table) do From 22fe652e89b1bd6b8cd6b9ae15b5fa84f3c38d7f Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sat, 2 Apr 2022 17:29:18 +0200 Subject: [PATCH 28/54] Report Refactor 3: Orders & Distributors --- .../reports/orders_and_distributors/base.rb | 63 ++++++++++ .../orders_and_distributors_report.rb | 111 ------------------ .../orders_and_distributors_report_spec.rb | 8 +- 3 files changed, 67 insertions(+), 115 deletions(-) create mode 100644 lib/reporting/reports/orders_and_distributors/base.rb delete mode 100644 lib/reporting/reports/orders_and_distributors/orders_and_distributors_report.rb diff --git a/lib/reporting/reports/orders_and_distributors/base.rb b/lib/reporting/reports/orders_and_distributors/base.rb new file mode 100644 index 0000000000..92e2b3b06e --- /dev/null +++ b/lib/reporting/reports/orders_and_distributors/base.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module OrdersAndDistributors + class Base < ReportObjectTemplate + def initialize(user, params = {}) + super(user, params) + end + + # rubocop:disable Metrics/AbcSize + def columns + { + order_date: proc { |line_item| line_item.order.completed_at.strftime("%F %T") }, + order_id: proc { |line_item| line_item.order.id }, + customer_name: proc { |line_item| line_item.order.bill_address.full_name }, + customer_email: proc { |line_item| line_item.order.email }, + customer_phone: proc { |line_item| line_item.order.bill_address.phone }, + customer_city: proc { |line_item| line_item.order.bill_address.city }, + sku: proc { |line_item| line_item.product.sku }, + item_name: proc { |line_item| line_item.product.name }, + variant: proc { |line_item| line_item.options_text }, + quantity: proc { |line_item| line_item.quantity }, + max_quantity: proc { |line_item| line_item.max_quantity }, + cost: proc { |line_item| line_item.price * line_item.quantity }, + shipping_cost: proc { |line_item| line_item.distribution_fee }, + payment_method: proc { |li| li.order.payments.first&.payment_method&.name }, + distributor: proc { |line_item| line_item.order.distributor&.name }, + distributor_address: proc { |line_item| line_item.order.distributor.address.address1 }, + distributor_city: proc { |line_item| line_item.order.distributor.address.city }, + distributor_postcode: proc { |line_item| line_item.order.distributor.address.zipcode }, + shipping_method: proc { |line_item| line_item.order.shipping_method&.name }, + shipping_instructions: proc { |line_item| line_item.order.special_instructions } + } + end + # rubocop:enable Metrics/AbcSize + + def search + permissions.visible_orders.select("DISTINCT spree_orders.*"). + complete.not_state(:canceled). + ransack(@params[:q]) + end + + def query_result + orders = search.result + # Mask non editable order details + editable_orders_ids = permissions.editable_orders.select(&:id).map(&:id) + orders + .filter { |order| order.in?(editable_orders_ids) } + .each { |order| OrderDataMasker.new(order).call } + # Get Line Items + orders.map(&:line_items).flatten + end + + private + + def permissions + @permissions ||= ::Permissions::Order.new(user, params[:q]) + end + end + end + end +end diff --git a/lib/reporting/reports/orders_and_distributors/orders_and_distributors_report.rb b/lib/reporting/reports/orders_and_distributors/orders_and_distributors_report.rb deleted file mode 100644 index afd1120412..0000000000 --- a/lib/reporting/reports/orders_and_distributors/orders_and_distributors_report.rb +++ /dev/null @@ -1,111 +0,0 @@ -# frozen_string_literal: true - -module Reporting - module Reports - module OrdersAndDistributors - class OrdersAndDistributorsReport < ReportObjectTemplate - def initialize(user, params = {}) - super(user, params) - @permissions = ::Permissions::Order.new(user, @params[:q]) - end - - def table_headers - [ - I18n.t(:report_header_order_date), - I18n.t(:report_header_order_id), - I18n.t(:report_header_customer_name), - I18n.t(:report_header_customer_email), - I18n.t(:report_header_customer_phone), - I18n.t(:report_header_customer_city), - I18n.t(:report_header_sku), - I18n.t(:report_header_item_name), - I18n.t(:report_header_variant), - I18n.t(:report_header_quantity), - I18n.t(:report_header_max_quantity), - I18n.t(:report_header_cost), - I18n.t(:report_header_shipping_cost), - I18n.t(:report_header_payment_method), - I18n.t(:report_header_distributor), - I18n.t(:report_header_distributor_address), - I18n.t(:report_header_distributor_city), - I18n.t(:report_header_distributor_postcode), - I18n.t(:report_header_shipping_method), - I18n.t(:report_header_shipping_instructions) - ] - end - - def search - @permissions.visible_orders.select("DISTINCT spree_orders.*"). - complete.not_state(:canceled). - ransack(@params[:q]) - end - - def table_rows - orders = search.result - - orders.select{ |order| orders_with_hidden_details(orders).include? order }.each do |order| - OrderDataMasker.new(order).call - end - - line_item_details orders - end - - private - - def orders_with_hidden_details(orders) - # If empty array is passed in, the where clause will return all line_items, which is bad - if @permissions.editable_orders.empty? - orders - else - orders. - where('spree_orders.id NOT IN (?)', - @permissions.editable_orders.select(&:id)) - end - end - - def line_item_details(orders) - order_and_distributor_details = [] - - orders.each do |order| - order.line_items.each do |line_item| - order_and_distributor_details << row_for(line_item, order) - end - end - - order_and_distributor_details - end - - # Returns a row with the data to display for the specified line_item and - # its order - # - # @param line_item [Spree::LineItem] - # @param order [Spree::Order] - # @return [Array] - def row_for(line_item, order) - [ - order.completed_at.strftime("%F %T"), - order.id, - order.bill_address.full_name, - order.email, - order.bill_address.phone, - order.bill_address.city, - line_item.product.sku, - line_item.product.name, - line_item.options_text, - line_item.quantity, - line_item.max_quantity, - line_item.price * line_item.quantity, - line_item.distribution_fee, - order.payments.first&.payment_method&.name, - order.distributor&.name, - order.distributor.address.address1, - order.distributor.address.city, - order.distributor.address.zipcode, - order.shipping_method&.name, - order.special_instructions - ] - end - end - end - end -end diff --git a/spec/lib/reports/orders_and_distributors_report_spec.rb b/spec/lib/reports/orders_and_distributors_report_spec.rb index a1a6eb196a..85a84ab788 100644 --- a/spec/lib/reports/orders_and_distributors_report_spec.rb +++ b/spec/lib/reports/orders_and_distributors_report_spec.rb @@ -5,10 +5,10 @@ require 'spec_helper' module Reporting module Reports module OrdersAndDistributors - describe OrdersAndDistributorsReport do + describe Base do describe 'orders and distributors report' do it 'should return a header row describing the report' do - subject = OrdersAndDistributorsReport.new nil + subject = Base.new nil expect(subject.table_headers).to eq( [ @@ -45,7 +45,7 @@ module Reporting end it 'should denormalise order and distributor details for display as csv' do - subject = OrdersAndDistributorsReport.new create(:admin_user), {} + subject = Base.new create(:admin_user), {} table = subject.table_rows @@ -77,7 +77,7 @@ module Reporting it "prints one row per line item" do create(:line_item_with_shipment, order: order) - subject = OrdersAndDistributorsReport.new(create(:admin_user)) + subject = Base.new(create(:admin_user)) table = subject.table_rows expect(table.size).to eq 2 From 944d40e0932eb2cad6d384ed69d94c5ae3abae76 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Thu, 7 Apr 2022 11:05:24 +0200 Subject: [PATCH 29/54] Report Refactor 3: BulkCoop --- lib/reporting/report_template.rb | 6 +- lib/reporting/reports/bulk_coop/allocation.rb | 47 +++ lib/reporting/reports/bulk_coop/base.rb | 131 +++++++ .../bulk_coop/bulk_coop_allocation_report.rb | 67 ---- .../reports/bulk_coop/bulk_coop_report.rb | 317 ---------------- .../bulk_coop/bulk_coop_supplier_report.rb | 65 ---- .../reports/bulk_coop/customer_payments.rb | 61 +++ .../reports/bulk_coop/packing_sheets.rb | 28 ++ .../reports/bulk_coop/supplier_report.rb | 53 +++ lib/reporting/reports/list.rb | 10 +- spec/lib/reports/bulk_coop_report_spec.rb | 351 +++++++++--------- spec/system/admin/reports_spec.rb | 16 +- 12 files changed, 516 insertions(+), 636 deletions(-) create mode 100644 lib/reporting/reports/bulk_coop/allocation.rb create mode 100644 lib/reporting/reports/bulk_coop/base.rb delete mode 100644 lib/reporting/reports/bulk_coop/bulk_coop_allocation_report.rb delete mode 100644 lib/reporting/reports/bulk_coop/bulk_coop_report.rb delete mode 100644 lib/reporting/reports/bulk_coop/bulk_coop_supplier_report.rb create mode 100644 lib/reporting/reports/bulk_coop/customer_payments.rb create mode 100644 lib/reporting/reports/bulk_coop/packing_sheets.rb create mode 100644 lib/reporting/reports/bulk_coop/supplier_report.rb diff --git a/lib/reporting/report_template.rb b/lib/reporting/report_template.rb index ca771fc269..9ba56b9e5b 100644 --- a/lib/reporting/report_template.rb +++ b/lib/reporting/report_template.rb @@ -9,11 +9,9 @@ module Reporting delegate :formatted_rules, :header_option?, :summary_row_option?, to: :ruler delegate :grouped_data, :rows, to: :grouper - OPTIONAL_HEADERS = [].freeze - - def initialize(user, params) + def initialize(user, params = {}) @user = user - @params = params || {} + @params = params @params = @params.permit!.to_h unless @params.is_a? Hash @ransack_params = @params[:q] || {} end diff --git a/lib/reporting/reports/bulk_coop/allocation.rb b/lib/reporting/reports/bulk_coop/allocation.rb new file mode 100644 index 0000000000..4805a90d73 --- /dev/null +++ b/lib/reporting/reports/bulk_coop/allocation.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module BulkCoop + class Allocation < Base + def query_result + table_items.group_by(&:order).values + end + + def columns + { + customer: :order_billing_address_name, + product: :product_name, + bulk_unit_size: :product_group_buy_unit_size, + variant: :full_name, + variant_value: :option_value_value, + variant_unit: :option_value_unit, + weight: :weight_from_unit_value, + sum_total: :total_amount, + total_available: :empty_cell, + unallocated: :empty_cell, + max_quantity_excess: :empty_cell + } + end + + def rules + [ + { + group_by: :product, + header: true, + summary_row: proc do |_key, items, rows| + line_items = items.flatten + { + sum_total: rows.sum(&:sum_total), + total_available: total_available(line_items), + unallocated: remainder(line_items), + max_quantity_excess: max_quantity_excess(line_items) + } + end + } + ] + end + end + end + end +end diff --git a/lib/reporting/reports/bulk_coop/base.rb b/lib/reporting/reports/bulk_coop/base.rb new file mode 100644 index 0000000000..c09bc656d6 --- /dev/null +++ b/lib/reporting/reports/bulk_coop/base.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module BulkCoop + class Base < ReportObjectTemplate + def message + I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") + end + + def search + report_line_items.orders + end + + def table_items + report_line_items.list(line_item_includes) + end + + private + + def line_item_includes + [ + { + order: [:bill_address], + variant: [{ option_values: :option_type }, { product: :supplier }] + }, + :option_values + ] + end + + def order_permissions + @order_permissions ||= ::Permissions::Order.new(@user) + end + + def report_line_items + @report_line_items ||= Reporting::LineItems.new( + order_permissions, + @params, + CompleteVisibleOrders.new(order_permissions).query + ) + end + + def empty_cell(_line_items) + "" + end + + def full_name(line_items) + line_items.first.full_name + end + + def group_buy_unit_size(line_items) + unit_size = line_items.first.variant.product.group_buy_unit_size || 0.0 + unit_size / (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 scaled_unit_value(variant) + (variant.unit_value || 0) / (variant.product.variant_unit_scale || 1) + end + + def option_value_value(line_items) + VariantUnits::OptionValueNamer.new(line_items.first).value + end + + def option_value_unit(line_items) + VariantUnits::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 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 remainder(line_items) + remainder = total_available(line_items) - total_amount(line_items) + remainder >= 0 ? remainder : '' + end + + def total_amount(line_items) + line_items.sum { |li| scaled_final_weight_volume(li) } + end + + def scaled_final_weight_volume(line_item) + (line_item.final_weight_volume || 0) / (line_item.product.variant_unit_scale || 1) + end + + def total_available(line_items) + units_required(line_items) * group_buy_unit_size(line_items) + 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 weight_from_unit_value(line_items) + line_items.first.weight_from_unit_value || 0 + end + end + end + end +end diff --git a/lib/reporting/reports/bulk_coop/bulk_coop_allocation_report.rb b/lib/reporting/reports/bulk_coop/bulk_coop_allocation_report.rb deleted file mode 100644 index 8295b04a93..0000000000 --- a/lib/reporting/reports/bulk_coop/bulk_coop_allocation_report.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true - -module Reporting - module Reports - module BulkCoop - class BulkCoopAllocationReport - def table_headers - [ - 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/lib/reporting/reports/bulk_coop/bulk_coop_report.rb b/lib/reporting/reports/bulk_coop/bulk_coop_report.rb deleted file mode 100644 index 62cb436868..0000000000 --- a/lib/reporting/reports/bulk_coop/bulk_coop_report.rb +++ /dev/null @@ -1,317 +0,0 @@ -# frozen_string_literal: true - -module Reporting - module Reports - module BulkCoop - class BulkCoopReport < ReportObjectTemplate - def initialize(user, params = {}) - super(user, params) - - @supplier_report = BulkCoopSupplierReport.new - @allocation_report = BulkCoopAllocationReport.new - @filter_canceled = false - end - - def message - I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") - end - - def table_headers - case params[:report_subtype] - when "bulk_coop_supplier_report" - @supplier_report.table_headers - when "bulk_coop_allocation" - @allocation_report.table_headers - 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 - report_line_items.list(line_item_includes) - end - - def table_rows - order_grouper = Reporting::OrderGrouper.new rules, columns, self - order_grouper.table(table_items) - end - - def rules - case params[:report_subtype] - 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| - ( if (lis.first.product.group_buy_unit_size || 0).zero? - 0 - else - ( lis.sum { |li| - [li.max_quantity || 0, - li.quantity || 0].max * (li.weight_from_unit_value || 0) - } / lis.first.product.group_buy_unit_size ) - end ).floor - }, - proc { |lis| - lis.sum { |li| - [li.max_quantity || 0, - li.quantity || 0].max * (li.weight_from_unit_value || 0) - } - ( ( if (lis.first.product.group_buy_unit_size || 0).zero? - 0 - else - ( lis.sum { |li| - [li.max_quantity || 0, - li.quantity || 0].max * (li.weight_from_unit_value || 0) - } / lis.first.product.group_buy_unit_size ) - end ).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_subtype] - 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 - - attr_reader :filter_canceled - - def line_item_includes - [ - { - order: [:bill_address], - variant: [{ option_values: :option_type }, { product: :supplier }] - }, - :option_values - ] - end - - def order_permissions - @order_permissions ||= ::Permissions::Order.new(@user, filter_canceled) - end - - def report_line_items - @report_line_items ||= Reporting::LineItems.new( - order_permissions, - @params, - CompleteVisibleOrders.new(order_permissions).query - ) - end - - def customer_payments_total_cost(line_items) - unique_orders(line_items).sum(&:total) - end - - def customer_payments_amount_owed(line_items) - unique_orders(line_items).sum(&:new_outstanding_balance) - end - - def customer_payments_amount_paid(line_items) - unique_orders(line_items).sum(&:payment_total) - end - - def unique_orders(line_items) - line_items.map(&:order).uniq - end - - def empty_cell(_line_items) - "" - end - - def full_name(line_items) - line_items.first.full_name - end - - def group_buy_unit_size(line_items) - unit_size = line_items.first.variant.product.group_buy_unit_size || 0.0 - unit_size / (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) - VariantUnits::OptionValueNamer.new(line_items.first).value - end - - def option_value_unit(line_items) - VariantUnits::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(_line_items) - 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/lib/reporting/reports/bulk_coop/bulk_coop_supplier_report.rb b/lib/reporting/reports/bulk_coop/bulk_coop_supplier_report.rb deleted file mode 100644 index 1806105424..0000000000 --- a/lib/reporting/reports/bulk_coop/bulk_coop_supplier_report.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -module Reporting - module Reports - module BulkCoop - class BulkCoopSupplierReport - def table_headers - [ - 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/lib/reporting/reports/bulk_coop/customer_payments.rb b/lib/reporting/reports/bulk_coop/customer_payments.rb new file mode 100644 index 0000000000..1935f96a60 --- /dev/null +++ b/lib/reporting/reports/bulk_coop/customer_payments.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module BulkCoop + class CustomerPayments < Base + def query_result + table_items.group_by(&:order).values + end + + def columns + { + customer: :order_billing_address_name, + date_of_order: :order_completed_at, + total_cost: :customer_payments_total_cost, + amount_owing: :customer_payments_amount_owed, + amount_paid: :customer_payments_amount_paid + } + end + + def rules + [ + { + group_by: :customer, + header: true, + summary_row: proc do |_key, _items, rows| + { + total_cost: rows.sum(&:total_cost), + amount_owing: rows.sum(&:amount_owing), + amount_paid: rows.sum(&:amount_paid), + } + end + } + ] + end + + private + + def customer_payments_total_cost(line_items) + unique_orders(line_items).sum(&:total) + end + + def customer_payments_amount_owed(line_items) + unique_orders(line_items).sum(&:new_outstanding_balance) + end + + def customer_payments_amount_paid(line_items) + unique_orders(line_items).sum(&:payment_total) + end + + def unique_orders(line_items) + line_items.map(&:order).uniq + end + + def order_completed_at(line_items) + line_items.first.order.completed_at + end + end + end + end +end diff --git a/lib/reporting/reports/bulk_coop/packing_sheets.rb b/lib/reporting/reports/bulk_coop/packing_sheets.rb new file mode 100644 index 0000000000..3086cb3c42 --- /dev/null +++ b/lib/reporting/reports/bulk_coop/packing_sheets.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module BulkCoop + class PackingSheets < Base + def query_result + table_items.group_by(&:order).values + end + + def columns + { + customer: :order_billing_address_name, + product: :product_name, + variant: :full_name, + sum_total: :total_quantity + } + end + + private + + def total_quantity(line_items) + line_items.sum(&:quantity) + end + end + end + end +end diff --git a/lib/reporting/reports/bulk_coop/supplier_report.rb b/lib/reporting/reports/bulk_coop/supplier_report.rb new file mode 100644 index 0000000000..6e797cd389 --- /dev/null +++ b/lib/reporting/reports/bulk_coop/supplier_report.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module BulkCoop + class SupplierReport < Base + def query_result + table_items.group_by(&:variant).values + end + + def columns + { + supplier: :variant_product_supplier_name, + product: :variant_product_name, + bulk_unit_size: :variant_product_group_buy_unit_size_f, + variant: :full_name, + variant_value: :option_value_value, + variant_unit: :option_value_unit, + weight: :weight_from_unit_value, + sum_total: :total_amount, + units_required: :empty_cell, + unallocated: :empty_cell, + max_quantity_excess: :empty_cell + } + end + + def rules + [ + { + group_by: :supplier, + header: true, + summary_row: proc do |_key, items, rows| + line_items = items.flatten + { + sum_total: rows.sum(&:sum_total), + units_required: units_required(line_items), + unallocated: remainder(line_items), + max_quantity_excess: max_quantity_excess(line_items) + } + end + } + ] + end + + private + + def variant_product_supplier_name(line_items) + line_items.first.variant.product.supplier.name + end + end + end + end +end diff --git a/lib/reporting/reports/list.rb b/lib/reporting/reports/list.rb index e8055ca9d8..6a7400e5cd 100644 --- a/lib/reporting/reports/list.rb +++ b/lib/reporting/reports/list.rb @@ -89,15 +89,15 @@ module Reporting def bulk_coop_report_types [ - bulk_coop_item(:bulk_coop_supplier_report), - bulk_coop_item(:bulk_coop_allocation), - bulk_coop_item(:bulk_coop_packing_sheets), - bulk_coop_item(:bulk_coop_customer_payments) + bulk_coop_item(:supplier_report), + bulk_coop_item(:allocation), + bulk_coop_item(:packing_sheets), + bulk_coop_item(:customer_payments) ] end def bulk_coop_item(key) - [I18n.t("order_management.reports.bulk_coop.filters.#{key}"), key] + [I18n.t("order_management.reports.bulk_coop.filters.bulk_coop_#{key}"), key] end def i18n_translate(key) diff --git a/spec/lib/reports/bulk_coop_report_spec.rb b/spec/lib/reports/bulk_coop_report_spec.rb index 49ed7d861d..cca38b28d4 100644 --- a/spec/lib/reports/bulk_coop_report_spec.rb +++ b/spec/lib/reports/bulk_coop_report_spec.rb @@ -2,185 +2,196 @@ require 'spec_helper' -describe Reporting::Reports::BulkCoop::BulkCoopReport do - subject { Reporting::Reports::BulkCoop::BulkCoopReport.new user, params } - let(:user) { create(:admin_user) } +# rubocop:disable Metrics/ModuleLength +module Reporting + module Reports + module BulkCoop + describe Base do + subject { Base.new user, params } + let(:user) { create(:admin_user) } - describe '#table_items' do - let(:params) { {} } + describe '#query_result' do + let(:params) { {} } + let(:d1) { create(:distributor_enterprise) } + let(:oc1) { create(:simple_order_cycle) } + let(:o1) { create(:order, completed_at: 1.day.ago, order_cycle: oc1, distributor: d1) } + let(:li1) { build(:line_item_with_shipment) } - let(:d1) { create(:distributor_enterprise) } - let(:oc1) { create(:simple_order_cycle) } - let(:o1) { create(:order, completed_at: 1.day.ago, order_cycle: oc1, distributor: d1) } - let(:li1) { build(:line_item_with_shipment) } + before { o1.line_items << li1 } - before { o1.line_items << li1 } + context "as a site admin" do + context 'when searching' do + let(:params) { + { q: { completed_at_gt: '', completed_at_lt: '', distributor_id_in: [] } } + } - context "as a site admin" do - context 'when searching' do - let(:params) { { q: { completed_at_gt: '', completed_at_lt: '', distributor_id_in: [] } } } + it "fetches completed orders" do + o2 = create(:order, state: 'cart') + o2.line_items << build(:line_item) + expect(subject.table_items).to eq([li1]) + end - it "fetches completed orders" do - o2 = create(:order, state: 'cart') - o2.line_items << build(:line_item) - expect(subject.table_items).to eq([li1]) + it 'shows canceled orders' do + o2 = create(:order, state: 'canceled', completed_at: 1.day.ago, order_cycle: oc1, + distributor: d1) + line_item = build(:line_item_with_shipment) + o2.line_items << line_item + expect(subject.table_items).to include(line_item) + end + end + + context 'when not searching' do + let(:params) { {} } + + it "fetches completed orders" do + o2 = create(:order, state: 'cart') + o2.line_items << build(:line_item) + expect(subject.table_items).to eq([li1]) + end + + it 'shows canceled orders' do + o2 = create(:order, state: 'canceled', completed_at: 1.day.ago, order_cycle: oc1, + distributor: d1) + line_item = build(:line_item_with_shipment) + o2.line_items << line_item + expect(subject.table_items).to include(line_item) + end + end + end + + context "filtering by date" do + it do + user = create(:admin_user) + o2 = create(:order, completed_at: 3.days.ago, order_cycle: oc1, distributor: d1) + li2 = build(:line_item_with_shipment) + o2.line_items << li2 + + report = Base.new user, {} + expect(report.table_items).to match_array [li1, li2] + + report = Base.new( + user, { q: { completed_at_gt: 2.days.ago } } + ) + expect(report.table_items).to eq([li1]) + + report = Base.new( + user, { q: { completed_at_lt: 2.days.ago } } + ) + expect(report.table_items).to eq([li2]) + end + end + + context "filtering by distributor" do + it do + user = create(:admin_user) + d2 = create(:distributor_enterprise) + o2 = create(:order, distributor: d2, order_cycle: oc1, + completed_at: Time.zone.now) + li2 = build(:line_item_with_shipment) + o2.line_items << li2 + + report = Base.new user, {} + expect(report.table_items).to match_array [li1, li2] + + report = Base.new( + user, { q: { distributor_id_in: [d1.id] } } + ) + expect(report.table_items).to eq([li1]) + + report = Base.new( + user, { q: { distributor_id_in: [d2.id] } } + ) + expect(report.table_items).to eq([li2]) + end + end + + context "as a manager of a supplier" do + let!(:user) { create(:user) } + subject { Base.new user, {} } + + let(:s1) { create(:supplier_enterprise) } + + before do + s1.enterprise_roles.create!(user: user) + end + + context "that has granted P-OC to the distributor" do + let(:o2) do + create(:order, distributor: d1, completed_at: 1.day.ago, + bill_address: create(:address), + ship_address: create(:address)) + end + let(:li2) do + build(:line_item_with_shipment, product: create(:simple_product, supplier: s1)) + end + + before do + o2.line_items << li2 + create(:enterprise_relationship, parent: s1, child: d1, + permissions_list: [:add_to_order_cycle]) + end + + it "shows line items supplied by my producers, with names hidden" do + expect(subject.table_items).to eq([li2]) + expect(subject.table_items.first.order.bill_address.firstname).to eq("HIDDEN") + end + end + + context "that has not granted P-OC to the distributor" do + let(:o2) do + create(:order, distributor: d1, completed_at: 1.day.ago, + bill_address: create(:address), + ship_address: create(:address)) + end + let(:li2) do + build(:line_item_with_shipment, product: create(:simple_product, supplier: s1)) + end + + before do + o2.line_items << li2 + end + + it "does not show line items supplied by my producers" do + expect(subject.table_items).to eq([]) + end + end + end end - it 'shows canceled orders' do - o2 = create(:order, state: 'canceled', completed_at: 1.day.ago, order_cycle: oc1, - distributor: d1) - line_item = build(:line_item_with_shipment) - o2.line_items << line_item - expect(subject.table_items).to include(line_item) + describe '#columns' do + context 'when report type is bulk_coop_customer_payments' do + subject { CustomerPayments.new user } + + it 'returns' do + expect(subject.columns.values).to match_array( + [ + :order_billing_address_name, + :order_completed_at, + :customer_payments_total_cost, + :customer_payments_amount_owed, + :customer_payments_amount_paid, + ] + ) + end + end + end + + # Yes, I know testing a private method is bad practice but report's design, tighly coupling + # Reporting::OrderGrouper and Base, makes it + # very hard to make things testeable without ending up in a wormwhole. This is a trade-off. + describe '#customer_payments_amount_owed' do + let(:params) { {} } + let(:user) { build(:user) } + let!(:line_item) { create(:line_item) } + let(:order) { line_item.order } + + it 'calls #new_outstanding_balance' do + expect_any_instance_of(Spree::Order).to receive(:new_outstanding_balance) + CustomerPayments.new(user).__send__(:customer_payments_amount_owed, [line_item]) + end end end - - context 'when not searching' do - let(:params) { {} } - - it "fetches completed orders" do - o2 = create(:order, state: 'cart') - o2.line_items << build(:line_item) - expect(subject.table_items).to eq([li1]) - end - - it 'shows canceled orders' do - o2 = create(:order, state: 'canceled', completed_at: 1.day.ago, order_cycle: oc1, - distributor: d1) - line_item = build(:line_item_with_shipment) - o2.line_items << line_item - expect(subject.table_items).to include(line_item) - end - end - end - - context "filtering by date" do - it do - user = create(:admin_user) - o2 = create(:order, completed_at: 3.days.ago, order_cycle: oc1, distributor: d1) - li2 = build(:line_item_with_shipment) - o2.line_items << li2 - - report = Reporting::Reports::BulkCoop::BulkCoopReport.new user, {} - expect(report.table_items).to match_array [li1, li2] - - report = Reporting::Reports::BulkCoop::BulkCoopReport.new( - user, { q: { completed_at_gt: 2.days.ago } } - ) - expect(report.table_items).to eq([li1]) - - report = Reporting::Reports::BulkCoop::BulkCoopReport.new( - user, { q: { completed_at_lt: 2.days.ago } } - ) - expect(report.table_items).to eq([li2]) - end - end - - context "filtering by distributor" do - it do - user = create(:admin_user) - d2 = create(:distributor_enterprise) - o2 = create(:order, distributor: d2, order_cycle: oc1, - completed_at: Time.zone.now) - li2 = build(:line_item_with_shipment) - o2.line_items << li2 - - report = Reporting::Reports::BulkCoop::BulkCoopReport.new user, {} - expect(report.table_items).to match_array [li1, li2] - - report = Reporting::Reports::BulkCoop::BulkCoopReport.new( - user, { q: { distributor_id_in: [d1.id] } } - ) - expect(report.table_items).to eq([li1]) - - report = Reporting::Reports::BulkCoop::BulkCoopReport.new( - user, { q: { distributor_id_in: [d2.id] } } - ) - expect(report.table_items).to eq([li2]) - end - end - - context "as a manager of a supplier" do - let!(:user) { create(:user) } - subject { Reporting::Reports::BulkCoop::BulkCoopReport.new user, {} } - - let(:s1) { create(:supplier_enterprise) } - - before do - s1.enterprise_roles.create!(user: user) - end - - context "that has granted P-OC to the distributor" do - let(:o2) do - create(:order, distributor: d1, completed_at: 1.day.ago, bill_address: create(:address), - ship_address: create(:address)) - end - let(:li2) do - build(:line_item_with_shipment, product: create(:simple_product, supplier: s1)) - end - - before do - o2.line_items << li2 - create(:enterprise_relationship, parent: s1, child: d1, - permissions_list: [:add_to_order_cycle]) - end - - it "shows line items supplied by my producers, with names hidden" do - expect(subject.table_items).to eq([li2]) - expect(subject.table_items.first.order.bill_address.firstname).to eq("HIDDEN") - end - end - - context "that has not granted P-OC to the distributor" do - let(:o2) do - create(:order, distributor: d1, completed_at: 1.day.ago, bill_address: create(:address), - ship_address: create(:address)) - end - let(:li2) do - build(:line_item_with_shipment, product: create(:simple_product, supplier: s1)) - end - - before do - o2.line_items << li2 - end - - it "does not show line items supplied by my producers" do - expect(subject.table_items).to eq([]) - end - end - end - end - - describe '#columns' do - context 'when report type is bulk_coop_customer_payments' do - let(:params) { { report_subtype: 'bulk_coop_customer_payments' } } - - it 'returns' do - expect(subject.columns).to eq( - [ - :order_billing_address_name, - :order_completed_at, - :customer_payments_total_cost, - :customer_payments_amount_owed, - :customer_payments_amount_paid, - ] - ) - end - end - end - - # Yes, I know testing a private method is bad practice but report's design, tighly coupling - # Reporting::OrderGrouper and Reporting::Reports::BulkCoop::BulkCoopReport, makes it - # very hard to make things testeable without ending up in a wormwhole. This is a trade-off. - describe '#customer_payments_amount_owed' do - let(:params) { {} } - let(:user) { build(:user) } - let!(:line_item) { create(:line_item) } - let(:order) { line_item.order } - - it 'calls #new_outstanding_balance' do - expect_any_instance_of(Spree::Order).to receive(:new_outstanding_balance) - subject.__send__(:customer_payments_amount_owed, [line_item]) end end end +# rubocop:enable Metrics/ModuleLength diff --git a/spec/system/admin/reports_spec.rb b/spec/system/admin/reports_spec.rb index f6f1c776db..db6f7e9ba2 100644 --- a/spec/system/admin/reports_spec.rb +++ b/spec/system/admin/reports_spec.rb @@ -390,7 +390,7 @@ describe ' click_link 'Bulk Co-Op' end - xit "generating Bulk Co-op Supplier Report" do + it "generating Bulk Co-op Supplier Report" do select "Bulk Co-op Supplier Report", from: "report_subtype" click_button 'Go' @@ -406,10 +406,10 @@ describe ' "Units Required", "Unallocated", "Max Quantity Excess" - ] + ].map(&:upcase) end - xit "generating Bulk Co-op Allocation report" do + it "generating Bulk Co-op Allocation report" do select "Bulk Co-op Allocation", from: "report_subtype" click_button 'Go' @@ -425,10 +425,10 @@ describe ' "Total available", "Unallocated", "Max Quantity Excess" - ] + ].map(&:upcase) end - xit "generating Bulk Co-op Packing Sheets report" do + it "generating Bulk Co-op Packing Sheets report" do select "Bulk Co-op Packing Sheets", from: "report_subtype" click_button 'Go' @@ -437,10 +437,10 @@ describe ' "Product", "Variant", "Sum Total" - ] + ].map(&:upcase) end - xit "generating Bulk Co-op Customer Payments report" do + it "generating Bulk Co-op Customer Payments report" do select "Bulk Co-op Customer Payments", from: "report_subtype" click_button 'Go' @@ -450,7 +450,7 @@ describe ' "Total Cost", "Amount Owing", "Amount Paid" - ] + ].map(&:upcase) end end From 93751f9ccb97ec2d83004fb9b4b1016a77284576 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 3 Apr 2022 16:32:27 +0200 Subject: [PATCH 30/54] Report Refactor 3: Customers --- lib/reporting/reports/customers/addresses.rb | 28 ++++++ lib/reporting/reports/customers/base.rb | 49 ++++++++++ .../reports/customers/customers_report.rb | 95 ------------------- .../reports/customers/mailing_list.rb | 18 ++++ .../admin/reports_controller_spec.rb | 4 +- spec/lib/reports/customers_report_spec.rb | 26 +++-- 6 files changed, 108 insertions(+), 112 deletions(-) create mode 100644 lib/reporting/reports/customers/addresses.rb create mode 100644 lib/reporting/reports/customers/base.rb delete mode 100644 lib/reporting/reports/customers/customers_report.rb create mode 100644 lib/reporting/reports/customers/mailing_list.rb diff --git a/lib/reporting/reports/customers/addresses.rb b/lib/reporting/reports/customers/addresses.rb new file mode 100644 index 0000000000..e51edc9aee --- /dev/null +++ b/lib/reporting/reports/customers/addresses.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module Customers + class Addresses < Base + def columns + { + first_name: proc { |order| order.billing_address.firstname }, + last_name: proc { |order| order.billing_address.lastname }, + billing_address: proc { |order| address_from(order.billing_address) }, + email: proc { |order| order.email }, + phone: proc { |order| order.billing_address.phone }, + hub: proc { |order| order.distributor&.name }, + hub_address: proc { |order| address_from(order.distributor&.address) }, + shipping_method: proc { |order| order.shipping_method&.name }, + } + end + + private + + def address_from(address) + [address&.address1, address&.address2, address&.city].join(" ") + end + end + end + end +end diff --git a/lib/reporting/reports/customers/base.rb b/lib/reporting/reports/customers/base.rb new file mode 100644 index 0000000000..f483d44de8 --- /dev/null +++ b/lib/reporting/reports/customers/base.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module Customers + class Base < ReportObjectTemplate + def query_result + filter Spree::Order.managed_by(@user) + .distributed_by_user(@user) + .complete.not_state(:canceled) + end + + def filter(orders) + filter_to_supplier filter_to_distributor filter_to_order_cycle orders + end + + def filter_to_supplier(orders) + if params[:supplier_id].to_i > 0 + orders.select do |order| + order.line_items.includes(:product) + .where("spree_products.supplier_id = ?", params[:supplier_id].to_i) + .references(:product) + .count + .positive? + end + else + orders + end + end + + def filter_to_distributor(orders) + if params[:distributor_id].to_i > 0 + orders.where(distributor_id: params[:distributor_id]) + else + orders + end + end + + def filter_to_order_cycle(orders) + if params[:order_cycle_id].to_i > 0 + orders.where(order_cycle_id: params[:order_cycle_id]) + else + orders + end + end + end + end + end +end diff --git a/lib/reporting/reports/customers/customers_report.rb b/lib/reporting/reports/customers/customers_report.rb deleted file mode 100644 index 323bf97551..0000000000 --- a/lib/reporting/reports/customers/customers_report.rb +++ /dev/null @@ -1,95 +0,0 @@ -# frozen_string_literal: true - -module Reporting - module Reports - module Customers - class CustomersReport < ReportObjectTemplate - def table_headers - if is_mailing_list? - [I18n.t(:report_header_email), - I18n.t(:report_header_first_name), - I18n.t(:report_header_last_name), - I18n.t(:report_header_suburb)] - else - [I18n.t(:report_header_first_name), - I18n.t(:report_header_last_name), - I18n.t(:report_header_billing_address), - I18n.t(:report_header_email), - I18n.t(:report_header_phone), - I18n.t(:report_header_hub), - I18n.t(:report_header_hub_address), - I18n.t(:report_header_shipping_method)] - end - end - - def table_rows - orders.map do |order| - if is_mailing_list? - [order.email, - order.billing_address.firstname, - order.billing_address.lastname, - order.billing_address.city] - else - ba = order.billing_address - da = order.distributor&.address - [ba.firstname, - ba.lastname, - [ba.address1, ba.address2, ba.city].join(" "), - order.email, - ba.phone, - order.distributor&.name, - [da&.address1, da&.address2, da&.city].join(" "), - order.shipping_method&.name] - end - end - end - - def orders - filter Spree::Order.managed_by(@user) - .distributed_by_user(@user) - .complete.not_state(:canceled) - end - - def filter(orders) - filter_to_supplier filter_to_distributor filter_to_order_cycle orders - end - - def filter_to_supplier(orders) - if params[:supplier_id].to_i > 0 - orders.select do |order| - order.line_items.includes(:product) - .where("spree_products.supplier_id = ?", params[:supplier_id].to_i) - .references(:product) - .count - .positive? - end - else - orders - end - end - - def filter_to_distributor(orders) - if params[:distributor_id].to_i > 0 - orders.where(distributor_id: params[:distributor_id]) - else - orders - end - end - - def filter_to_order_cycle(orders) - if params[:order_cycle_id].to_i > 0 - orders.where(order_cycle_id: params[:order_cycle_id]) - else - orders - end - end - - private - - def is_mailing_list? - params[:report_subtype] == "mailing_list" - end - end - end - end -end diff --git a/lib/reporting/reports/customers/mailing_list.rb b/lib/reporting/reports/customers/mailing_list.rb new file mode 100644 index 0000000000..492a0abb4d --- /dev/null +++ b/lib/reporting/reports/customers/mailing_list.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module Customers + class MailingList < Base + def columns + { + email: proc { |order| order.email }, + first_name: proc { |order| order.billing_address.firstname }, + last_name: proc { |order| order.billing_address.lastname }, + suburb: proc { |order| order.billing_address.city }, + } + end + end + end + end +end diff --git a/spec/controllers/admin/reports_controller_spec.rb b/spec/controllers/admin/reports_controller_spec.rb index 586efbe66e..584d0a135e 100644 --- a/spec/controllers/admin/reports_controller_spec.rb +++ b/spec/controllers/admin/reports_controller_spec.rb @@ -295,8 +295,8 @@ describe Admin::ReportsController, type: :controller do expect(assigns(:report_subtypes)).to eq(subject.reports[:customers]) end - it "creates a CustomersReport" do - allow(Reporting::Reports::Customers::CustomersReport).to receive(:new) + it "creates a report object" do + allow(Reporting::Reports::Customers::Base).to receive(:new) .and_return(report = double(:report)) allow(report).to receive(:table_headers).and_return [] allow(report).to receive(:table_rows).and_return [] diff --git a/spec/lib/reports/customers_report_spec.rb b/spec/lib/reports/customers_report_spec.rb index dc73ac7617..f9a1c1a77e 100644 --- a/spec/lib/reports/customers_report_spec.rb +++ b/spec/lib/reports/customers_report_spec.rb @@ -5,19 +5,17 @@ require 'spec_helper' module Reporting module Reports module Customers - describe CustomersReport do + describe Base do context "as a site admin" do let(:user) do user = create(:user) user.spree_roles << Spree::Role.find_or_create_by!(name: 'admin') user end - subject { CustomersReport.new user, {} } + subject { Base.new user, {} } describe "mailing list report" do - before do - allow(subject).to receive(:params).and_return(report_subtype: "mailing_list") - end + subject { MailingList.new user, {} } it "returns headers for mailing_list" do expect(subject.table_headers).to eq(["Email", "First Name", "Last Name", "Suburb"]) @@ -28,7 +26,7 @@ module Reporting address = double(:billing_address, firstname: "Firsty", lastname: "Lasty", city: "Suburbia") allow(order).to receive(:billing_address).and_return address - allow(subject).to receive(:orders).and_return [order] + allow(subject).to receive(:query_result).and_return [order] expect(subject.table_rows).to eq([[ "test@test.com", "Firsty", "Lasty", "Suburbia" @@ -37,9 +35,7 @@ module Reporting end describe "addresses report" do - before do - allow(subject).to receive(:params).and_return(report_subtype: "addresses") - end + subject { Addresses.new user, {} } it "returns headers for addresses" do expect(subject.table_headers).to eq(["First Name", "Last Name", "Billing Address", "Email", @@ -52,7 +48,7 @@ module Reporting o = create(:order, distributor: d, bill_address: a) o.shipments << create(:shipment) - allow(subject).to receive(:orders).and_return [o] + allow(subject).to receive(:query_result).and_return [o] expect(subject.table_rows).to eq([[ a.firstname, a.lastname, [a.address1, a.address2, a.city].join(" "), @@ -67,13 +63,13 @@ module Reporting it "fetches completed orders" do o1 = create(:order) o2 = create(:order, completed_at: 1.day.ago) - expect(subject.orders).to eq([o2]) + expect(subject.query_result).to eq([o2]) end it "does not show cancelled orders" do o1 = create(:order, state: "canceled", completed_at: 1.day.ago) o2 = create(:order, completed_at: 1.day.ago) - expect(subject.orders).to eq([o2]) + expect(subject.query_result).to eq([o2]) end end end @@ -86,7 +82,7 @@ module Reporting user end - subject { CustomersReport.new user, {} } + subject { Base.new user, {} } describe "fetching orders" do let(:supplier) { create(:supplier_enterprise) } @@ -103,7 +99,7 @@ module Reporting o2 = create(:order, distributor: d2, completed_at: 1.day.ago) expect(subject).to receive(:filter).with([o1]).and_return([o1]) - expect(subject.orders).to eq([o1]) + expect(subject.query_result).to eq([o1]) end it "does not show orders through a hub that the current user does not manage" do @@ -113,7 +109,7 @@ module Reporting # When I fetch orders, I should see no orders expect(subject).to receive(:filter).with([]).and_return([]) - expect(subject.orders).to eq([]) + expect(subject.query_result).to eq([]) end end From 3808398807e9c92df8e55db251b6907272c7e7cd Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 3 Apr 2022 17:27:08 +0200 Subject: [PATCH 31/54] Report Refactor 3: Payments --- lib/reporting/reports/payments/base.rb | 17 +++ .../payments/itemised_payment_totals.rb | 22 +++ .../reports/payments/payment_totals.rb | 32 ++++ .../payments/payments_by_payment_type.rb | 27 ++++ .../reports/payments/payments_report.rb | 140 ------------------ 5 files changed, 98 insertions(+), 140 deletions(-) create mode 100644 lib/reporting/reports/payments/base.rb create mode 100644 lib/reporting/reports/payments/itemised_payment_totals.rb create mode 100644 lib/reporting/reports/payments/payment_totals.rb create mode 100644 lib/reporting/reports/payments/payments_by_payment_type.rb delete mode 100644 lib/reporting/reports/payments/payments_report.rb diff --git a/lib/reporting/reports/payments/base.rb b/lib/reporting/reports/payments/base.rb new file mode 100644 index 0000000000..aa3b74701c --- /dev/null +++ b/lib/reporting/reports/payments/base.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module Payments + class Base < ReportObjectTemplate + def search + Spree::Order.complete.not_state(:canceled).managed_by(@user).ransack(params[:q]) + end + + def query_result + search.result.group_by { |order| [order.payment_state, order.distributor] }.values + end + end + end + end +end diff --git a/lib/reporting/reports/payments/itemised_payment_totals.rb b/lib/reporting/reports/payments/itemised_payment_totals.rb new file mode 100644 index 0000000000..b571b2b97d --- /dev/null +++ b/lib/reporting/reports/payments/itemised_payment_totals.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module Payments + class ItemisedPaymentTotals < Base + def columns + { + payment_state: proc { |orders| orders.first.payment_state }, + distributor: proc { |orders| orders.first.distributor.name }, + product_total_price: proc { |orders| orders.to_a.sum(&:item_total) }, + shipping_total_price: proc { |orders| orders.sum(&:ship_total) }, + outstanding_balance_price: proc do |orders| + orders.sum { |order| order.outstanding_balance.to_f } + end, + total_price: proc { |orders| orders.map(&:total).sum } + } + end + end + end + end +end diff --git a/lib/reporting/reports/payments/payment_totals.rb b/lib/reporting/reports/payments/payment_totals.rb new file mode 100644 index 0000000000..f194aa2aa4 --- /dev/null +++ b/lib/reporting/reports/payments/payment_totals.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module Payments + class PaymentTotals < Base + def columns + { + payment_state: proc { |orders| orders.first.payment_state }, + distributor: proc { |orders| orders.first.distributor.name }, + product_total_price: proc { |orders| orders.to_a.sum(&:item_total) }, + shipping_total_price: proc { |orders| orders.sum(&:ship_total) }, + total_price: proc { |orders| orders.map(&:total).sum }, + eft_price: proc { |orders| total_by_payment_method(orders, "EFT") }, + paypal_price: proc { |orders| total_by_payment_method(orders, "PayPal") }, + outstanding_balance_price: proc { |orders| + orders.sum{ |order| order.outstanding_balance.to_f } + } + } + end + + private + + def total_by_payment_method(orders, pay_method) + orders.map(&:payments).flatten.select { |payment| + payment.completed? && payment.payment_method.name.to_s.include?(pay_method) + }.sum(&:amount) + end + end + end + end +end diff --git a/lib/reporting/reports/payments/payments_by_payment_type.rb b/lib/reporting/reports/payments/payments_by_payment_type.rb new file mode 100644 index 0000000000..766e0efa20 --- /dev/null +++ b/lib/reporting/reports/payments/payments_by_payment_type.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module Payments + class PaymentsByPaymentType < Base + def query_result + payments = search.result.includes(payments: :payment_method).map do |order| + order.payments.select(&:completed?) + end.flatten + payments.group_by { |payment| + [payment.order.payment_state, payment.order.distributor, payment.payment_method] + }.values + end + + def columns + { + payment_state: proc { |payments| payments.first.order.payment_state }, + distributor: proc { |payments| payments.first.order.distributor.name }, + payment_type: proc { |payments| payments.first.payment_method.name }, + total_price: proc { |payments| payments.sum(&:amount) } + } + end + end + end + end +end diff --git a/lib/reporting/reports/payments/payments_report.rb b/lib/reporting/reports/payments/payments_report.rb deleted file mode 100644 index c7f02b6b12..0000000000 --- a/lib/reporting/reports/payments/payments_report.rb +++ /dev/null @@ -1,140 +0,0 @@ -# frozen_string_literal: true - -module Reporting - module Reports - module Payments - class PaymentsReport < ReportObjectTemplate - def table_headers - case params[:report_subtype] - when "payments_by_payment_type" - I18n.t(:report_header_payment_type) - [I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor), I18n.t(:report_header_payment_type), - I18n.t(:report_header_total_price, currency: currency_symbol)] - when "itemised_payment_totals" - [I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor), - I18n.t(:report_header_product_total_price, currency: currency_symbol), - I18n.t(:report_header_shipping_total_price, currency: currency_symbol), - I18n.t(:report_header_outstanding_balance_price, currency: currency_symbol), - I18n.t(:report_header_total_price, currency: currency_symbol)] - when "payment_totals" - [I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor), - I18n.t(:report_header_product_total_price, currency: currency_symbol), - I18n.t(:report_header_shipping_total_price, currency: currency_symbol), - I18n.t(:report_header_total_price, currency: currency_symbol), - I18n.t(:report_header_eft_price, currency: currency_symbol), - I18n.t(:report_header_paypal_price, currency: currency_symbol), - I18n.t(:report_header_outstanding_balance_price, currency: currency_symbol)] - else - [I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor), I18n.t(:report_header_payment_type), - I18n.t(:report_header_total_price, currency: currency_symbol)] - end - end - - def search - Spree::Order.complete.not_state(:canceled).managed_by(@user).ransack(params[:q]) - end - - def table_items - orders = search.result - payments = orders.includes(:payments).map do |order| - order.payments.select(&:completed?) - end.flatten - - case params[:report_subtype] - when "payments_by_payment_type" - payments - when "itemised_payment_totals" - orders - when "payment_totals" - orders - else - payments - end - end - - def table_rows - order_grouper = Reporting::OrderGrouper.new rules, columns, self - order_grouper.table(table_items) - end - - def rules - case params[:report_subtype] - when "payments_by_payment_type" - [{ group_by: proc { |payment| payment.order.payment_state }, - sort_by: proc { |payment_state| payment_state } }, - { group_by: proc { |payment| payment.order.distributor }, - sort_by: proc { |distributor| distributor.name } }, - { group_by: proc { |payment| - Spree::PaymentMethod.unscoped { - payment.payment_method - } - }, - sort_by: proc { |method| method.name } }] - when "itemised_payment_totals" - [{ group_by: proc { |order| order.payment_state }, - sort_by: proc { |payment_state| payment_state } }, - { group_by: proc { |order| order.distributor }, - sort_by: proc { |distributor| distributor.name } }] - when "payment_totals" - [{ group_by: proc { |order| order.payment_state }, - sort_by: proc { |payment_state| payment_state } }, - { group_by: proc { |order| order.distributor }, - sort_by: proc { |distributor| distributor.name } }] - else - [{ group_by: proc { |payment| payment.order.payment_state }, - sort_by: proc { |payment_state| payment_state } }, - { group_by: proc { |payment| payment.order.distributor }, - sort_by: proc { |distributor| distributor.name } }, - { group_by: proc { |payment| payment.payment_method }, - sort_by: proc { |method| method.name } }] - end - end - - def columns - case params[:report_subtype] - when "payments_by_payment_type" - [proc { |payments| payments.first.order.payment_state }, - proc { |payments| payments.first.order.distributor.name }, - proc { |payments| payments.first.payment_method.name }, - proc { |payments| payments.sum(&:amount) }] - when "itemised_payment_totals" - [proc { |orders| orders.first.payment_state }, - proc { |orders| orders.first.distributor.name }, - proc { |orders| orders.to_a.sum(&:item_total) }, - proc { |orders| orders.sum(&:ship_total) }, - proc { |orders| orders.sum{ |order| order.outstanding_balance.to_f } }, - proc { |orders| orders.map(&:total).sum }] - when "payment_totals" - [proc { |orders| orders.first.payment_state }, - proc { |orders| orders.first.distributor.name }, - proc { |orders| orders.to_a.sum(&:item_total) }, - proc { |orders| orders.sum(&:ship_total) }, - proc { |orders| orders.map(&:total).sum }, - proc { |orders| - orders.sum { |o| - o.payments.select { |payment| - payment.completed? && - (payment.payment_method.name.to_s.include? "EFT") - }.sum(&:amount) - } - }, - proc { |orders| - orders.sum { |o| - o.payments.select { |payment| - payment.completed? && - (payment.payment_method.name.to_s.include? "PayPal") - }.sum(&:amount) - } - }, - proc { |orders| orders.sum{ |order| order.outstanding_balance.to_f } }] - else - [proc { |payments| payments.first.order.payment_state }, - proc { |payments| payments.first.order.distributor.name }, - proc { |payments| payments.first.payment_method.name }, - proc { |payments| payments.sum(&:amount) }] - end - end - end - end - end -end From 71aca960eea09e9ba9f4c48c4c81c12150d65707 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sun, 3 Apr 2022 20:53:43 +0200 Subject: [PATCH 32/54] Report Refactor 3: Order Cycle Management --- app/models/spree/address.rb | 4 + .../filters/_order_cycle_management.html.haml | 6 +- lib/reporting/reports/customers/addresses.rb | 10 +- .../reports/order_cycle_management/base.rb | 81 +++++++++ .../order_cycle_management/delivery.rb | 35 ++++ .../order_cycle_management_report.rb | 160 ------------------ .../order_cycle_management/payment_methods.rb | 28 +++ .../order_cycle_management_report_spec.rb | 14 +- spec/system/admin/reports_spec.rb | 4 +- 9 files changed, 162 insertions(+), 180 deletions(-) create mode 100644 lib/reporting/reports/order_cycle_management/base.rb create mode 100644 lib/reporting/reports/order_cycle_management/delivery.rb delete mode 100644 lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb create mode 100644 lib/reporting/reports/order_cycle_management/payment_methods.rb diff --git a/app/models/spree/address.rb b/app/models/spree/address.rb index 3538336d7d..e4dd2cf5ed 100644 --- a/app/models/spree/address.rb +++ b/app/models/spree/address.rb @@ -105,6 +105,10 @@ module Spree render_address([city, zipcode, state&.name]) end + def address_and_city + [address1, address2, city].select(&:present?).join(' ') + end + private def require_zipcode? diff --git a/app/views/admin/reports/filters/_order_cycle_management.html.haml b/app/views/admin/reports/filters/_order_cycle_management.html.haml index dc3977266c..3699b5cc63 100644 --- a/app/views/admin/reports/filters/_order_cycle_management.html.haml +++ b/app/views/admin/reports/filters/_order_cycle_management.html.haml @@ -11,8 +11,8 @@ .row .alpha.two.columns= label_tag nil, t(:report_payment) - .omega.fourteen.columns= select_tag(:payment_method_in, options_for_select(report_payment_method_options(@report.orders), params[:payment_method_in]), {class: "select2 fullwidth", multiple: true}) + .omega.fourteen.columns= select_tag(:payment_method_in, options_for_select(report_payment_method_options(@report.query_result), params[:payment_method_in]), {class: "select2 fullwidth", multiple: true}) .row - .alpha.two.columns= label_tag nil, "#{t(:shipping_methods)}: " - .omega.fourteen.columns= select_tag(:shipping_method_in, options_for_select(report_shipping_method_options(@report.orders), params[:shipping_method_in]), {class: "select2 fullwidth", multiple: true}) + .alpha.two.columns= label_tag nil, t(:shipping_methods) + .omega.fourteen.columns= select_tag(:shipping_method_in, options_for_select(report_shipping_method_options(@report.query_result), params[:shipping_method_in]), {class: "select2 fullwidth", multiple: true}) diff --git a/lib/reporting/reports/customers/addresses.rb b/lib/reporting/reports/customers/addresses.rb index e51edc9aee..fa024c446d 100644 --- a/lib/reporting/reports/customers/addresses.rb +++ b/lib/reporting/reports/customers/addresses.rb @@ -8,20 +8,14 @@ module Reporting { first_name: proc { |order| order.billing_address.firstname }, last_name: proc { |order| order.billing_address.lastname }, - billing_address: proc { |order| address_from(order.billing_address) }, + billing_address: proc { |order| order.billing_address.address_and_city }, email: proc { |order| order.email }, phone: proc { |order| order.billing_address.phone }, hub: proc { |order| order.distributor&.name }, - hub_address: proc { |order| address_from(order.distributor&.address) }, + hub_address: proc { |order| order.distributor&.address&.address_and_city }, shipping_method: proc { |order| order.shipping_method&.name }, } end - - private - - def address_from(address) - [address&.address1, address&.address2, address&.city].join(" ") - end end end end diff --git a/lib/reporting/reports/order_cycle_management/base.rb b/lib/reporting/reports/order_cycle_management/base.rb new file mode 100644 index 0000000000..5101285beb --- /dev/null +++ b/lib/reporting/reports/order_cycle_management/base.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module OrderCycleManagement + class Base < ReportObjectTemplate + DEFAULT_DATE_INTERVAL = { from: -1.month, to: 1.day }.freeze + + def initialize(user, params = {}) + super(user, params) + params[:q] ||= {} + params[:q][:completed_at_gt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:from] + params[:q][:completed_at_lt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:to] + end + + def search + Spree::Order. + finalized. + not_state(:canceled). + distributed_by_user(@user). + managed_by(@user). + ransack(params[:q]) + end + + # This result is used in _order_cucle_management.html so caching it + def query_result + @query_result ||= orders + end + + def orders + search_result = search.result.order(:completed_at) + orders = OutstandingBalance.new(search_result).query.select('spree_orders.*') + + filter(orders) + end + + def filter(orders) + filter_to_payment_method filter_to_shipping_method filter_to_order_cycle orders + end + + private + + def filter_to_payment_method(orders) + if params[:payment_method_in].present? + orders + .joins(payments: :payment_method) + .where(spree_payments: { payment_method_id: params[:payment_method_in] }) + else + orders + end + end + + def filter_to_shipping_method(orders) + if params[:shipping_method_in].present? + orders + .joins(shipments: :shipping_rates) + .where(spree_shipping_rates: { + selected: true, + shipping_method_id: params[:shipping_method_in] + }) + else + orders + end + end + + def filter_to_order_cycle(orders) + if params[:order_cycle_id].present? + orders.where(order_cycle_id: params[:order_cycle_id]) + else + orders + end + end + + def customer_code(email) + customer = Customer.where(email: email).first + customer.nil? ? "" : customer.code + end + end + end + end +end diff --git a/lib/reporting/reports/order_cycle_management/delivery.rb b/lib/reporting/reports/order_cycle_management/delivery.rb new file mode 100644 index 0000000000..3208ce7624 --- /dev/null +++ b/lib/reporting/reports/order_cycle_management/delivery.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module OrderCycleManagement + class Delivery < Base + # rubocop:disable Metrics/AbcSize + def columns + { + first_name: proc { |order| order.shipping_address.firstname }, + last_name: proc { |order| order.shipping_address.lastname }, + hub: proc { |order| order.distributor&.name }, + customer_code: proc { |order| customer_code(order.email) }, + delivery_address: proc { |order| order.shipping_address.address_and_city }, + delivery_postcode: proc { |order| order.shipping_address.zipcode }, + phone: proc { |order| order.shipping_address.phone }, + shipping_method: proc { |order| order.shipping_method&.name }, + payment_method: proc { |order| order.payments.first&.payment_method&.name }, + amount: proc { |order| order.total }, + balance: proc { |order| order.balance_value }, + temp_controlled_items: proc { |order| has_temperature_controlled_items?(order) }, + special_instructions: proc { |order| order.special_instructions }, + } + end + # rubocop:enable Metrics/AbcSize + + def has_temperature_controlled_items?(order) + order.line_items.any? { |line_item| + line_item.product.shipping_category&.temperature_controlled + } + end + end + end + end +end diff --git a/lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb b/lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb deleted file mode 100644 index 9236725549..0000000000 --- a/lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb +++ /dev/null @@ -1,160 +0,0 @@ -# frozen_string_literal: true - -module Reporting - module Reports - module OrderCycleManagement - class OrderCycleManagementReport < ReportObjectTemplate - DEFAULT_DATE_INTERVAL = { from: -1.month, to: 1.day }.freeze - - def initialize(user, params = {}) - super(user, params) - params[:q] ||= {} - params[:q][:completed_at_gt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:from] - params[:q][:completed_at_lt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:to] - end - - def table_headers - if is_payment_methods? - [ - I18n.t(:report_header_first_name), - I18n.t(:report_header_last_name), - I18n.t(:report_header_hub), - I18n.t(:report_header_hub_code), - I18n.t(:report_header_email), - I18n.t(:report_header_phone), - I18n.t(:report_header_shipping_method), - I18n.t(:report_header_payment_method), - I18n.t(:report_header_amount), - I18n.t(:report_header_balance), - ] - else - [ - I18n.t(:report_header_first_name), - I18n.t(:report_header_last_name), - I18n.t(:report_header_hub), - I18n.t(:report_header_hub_code), - I18n.t(:report_header_delivery_address), - I18n.t(:report_header_delivery_postcode), - I18n.t(:report_header_phone), - I18n.t(:report_header_shipping_method), - I18n.t(:report_header_payment_method), - I18n.t(:report_header_amount), - I18n.t(:report_header_balance), - I18n.t(:report_header_temp_controlled_items), - I18n.t(:report_header_special_instructions), - ] - end - end - - def search - Spree::Order. - finalized. - not_state(:canceled). - distributed_by_user(@user). - managed_by(@user). - ransack(params[:q]) - end - - def orders - search_result = search.result.order(:completed_at) - orders_with_balance = OutstandingBalance.new(search_result). - query. - select('spree_orders.*') - - filter(orders_with_balance) - end - - def table_rows - if is_payment_methods? - orders.map { |o| payment_method_row o } - else - orders.map { |o| delivery_row o } - end - end - - def filter(search_result) - filter_to_payment_method filter_to_shipping_method filter_to_order_cycle search_result - end - - private - - # This method relies on `balance_value` as a computed DB column. See `CompleteOrdersWithBalance` - # for reference. - def balance(order) - order.balance_value - end - - def payment_method_row(order) - ba = order.billing_address - [ba&.firstname, - ba&.lastname, - order.distributor&.name, - customer_code(order.email), - order.email, - ba&.phone, - order.shipping_method&.name, - order.payments.last&.payment_method&.name, - order.total, - balance(order)] - end - - def delivery_row(order) - sa = order.shipping_address - [sa.firstname, - sa.lastname, - order.distributor&.name, - customer_code(order.email), - "#{sa.address1} #{sa.address2} #{sa.city}", - sa.zipcode, - sa.phone, - order.shipping_method&.name, - order.payments.first&.payment_method&.name, - order.total, - balance(order), - has_temperature_controlled_items?(order), - order.special_instructions] - end - - def filter_to_payment_method(orders) - if params[:payment_method_in].present? - orders.joins(payments: :payment_method).where(spree_payments: { payment_method_id: params[:payment_method_in] }) - else - orders - end - end - - def filter_to_shipping_method(orders) - if params[:shipping_method_in].present? - orders.joins(shipments: :shipping_rates).where(spree_shipping_rates: { selected: true, - shipping_method_id: params[:shipping_method_in] }) - else - orders - end - end - - def filter_to_order_cycle(orders) - if params[:order_cycle_id].present? - orders.where(order_cycle_id: params[:order_cycle_id]) - else - orders - end - end - - def has_temperature_controlled_items?(order) - order.line_items.any? { |line_item| - line_item.product.shipping_category&.temperature_controlled - } - end - - def is_payment_methods? - params[:report_subtype] == "payment_methods" - end - - def customer_code(email) - customer = Customer.where(email: email).first - customer.nil? ? "" : customer.code - end - end - end - end -end diff --git a/lib/reporting/reports/order_cycle_management/payment_methods.rb b/lib/reporting/reports/order_cycle_management/payment_methods.rb new file mode 100644 index 0000000000..6556d762e5 --- /dev/null +++ b/lib/reporting/reports/order_cycle_management/payment_methods.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module OrderCycleManagement + class PaymentMethods < Base + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/CyclomaticComplexity + def columns + { + first_name: proc { |order| order.billing_address&.firstname }, + last_name: proc { |order| order.billing_address&.lastname }, + hub: proc { |order| order.distributor&.name }, + customer_code: proc { |order| customer_code(order.email) }, + email: proc { |order| order.email }, + phone: proc { |order| order.billing_address&.phone }, + shipping_method: proc { |order| order.shipping_method&.name }, + payment_method: proc { |order| order.payments.last&.payment_method&.name }, + amount: proc { |order| order.total }, + balance: proc { |order| order.balance_value }, + } + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/CyclomaticComplexity + end + end + end +end diff --git a/spec/lib/reports/order_cycle_management_report_spec.rb b/spec/lib/reports/order_cycle_management_report_spec.rb index 9cdb4846e8..8676d7795f 100644 --- a/spec/lib/reports/order_cycle_management_report_spec.rb +++ b/spec/lib/reports/order_cycle_management_report_spec.rb @@ -5,9 +5,9 @@ require 'spec_helper' module Reporting module Reports module OrderCycleManagement - describe OrderCycleManagementReport do + describe Base do context "as a site admin" do - subject { OrderCycleManagementReport.new(user, params) } + subject { Base.new(user, params) } let(:params) { {} } let(:user) do @@ -63,7 +63,7 @@ module Reporting context "as an enterprise user" do let!(:user) { create(:user) } - subject { OrderCycleManagementReport.new user, {} } + subject { Base.new user, {} } describe "fetching orders" do let(:supplier) { create(:supplier_enterprise) } @@ -148,13 +148,13 @@ module Reporting end describe '#table_rows' do - subject { OrderCycleManagementReport.new(user, params) } + subject { Base.new(user, params) } let(:distributor) { create(:distributor_enterprise) } before { distributor.enterprise_roles.create!(user: user) } context 'when the report type is payment_methods' do - let(:params) { { report_subtype: 'payment_methods' } } + subject { PaymentMethods.new(user) } let!(:order) do create( @@ -180,8 +180,8 @@ module Reporting end end - context 'when the report type is not payment_methods' do - let(:params) { {} } + context 'when the report type is delivery' do + subject { Delivery.new(user) } let!(:order) do create( :completed_order_with_totals, diff --git a/spec/system/admin/reports_spec.rb b/spec/system/admin/reports_spec.rb index db6f7e9ba2..679eebc830 100644 --- a/spec/system/admin/reports_spec.rb +++ b/spec/system/admin/reports_spec.rb @@ -73,7 +73,7 @@ describe ' rows = find("table.report__table").all("thead tr") table = rows.map { |r| r.all("th").map { |c| c.text.strip } } expect(table.sort).to eq([ - ["First Name", "Last Name", "Hub", "Hub Code", "Email", "Phone", "Shipping Method", + ["First Name", "Last Name", "Hub", "Customer Code", "Email", "Phone", "Shipping Method", "Payment Method", "Amount", "Balance"].map(&:upcase) ].sort) end @@ -84,7 +84,7 @@ describe ' rows = find("table.report__table").all("thead tr") table = rows.map { |r| r.all("th").map { |c| c.text.strip } } expect(table.sort).to eq([ - ["First Name", "Last Name", "Hub", "Hub Code", "Delivery Address", "Delivery Postcode", + ["First Name", "Last Name", "Hub", "Customer Code", "Delivery Address", "Delivery Postcode", "Phone", "Shipping Method", "Payment Method", "Amount", "Balance", "Temp Controlled Items?", "Special Instructions"].map(&:upcase) ].sort) From b3e1ffe9e2c0e71f4bfbc3aa37c37476953709e7 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 6 Apr 2022 11:06:40 +0200 Subject: [PATCH 33/54] Report Refactor 3: Products & Inventory --- .rubocop_todo.yml | 4 +- .../products_and_inventory/all_products.rb | 13 ++++ ...oducts_and_inventory_report.rb => base.rb} | 71 ++++++++--------- .../products_and_inventory/inventory.rb | 10 +++ ...ttuce_share_report.rb => lettuce_share.rb} | 76 +++++++------------ .../products_and_inventory_default_report.rb | 63 --------------- .../admin/reports_controller_spec.rb | 2 +- spec/lib/reports/lettuce_share_report_spec.rb | 28 +++---- ... => products_and_inventory_report_spec.rb} | 27 ++++--- 9 files changed, 117 insertions(+), 177 deletions(-) create mode 100644 lib/reporting/reports/products_and_inventory/all_products.rb rename lib/reporting/reports/products_and_inventory/{products_and_inventory_report.rb => base.rb} (68%) create mode 100644 lib/reporting/reports/products_and_inventory/inventory.rb rename lib/reporting/reports/products_and_inventory/{lettuce_share_report.rb => lettuce_share.rb} (50%) delete mode 100644 lib/reporting/reports/products_and_inventory/products_and_inventory_default_report.rb rename spec/lib/reports/{products_and_inventory_default_report_spec.rb => products_and_inventory_report_spec.rb} (93%) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 13d9769853..f8e25f898e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -190,7 +190,7 @@ Layout/LineLength: - 'spec/lib/reports/order_grouper_spec.rb' - 'spec/lib/reports/orders_and_fulfillment/orders_and_fulfillment_report_spec.rb' - 'spec/lib/reports/packing/packing_report_spec.rb' - - 'spec/lib/reports/products_and_inventory_default_report_spec.rb' + - 'spec/lib/reports/products_and_inventory_report_spec.rb' - 'spec/lib/reports/users_and_enterprises_report_spec.rb' - 'spec/lib/reports/xero_invoices_report_spec.rb' - 'spec/lib/stripe/authorize_response_patcher_spec.rb' @@ -646,7 +646,7 @@ Metrics/ModuleLength: - 'spec/lib/reports/order_grouper_spec.rb' - 'spec/lib/reports/orders_and_fulfillment/customer_totals_report_spec.rb' - 'spec/lib/reports/orders_and_fulfillment/orders_and_fulfillment_report_spec.rb' - - 'spec/lib/reports/products_and_inventory_default_report_spec.rb' + - 'spec/lib/reports/products_and_inventory_report_spec.rb' - 'spec/lib/reports/users_and_enterprises_report_spec.rb' - 'spec/models/spree/adjustment_spec.rb' - 'spec/models/spree/credit_card_spec.rb' diff --git a/lib/reporting/reports/products_and_inventory/all_products.rb b/lib/reporting/reports/products_and_inventory/all_products.rb new file mode 100644 index 0000000000..f433f83287 --- /dev/null +++ b/lib/reporting/reports/products_and_inventory/all_products.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module ProductsAndInventory + class AllProducts < Base + def filter_on_hand(variants) + variants # do not filter + end + end + end + end +end diff --git a/lib/reporting/reports/products_and_inventory/products_and_inventory_report.rb b/lib/reporting/reports/products_and_inventory/base.rb similarity index 68% rename from lib/reporting/reports/products_and_inventory/products_and_inventory_report.rb rename to lib/reporting/reports/products_and_inventory/base.rb index b8dc78977b..b0928cf0fb 100644 --- a/lib/reporting/reports/products_and_inventory/products_and_inventory_report.rb +++ b/lib/reporting/reports/products_and_inventory/base.rb @@ -5,37 +5,32 @@ require 'open_food_network/scope_variant_to_hub' module Reporting module Reports module ProductsAndInventory - class ProductsAndInventoryReport < ReportObjectTemplate - delegate :table_rows, :table_headers, :rules, :columns, :sku_for, to: :report - - def report - @report ||= report_klass.new(self) - end - - def report_type - params[:report_subtype] - end - - def report_klass - if report_type == 'lettuce_share' - LettuceShareReport - else - ProductsAndInventoryDefaultReport - end - end - - def permissions - @permissions ||= OpenFoodNetwork::Permissions.new(@user) - end - - def visible_products - @visible_products ||= permissions.visible_products - end - - def variants + class Base < ReportObjectTemplate + def query_result filter(child_variants) end + # rubocop:disable Metrics/AbcSize + def columns + { + supplier: proc { |variant| variant.product.supplier.name }, + producer_suburb: proc { |variant| variant.product.supplier.address.city }, + product: proc { |variant| variant.product.name }, + product_properties: proc { |v| v.product.properties.map(&:name).join(", ") }, + taxons: proc { |variant| variant.product.taxons.map(&:name).join(", ") }, + variant_value: proc { |variant| variant.full_name }, + price: proc { |variant| variant.price }, + group_buy_unit_quantity: proc { |variant| variant.product.group_buy_unit_size }, + amount: proc { |_variant| "" }, + sku: proc { |variant| variant.sku.presence || variant.product.sku }, + } + end + # rubocop:enable Metrics/AbcSize + + def filter(variants) + filter_on_hand filter_to_distributor filter_to_order_cycle filter_to_supplier variants + end + def child_variants Spree::Variant. where(is_master: false). @@ -45,17 +40,23 @@ module Reporting order('spree_products.name') end - def filter(variants) - filter_on_hand filter_to_distributor filter_to_order_cycle filter_to_supplier variants + private + + def report_type + params[:report_subtype] + end + + def visible_products + @visible_products ||= permissions.visible_products + end + + def permissions + @permissions ||= OpenFoodNetwork::Permissions.new(@user) end # Using the `in_stock?` method allows overrides by distributors. def filter_on_hand(variants) - if report_type == 'inventory' - variants.select(&:in_stock?) - else - variants - end + variants.select(&:in_stock?) end def filter_to_supplier(variants) diff --git a/lib/reporting/reports/products_and_inventory/inventory.rb b/lib/reporting/reports/products_and_inventory/inventory.rb new file mode 100644 index 0000000000..b89fde6c32 --- /dev/null +++ b/lib/reporting/reports/products_and_inventory/inventory.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module ProductsAndInventory + class Inventory < Base + end + end + end +end diff --git a/lib/reporting/reports/products_and_inventory/lettuce_share_report.rb b/lib/reporting/reports/products_and_inventory/lettuce_share.rb similarity index 50% rename from lib/reporting/reports/products_and_inventory/lettuce_share_report.rb rename to lib/reporting/reports/products_and_inventory/lettuce_share.rb index 7f3ec04a93..96100b1ecb 100644 --- a/lib/reporting/reports/products_and_inventory/lettuce_share_report.rb +++ b/lib/reporting/reports/products_and_inventory/lettuce_share.rb @@ -1,59 +1,39 @@ # frozen_string_literal: true -# require 'variant_units/option_value_namer' - module Reporting module Reports module ProductsAndInventory - class LettuceShareReport - attr_reader :context - - delegate :variants, :render_table, to: :context - - def initialize(context) - @context = context - end - - def table_headers - # NOTE: These are NOT to be translated, they need to be in this exact format to work with LettucShare - [ - "PRODUCT", - "Description", - "Qty", - "Pack Size", - "Unit", - "Unit Price", - "Total", - "GST incl.", - "Grower and growing method", - "Taxon" - ] - end - - def table_rows - variants.select(&:in_stock?) - .map do |variant| - [ - variant.product.name, - variant.full_name, - '', - VariantUnits::OptionValueNamer.new(variant).value, - VariantUnits::OptionValueNamer.new(variant).unit, - variant.price, - '', - gst(variant), - grower_and_method(variant), - variant.product.primary_taxon.name - ] - end - end - - def rules - [] + class LettuceShare < Base + # NOTE: These are NOT to be translated, they need to be in this exact format + # to work with LettucShare + def custom_headers + { + product: "PRODUCT", + description: "Description", + quantity: "Qty", + pack_size: "Pack Size", + unit: "Unit", + unit_price: "Unit Price", + total: "Total", + gst: "GST incl.", + grower: "Grower and growing method", + taxon: "Taxon" + } end def columns - {} + { + product: proc { |variant| variant.product.name }, + description: proc { |variant| variant.full_name }, + quantity: proc { |_variant| '' }, + pack_size: proc { |variant| VariantUnits::OptionValueNamer.new(variant).value }, + unit: proc { |variant| VariantUnits::OptionValueNamer.new(variant).unit }, + unit_price: proc { |variant| variant.price }, + total: proc { |_variant| '' }, + gst: proc { |variant| gst(variant) }, + grower: proc { |variant| grower_and_method(variant) }, + taxon: proc { |variant| variant.product.primary_taxon.name } + } end private diff --git a/lib/reporting/reports/products_and_inventory/products_and_inventory_default_report.rb b/lib/reporting/reports/products_and_inventory/products_and_inventory_default_report.rb deleted file mode 100644 index 6d8307a611..0000000000 --- a/lib/reporting/reports/products_and_inventory/products_and_inventory_default_report.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -module Reporting - module Reports - module ProductsAndInventory - class ProductsAndInventoryDefaultReport - attr_reader :context - - delegate :variants, :render_table, to: :context - - def initialize(context) - @context = context - end - - def table_headers - [ - I18n.t(:report_header_supplier), - I18n.t(:report_header_producer_suburb), - I18n.t(:report_header_product), - I18n.t(:report_header_product_properties), - I18n.t(:report_header_taxons), - I18n.t(:report_header_variant_value), - I18n.t(:report_header_price), - I18n.t(:report_header_group_buy_unit_quantity), - I18n.t(:report_header_amount), - I18n.t(:report_header_sku) - ] - end - - def table_rows - variants.map do |variant| - [ - variant.product.supplier.name, - variant.product.supplier.address.city, - variant.product.name, - variant.product.properties.map(&:name).join(", "), - variant.product.taxons.map(&:name).join(", "), - variant.full_name, - variant.price, - variant.product.group_buy_unit_size, - "", - sku_for(variant) - ] - end - end - - def rules - [] - end - - def columns - {} - end - - def sku_for(variant) - return variant.sku if variant.sku.present? - - variant.product.sku - end - end - end - end -end diff --git a/spec/controllers/admin/reports_controller_spec.rb b/spec/controllers/admin/reports_controller_spec.rb index 584d0a135e..c306451f95 100644 --- a/spec/controllers/admin/reports_controller_spec.rb +++ b/spec/controllers/admin/reports_controller_spec.rb @@ -246,7 +246,7 @@ describe Admin::ReportsController, type: :controller do end it "creates a ProductAndInventoryReport" do - allow(Reporting::Reports::ProductsAndInventory::ProductsAndInventoryReport).to receive(:new) + allow(Reporting::Reports::ProductsAndInventory::Base).to receive(:new) .and_return(report = double(:report)) allow(report).to receive(:table_headers).and_return [] allow(report).to receive(:table_rows).and_return [] diff --git a/spec/lib/reports/lettuce_share_report_spec.rb b/spec/lib/reports/lettuce_share_report_spec.rb index f0c1e3d554..d9af25be08 100644 --- a/spec/lib/reports/lettuce_share_report_spec.rb +++ b/spec/lib/reports/lettuce_share_report_spec.rb @@ -5,12 +5,9 @@ require 'spec_helper' module Reporting module Reports module ProductsAndInventory - describe LettuceShareReport do + describe LettuceShare do let(:user) { create(:user) } - let(:base_report) { - ProductsAndInventoryReport.new(user, { report_subtype: 'lettuce_share' }) - } - let(:report) { base_report.report } + let(:report) { LettuceShare.new(user) } let(:variant) { create(:variant) } describe "grower and method" do @@ -54,18 +51,17 @@ module Reporting } it "all items" do - allow(base_report).to receive(:child_variants) { - Spree::Variant.where(id: [variant, variant2, variant3]) - } + allow(report).to receive(:child_variants) { + Spree::Variant.where(id: [variant, variant2, variant3]) + } expect(report.table_rows.count).to eq 3 end it "only available items" do variant.on_hand = 0 - allow(base_report).to receive(:child_variants) { - Spree::Variant.where(id: [variant, variant2, variant3, - variant4]) - } + allow(report).to receive(:child_variants) { + Spree::Variant.where(id: [variant, variant2, variant3, variant4]) + } expect(report.table_rows.count).to eq 3 end @@ -75,10 +71,10 @@ module Reporting # create the overrides variant2_override variant3_override - allow(base_report).to receive(:child_variants) { - Spree::Variant.where(id: [variant, variant2, variant3]) - } - allow(base_report).to receive(:params) { + allow(report).to receive(:child_variants) { + Spree::Variant.where(id: [variant, variant2, variant3]) + } + allow(report).to receive(:params) { { distributor_id: hub.id, report_subtype: 'lettuce_share' } } rows = report.table_rows diff --git a/spec/lib/reports/products_and_inventory_default_report_spec.rb b/spec/lib/reports/products_and_inventory_report_spec.rb similarity index 93% rename from spec/lib/reports/products_and_inventory_default_report_spec.rb rename to spec/lib/reports/products_and_inventory_report_spec.rb index f57a775844..7ed6034437 100644 --- a/spec/lib/reports/products_and_inventory_default_report_spec.rb +++ b/spec/lib/reports/products_and_inventory_report_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' module Reporting module Reports module ProductsAndInventory - describe ProductsAndInventoryDefaultReport do + describe Base do context "As a site admin" do let(:user) do user = create(:user) @@ -13,7 +13,7 @@ module Reporting user end subject do - ProductsAndInventoryReport.new user, {} + Base.new user, {} end it "Should return headers" do @@ -48,7 +48,7 @@ module Reporting :taxons).and_return [double(name: "taxon1"), double(name: "taxon2")] allow(variant).to receive_message_chain(:product, :group_buy_unit_size).and_return(21) - allow(subject).to receive(:variants).and_return [variant] + allow(subject).to receive(:query_result).and_return [variant] expect(subject.table_rows).to eq([[ "Supplier", @@ -67,7 +67,7 @@ module Reporting it "fetches variants for some params" do expect(subject).to receive(:child_variants).and_return ["children"] expect(subject).to receive(:filter).with(['children']).and_return ["filter_children"] - expect(subject.variants).to eq(["filter_children"]) + expect(subject.query_result).to eq(["filter_children"]) end end @@ -82,7 +82,7 @@ module Reporting end subject do - ProductsAndInventoryReport.new enterprise_user, {} + Base.new enterprise_user, {} end describe "fetching child variants" do @@ -112,7 +112,7 @@ module Reporting product1 = create(:simple_product, supplier: supplier, on_hand: 99) product2 = create(:simple_product, supplier: supplier, on_hand: 0) - allow(subject).to receive(:params).and_return(report_subtype: 'inventory') + subject = Inventory.new enterprise_user expect(subject.filter(variants)).to eq([product1.variants.first]) end end @@ -225,17 +225,17 @@ module Reporting ] ) + subject = Inventory.new enterprise_user allow(subject).to receive(:params).and_return( order_cycle_id: order_cycle.id, supplier_id: supplier.id, - distributor_id: distributor.id, - report_subtype: 'inventory' + distributor_id: distributor.id ) expect(subject.filter(variants)).to match_array [not_filtered_variant] # And it integrates with the ordering of the `variants` method. - expect(subject.variants).to match_array [not_filtered_variant] + expect(subject.query_result).to match_array [not_filtered_variant] end end @@ -243,12 +243,15 @@ module Reporting let(:variant) { create(:variant) } let(:product) { variant.product } - before { product.update_attribute(:sku, "Product SKU") } + before { + product.update_attribute(:sku, "Product SKU") + allow(subject).to receive(:query_result).and_return([variant]) + } context "when the variant has an SKU set" do before { variant.update_attribute(:sku, "Variant SKU") } it "returns it" do - expect(subject.__send__(:sku_for, variant)).to eq "Variant SKU" + expect(subject.rows.first.sku).to eq "Variant SKU" end end @@ -256,7 +259,7 @@ module Reporting before { variant.update_attribute(:sku, "") } it "returns the product's SKU" do - expect(subject.__send__(:sku_for, variant)).to eq "Product SKU" + expect(subject.rows.first.sku).to eq "Product SKU" end end end From 42da4439018c57a867ac54f0b112eef50f425bb2 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Thu, 7 Apr 2022 08:52:23 +0200 Subject: [PATCH 34/54] Report Refactor 3: Enterprise FeeSummary This one as actually not really been refactored, too much work to rewrite it so I just encapsulated the result within the new methods definitions --- .../reports/enterprise_fee_summaries_spec.rb | 162 -- ...terprise_fee_summary_report.rb => base.rb} | 51 +- .../enterprise_fee_summary_report_spec.rb | 1454 ++++++++--------- .../reports/enterprise_fee_summaries_spec.rb | 162 ++ 4 files changed, 910 insertions(+), 919 deletions(-) delete mode 100644 engines/order_management/spec/features/order_management/reports/enterprise_fee_summaries_spec.rb rename lib/reporting/reports/enterprise_fee_summary/{enterprise_fee_summary_report.rb => base.rb} (64%) create mode 100644 spec/system/admin/reports/enterprise_fee_summaries_spec.rb diff --git a/engines/order_management/spec/features/order_management/reports/enterprise_fee_summaries_spec.rb b/engines/order_management/spec/features/order_management/reports/enterprise_fee_summaries_spec.rb deleted file mode 100644 index a7dbc0c00e..0000000000 --- a/engines/order_management/spec/features/order_management/reports/enterprise_fee_summaries_spec.rb +++ /dev/null @@ -1,162 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -# feature "enterprise fee summaries", js: true do -# include AuthenticationHelper -# include WebHelper - -# let!(:distributor) { create(:distributor_enterprise) } -# let!(:other_distributor) { create(:distributor_enterprise) } - -# let!(:order_cycle) { create(:simple_order_cycle, coordinator: distributor) } -# let!(:other_order_cycle) { create(:simple_order_cycle, coordinator: other_distributor) } - -# before do -# login_as current_user -# end - -# describe "navigation" do -# context "when accessing the report as an superadmin" do -# let(:current_user) { create(:admin_user) } - -# it "shows link and allows access to the report" do -# visit spree.admin_reports_path -# click_on I18n.t("admin.reports.enterprise_fee_summary.name") -# expect(page).to have_button("Go") -# end -# end - -# context "when accessing the report as an admin" do -# let(:current_user) { distributor.owner } - -# it "shows link and allows access to the report" do -# visit spree.admin_reports_path -# click_on I18n.t("admin.reports.enterprise_fee_summary.name") -# expect(page).to have_button("Go") -# end -# end - -# context "when accessing the report as an enterprise user without sufficient permissions" do -# let(:current_user) { create(:user) } - -# it "does not allow access to the report" do -# visit spree.admin_reports_path -# expect(page).to have_no_link(I18n.t("admin.reports.enterprise_fee_summary.name")) -# visit main_app.new_order_management_reports_enterprise_fee_summary_path -# expect(page).to have_content(I18n.t("unauthorized")) -# end -# end -# end - -# describe "smoke test for filters" do -# before do -# visit main_app.new_order_management_reports_enterprise_fee_summary_path -# end - -# context "when logged in as admin" do -# let(:current_user) { create(:admin_user) } - -# it "shows all available options" do -# expect(page).to have_select "q_report_order_cycle_ids", with_options: [order_cycle.name] -# end -# end - -# context "when logged in as enterprise user" do -# let!(:order) do -# create(:completed_order_with_fees, order_cycle: order_cycle, -# distributor: distributor) -# end -# let(:current_user) { distributor.owner } - -# it "shows available options for the enterprise" do -# expect(page).to have_select "q_report_order_cycle_ids", options: [order_cycle.name] -# end -# end -# end - -# describe "csv downloads" do -# around do |example| -# with_empty_downloads_folder { example.run } -# end - -# describe "smoke test for generation of report based on permissions" do -# before do -# visit main_app.new_order_management_reports_enterprise_fee_summary_path -# end - -# context "when logged in as admin" do -# let!(:order) do -# create(:completed_order_with_fees, order_cycle: order_cycle, -# distributor: distributor) -# end -# let(:current_user) { create(:admin_user) } - -# it "generates file with data for all enterprises" do -# check I18n.t("filters.report_format_csv", scope: i18n_scope) -# click_on "Go" - -# expect(downloaded_filename).to include ".csv" -# expect(downloaded_content).to have_content(distributor.name) -# end -# end - -# context "when logged in as enterprise user" do -# let!(:order) do -# create(:completed_order_with_fees, order_cycle: order_cycle, -# distributor: distributor) -# end -# let!(:other_order) do -# create(:completed_order_with_fees, order_cycle: other_order_cycle, -# distributor: other_distributor) -# end -# let(:current_user) { distributor.owner } - -# it "generates file with data for the enterprise" do -# check I18n.t("filters.report_format_csv", scope: i18n_scope) -# click_on "Go" - -# expect(downloaded_filename).to include ".csv" -# csv_content = downloaded_content -# expect(csv_content).to have_content(distributor.name) -# expect(csv_content).not_to have_content(other_distributor.name) -# end -# end -# end - -# describe "smoke test for filtering report based on filters" do -# let!(:second_distributor) { create(:distributor_enterprise) } -# let!(:second_order_cycle) { create(:simple_order_cycle, coordinator: second_distributor) } - -# let!(:order) do -# create(:completed_order_with_fees, order_cycle: order_cycle, -# distributor: distributor) -# end -# let!(:second_order) do -# create(:completed_order_with_fees, order_cycle: second_order_cycle, -# distributor: second_distributor) -# end - -# let(:current_user) { create(:admin_user) } - -# before do -# visit main_app.new_order_management_reports_enterprise_fee_summary_path -# end - -# it "generates file with data for selected order cycle" do -# select order_cycle.name, from: "report_order_cycle_ids" -# check I18n.t("filters.report_format_csv", scope: i18n_scope) -# click_on "Go" - -# expect(downloaded_filename).to include ".csv" -# csv_content = downloaded_content -# expect(csv_content).to have_content(distributor.name) -# expect(csv_content).not_to have_content(second_distributor.name) -# end -# end -# end - -# def i18n_scope -# "order_management.reports.enterprise_fee_summaries" -# end -# end diff --git a/lib/reporting/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb b/lib/reporting/reports/enterprise_fee_summary/base.rb similarity index 64% rename from lib/reporting/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb rename to lib/reporting/reports/enterprise_fee_summary/base.rb index 69f443aca8..6ffa34a67a 100644 --- a/lib/reporting/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb +++ b/lib/reporting/reports/enterprise_fee_summary/base.rb @@ -3,7 +3,7 @@ module Reporting module Reports module EnterpriseFeeSummary - class EnterpriseFeeSummaryReport < ReportObjectTemplate + class Base < ReportObjectTemplate attr_accessor :permissions, :parameters def initialize(user, params = {}) @@ -13,33 +13,29 @@ module Reporting p['start_at'] = p.delete('completed_at_gt') p['end_at'] = p.delete('completed_at_lt') end - @parameters = Reporting::Reports::EnterpriseFeeSummary::Parameters.new(p || {}) + @parameters = Parameters.new(p || {}) @parameters.validate! @permissions = Permissions.new(user) @parameters.authorize!(@permissions) end + def translate_header(key) + I18n.t("header.#{key}", scope: i18n_scope) + end + + def i18n_scope + "order_management.reports.enterprise_fee_summary.formats.csv" + end + def message I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") end - def table_headers - data_row_attributes.map do |attribute| - header_label(attribute) - end + def query_result + enterprise_fee_type_total_list.sort end - def table_rows - enterprise_fee_type_total_list.sort.map do |data| - data_row_attributes.map do |attribute| - data.public_send(attribute) - end - end - end - - private - - def data_row_attributes + def data_attributes [ :fee_type, :enterprise_name, @@ -52,13 +48,15 @@ module Reporting ] end - def header_label(attribute) - I18n.t("header.#{attribute}", scope: i18n_scope) + # This report calculate data in a different way, so we just encapsulate the result + # in the columns method + def columns + data_attributes.map { |field| + [field.to_sym, proc { |data| data.public_send(field) }] + }.to_h end - def i18n_scope - "order_management.reports.enterprise_fee_summary.formats.csv" - end + private def enterprise_fees_by_customer if parameters.order_cycle_ids.empty? @@ -73,19 +71,12 @@ module Reporting summarizer = Summarizer.new(total_data) ReportData::EnterpriseFeeTypeTotal.new.tap do |total| - enterprise_fee_type_summarizer_to_total_attributes.each do |attribute| + data_attributes.each do |attribute| total.public_send("#{attribute}=", summarizer.public_send(attribute)) end end end end - - def enterprise_fee_type_summarizer_to_total_attributes - [ - :fee_type, :enterprise_name, :fee_name, :customer_name, :fee_placement, - :fee_calculated_on_transfer_through_name, :tax_category_name, :total_amount - ] - end end end end diff --git a/spec/lib/reports/enterprise_fee_summary/enterprise_fee_summary_report_spec.rb b/spec/lib/reports/enterprise_fee_summary/enterprise_fee_summary_report_spec.rb index c5db21860a..0487954d33 100644 --- a/spec/lib/reports/enterprise_fee_summary/enterprise_fee_summary_report_spec.rb +++ b/spec/lib/reports/enterprise_fee_summary/enterprise_fee_summary_report_spec.rb @@ -2,730 +2,730 @@ require "spec_helper" -# rubocop:disable Layout/LineLength -# describe Reporting::Reports::EnterpriseFeeSummary::EnterpriseFeeSummaryReport do -# let(:report_klass) { Reporting::Reports::EnterpriseFeeSummary } - -# # Basic data. -# let!(:shipping_method) do -# create(:shipping_method, :per_item, amount: 1, name: "Sample Shipping Method") -# end - -# let!(:payment_method) do -# create(:payment_method, :per_item, amount: 2, name: "Sample Payment Method") -# end - -# # Create enterprises. -# let!(:distributor) do -# create(:distributor_enterprise, name: "Sample Distributor").tap do |enterprise| -# payment_method.distributors << enterprise -# shipping_method.distributors << enterprise -# end -# end -# let!(:producer) { create(:supplier_enterprise, name: "Sample Producer") } -# let!(:coordinator) { create(:enterprise, name: "Sample Coordinator") } - -# # Add some fee noise. -# let!(:other_distributor_fee) { create(:enterprise_fee, :per_item, enterprise: distributor) } -# let!(:other_producer_fee) { create(:enterprise_fee, :per_item, enterprise: producer) } -# let!(:other_coordinator_fee) { create(:enterprise_fee, :per_item, enterprise: coordinator) } - -# # Set up other requirements for ordering. -# let!(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator) } -# let!(:product) { create(:product, tax_category: product_tax_category) } -# let!(:product_tax_category) { create(:tax_category, name: "Sample Product Tax") } -# let!(:variant) { prepare_variant } - -# # Create customers. -# let!(:customer) { create(:customer, first_name: "Sample", last_name: "Customer") } -# let!(:another_customer) { create(:customer, first_name: "Another", last_name: "Customer") } - -# # Setup up permissions and report. -# let!(:current_user) { create(:admin_user) } - -# let(:permissions) { report_klass::Permissions.new(current_user) } -# let(:parameters) { report_klass::Parameters.new } -# let(:service) { described_class.new(permissions, parameters) } - -# describe "grouping and sorting of entries" do -# let!(:order_cycle) do -# create(:simple_order_cycle, coordinator: coordinator, coordinator_fees: order_cycle_fees) -# end - -# let!(:variant) do -# prepare_variant(incoming_exchange_fees: variant_incoming_exchange_fees, -# outgoing_exchange_fees: variant_outgoing_exchange_fees) -# end - -# let!(:order_cycle_fees) do -# [ -# create(:enterprise_fee, :per_item, name: "Coordinator Fee 1", enterprise: coordinator, -# fee_type: "admin", amount: 512.0, -# tax_category: coordinator_tax_category), -# create(:enterprise_fee, :per_item, name: "Coordinator Fee 2", enterprise: coordinator, -# fee_type: "sales", amount: 1024.0, -# inherits_tax_category: true) -# ] -# end -# let!(:coordinator_tax_category) { create(:tax_category, name: "Sample Coordinator Tax") } - -# let!(:variant_incoming_exchange_fees) do -# [ -# create(:enterprise_fee, :per_item, name: "Producer Fee 1", enterprise: producer, -# fee_type: "sales", amount: 64.0, -# tax_category: producer_tax_category), -# create(:enterprise_fee, :per_item, name: "Producer Fee 2", enterprise: producer, -# fee_type: "sales", amount: 128.0, -# inherits_tax_category: true) -# ] -# end -# let!(:producer_tax_category) { create(:tax_category, name: "Sample Producer Tax") } - -# let!(:variant_outgoing_exchange_fees) do -# [ -# create(:enterprise_fee, :per_item, name: "Distributor Fee 1", enterprise: distributor, -# fee_type: "admin", amount: 4.0, -# tax_category: distributor_tax_category), -# create(:enterprise_fee, :per_item, name: "Distributor Fee 2", enterprise: distributor, -# fee_type: "sales", amount: 8.0, -# inherits_tax_category: true) -# ] -# end -# let!(:distributor_tax_category) { create(:tax_category, name: "Sample Distributor Tax") } - -# let!(:customer_order) { prepare_order(customer: customer) } -# let!(:customer_incomplete_order) { prepare_incomplete_order(customer: customer) } -# let!(:second_customer_order) { prepare_order(customer: customer) } -# let!(:other_customer_order) { prepare_order(customer: another_customer) } - -# it "groups and sorts entries correctly" do -# totals = service.list - -# expect(totals.length).to eq(16) - -# # Data is sorted by the following, in order: -# # * fee_type -# # * enterprise_name -# # * fee_name -# # * customer_name -# # * fee_placement -# # * fee_calculated_on_transfer_through_name -# # * tax_category_name -# # * total_amount - -# expected_result = [ -# ["Admin", "Sample Coordinator", "Coordinator Fee 1", "Another Customer", -# "Coordinator", "All", "Sample Coordinator Tax", "512.00"], -# ["Admin", "Sample Coordinator", "Coordinator Fee 1", "Sample Customer", -# "Coordinator", "All", "Sample Coordinator Tax", "1024.00"], -# ["Admin", "Sample Distributor", "Distributor Fee 1", "Another Customer", -# "Outgoing", "Sample Distributor", "Sample Distributor Tax", "4.00"], -# ["Admin", "Sample Distributor", "Distributor Fee 1", "Sample Customer", -# "Outgoing", "Sample Distributor", "Sample Distributor Tax", "8.00"], -# ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Another Customer", -# nil, nil, nil, "2.00"], -# ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", -# nil, nil, nil, "4.00"], -# ["Sales", "Sample Coordinator", "Coordinator Fee 2", "Another Customer", -# "Coordinator", "All", "Various", "1024.00"], -# ["Sales", "Sample Coordinator", "Coordinator Fee 2", "Sample Customer", -# "Coordinator", "All", "Various", "2048.00"], -# ["Sales", "Sample Distributor", "Distributor Fee 2", "Another Customer", -# "Outgoing", "Sample Distributor", "Sample Product Tax", "8.00"], -# ["Sales", "Sample Distributor", "Distributor Fee 2", "Sample Customer", -# "Outgoing", "Sample Distributor", "Sample Product Tax", "16.00"], -# ["Sales", "Sample Producer", "Producer Fee 1", "Another Customer", -# "Incoming", "Sample Producer", "Sample Producer Tax", "64.00"], -# ["Sales", "Sample Producer", "Producer Fee 1", "Sample Customer", -# "Incoming", "Sample Producer", "Sample Producer Tax", "128.00"], -# ["Sales", "Sample Producer", "Producer Fee 2", "Another Customer", -# "Incoming", "Sample Producer", "Sample Product Tax", "128.00"], -# ["Sales", "Sample Producer", "Producer Fee 2", "Sample Customer", -# "Incoming", "Sample Producer", "Sample Product Tax", "256.00"], -# ["Shipment", "Sample Distributor", "Sample Shipping Method", "Another Customer", -# nil, nil, "Platform Rate", "1.00"], -# ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", -# nil, nil, "Platform Rate", "2.00"] -# ] - -# expected_result.each_with_index do |expected_attributes, row_index| -# expect_total_attributes(totals[row_index], expected_attributes) -# end -# end -# end - -# describe "data exclusions" do -# describe "invalid adjustments (through 'eligible') like failed payments" do -# let!(:customer_order) { prepare_order(customer: customer) } - -# before do -# # Make the payment fail. See Spree::Payment#revoke_adjustment_eligibility. -# payment = customer_order.payments.first -# payment.state = "failed" -# payment.save! -# end - -# it "is included" do -# totals = service.list - -# expect(totals.length).to eq(1) - -# expected_result = [ -# ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", -# nil, nil, "Platform Rate", "1.00"] -# ] - -# expected_result.each_with_index do |expected_attributes, row_index| -# expect_total_attributes(totals[row_index], expected_attributes) -# end -# end -# end - -# describe "non-mandatory $0 adjustments (through 'eligible')" do -# let!(:variant) { prepare_variant(outgoing_exchange_fees: [enterprise_fee]) } - -# let!(:enterprise_fee) do -# create(:enterprise_fee, :per_item, name: "Sample Enterprise Fee", enterprise: distributor, -# fee_type: "admin", amount: 0) -# end - -# let!(:customer_order) { prepare_order(customer: customer) } - -# before do -# # Change "eligible" in enterprise fee adjustment to false. $0 adjustments that are not -# # mandatory are set to be ineligible, but there are no non-mandatory adjustments supported -# # by the report yet. -# adjustment = Spree::Adjustment.where(originator_type: "EnterpriseFee").first -# adjustment.eligible = false -# adjustment.save! -# end - -# it "is included" do -# totals = service.list - -# expect(totals.length).to eq(2) - -# expected_result = [ -# ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", -# nil, nil, nil, "2.00"], -# ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", -# nil, nil, "Platform Rate", "1.00"] -# ] - -# expected_result.each_with_index do |expected_attributes, row_index| -# expect_total_attributes(totals[row_index], expected_attributes) -# end -# end -# end - -# describe "$0 mandatory adjustments" do -# let!(:payment_method) do -# create(:payment_method, :per_item, amount: 0, name: "Sample Payment Method") -# end - -# let!(:customer_order) { prepare_order(customer: customer) } - -# it "is included" do -# totals = service.list - -# expect(totals.length).to eq(1) - -# expected_result = [ -# ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", -# nil, nil, "Platform Rate", "1.00"] -# ] - -# expected_result.each_with_index do |expected_attributes, row_index| -# expect_total_attributes(totals[row_index], expected_attributes) -# end -# end -# end -# end - -# describe "handling of more complex cases" do -# context "with non-sender fee for incoming exchange and non-receiver fee for outgoing" do -# let!(:variant) do -# prepare_variant(incoming_exchange_fees: variant_incoming_exchange_fees, -# outgoing_exchange_fees: variant_outgoing_exchange_fees) -# end -# let!(:variant_incoming_exchange_fees) { [coordinator_fee, distributor_fee] } -# let!(:variant_outgoing_exchange_fees) { [producer_fee, coordinator_fee] } - -# let!(:producer_fee) do -# tax_category = create(:tax_category, name: "Sample Producer Tax") -# create(:enterprise_fee, :per_item, name: "Sample Producer Fee", enterprise: producer, -# fee_type: "sales", amount: 64.0, -# tax_category: tax_category) -# end -# let!(:coordinator_fee) do -# tax_category = create(:tax_category, name: "Sample Coordinator Tax") -# create(:enterprise_fee, :per_item, name: "Sample Coordinator Fee", enterprise: coordinator, -# fee_type: "admin", amount: 512.0, -# tax_category: tax_category) -# end -# let!(:distributor_fee) do -# tax_category = create(:tax_category, name: "Sample Distributor Tax") -# create(:enterprise_fee, :per_item, name: "Sample Distributor Fee", enterprise: distributor, -# fee_type: "admin", amount: 4.0, -# tax_category: tax_category) -# end - -# let!(:customer_order) { prepare_order(customer: customer) } - -# it "fetches data correctly" do -# totals = service.list - -# expect(totals.length).to eq(6) - -# expected_result = [ -# ["Admin", "Sample Coordinator", "Sample Coordinator Fee", "Sample Customer", -# "Incoming", "Sample Producer", "Sample Coordinator Tax", "512.00"], -# ["Admin", "Sample Coordinator", "Sample Coordinator Fee", "Sample Customer", -# "Outgoing", "Sample Distributor", "Sample Coordinator Tax", "512.00"], -# ["Admin", "Sample Distributor", "Sample Distributor Fee", "Sample Customer", -# "Incoming", "Sample Producer", "Sample Distributor Tax", "4.00"], -# ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", -# nil, nil, nil, "2.00"], -# ["Sales", "Sample Producer", "Sample Producer Fee", "Sample Customer", -# "Outgoing", "Sample Distributor", "Sample Producer Tax", "64.00"], -# ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", -# nil, nil, "Platform Rate", "1.00"] -# ] - -# expected_result.each_with_index do |expected_attributes, row_index| -# expect_total_attributes(totals[row_index], expected_attributes) -# end -# end -# end - -# context "with order-based enterprise fee calculator" do -# let!(:producer_fee) do -# tax_category = create(:tax_category, name: "Producer Tax A") -# create(:enterprise_fee, :flat_rate, name: "Producer Fee A", enterprise: producer, -# fee_type: "sales", tax_category: tax_category, -# amount: 10) -# end -# let!(:coordinator_fee) do -# tax_category = create(:tax_category, name: "Coordinator Tax A") -# create(:enterprise_fee, :flat_rate, name: "Coordinator Fee A", enterprise: coordinator, -# fee_type: "admin", tax_category: tax_category, -# amount: 15) -# end -# let!(:coordinator_fee_inheriting_product_tax_category) do -# create(:enterprise_fee, :flat_rate, name: "Coordinator Fee B", enterprise: coordinator, -# fee_type: "admin", inherits_tax_category: true, -# amount: 20) -# end -# let!(:coordinator_fee_without_tax) do -# create(:enterprise_fee, :flat_rate, name: "Coordinator Fee C", enterprise: coordinator, -# fee_type: "admin", inherits_tax_category: false, -# amount: 25) -# end -# let!(:distributor_fee) do -# create(:enterprise_fee, :flat_rate, name: "Distributor Fee A", enterprise: distributor, -# fee_type: "admin", inherits_tax_category: false, -# amount: 30) -# end - -# let!(:coordinator_fees) do -# [ -# coordinator_fee, -# coordinator_fee_inheriting_product_tax_category, -# coordinator_fee_without_tax -# ] -# end - -# let!(:order_cycle) do -# create(:simple_order_cycle, coordinator: coordinator, coordinator_fees: coordinator_fees) -# end - -# let!(:variant_incoming_exchange_fees) { [producer_fee, coordinator_fee, distributor_fee] } -# let!(:variant_outgoing_exchange_fees) { [producer_fee, coordinator_fee, distributor_fee] } - -# let!(:variant) do -# prepare_variant(incoming_exchange_fees: variant_incoming_exchange_fees, -# outgoing_exchange_fees: variant_outgoing_exchange_fees) -# end - -# let!(:customer_order) { prepare_order(customer: customer) } - -# it "fetches data correctly" do -# totals = service.list - -# expect(totals.length).to eq(11) - -# entire_orders_text = i18n_translate("fee_calculated_on_transfer_through_entire_orders", -# distributor: "Sample Distributor") -# various_tax_categories_text = i18n_translate("tax_category_various") - -# expected_result = [ -# ["Admin", "Sample Coordinator", "Coordinator Fee A", "Sample Customer", -# "Coordinator", "All", "Coordinator Tax A", "15.00"], -# ["Admin", "Sample Coordinator", "Coordinator Fee A", "Sample Customer", -# "Incoming", entire_orders_text, "Coordinator Tax A", "15.00"], -# ["Admin", "Sample Coordinator", "Coordinator Fee A", "Sample Customer", -# "Outgoing", entire_orders_text, "Coordinator Tax A", "15.00"], -# ["Admin", "Sample Coordinator", "Coordinator Fee B", "Sample Customer", -# "Coordinator", "All", various_tax_categories_text, "20.00"], -# ["Admin", "Sample Coordinator", "Coordinator Fee C", "Sample Customer", -# "Coordinator", "All", nil, "25.00"], -# ["Admin", "Sample Distributor", "Distributor Fee A", "Sample Customer", -# "Incoming", entire_orders_text, various_tax_categories_text, "30.00"], -# ["Admin", "Sample Distributor", "Distributor Fee A", "Sample Customer", -# "Outgoing", entire_orders_text, various_tax_categories_text, "30.00"], -# ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", -# nil, nil, nil, "2.00"], -# ["Sales", "Sample Producer", "Producer Fee A", "Sample Customer", -# "Incoming", entire_orders_text, "Producer Tax A", "10.00"], -# ["Sales", "Sample Producer", "Producer Fee A", "Sample Customer", -# "Outgoing", entire_orders_text, "Producer Tax A", "10.00"], -# ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", -# nil, nil, "Platform Rate", "1.00"] -# ] - -# expected_result.each_with_index do |expected_attributes, row_index| -# expect_total_attributes(totals[row_index], expected_attributes) -# end -# end -# end -# end - -# describe "filtering results based on permissions" do -# let!(:distributor_a) do -# create(:distributor_enterprise, name: "Distributor A", payment_methods: [payment_method], -# shipping_methods: [shipping_method]) -# end -# let!(:distributor_b) do -# create(:distributor_enterprise, name: "Distributor B", payment_methods: [payment_method], -# shipping_methods: [shipping_method]) -# end - -# let!(:order_cycle_a) { create(:simple_order_cycle, coordinator: coordinator) } -# let!(:order_cycle_b) { create(:simple_order_cycle, coordinator: coordinator) } - -# let!(:variant_a) { prepare_variant(distributor: distributor_a, order_cycle: order_cycle_a) } -# let!(:variant_b) { prepare_variant(distributor: distributor_b, order_cycle: order_cycle_b) } - -# let!(:order_a) { prepare_order(order_cycle: order_cycle_a, distributor: distributor_a) } -# let!(:order_b) { prepare_order(order_cycle: order_cycle_b, distributor: distributor_b) } - -# context "when admin" do -# let!(:current_user) { create(:admin_user) } - -# it "includes all order cycles" do -# totals = service.list - -# expect_total_matches(totals, 2, fee_type: "Shipment") -# expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") -# expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor B") -# end -# end - -# context "when enterprise owner for distributor" do -# let!(:current_user) { distributor_a.owner } - -# it "does not include unrelated order cycles" do -# totals = service.list - -# expect_total_matches(totals, 1, fee_type: "Shipment") -# expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") -# end -# end -# end - -# describe "filters entries correctly" do -# let(:parameters) { report_klass::Parameters.new(parameters_attributes) } - -# context "filtering by completion date" do -# let(:timestamp) { Time.zone.local(2018, 1, 5, 14, 30, 5) } - -# let!(:customer_a) { create(:customer, first_name: "Customer", last_name: "A") } -# let!(:customer_b) { create(:customer, first_name: "Customer", last_name: "B") } -# let!(:customer_c) { create(:customer, first_name: "Customer", last_name: "C") } - -# let!(:order_placed_before_timestamp) do -# prepare_order(customer: customer_a).tap do |order| -# order.update_column(:completed_at, timestamp - 1.second) -# end -# end - -# let!(:order_placed_during_timestamp) do -# prepare_order(customer: customer_b).tap do |order| -# order.update_column(:completed_at, timestamp) -# end -# end - -# let!(:order_placed_after_timestamp) do -# prepare_order(customer: customer_c).tap do |order| -# order.update_column(:completed_at, timestamp + 1.second) -# end -# end - -# context "on or after start_at" do -# let(:parameters_attributes) { { start_at: timestamp } } - -# it "filters entries" do -# totals = service.list - -# expect_total_matches(totals, 0, fee_type: "Shipment", customer_name: "Customer A") -# expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer B") -# expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer C") -# end -# end - -# context "on or before end_at" do -# let(:parameters_attributes) { { end_at: timestamp } } - -# it "filters entries" do -# totals = service.list - -# expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer A") -# expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer B") -# expect_total_matches(totals, 0, fee_type: "Shipment", customer_name: "Customer C") -# end -# end -# end - -# describe "for specified shops" do -# let!(:distributor_a) do -# create(:distributor_enterprise, name: "Distributor A", payment_methods: [payment_method], -# shipping_methods: [shipping_method]) -# end -# let!(:distributor_b) do -# create(:distributor_enterprise, name: "Distributor B", payment_methods: [payment_method], -# shipping_methods: [shipping_method]) -# end -# let!(:distributor_c) do -# create(:distributor_enterprise, name: "Distributor C", payment_methods: [payment_method], -# shipping_methods: [shipping_method]) -# end - -# let!(:order_a) { prepare_order(distributor: distributor_a) } -# let!(:order_b) { prepare_order(distributor: distributor_b) } -# let!(:order_c) { prepare_order(distributor: distributor_c) } - -# let(:parameters_attributes) { { distributor_ids: [distributor_a.id, distributor_b.id] } } - -# it "filters entries" do -# totals = service.list - -# expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") -# expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor B") -# expect_total_matches(totals, 0, fee_type: "Shipment", enterprise_name: "Distributor C") -# end -# end - -# describe "for specified suppliers" do -# let!(:producer_a) { create(:supplier_enterprise, name: "Producer A") } -# let!(:producer_b) { create(:supplier_enterprise, name: "Producer B") } -# let!(:producer_c) { create(:supplier_enterprise, name: "Producer C") } - -# let!(:fee_a) { create(:enterprise_fee, name: "Fee A", enterprise: producer_a, amount: 1) } -# let!(:fee_b) { create(:enterprise_fee, name: "Fee B", enterprise: producer_b, amount: 1) } -# let!(:fee_c) { create(:enterprise_fee, name: "Fee C", enterprise: producer_c, amount: 1) } - -# let!(:product_a) { create(:product, supplier: producer_a) } -# let!(:product_b) { create(:product, supplier: producer_b) } -# let!(:product_c) { create(:product, supplier: producer_c) } - -# let!(:variant_a) do -# prepare_variant(product: product_a, producer: producer_a, incoming_exchange_fees: [fee_a]) -# end -# let!(:variant_b) do -# prepare_variant(product: product_b, producer: producer_b, incoming_exchange_fees: [fee_b]) -# end -# let!(:variant_c) do -# prepare_variant(product: product_c, producer: producer_c, incoming_exchange_fees: [fee_c]) -# end - -# let!(:order_a) { prepare_order(variant: variant_a) } -# let!(:order_b) { prepare_order(variant: variant_b) } -# let!(:order_c) { prepare_order(variant: variant_c) } - -# let(:parameters_attributes) { { producer_ids: [producer_a.id, producer_b.id] } } - -# it "filters entries" do -# totals = service.list - -# expect_total_matches(totals, 1, fee_name: "Fee A", enterprise_name: "Producer A") -# expect_total_matches(totals, 1, fee_name: "Fee B", enterprise_name: "Producer B") -# expect_total_matches(totals, 0, fee_name: "Fee C", enterprise_name: "Producer C") -# end -# end - -# describe "for specified order cycles" do -# let!(:distributor_a) do -# create(:distributor_enterprise, name: "Distributor A", payment_methods: [payment_method], -# shipping_methods: [shipping_method]) -# end -# let!(:distributor_b) do -# create(:distributor_enterprise, name: "Distributor B", payment_methods: [payment_method], -# shipping_methods: [shipping_method]) -# end -# let!(:distributor_c) do -# create(:distributor_enterprise, name: "Distributor C", payment_methods: [payment_method], -# shipping_methods: [shipping_method]) -# end - -# let!(:order_cycle_a) { create(:simple_order_cycle, coordinator: coordinator) } -# let!(:order_cycle_b) { create(:simple_order_cycle, coordinator: coordinator) } -# let!(:order_cycle_c) { create(:simple_order_cycle, coordinator: coordinator) } - -# let!(:variant_a) { prepare_variant(distributor: distributor_a, order_cycle: order_cycle_a) } -# let!(:variant_b) { prepare_variant(distributor: distributor_b, order_cycle: order_cycle_b) } -# let!(:variant_c) { prepare_variant(distributor: distributor_c, order_cycle: order_cycle_c) } - -# let!(:order_a) { prepare_order(order_cycle: order_cycle_a, distributor: distributor_a) } -# let!(:order_b) { prepare_order(order_cycle: order_cycle_b, distributor: distributor_b) } -# let!(:order_c) { prepare_order(order_cycle: order_cycle_c, distributor: distributor_c) } - -# let(:parameters_attributes) { { order_cycle_ids: [order_cycle_a.id, order_cycle_b.id] } } - -# it "filters entries" do -# totals = service.list - -# expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") -# expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor B") -# expect_total_matches(totals, 0, fee_type: "Shipment", enterprise_name: "Distributor C") -# end -# end - -# describe "for specified enterprise fees" do -# let!(:fee_a) { create(:enterprise_fee, name: "Fee A", enterprise: distributor, amount: 1) } -# let!(:fee_b) { create(:enterprise_fee, name: "Fee B", enterprise: distributor, amount: 1) } -# let!(:fee_c) { create(:enterprise_fee, name: "Fee C", enterprise: distributor, amount: 1) } - -# let!(:variant) { prepare_variant(outgoing_exchange_fees: variant_outgoing_exchange_fees) } -# let!(:variant_outgoing_exchange_fees) { [fee_a, fee_b, fee_c] } - -# let!(:order) { prepare_order(variant: variant) } - -# let(:parameters_attributes) { { enterprise_fee_ids: [fee_a.id, fee_b.id] } } - -# it "filters entries" do -# totals = service.list - -# expect_total_matches(totals, 1, fee_name: "Fee A") -# expect_total_matches(totals, 1, fee_name: "Fee B") -# expect_total_matches(totals, 0, fee_name: "Fee C") -# end -# end - -# describe "for specified shipping methods" do -# let!(:shipping_method_a) do -# method = create(:shipping_method, name: "Shipping A", distributors: [distributor]) -# method.calculator.update_attribute(:preferred_amount, 1) -# method -# end -# let!(:shipping_method_b) do -# method = create(:shipping_method, name: "Shipping B", distributors: [distributor]) -# method.calculator.update_attribute(:preferred_amount, 1) -# method -# end -# let!(:shipping_method_c) do -# create(:shipping_method, name: "Shipping C", distributors: [distributor]) -# end - -# let!(:order_a) { prepare_order(shipping_method: shipping_method_a) } -# let!(:order_b) { prepare_order(shipping_method: shipping_method_b) } -# let!(:order_c) { prepare_order(shipping_method: shipping_method_c) } - -# let(:parameters_attributes) do -# { shipping_method_ids: [shipping_method_a.id, shipping_method_b.id] } -# end - -# it "filters entries" do -# totals = service.list - -# expect_total_matches(totals, 1, fee_name: "Shipping A") -# expect_total_matches(totals, 1, fee_name: "Shipping B") -# expect_total_matches(totals, 0, fee_name: "Shipping C") -# end -# end - -# describe "for specified payment methods" do -# let!(:payment_method_a) do -# method = create(:payment_method, name: "Payment A", distributors: [distributor]) -# method.calculator.update_attribute(:preferred_amount, 1) -# method -# end -# let!(:payment_method_b) do -# method = create(:payment_method, name: "Payment B", distributors: [distributor]) -# method.calculator.update_attribute(:preferred_amount, 1) -# method -# end -# let!(:payment_method_c) do -# create(:payment_method, name: "Payment C", distributors: [distributor]) -# end - -# let!(:order_a) { prepare_order(payment_method: payment_method_a) } -# let!(:order_b) { prepare_order(payment_method: payment_method_b) } -# let!(:order_c) { prepare_order(payment_method: payment_method_c) } - -# let(:parameters_attributes) do -# { payment_method_ids: [payment_method_a.id, payment_method_b.id] } -# end - -# it "filters entries" do -# totals = service.list - -# expect_total_matches(totals, 1, fee_name: "Payment A") -# expect_total_matches(totals, 1, fee_name: "Payment B") -# expect_total_matches(totals, 0, fee_name: "Payment C") -# end -# end -# end - -# # Helper methods for example group - -# def i18n_translate(translation_key, options = {}) -# I18n.t("order_management.reports.enterprise_fee_summary.#{translation_key}", **options) -# end - -# def expect_total_attributes(total, expected_attribute_list) -# actual_attribute_list = [total.fee_type, total.enterprise_name, total.fee_name, -# total.customer_name, total.fee_placement, -# total.fee_calculated_on_transfer_through_name, total.tax_category_name, -# total.total_amount] -# expect(actual_attribute_list).to eq(expected_attribute_list) -# end - -# def expect_total_matches(totals, count, attributes) -# expect(count_totals(totals, attributes)).to eq(count) -# end - -# def default_order_options -# { customer: customer, distributor: distributor, order_cycle: order_cycle, -# shipping_method: shipping_method, variant: variant } -# end - -# def prepare_incomplete_order(options = {}) -# target_options = default_order_options.merge(options) -# create(:order, :with_line_item, target_options) -# end - -# def prepare_order(options = {}) -# factory_trait_options = { payment_method: payment_method } -# target_options = default_order_options.merge(factory_trait_options).merge(options) -# create(:order, :with_line_item, :completed, target_options) -# end - -# def default_variant_options -# { product: product, producer: producer, is_master: false, coordinator: coordinator, -# distributor: distributor, order_cycle: order_cycle } -# end - -# def prepare_variant(options = {}) -# target_options = default_variant_options.merge(options) -# create(:variant, :with_order_cycle, target_options) -# end - -# def count_totals(totals, attributes) -# totals.count do |data| -# attributes.all? do |attribute_name, attribute_value| -# data.public_send(attribute_name) == attribute_value -# end -# end -# end -# end -# rubocop:enable Layout/LineLength +describe Reporting::Reports::EnterpriseFeeSummary::Base do + let(:report_module) { Reporting::Reports::EnterpriseFeeSummary } + + # Basic data. + let!(:shipping_method) do + create(:shipping_method, :per_item, amount: 1, name: "Sample Shipping Method") + end + + let!(:payment_method) do + create(:payment_method, :per_item, amount: 2, name: "Sample Payment Method") + end + + # Create enterprises. + let!(:distributor) do + create(:distributor_enterprise, name: "Sample Distributor").tap do |enterprise| + payment_method.distributors << enterprise + shipping_method.distributors << enterprise + end + end + let!(:producer) { create(:supplier_enterprise, name: "Sample Producer") } + let!(:coordinator) { create(:enterprise, name: "Sample Coordinator") } + + # Add some fee noise. + let!(:other_distributor_fee) { create(:enterprise_fee, :per_item, enterprise: distributor) } + let!(:other_producer_fee) { create(:enterprise_fee, :per_item, enterprise: producer) } + let!(:other_coordinator_fee) { create(:enterprise_fee, :per_item, enterprise: coordinator) } + + # Set up other requirements for ordering. + let!(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator) } + let!(:product) { create(:product, tax_category: product_tax_category) } + let!(:product_tax_category) { create(:tax_category, name: "Sample Product Tax") } + let!(:variant) { prepare_variant } + + # Create customers. + let!(:customer) { create(:customer, first_name: "Sample", last_name: "Customer") } + let!(:another_customer) { create(:customer, first_name: "Another", last_name: "Customer") } + + # Setup up permissions and report. + let!(:current_user) { create(:admin_user) } + let(:parameters) { report_module::Parameters.new } + let(:subject) { + report = described_class.new(current_user) + allow(report).to receive(:parameters).and_return(parameters) + report + } + + describe "grouping and sorting of entries" do + let!(:order_cycle) do + create(:simple_order_cycle, coordinator: coordinator, coordinator_fees: order_cycle_fees) + end + + let!(:variant) do + prepare_variant(incoming_exchange_fees: variant_incoming_exchange_fees, + outgoing_exchange_fees: variant_outgoing_exchange_fees) + end + + let!(:order_cycle_fees) do + [ + create(:enterprise_fee, :per_item, name: "Coordinator Fee 1", enterprise: coordinator, + fee_type: "admin", amount: 512.0, + tax_category: coordinator_tax_category), + create(:enterprise_fee, :per_item, name: "Coordinator Fee 2", enterprise: coordinator, + fee_type: "sales", amount: 1024.0, + inherits_tax_category: true) + ] + end + let!(:coordinator_tax_category) { create(:tax_category, name: "Sample Coordinator Tax") } + + let!(:variant_incoming_exchange_fees) do + [ + create(:enterprise_fee, :per_item, name: "Producer Fee 1", enterprise: producer, + fee_type: "sales", amount: 64.0, + tax_category: producer_tax_category), + create(:enterprise_fee, :per_item, name: "Producer Fee 2", enterprise: producer, + fee_type: "sales", amount: 128.0, + inherits_tax_category: true) + ] + end + let!(:producer_tax_category) { create(:tax_category, name: "Sample Producer Tax") } + + let!(:variant_outgoing_exchange_fees) do + [ + create(:enterprise_fee, :per_item, name: "Distributor Fee 1", enterprise: distributor, + fee_type: "admin", amount: 4.0, + tax_category: distributor_tax_category), + create(:enterprise_fee, :per_item, name: "Distributor Fee 2", enterprise: distributor, + fee_type: "sales", amount: 8.0, + inherits_tax_category: true) + ] + end + let!(:distributor_tax_category) { create(:tax_category, name: "Sample Distributor Tax") } + + let!(:customer_order) { prepare_order(customer: customer) } + let!(:customer_incomplete_order) { prepare_incomplete_order(customer: customer) } + let!(:second_customer_order) { prepare_order(customer: customer) } + let!(:other_customer_order) { prepare_order(customer: another_customer) } + + it "groups and sorts entries correctly" do + totals = subject.query_result + + expect(totals.length).to eq(16) + + # Data is sorted by the following, in order: + # * fee_type + # * enterprise_name + # * fee_name + # * customer_name + # * fee_placement + # * fee_calculated_on_transfer_through_name + # * tax_category_name + # * total_amount + + expected_result = [ + ["Admin", "Sample Coordinator", "Coordinator Fee 1", "Another Customer", + "Coordinator", "All", "Sample Coordinator Tax", "512.00"], + ["Admin", "Sample Coordinator", "Coordinator Fee 1", "Sample Customer", + "Coordinator", "All", "Sample Coordinator Tax", "1024.00"], + ["Admin", "Sample Distributor", "Distributor Fee 1", "Another Customer", + "Outgoing", "Sample Distributor", "Sample Distributor Tax", "4.00"], + ["Admin", "Sample Distributor", "Distributor Fee 1", "Sample Customer", + "Outgoing", "Sample Distributor", "Sample Distributor Tax", "8.00"], + ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Another Customer", + nil, nil, nil, "2.00"], + ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", + nil, nil, nil, "4.00"], + ["Sales", "Sample Coordinator", "Coordinator Fee 2", "Another Customer", + "Coordinator", "All", "Various", "1024.00"], + ["Sales", "Sample Coordinator", "Coordinator Fee 2", "Sample Customer", + "Coordinator", "All", "Various", "2048.00"], + ["Sales", "Sample Distributor", "Distributor Fee 2", "Another Customer", + "Outgoing", "Sample Distributor", "Sample Product Tax", "8.00"], + ["Sales", "Sample Distributor", "Distributor Fee 2", "Sample Customer", + "Outgoing", "Sample Distributor", "Sample Product Tax", "16.00"], + ["Sales", "Sample Producer", "Producer Fee 1", "Another Customer", + "Incoming", "Sample Producer", "Sample Producer Tax", "64.00"], + ["Sales", "Sample Producer", "Producer Fee 1", "Sample Customer", + "Incoming", "Sample Producer", "Sample Producer Tax", "128.00"], + ["Sales", "Sample Producer", "Producer Fee 2", "Another Customer", + "Incoming", "Sample Producer", "Sample Product Tax", "128.00"], + ["Sales", "Sample Producer", "Producer Fee 2", "Sample Customer", + "Incoming", "Sample Producer", "Sample Product Tax", "256.00"], + ["Shipment", "Sample Distributor", "Sample Shipping Method", "Another Customer", + nil, nil, "Platform Rate", "1.00"], + ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", + nil, nil, "Platform Rate", "2.00"] + ] + + expected_result.each_with_index do |expected_attributes, row_index| + expect_total_attributes(totals[row_index], expected_attributes) + end + end + end + + describe "data exclusions" do + describe "invalid adjustments (through 'eligible') like failed payments" do + let!(:customer_order) { prepare_order(customer: customer) } + + before do + # Make the payment fail. See Spree::Payment#revoke_adjustment_eligibility. + payment = customer_order.payments.first + payment.state = "failed" + payment.save! + end + + it "is included" do + totals = subject.query_result + + expect(totals.length).to eq(1) + + expected_result = [ + ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", + nil, nil, "Platform Rate", "1.00"] + ] + + expected_result.each_with_index do |expected_attributes, row_index| + expect_total_attributes(totals[row_index], expected_attributes) + end + end + end + + describe "non-mandatory $0 adjustments (through 'eligible')" do + let!(:variant) { prepare_variant(outgoing_exchange_fees: [enterprise_fee]) } + + let!(:enterprise_fee) do + create(:enterprise_fee, :per_item, name: "Sample Enterprise Fee", enterprise: distributor, + fee_type: "admin", amount: 0) + end + + let!(:customer_order) { prepare_order(customer: customer) } + + before do + # Change "eligible" in enterprise fee adjustment to false. $0 adjustments that are not + # mandatory are set to be ineligible, but there are no non-mandatory adjustments supported + # by the report yet. + adjustment = Spree::Adjustment.where(originator_type: "EnterpriseFee").first + adjustment.eligible = false + adjustment.save! + end + + it "is included" do + totals = subject.query_result + + expect(totals.length).to eq(2) + + expected_result = [ + ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", + nil, nil, nil, "2.00"], + ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", + nil, nil, "Platform Rate", "1.00"] + ] + + expected_result.each_with_index do |expected_attributes, row_index| + expect_total_attributes(totals[row_index], expected_attributes) + end + end + end + + describe "$0 mandatory adjustments" do + let!(:payment_method) do + create(:payment_method, :per_item, amount: 0, name: "Sample Payment Method") + end + + let!(:customer_order) { prepare_order(customer: customer) } + + it "is included" do + totals = subject.query_result + + expect(totals.length).to eq(1) + + expected_result = [ + ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", + nil, nil, "Platform Rate", "1.00"] + ] + + expected_result.each_with_index do |expected_attributes, row_index| + expect_total_attributes(totals[row_index], expected_attributes) + end + end + end + end + + describe "handling of more complex cases" do + context "with non-sender fee for incoming exchange and non-receiver fee for outgoing" do + let!(:variant) do + prepare_variant(incoming_exchange_fees: variant_incoming_exchange_fees, + outgoing_exchange_fees: variant_outgoing_exchange_fees) + end + let!(:variant_incoming_exchange_fees) { [coordinator_fee, distributor_fee] } + let!(:variant_outgoing_exchange_fees) { [producer_fee, coordinator_fee] } + + let!(:producer_fee) do + tax_category = create(:tax_category, name: "Sample Producer Tax") + create(:enterprise_fee, :per_item, name: "Sample Producer Fee", enterprise: producer, + fee_type: "sales", amount: 64.0, + tax_category: tax_category) + end + let!(:coordinator_fee) do + tax_category = create(:tax_category, name: "Sample Coordinator Tax") + create(:enterprise_fee, :per_item, name: "Sample Coordinator Fee", enterprise: coordinator, + fee_type: "admin", amount: 512.0, + tax_category: tax_category) + end + let!(:distributor_fee) do + tax_category = create(:tax_category, name: "Sample Distributor Tax") + create(:enterprise_fee, :per_item, name: "Sample Distributor Fee", enterprise: distributor, + fee_type: "admin", amount: 4.0, + tax_category: tax_category) + end + + let!(:customer_order) { prepare_order(customer: customer) } + + it "fetches data correctly" do + totals = subject.query_result + + expect(totals.length).to eq(6) + + expected_result = [ + ["Admin", "Sample Coordinator", "Sample Coordinator Fee", "Sample Customer", + "Incoming", "Sample Producer", "Sample Coordinator Tax", "512.00"], + ["Admin", "Sample Coordinator", "Sample Coordinator Fee", "Sample Customer", + "Outgoing", "Sample Distributor", "Sample Coordinator Tax", "512.00"], + ["Admin", "Sample Distributor", "Sample Distributor Fee", "Sample Customer", + "Incoming", "Sample Producer", "Sample Distributor Tax", "4.00"], + ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", + nil, nil, nil, "2.00"], + ["Sales", "Sample Producer", "Sample Producer Fee", "Sample Customer", + "Outgoing", "Sample Distributor", "Sample Producer Tax", "64.00"], + ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", + nil, nil, "Platform Rate", "1.00"] + ] + + expected_result.each_with_index do |expected_attributes, row_index| + expect_total_attributes(totals[row_index], expected_attributes) + end + end + end + + context "with order-based enterprise fee calculator" do + let!(:producer_fee) do + tax_category = create(:tax_category, name: "Producer Tax A") + create(:enterprise_fee, :flat_rate, name: "Producer Fee A", enterprise: producer, + fee_type: "sales", tax_category: tax_category, + amount: 10) + end + let!(:coordinator_fee) do + tax_category = create(:tax_category, name: "Coordinator Tax A") + create(:enterprise_fee, :flat_rate, name: "Coordinator Fee A", enterprise: coordinator, + fee_type: "admin", tax_category: tax_category, + amount: 15) + end + let!(:coordinator_fee_inheriting_product_tax_category) do + create(:enterprise_fee, :flat_rate, name: "Coordinator Fee B", enterprise: coordinator, + fee_type: "admin", inherits_tax_category: true, + amount: 20) + end + let!(:coordinator_fee_without_tax) do + create(:enterprise_fee, :flat_rate, name: "Coordinator Fee C", enterprise: coordinator, + fee_type: "admin", inherits_tax_category: false, + amount: 25) + end + let!(:distributor_fee) do + create(:enterprise_fee, :flat_rate, name: "Distributor Fee A", enterprise: distributor, + fee_type: "admin", inherits_tax_category: false, + amount: 30) + end + + let!(:coordinator_fees) do + [ + coordinator_fee, + coordinator_fee_inheriting_product_tax_category, + coordinator_fee_without_tax + ] + end + + let!(:order_cycle) do + create(:simple_order_cycle, coordinator: coordinator, coordinator_fees: coordinator_fees) + end + + let!(:variant_incoming_exchange_fees) { [producer_fee, coordinator_fee, distributor_fee] } + let!(:variant_outgoing_exchange_fees) { [producer_fee, coordinator_fee, distributor_fee] } + + let!(:variant) do + prepare_variant(incoming_exchange_fees: variant_incoming_exchange_fees, + outgoing_exchange_fees: variant_outgoing_exchange_fees) + end + + let!(:customer_order) { prepare_order(customer: customer) } + + it "fetches data correctly" do + totals = subject.query_result + + expect(totals.length).to eq(11) + + entire_orders_text = i18n_translate("fee_calculated_on_transfer_through_entire_orders", + distributor: "Sample Distributor") + various_tax_categories_text = i18n_translate("tax_category_various") + + expected_result = [ + ["Admin", "Sample Coordinator", "Coordinator Fee A", "Sample Customer", + "Coordinator", "All", "Coordinator Tax A", "15.00"], + ["Admin", "Sample Coordinator", "Coordinator Fee A", "Sample Customer", + "Incoming", entire_orders_text, "Coordinator Tax A", "15.00"], + ["Admin", "Sample Coordinator", "Coordinator Fee A", "Sample Customer", + "Outgoing", entire_orders_text, "Coordinator Tax A", "15.00"], + ["Admin", "Sample Coordinator", "Coordinator Fee B", "Sample Customer", + "Coordinator", "All", various_tax_categories_text, "20.00"], + ["Admin", "Sample Coordinator", "Coordinator Fee C", "Sample Customer", + "Coordinator", "All", nil, "25.00"], + ["Admin", "Sample Distributor", "Distributor Fee A", "Sample Customer", + "Incoming", entire_orders_text, various_tax_categories_text, "30.00"], + ["Admin", "Sample Distributor", "Distributor Fee A", "Sample Customer", + "Outgoing", entire_orders_text, various_tax_categories_text, "30.00"], + ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", + nil, nil, nil, "2.00"], + ["Sales", "Sample Producer", "Producer Fee A", "Sample Customer", + "Incoming", entire_orders_text, "Producer Tax A", "10.00"], + ["Sales", "Sample Producer", "Producer Fee A", "Sample Customer", + "Outgoing", entire_orders_text, "Producer Tax A", "10.00"], + ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", + nil, nil, "Platform Rate", "1.00"] + ] + + expected_result.each_with_index do |expected_attributes, row_index| + expect_total_attributes(totals[row_index], expected_attributes) + end + end + end + end + + describe "filtering results based on permissions" do + let!(:distributor_a) do + create(:distributor_enterprise, name: "Distributor A", payment_methods: [payment_method], + shipping_methods: [shipping_method]) + end + let!(:distributor_b) do + create(:distributor_enterprise, name: "Distributor B", payment_methods: [payment_method], + shipping_methods: [shipping_method]) + end + + let!(:order_cycle_a) { create(:simple_order_cycle, coordinator: coordinator) } + let!(:order_cycle_b) { create(:simple_order_cycle, coordinator: coordinator) } + + let!(:variant_a) { prepare_variant(distributor: distributor_a, order_cycle: order_cycle_a) } + let!(:variant_b) { prepare_variant(distributor: distributor_b, order_cycle: order_cycle_b) } + + let!(:order_a) { prepare_order(order_cycle: order_cycle_a, distributor: distributor_a) } + let!(:order_b) { prepare_order(order_cycle: order_cycle_b, distributor: distributor_b) } + + context "when admin" do + let!(:current_user) { create(:admin_user) } + + it "includes all order cycles" do + totals = subject.query_result + + expect_total_matches(totals, 2, fee_type: "Shipment") + expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") + expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor B") + end + end + + context "when enterprise owner for distributor" do + let!(:current_user) { distributor_a.owner } + + it "does not include unrelated order cycles" do + totals = subject.query_result + + expect_total_matches(totals, 1, fee_type: "Shipment") + expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") + end + end + end + + describe "filters entries correctly" do + let(:parameters) { report_module::Parameters.new(parameters_attributes) } + + context "filtering by completion date" do + let(:timestamp) { Time.zone.local(2018, 1, 5, 14, 30, 5) } + + let!(:customer_a) { create(:customer, first_name: "Customer", last_name: "A") } + let!(:customer_b) { create(:customer, first_name: "Customer", last_name: "B") } + let!(:customer_c) { create(:customer, first_name: "Customer", last_name: "C") } + + let!(:order_placed_before_timestamp) do + prepare_order(customer: customer_a).tap do |order| + order.update_column(:completed_at, timestamp - 1.second) + end + end + + let!(:order_placed_during_timestamp) do + prepare_order(customer: customer_b).tap do |order| + order.update_column(:completed_at, timestamp) + end + end + + let!(:order_placed_after_timestamp) do + prepare_order(customer: customer_c).tap do |order| + order.update_column(:completed_at, timestamp + 1.second) + end + end + + context "on or after start_at" do + let(:parameters_attributes) { { start_at: timestamp } } + + it "filters entries" do + totals = subject.query_result + + expect_total_matches(totals, 0, fee_type: "Shipment", customer_name: "Customer A") + expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer B") + expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer C") + end + end + + context "on or before end_at" do + let(:parameters_attributes) { { end_at: timestamp } } + + it "filters entries" do + totals = subject.query_result + + expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer A") + expect_total_matches(totals, 1, fee_type: "Shipment", customer_name: "Customer B") + expect_total_matches(totals, 0, fee_type: "Shipment", customer_name: "Customer C") + end + end + end + + describe "for specified shops" do + let!(:distributor_a) do + create(:distributor_enterprise, name: "Distributor A", payment_methods: [payment_method], + shipping_methods: [shipping_method]) + end + let!(:distributor_b) do + create(:distributor_enterprise, name: "Distributor B", payment_methods: [payment_method], + shipping_methods: [shipping_method]) + end + let!(:distributor_c) do + create(:distributor_enterprise, name: "Distributor C", payment_methods: [payment_method], + shipping_methods: [shipping_method]) + end + + let!(:order_a) { prepare_order(distributor: distributor_a) } + let!(:order_b) { prepare_order(distributor: distributor_b) } + let!(:order_c) { prepare_order(distributor: distributor_c) } + + let(:parameters_attributes) { { distributor_ids: [distributor_a.id, distributor_b.id] } } + + it "filters entries" do + totals = subject.query_result + + expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") + expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor B") + expect_total_matches(totals, 0, fee_type: "Shipment", enterprise_name: "Distributor C") + end + end + + describe "for specified suppliers" do + let!(:producer_a) { create(:supplier_enterprise, name: "Producer A") } + let!(:producer_b) { create(:supplier_enterprise, name: "Producer B") } + let!(:producer_c) { create(:supplier_enterprise, name: "Producer C") } + + let!(:fee_a) { create(:enterprise_fee, name: "Fee A", enterprise: producer_a, amount: 1) } + let!(:fee_b) { create(:enterprise_fee, name: "Fee B", enterprise: producer_b, amount: 1) } + let!(:fee_c) { create(:enterprise_fee, name: "Fee C", enterprise: producer_c, amount: 1) } + + let!(:product_a) { create(:product, supplier: producer_a) } + let!(:product_b) { create(:product, supplier: producer_b) } + let!(:product_c) { create(:product, supplier: producer_c) } + + let!(:variant_a) do + prepare_variant(product: product_a, producer: producer_a, incoming_exchange_fees: [fee_a]) + end + let!(:variant_b) do + prepare_variant(product: product_b, producer: producer_b, incoming_exchange_fees: [fee_b]) + end + let!(:variant_c) do + prepare_variant(product: product_c, producer: producer_c, incoming_exchange_fees: [fee_c]) + end + + let!(:order_a) { prepare_order(variant: variant_a) } + let!(:order_b) { prepare_order(variant: variant_b) } + let!(:order_c) { prepare_order(variant: variant_c) } + + let(:parameters_attributes) { { producer_ids: [producer_a.id, producer_b.id] } } + + it "filters entries" do + totals = subject.query_result + + expect_total_matches(totals, 1, fee_name: "Fee A", enterprise_name: "Producer A") + expect_total_matches(totals, 1, fee_name: "Fee B", enterprise_name: "Producer B") + expect_total_matches(totals, 0, fee_name: "Fee C", enterprise_name: "Producer C") + end + end + + describe "for specified order cycles" do + let!(:distributor_a) do + create(:distributor_enterprise, name: "Distributor A", payment_methods: [payment_method], + shipping_methods: [shipping_method]) + end + let!(:distributor_b) do + create(:distributor_enterprise, name: "Distributor B", payment_methods: [payment_method], + shipping_methods: [shipping_method]) + end + let!(:distributor_c) do + create(:distributor_enterprise, name: "Distributor C", payment_methods: [payment_method], + shipping_methods: [shipping_method]) + end + + let!(:order_cycle_a) { create(:simple_order_cycle, coordinator: coordinator) } + let!(:order_cycle_b) { create(:simple_order_cycle, coordinator: coordinator) } + let!(:order_cycle_c) { create(:simple_order_cycle, coordinator: coordinator) } + + let!(:variant_a) { prepare_variant(distributor: distributor_a, order_cycle: order_cycle_a) } + let!(:variant_b) { prepare_variant(distributor: distributor_b, order_cycle: order_cycle_b) } + let!(:variant_c) { prepare_variant(distributor: distributor_c, order_cycle: order_cycle_c) } + + let!(:order_a) { prepare_order(order_cycle: order_cycle_a, distributor: distributor_a) } + let!(:order_b) { prepare_order(order_cycle: order_cycle_b, distributor: distributor_b) } + let!(:order_c) { prepare_order(order_cycle: order_cycle_c, distributor: distributor_c) } + + let(:parameters_attributes) { { order_cycle_ids: [order_cycle_a.id, order_cycle_b.id] } } + + it "filters entries" do + totals = subject.query_result + + expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor A") + expect_total_matches(totals, 1, fee_type: "Shipment", enterprise_name: "Distributor B") + expect_total_matches(totals, 0, fee_type: "Shipment", enterprise_name: "Distributor C") + end + end + + describe "for specified enterprise fees" do + let!(:fee_a) { create(:enterprise_fee, name: "Fee A", enterprise: distributor, amount: 1) } + let!(:fee_b) { create(:enterprise_fee, name: "Fee B", enterprise: distributor, amount: 1) } + let!(:fee_c) { create(:enterprise_fee, name: "Fee C", enterprise: distributor, amount: 1) } + + let!(:variant) { prepare_variant(outgoing_exchange_fees: variant_outgoing_exchange_fees) } + let!(:variant_outgoing_exchange_fees) { [fee_a, fee_b, fee_c] } + + let!(:order) { prepare_order(variant: variant) } + + let(:parameters_attributes) { { enterprise_fee_ids: [fee_a.id, fee_b.id] } } + + it "filters entries" do + totals = subject.query_result + + expect_total_matches(totals, 1, fee_name: "Fee A") + expect_total_matches(totals, 1, fee_name: "Fee B") + expect_total_matches(totals, 0, fee_name: "Fee C") + end + end + + describe "for specified shipping methods" do + let!(:shipping_method_a) do + method = create(:shipping_method, name: "Shipping A", distributors: [distributor]) + method.calculator.update_attribute(:preferred_amount, 1) + method + end + let!(:shipping_method_b) do + method = create(:shipping_method, name: "Shipping B", distributors: [distributor]) + method.calculator.update_attribute(:preferred_amount, 1) + method + end + let!(:shipping_method_c) do + create(:shipping_method, name: "Shipping C", distributors: [distributor]) + end + + let!(:order_a) { prepare_order(shipping_method: shipping_method_a) } + let!(:order_b) { prepare_order(shipping_method: shipping_method_b) } + let!(:order_c) { prepare_order(shipping_method: shipping_method_c) } + + let(:parameters_attributes) do + { shipping_method_ids: [shipping_method_a.id, shipping_method_b.id] } + end + + it "filters entries" do + totals = subject.query_result + + expect_total_matches(totals, 1, fee_name: "Shipping A") + expect_total_matches(totals, 1, fee_name: "Shipping B") + expect_total_matches(totals, 0, fee_name: "Shipping C") + end + end + + describe "for specified payment methods" do + let!(:payment_method_a) do + method = create(:payment_method, name: "Payment A", distributors: [distributor]) + method.calculator.update_attribute(:preferred_amount, 1) + method + end + let!(:payment_method_b) do + method = create(:payment_method, name: "Payment B", distributors: [distributor]) + method.calculator.update_attribute(:preferred_amount, 1) + method + end + let!(:payment_method_c) do + create(:payment_method, name: "Payment C", distributors: [distributor]) + end + + let!(:order_a) { prepare_order(payment_method: payment_method_a) } + let!(:order_b) { prepare_order(payment_method: payment_method_b) } + let!(:order_c) { prepare_order(payment_method: payment_method_c) } + + let(:parameters_attributes) do + { payment_method_ids: [payment_method_a.id, payment_method_b.id] } + end + + it "filters entries" do + totals = subject.query_result + + expect_total_matches(totals, 1, fee_name: "Payment A") + expect_total_matches(totals, 1, fee_name: "Payment B") + expect_total_matches(totals, 0, fee_name: "Payment C") + end + end + end + + # Helper methods for example group + + def i18n_translate(translation_key, options = {}) + I18n.t("order_management.reports.enterprise_fee_summary.#{translation_key}", **options) + end + + def expect_total_attributes(total, expected_attribute_list) + actual_attribute_list = [total.fee_type, total.enterprise_name, total.fee_name, + total.customer_name, total.fee_placement, + total.fee_calculated_on_transfer_through_name, total.tax_category_name, + total.total_amount] + expect(actual_attribute_list).to eq(expected_attribute_list) + end + + def expect_total_matches(totals, count, attributes) + expect(count_totals(totals, attributes)).to eq(count) + end + + def default_order_options + { customer: customer, distributor: distributor, order_cycle: order_cycle, + shipping_method: shipping_method, variant: variant } + end + + def prepare_incomplete_order(options = {}) + target_options = default_order_options.merge(options) + create(:order, :with_line_item, target_options) + end + + def prepare_order(options = {}) + factory_trait_options = { payment_method: payment_method } + target_options = default_order_options.merge(factory_trait_options).merge(options) + create(:order, :with_line_item, :completed, target_options) + end + + def default_variant_options + { product: product, producer: producer, is_master: false, coordinator: coordinator, + distributor: distributor, order_cycle: order_cycle } + end + + def prepare_variant(options = {}) + target_options = default_variant_options.merge(options) + create(:variant, :with_order_cycle, target_options) + end + + def count_totals(totals, attributes) + totals.count do |data| + attributes.all? do |attribute_name, attribute_value| + data.public_send(attribute_name) == attribute_value + end + end + end +end diff --git a/spec/system/admin/reports/enterprise_fee_summaries_spec.rb b/spec/system/admin/reports/enterprise_fee_summaries_spec.rb new file mode 100644 index 0000000000..51bf9b41f2 --- /dev/null +++ b/spec/system/admin/reports/enterprise_fee_summaries_spec.rb @@ -0,0 +1,162 @@ +# frozen_string_literal: true + +require "spec_helper" + +feature "enterprise fee summaries", js: true do + include AuthenticationHelper + include WebHelper + + let!(:distributor) { create(:distributor_enterprise) } + let!(:other_distributor) { create(:distributor_enterprise) } + + let!(:order_cycle) { create(:simple_order_cycle, coordinator: distributor) } + let!(:other_order_cycle) { create(:simple_order_cycle, coordinator: other_distributor) } + + before do + login_as current_user + end + + describe "navigation" do + context "when accessing the report as an superadmin" do + let(:current_user) { create(:admin_user) } + + it "shows link and allows access to the report" do + visit spree.admin_reports_path + click_on I18n.t("admin.reports.enterprise_fee_summary.name") + expect(page).to have_button("Go") + end + end + + context "when accessing the report as an admin" do + let(:current_user) { distributor.owner } + + it "shows link and allows access to the report" do + visit spree.admin_reports_path + click_on I18n.t("admin.reports.enterprise_fee_summary.name") + expect(page).to have_button("Go") + end + end + + context "when accessing the report as an enterprise user without sufficient permissions" do + let(:current_user) { create(:user) } + + it "does not allow access to the report" do + visit spree.admin_reports_path + expect(page).to have_no_link(I18n.t("admin.reports.enterprise_fee_summary.name")) + visit main_app.admin_report_path(report_type: 'enterprise_fee_summary') + expect(page).to have_content(I18n.t("unauthorized")) + end + end + end + + describe "smoke test for filters" do + before do + visit main_app.admin_report_path(report_type: 'enterprise_fee_summary') + end + + context "when logged in as admin" do + let(:current_user) { create(:admin_user) } + + it "shows all available options" do + expect(page).to have_select "q_order_cycle_ids", with_options: [order_cycle.name] + end + end + + context "when logged in as enterprise user" do + let!(:order) do + create(:completed_order_with_fees, order_cycle: order_cycle, + distributor: distributor) + end + let(:current_user) { distributor.owner } + + it "shows available options for the enterprise" do + expect(page).to have_select "q_order_cycle_ids", options: [order_cycle.name] + end + end + end + + describe "csv downloads" do + around do |example| + with_empty_downloads_folder { example.run } + end + + describe "smoke test for generation of report based on permissions" do + before do + visit main_app.admin_report_path(report_type: 'enterprise_fee_summary') + end + + context "when logged in as admin" do + let!(:order) do + create(:completed_order_with_fees, order_cycle: order_cycle, + distributor: distributor) + end + let(:current_user) { create(:admin_user) } + + it "generates file with data for all enterprises" do + check I18n.t("filters.report_format_csv", scope: i18n_scope) + click_on "Go" + + expect(downloaded_filename).to include ".csv" + expect(downloaded_content).to have_content(distributor.name) + end + end + + context "when logged in as enterprise user" do + let!(:order) do + create(:completed_order_with_fees, order_cycle: order_cycle, + distributor: distributor) + end + let!(:other_order) do + create(:completed_order_with_fees, order_cycle: other_order_cycle, + distributor: other_distributor) + end + let(:current_user) { distributor.owner } + + it "generates file with data for the enterprise" do + check I18n.t("filters.report_format_csv", scope: i18n_scope) + click_on "Go" + + expect(downloaded_filename).to include ".csv" + csv_content = downloaded_content + expect(csv_content).to have_content(distributor.name) + expect(csv_content).not_to have_content(other_distributor.name) + end + end + end + + describe "smoke test for filtering report based on filters" do + let!(:second_distributor) { create(:distributor_enterprise) } + let!(:second_order_cycle) { create(:simple_order_cycle, coordinator: second_distributor) } + + let!(:order) do + create(:completed_order_with_fees, order_cycle: order_cycle, + distributor: distributor) + end + let!(:second_order) do + create(:completed_order_with_fees, order_cycle: second_order_cycle, + distributor: second_distributor) + end + + let(:current_user) { create(:admin_user) } + + before do + visit main_app.admin_report_path(report_type: 'enterprise_fee_summary') + end + + it "generates file with data for selected order cycle" do + select order_cycle.name, from: "report_order_cycle_ids" + check I18n.t("filters.report_format_csv", scope: i18n_scope) + click_on "Go" + + expect(downloaded_filename).to include ".csv" + csv_content = downloaded_content + expect(csv_content).to have_content(distributor.name) + expect(csv_content).not_to have_content(second_distributor.name) + end + end + end + + def i18n_scope + "order_management.reports.enterprise_fee_summaries" + end +end From cd30012334aa0708fd7f778d7a2db3c338752026 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 6 Apr 2022 20:29:02 +0200 Subject: [PATCH 35/54] Report Refactor 3: Xero Invoices This one as actually not really been refactored, too much work to rewrite it so I just encapsulated the result within the new methods definitions --- .rubocop_todo.yml | 10 +-- .../reports/filters/_xero_invoices.html.haml | 26 ++++---- config/locales/en.yml | 9 +-- .../{xero_invoices_report.rb => base.rb} | 62 ++++++++++++------- .../reports/xero_invoices/detailed.rb | 10 +++ .../reports/xero_invoices/summary.rb | 10 +++ spec/lib/reports/xero_invoices_report_spec.rb | 23 +++---- spec/system/admin/reports_spec.rb | 4 +- 8 files changed, 97 insertions(+), 57 deletions(-) rename lib/reporting/reports/xero_invoices/{xero_invoices_report.rb => base.rb} (79%) create mode 100644 lib/reporting/reports/xero_invoices/detailed.rb create mode 100644 lib/reporting/reports/xero_invoices/summary.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f8e25f898e..b5db2cea97 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -128,7 +128,7 @@ Layout/LineLength: - 'lib/reporting/reports/payments/payments_report.rb' - 'lib/reporting/reports/products_and_inventory/lettuce_share_report.rb' - 'lib/reporting/reports/sales_tax/sales_tax_report.rb' - - 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb' + - 'lib/reporting/reports/xero_invoices/base.rb' - 'lib/spree/localized_number.rb' - 'lib/tasks/data.rake' - 'lib/tasks/enterprises.rake' @@ -536,7 +536,7 @@ Metrics/ClassLength: - 'lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb' - 'lib/open_food_network/order_cycle_permissions.rb' - 'lib/reporting/reports/payments/payments_report.rb' - - 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb' + - 'lib/reporting/reports/xero_invoices/base.rb' - 'lib/reporting/report_grouper.rb' # Offense count: 39 @@ -571,7 +571,7 @@ Metrics/CyclomaticComplexity: - 'lib/reporting/reports/customers/customers_report.rb' - 'lib/reporting/reports/orders_and_fulfillment/customer_totals_report.rb' - 'lib/reporting/reports/payments/payments_report.rb' - - 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb' + - 'lib/reporting/reports/xero_invoices/base.rb' - 'lib/spree/core/controller_helpers/order.rb' - 'lib/spree/core/controller_helpers/respond_with.rb' - 'lib/spree/localized_number.rb' @@ -600,7 +600,7 @@ Metrics/MethodLength: - 'lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb' - 'lib/open_food_network/order_cycle_permissions.rb' - 'lib/reporting/reports/payments/payments_report.rb' - - 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb' + - 'lib/reporting/reports/xero_invoices/base.rb' - 'lib/tasks/sample_data/product_factory.rb' # Offense count: 54 @@ -668,7 +668,7 @@ Metrics/ParameterLists: Exclude: - 'app/helpers/angular_form_builder.rb' - 'app/models/product_import/entry_processor.rb' - - 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb' + - 'lib/reporting/reports/xero_invoices/base.rb' - 'spec/support/controller_requests_helper.rb' - 'spec/system/admin/reports_spec.rb' diff --git a/app/views/admin/reports/filters/_xero_invoices.html.haml b/app/views/admin/reports/filters/_xero_invoices.html.haml index c3c7f765cb..d7cc558445 100644 --- a/app/views/admin/reports/filters/_xero_invoices.html.haml +++ b/app/views/admin/reports/filters/_xero_invoices.html.haml @@ -9,15 +9,17 @@ options_for_select(report_order_cycle_options(@data.order_cycles), params.dig(:q, :order_cycle_id_eq)), {:include_blank => true}, {:class => "select2 fullwidth light"}) -.row - .two.columns.alpha= label_tag :initial_invoice_number, t(:initial_invoice_number) - .fourteen.columns.omega= text_field_tag :initial_invoice_number, params[:initial_invoice_number] -.row - .two.columns.alpha= label_tag :invoice_date, t(:invoice_date) - .fourteen.columns.omega= text_field_tag :invoice_date, params[:invoice_date], class: 'datetimepicker' -.row - .two.columns.alpha= label_tag :due_date, t(:due_date) - .fourteen.columns.omega= text_field_tag :due_date, params[:due_date], class: 'datetimepicker' -.row - .two.columns.alpha= label_tag :account_code, t(:account_code) - .fourteen.columns.omega= text_field_tag :account_code, params[:account_code] +%fieldset.no-border-bottom.print-hidden{ style: "padding-bottom: 0" } + %legend{ align: 'center'}= t(:report_xero_configuration) + .row + .two.columns.alpha= label_tag :initial_invoice_number, t(:initial_invoice_number) + .fourteen.columns.omega= number_field_tag :initial_invoice_number, params[:initial_invoice_number] + .row + .two.columns.alpha= label_tag :invoice_date, t(:invoice_date) + .fourteen.columns.omega= text_field_tag :invoice_date, params[:invoice_date], class: 'datetimepicker' + .row + .two.columns.alpha= label_tag :due_date, t(:due_date) + .fourteen.columns.omega= text_field_tag :due_date, params[:due_date], class: 'datetimepicker' + .row + .two.columns.alpha= label_tag :account_code, t(:account_code) + .fourteen.columns.omega= text_field_tag :account_code, params[:account_code] diff --git a/config/locales/en.yml b/config/locales/en.yml index 7209c01ac6..5c7d97d98e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2767,10 +2767,11 @@ See the %{link} to find out more about %{sitename}'s features and to start using report_header_transaction_fee: Transaction Fee (no tax) report_header_total_untaxable_admin: Total untaxable admin adjustments (no tax) report_header_total_taxable_admin: Total taxable admin adjustments (tax inclusive) - initial_invoice_number: "Initial invoice number:" - invoice_date: "Invoice date:" - due_date: "Due date:" - account_code: "Account code:" + report_xero_configuration: Xero Configuration + initial_invoice_number: "Initial invoice number" + invoice_date: "Invoice date" + due_date: "Due date" + account_code: "Account code" equals: "Equals" contains: "contains" discount: "Discount" diff --git a/lib/reporting/reports/xero_invoices/xero_invoices_report.rb b/lib/reporting/reports/xero_invoices/base.rb similarity index 79% rename from lib/reporting/reports/xero_invoices/xero_invoices_report.rb rename to lib/reporting/reports/xero_invoices/base.rb index 696857e918..3ca44c019c 100644 --- a/lib/reporting/reports/xero_invoices/xero_invoices_report.rb +++ b/lib/reporting/reports/xero_invoices/base.rb @@ -3,23 +3,36 @@ module Reporting module Reports module XeroInvoices - class XeroInvoicesReport < ReportObjectTemplate + class Base < ReportObjectTemplate def initialize(user, params = {}) + params.reverse_merge!(report_subtype: 'summary', + invoice_date: Time.zone.today, + due_date: Time.zone.today + 1.month, + account_code: 'food sales') super(user, params) - - @params = @params. - symbolize_keys. - reject { |_k, v| v.blank? }. - reverse_merge( report_subtype: 'summary', - invoice_date: Time.zone.today, - due_date: Time.zone.today + 1.month, - account_code: 'food sales' ) end - def table_headers - # NOTE: These are NOT to be translated, they need to be in this exact format to work with Xero + def xero_columns + # These are NOT to be translated, they need to be in this exact format to work with Xero %w(*ContactName EmailAddress POAddressLine1 POAddressLine2 POAddressLine3 POAddressLine4 - POCity PORegion POPostalCode POCountry *InvoiceNumber Reference *InvoiceDate *DueDate InventoryItemCode *Description *Quantity *UnitAmount Discount *AccountCode *TaxType TrackingName1 TrackingOption1 TrackingName2 TrackingOption2 Currency BrandingTheme Paid?) + POCity PORegion POPostalCode POCountry *InvoiceNumber Reference *InvoiceDate *DueDate + InventoryItemCode *Description *Quantity *UnitAmount Discount *AccountCode *TaxType + TrackingName1 TrackingOption1 TrackingName2 TrackingOption2 Currency + BrandingTheme Paid?) + end + + def custom_headers + xero_columns.index_by(&:to_sym) + end + + # This report calculate data in a specific way, so instead of refactoring it + # we just encapsulate the result in the columns method + def columns + result = {} + xero_columns.each_with_index do |header, id| + result[header.to_sym] = proc { |row| row[id] } + end + result end def search @@ -27,14 +40,15 @@ module Reporting permissions.editable_orders.complete.not_state(:canceled).ransack(params[:q]) end - def orders - search.result.reorder('id DESC') - end + # In the new way of managing reports, query_result should be an ActiveRecordRelation + # Here we directly transform the ActiveRecordRelation into table_rows without using the + # new ReportGrouper, so we can keep the old report without refactoring it + def query_result + search_result = search.result.reorder('id DESC') - def table_rows rows = [] - orders.each_with_index do |order, i| + search_result.each_with_index do |order, i| invoice_number = invoice_number_for(order, i) rows += detail_rows_for_order(order, invoice_number, params) if detail? rows += summary_rows_for_order(order, invoice_number, params) @@ -137,6 +151,7 @@ module Reporting row order, '', description, '1', amount, invoice_number, tax_type, opts end + # rubocop:disable Metrics/AbcSize def row(order, sku, description, quantity, amount, invoice_number, tax_type, opts = {}) return nil if amount == 0 @@ -147,13 +162,13 @@ module Reporting '', '', order.bill_address&.city, - order.bill_address&.state, + order.bill_address&.state.to_s, order.bill_address&.zipcode, order.bill_address&.country&.name, invoice_number, order.number, - opts[:invoice_date], - opts[:due_date], + opts[:invoice_date].to_date.to_s, + opts[:due_date].to_date.to_s, sku, description, quantity, @@ -169,6 +184,7 @@ module Reporting '', order.paid? ? I18n.t(:y) : I18n.t(:n)] end + # rubocop:enable Metrics/AbcSize def admin_adjustments(order) order.adjustments.admin @@ -179,7 +195,11 @@ module Reporting end def invoice_number_for(order, idx) - params[:initial_invoice_number] ? params[:initial_invoice_number].to_i + idx : order.number + if params[:initial_invoice_number].present? + params[:initial_invoice_number].to_i + idx + else + order.number + end end def total_untaxable_products(order) diff --git a/lib/reporting/reports/xero_invoices/detailed.rb b/lib/reporting/reports/xero_invoices/detailed.rb new file mode 100644 index 0000000000..239144d46b --- /dev/null +++ b/lib/reporting/reports/xero_invoices/detailed.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module XeroInvoices + class Detailed < Base + end + end + end +end diff --git a/lib/reporting/reports/xero_invoices/summary.rb b/lib/reporting/reports/xero_invoices/summary.rb new file mode 100644 index 0000000000..6a45aa2149 --- /dev/null +++ b/lib/reporting/reports/xero_invoices/summary.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module XeroInvoices + class Summary < Base + end + end + end +end diff --git a/spec/lib/reports/xero_invoices_report_spec.rb b/spec/lib/reports/xero_invoices_report_spec.rb index 3348abb510..7f4d4cb840 100644 --- a/spec/lib/reports/xero_invoices_report_spec.rb +++ b/spec/lib/reports/xero_invoices_report_spec.rb @@ -5,31 +5,28 @@ require 'spec_helper' module Reporting module Reports module XeroInvoices - describe XeroInvoicesReport do - subject { XeroInvoicesReport.new user, {} } + describe Base do + subject { Base.new user, {} } let(:user) { create(:user) } describe "option defaults" do - let(:report) { - XeroInvoicesReport.new user, initial_invoice_number: '', invoice_date: '', due_date: '', - account_code: '' - } + let(:report) { Base.new user } around { |example| Timecop.travel(Time.zone.local(2015, 5, 5, 14, 0, 0)) { example.run } } it "uses defaults when blank params are passed" do - expect(report.instance_variable_get(:@params)).to eq(invoice_date: Date.civil(2015, 5, 5), - due_date: Date.civil(2015, 6, 5), - account_code: 'food sales', - report_subtype: 'summary' ) + expect(report.params).to eq(invoice_date: Date.civil(2015, 5, 5), + due_date: Date.civil(2015, 6, 5), + account_code: 'food sales', + report_subtype: 'summary' ) end end describe "summary rows" do let(:report) { - XeroInvoicesReport.new user, initial_invoice_number: '', invoice_date: '', due_date: '', - account_code: '' + Base.new user, initial_invoice_number: '', invoice_date: '', due_date: '', + account_code: '' } let(:order) { double(:order) } let(:summary_rows) { report.__send__(:summary_rows_for_order, order, 1, {}) } @@ -86,7 +83,7 @@ module Reporting end describe "when an initial invoice number is given" do - subject { XeroInvoicesReport.new user, initial_invoice_number: '123' } + subject { Base.new user, initial_invoice_number: '123' } it "increments the number by the index" do expect(subject.send(:invoice_number_for, order, 456)).to eq(579) diff --git a/spec/system/admin/reports_spec.rb b/spec/system/admin/reports_spec.rb index 679eebc830..aa9400fa35 100644 --- a/spec/system/admin/reports_spec.rb +++ b/spec/system/admin/reports_spec.rb @@ -582,8 +582,8 @@ describe ' fill_in 'account_code', with: 'abc123' click_button 'Go' - opts = { invoice_number: '5', invoice_date: '2021-02-12 00:00', - due_date: '2021-03-12 00:00', account_code: 'abc123' } + opts = { invoice_number: '5', invoice_date: '2021-02-12', + due_date: '2021-03-12', account_code: 'abc123' } expect(xero_invoice_table).to match_table [ xero_invoice_header, From c7c5965eb5bec6174a38d0891513122fdb48641b Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Tue, 12 Apr 2022 21:07:57 +0200 Subject: [PATCH 36/54] Report Refactor 3 Clean No longer need to handle old report class Remove no longer used OrderGrouper service sq --- app/views/admin/reports/_table.html.haml | 11 +- lib/reporting/order_grouper.rb | 88 ----------- lib/reporting/report_loader.rb | 9 +- spec/lib/reports/bulk_coop_report_spec.rb | 4 +- spec/lib/reports/order_grouper_spec.rb | 175 ---------------------- spec/lib/reports/report_loader_spec.rb | 16 +- 6 files changed, 15 insertions(+), 288 deletions(-) delete mode 100644 lib/reporting/order_grouper.rb delete mode 100644 spec/lib/reports/order_grouper_spec.rb diff --git a/app/views/admin/reports/_table.html.haml b/app/views/admin/reports/_table.html.haml index 42f7ec67cf..091d70e632 100644 --- a/app/views/admin/reports/_table.html.haml +++ b/app/views/admin/reports/_table.html.haml @@ -11,12 +11,5 @@ - if report.grouped_data.present? = render partial: 'admin/reports/row_group', locals: { report: report, data: report.grouped_data } - else - - report.table_rows.each do |row| - - if row - %tr - - row.each do |cell| - %td - = cell - - if report.table_rows.empty? - %tr - %td{colspan: report.table_headers.count}= t(:none) + %tr + %td{colspan: report.table_headers.count}= t(:none) diff --git a/lib/reporting/order_grouper.rb b/lib/reporting/order_grouper.rb deleted file mode 100644 index dc78dfff8c..0000000000 --- a/lib/reporting/order_grouper.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true - -module Reporting - class OrderGrouper - def initialize(rules, column_constructors, report = nil) - @rules = rules - @column_constructors = column_constructors - @report = report - end - - def build_tree(items, remaining_rules) - rules = remaining_rules.clone - if rules.any? - rule = rules.delete_at(0) # Remove current rule for subsequent groupings - group_and_sort(rule, rules, items) - else - items - end - end - - def group_and_sort(rule, remaining_rules, items) - branch = {} - groups = items.group_by { |item| rule[:group_by].call(item) } - - sorted_groups = groups.sort_by { |key, _value| rule[:sort_by].call(key) } - - sorted_groups.each do |property, items_by_property| - branch[property] = build_tree(items_by_property, remaining_rules) - - next if rule[:summary_columns].nil? || is_leaf_node(branch[property]) - - branch[property][:summary_row] = { - items: items_by_property, - columns: rule[:summary_columns] - } - end - - branch - end - - def build_table(groups) - rows = [] - if is_leaf_node(groups) - rows << build_row(groups) - else - groups.each do |key, group| - if key == :summary_row - rows << build_summary_row(group[:columns], group[:items]) - else - build_table(group).each { |g| rows << g } - end - end - end - rows - end - - def table(items) - tree = build_tree(items, @rules) - build_table(tree) - end - - private - - def build_cell(column_constructor, items) - if column_constructor.is_a?(Symbol) - @report.__send__(column_constructor, items) - else - column_constructor.call(items) - end - end - - def build_row(groups) - @column_constructors.map do |column_constructor| - build_cell(column_constructor, groups) - end - end - - def build_summary_row(summary_row_column_constructors, items) - summary_row_column_constructors.map do |summary_row_column_constructor| - build_cell(summary_row_column_constructor, items) - end - end - - def is_leaf_node(node) - node.is_a? Array - end - end -end diff --git a/lib/reporting/report_loader.rb b/lib/reporting/report_loader.rb index 29dee2f794..f38245c209 100644 --- a/lib/reporting/report_loader.rb +++ b/lib/reporting/report_loader.rb @@ -4,17 +4,14 @@ module Reporting class ReportLoader def initialize(report_type, report_subtype = nil) @report_type = report_type - @report_subtype = report_subtype || "base" + @report_subtype = report_subtype end - # We currently use two types of report : old report inheriting from ReportObjectReport - # And new ones inheriting gtom ReportQueryReport - # They use different namespace and we try to load them both def report_class - "#{report_module}::#{report_type.camelize}Report".constantize + "#{report_module}::#{report_subtype.camelize}".constantize rescue NameError begin - "#{report_module}::#{report_subtype.camelize}".constantize + "#{report_module}::Base".constantize rescue NameError raise Reporting::Errors::ReportNotFound end diff --git a/spec/lib/reports/bulk_coop_report_spec.rb b/spec/lib/reports/bulk_coop_report_spec.rb index cca38b28d4..b648e6cbe6 100644 --- a/spec/lib/reports/bulk_coop_report_spec.rb +++ b/spec/lib/reports/bulk_coop_report_spec.rb @@ -177,8 +177,8 @@ module Reporting end # Yes, I know testing a private method is bad practice but report's design, tighly coupling - # Reporting::OrderGrouper and Base, makes it - # very hard to make things testeable without ending up in a wormwhole. This is a trade-off. + # makes it very hard to make things testeable without ending up in a wormwhole. + # This is a trade-off. describe '#customer_payments_amount_owed' do let(:params) { {} } let(:user) { build(:user) } diff --git a/spec/lib/reports/order_grouper_spec.rb b/spec/lib/reports/order_grouper_spec.rb deleted file mode 100644 index 630b5de196..0000000000 --- a/spec/lib/reports/order_grouper_spec.rb +++ /dev/null @@ -1,175 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Reporting - describe OrderGrouper do - before(:each) do - @items = [1, 2, 3, 4] - end - - context "constructing the table" do - it "should build a tree then build a table" do - rules = [{ group_by: proc { |sentence| - sentence.paragraph.chapter - }, sort_by: proc { |chapter| - chapter.name - }, summary_columns: [proc { |is| - is.first.paragraph.chapter.name - }, proc { |_is| - "TOTAL" - }, proc { |_is| - "" - }, proc { |is| - is.sum(&:property1) - }] }, - { group_by: proc { |sentence| sentence.paragraph }, sort_by: proc { |paragraph| - paragraph.name - } }] - columns = [proc { |is| is.first.paragraph.chapter.name }, proc { |is| - is.first.paragraph.name - }, proc { |is| - is.first.name - }, proc { |is| - is.sum(&:property1) - }] - - subject = OrderGrouper.new rules, columns - - tree = double(:tree) - expect(subject).to receive(:build_tree).with(@items, rules).and_return(tree) - expect(subject).to receive(:build_table).with(tree) - - subject.table(@items) - end - end - - context "grouping items without rules" do - it "returns the original array when no rules are provided" do - rules = [] - column1 = double(:col1) - column2 = double(:col2) - columns = [column1, column2] - subject = OrderGrouper.new rules, columns - - expect(rules).to receive(:clone).and_return(rules) - expect(subject.build_tree(@items, rules)).to eq(@items) - end - end - - context "grouping items with rules" do - before(:each) do - @rule1 = double(:rule1) - rule2 = double(:rule2) - @rules = [@rule1, rule2] - @remaining_rules = [rule2] - column1 = double(:col1) - column2 = double(:col2) - @columns = [column1, column2] - end - - it "builds branches by removing a rule from 'rules' and running group_and_sort" do - subject = OrderGrouper.new @rules, @columns - - expect(@rules).to receive(:clone).and_return(@rules) - expect(@rules).to receive(:delete_at).with(0) - grouped_tree = double(:grouped_tree) - expect(subject).to receive(:group_and_sort).and_return(grouped_tree) - - expect(subject.build_tree(@items, @rules)).to eq(grouped_tree) - end - - it "separates the first rule from rules before sending to group_and_sort" do - subject = OrderGrouper.new @rules, @columns - - grouped_tree = double(:grouped_tree) - expect(subject).to receive(:group_and_sort).with(@rule1, @rules[1..], - @items).and_return(grouped_tree) - - expect(subject.build_tree(@items, @rules)).to eq(grouped_tree) - end - - it "should group, then sort, send each group to build_tree, and return a branch" do - summary_columns_object = double(:summary_columns) - allow(@rule1).to receive(:[]).with(:summary_columns) { summary_columns_object } - - subject = OrderGrouper.new @rules, @columns - - number_of_categories = 3 - groups = double(:groups) - expect(@items).to receive(:group_by).and_return(groups) - sorted_groups = {} - 1.upto(number_of_categories) { |i| - sorted_groups[i] = double(:group, name: "Group #{i}" ) - } - expect(groups).to receive(:sort_by).and_return(sorted_groups) - group = { group1: 1, group2: 2, group3: 3 } - expect(subject).to receive(:build_tree).exactly(number_of_categories).times.and_return(group) - - group_tree = {} - 1.upto(number_of_categories) { |i| group_tree[i] = group } - 1.upto(number_of_categories) { |i| group_tree[i][:summary_row] = summary_columns_object } - expect(subject.group_and_sort(@rule1, @remaining_rules, @items)).to eq(group_tree) - end - end - - context "building the table Array" do - before(:each) do - rule1 = double(:rule1) - rule2 = double(:rule2) - @rules = [rule1, rule2] - @column1 = double(:col1, call: "Column1") - @column2 = double(:col2, call: "Column2") - @columns = [@column1, @column2] - - sumcol1 = double(:sumcol1, call: "SumColumn1") - sumcol2 = double(:sumcol2, call: "SumColumn2") - @sumcols = [sumcol1, sumcol2] - - item1 = double(:item1) - item2 = double(:item2) - item3 = double(:item3) - @items1 = [item1, item2] - @items2 = [item2, item3] - @items3 = [item3, item1] - end - it "should return columns when given an Array" do - subject = OrderGrouper.new @rules, @columns - - expect(@column1).to receive(:call) - expect(@column2).to receive(:call) - - expect(subject.build_table(@items1)).to eq([["Column1", "Column2"]]) - end - - it "should return a row for each key-value pair when given a Hash" do - groups = { items1: @items1, items2: @items2, items3: @items3 } - - subject = OrderGrouper.new @rules, @columns - - # subject.should_receive(:build_table).exactly(2).times - - expected_return = [] - groups.length.times { expected_return << ["Column1", "Column2"] } - expect(subject.build_table(groups)).to eq(expected_return) - end - - it "should return an extra row when a :summary_row key appears in a given Hash" do - groups = { items1: @items1, items2: @items2, items3: @items3, - summary_row: { items: { items2: @items2, items3: @items3 }, columns: @sumcols } } - - subject = OrderGrouper.new @rules, @columns - - expected_return = [] - groups.each do |key, _group| - expected_return << if key == :summary_row - ["SumColumn1", "SumColumn2"] - else - ["Column1", "Column2"] - end - end - expect(subject.build_table(groups)).to eq(expected_return) - end - end - end -end diff --git a/spec/lib/reports/report_loader_spec.rb b/spec/lib/reports/report_loader_spec.rb index 1bb8ff00ad..930c5af1ac 100644 --- a/spec/lib/reports/report_loader_spec.rb +++ b/spec/lib/reports/report_loader_spec.rb @@ -30,14 +30,6 @@ describe Reporting::ReportLoader do end end - describe "given report type and subtype for old reports" do - let(:arguments) { ["orange", "subtype"] } - - it "returns a report class when given type and subtype" do - expect(service.report_class).to eq Reporting::Reports::Orange::OrangeReport - end - end - describe "given report type only" do context "when the report has no subtypes" do let(:arguments) { ["bananas"] } @@ -47,6 +39,14 @@ describe Reporting::ReportLoader do end end + context "when the subtype is not implemented, fallback to base" do + let(:arguments) { ["bananas", "not_existing"] } + + it "returns base class" do + expect(service.report_class).to eq Reporting::Reports::Bananas::Base + end + end + context "given a report type that does not exist" do let(:arguments) { ["apples"] } From 9874c7996eae9ef0c942d1e04354b42268d5e48c Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Mon, 4 Apr 2022 19:16:15 +0200 Subject: [PATCH 37/54] Reports: Adds Print Button --- app/views/admin/reports/show.html.haml | 7 +++++-- app/webpacker/css/admin/reports.scss | 23 ++++++++++++++++------ app/webpacker/css/admin/shared/layout.scss | 2 +- config/locales/en.yml | 1 + 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index 716edfab41..3fc60d9d8b 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -10,8 +10,11 @@ .actions.filter-actions = button t(:go), "report__submit-btn" -- if @report.message.present? - %p.report__message.print-hidden= @report.message +.report__header.print-hidden + - if @report.message.present? + %p.report__message= @report.message + - if request.post? + %button.btn-print.icon-print{ onclick: "window.print()"}= t(:report_print) / We don't want to render data unless search params are supplied. / Compiling data can take a long time. diff --git a/app/webpacker/css/admin/reports.scss b/app/webpacker/css/admin/reports.scss index a8faa18ae0..fceab15629 100644 --- a/app/webpacker/css/admin/reports.scss +++ b/app/webpacker/css/admin/reports.scss @@ -1,5 +1,5 @@ table.report__table { - margin-top: 2em; + margin-top: 1em; @media print { margin: 0; } @@ -31,14 +31,25 @@ table.report__table { } } -.report__message { +.report__header { + display: flex; margin-top: 2em; - border: 1px solid $pale-blue; - border-radius: .5em; - padding: .5em; - text-align: center; + justify-content: flex-end; + + .btn-print { + margin-left: 1em; + } + .report__message { + border: 1px solid $pale-blue; + border-radius: .5em; + padding: .5em; + text-align: center; + flex-grow: 1; + } } + + .customer-names-tip { margin-top: 1em; } diff --git a/app/webpacker/css/admin/shared/layout.scss b/app/webpacker/css/admin/shared/layout.scss index 2e97bf7388..0df03e79c0 100644 --- a/app/webpacker/css/admin/shared/layout.scss +++ b/app/webpacker/css/admin/shared/layout.scss @@ -70,7 +70,7 @@ } @media print { .print-hidden { - display: none; + display: none !important; } } diff --git a/config/locales/en.yml b/config/locales/en.yml index 5c7d97d98e..65f2d94efe 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2631,6 +2631,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using report_tax_rates: Tax rates report_tax_types: Tax types report_filters: Report Filters + report_print: Print Report report_render_options: Rendering Options report_header_order_cycle: Order Cycle report_header_user: User From eeb525aedb79487a600cc32edaa5845cc9c669b5 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 6 Apr 2022 10:39:51 +0200 Subject: [PATCH 38/54] Reports: Ability to hide columns --- .../admin/reports/_rendering_options.html.haml | 7 +++++++ config/locales/en.yml | 1 + lib/reporting/report_grouper.rb | 4 +--- lib/reporting/report_template.rb | 14 ++++++++++++-- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/app/views/admin/reports/_rendering_options.html.haml b/app/views/admin/reports/_rendering_options.html.haml index dc8395f584..9da74a6a11 100644 --- a/app/views/admin/reports/_rendering_options.html.haml +++ b/app/views/admin/reports/_rendering_options.html.haml @@ -17,6 +17,13 @@ = check_box_tag :display_summary_row, true, params[:display_summary_row] = label_tag :display_summary_row, t(".summary_row") +- if @report.available_headers.present? + .row + .alpha.two.columns= label_tag nil, t(:report_hide_columns) + .omega.fourteen.columns + = select_tag(:fields_to_hide, options_for_select(@report.available_headers, params[:fields_to_hide]), + class: "select2 fullwidth", multiple: true) + .row.rendering-options .alpha.two.columns = label_tag :report_format, t(".generate_report") diff --git a/config/locales/en.yml b/config/locales/en.yml index 65f2d94efe..3b9088eb2b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2625,6 +2625,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using report_payment_totals: 'Payment Totals' report_all: 'all' report_order_cycle: "Order Cycle" + report_hide_columns: Columns to Hide report_enterprises: "Enterprises" report_enterprise_fee: "Fees Names" report_users: "Users" diff --git a/lib/reporting/report_grouper.rb b/lib/reporting/report_grouper.rb index 21cddf3108..4bdd8756c3 100644 --- a/lib/reporting/report_grouper.rb +++ b/lib/reporting/report_grouper.rb @@ -139,9 +139,7 @@ module Reporting end def slice_row_fields(row) - result = row.clone - report.fields_to_hide.each { |field| result.delete_field!(field) } - result + OpenStruct.new(row.to_h.reject { |k, _v| k.in?(report.fields_to_hide) }) end # Compute the query result item into a result row diff --git a/lib/reporting/report_template.rb b/lib/reporting/report_template.rb index 9ba56b9e5b..ff0f8d8b4f 100644 --- a/lib/reporting/report_template.rb +++ b/lib/reporting/report_template.rb @@ -28,11 +28,17 @@ module Reporting Ransack::Search.new(Spree::Order) end + def available_headers + columns.is_a?(Hash) ? columns.keys.map { |key| [translate_header(key), key] } : nil + rescue NotImplementedError + nil + end + # Can be re implemented in subclasses if they not use yet the new syntax # with columns method def table_headers columns.keys.filter{ |key| !key.in?(fields_to_hide) }.map do |key| - custom_headers[key] || I18n.t("report_header_#{key}") + translate_header(key) end end @@ -57,7 +63,11 @@ module Reporting formatted_rules.map { |rule| rule[:fields_used_in_header] }.flatten.reject(&:blank?) else [] - end + end.concat(params_fields_to_hide) + end + + def params_fields_to_hide + params[:fields_to_hide]&.map(&:to_sym) || [] end # Rules for grouping, ordering, and summary rows From b259f59d1ed27f58b7813f4b39c01cb8de7900a1 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 6 Apr 2022 11:06:55 +0200 Subject: [PATCH 39/54] Packing Report: Adds new subreport "Pack by product" --- config/locales/en.yml | 1 + lib/reporting/reports/list.rb | 3 +- lib/reporting/reports/packing/product.rb | 51 ++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 lib/reporting/reports/packing/product.rb diff --git a/config/locales/en.yml b/config/locales/en.yml index 3b9088eb2b..7224a5d9da 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1272,6 +1272,7 @@ en: tax_rates: Tax Rates pack_by_customer: Pack By Customer pack_by_supplier: Pack By Supplier + pack_by_product: Pack By Product orders_and_distributors: name: Orders And Distributors description: Orders with distributor details diff --git a/lib/reporting/reports/list.rb b/lib/reporting/reports/list.rb index 6a7400e5cd..f43e61debd 100644 --- a/lib/reporting/reports/list.rb +++ b/lib/reporting/reports/list.rb @@ -76,7 +76,8 @@ module Reporting def packing_report_types [ [i18n_translate("pack_by_customer"), :customer], - [i18n_translate("pack_by_supplier"), :supplier] + [i18n_translate("pack_by_supplier"), :supplier], + [i18n_translate("pack_by_product"), :product] ] end diff --git a/lib/reporting/reports/packing/product.rb b/lib/reporting/reports/packing/product.rb new file mode 100644 index 0000000000..639061f875 --- /dev/null +++ b/lib/reporting/reports/packing/product.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Reporting + module Reports + module Packing + class Product < Base + def columns + # Reorder default columns + super.slice(:hub, :supplier, :product, :variant, + :customer_code, :last_name, :first_name, :phone, + :quantity, :price, :temp_controlled) + end + + def rules + [ + { + group_by: :hub, + header: true, + header_class: "h1 with-background text-center", + }, + { + group_by: :supplier, + header: true, + header_class: "h1", + }, + { + group_by: proc { |_item, row| "#{row.product} - #{row.variant}" }, + header: true, + fields_used_in_header: [:product, :variant], + summary_row: summary_row, + header_class: "h3", + } + ] + end + + def ordering_fields + lambda do + [ + distributor_alias[:name], + Arel.sql("supplier"), + Arel.sql("product"), + Arel.sql("variant"), + bill_address_alias[:lastname], + order_table[:id], + ] + end + end + end + end + end +end From 5105ea345fcbd02b6956df2508bb32f5db53c381 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Thu, 7 Apr 2022 19:28:19 +0200 Subject: [PATCH 40/54] Reports Refactor 4: Final Touch Split report_template Clean code Adds spec --- .rubocop_todo.yml | 2 +- app/views/admin/reports/_row_group.haml | 4 +- lib/reporting/report_headers_builder.rb | 45 ++++ lib/reporting/report_object_template.rb | 30 --- lib/reporting/report_query_template.rb | 19 +- lib/reporting/report_renderer.rb | 61 +++-- ...port_grouper.rb => report_rows_builder.rb} | 12 +- lib/reporting/report_template.rb | 61 ++--- lib/reporting/reports/bulk_coop/base.rb | 2 +- lib/reporting/reports/customers/base.rb | 2 +- .../reports/enterprise_fee_summary/base.rb | 6 +- .../reports/order_cycle_management/base.rb | 6 +- .../reports/orders_and_distributors/base.rb | 6 +- .../reports/orders_and_fulfillment/base.rb | 4 +- lib/reporting/reports/packing/base.rb | 10 +- lib/reporting/reports/payments/base.rb | 4 +- .../reports/products_and_inventory/base.rb | 2 +- lib/reporting/reports/sales_tax/base.rb | 4 +- .../reports/users_and_enterprises/base.rb | 2 +- lib/reporting/reports/xero_invoices/base.rb | 4 +- spec/lib/reports/report_renderer_spec.rb | 64 +---- spec/lib/reports/report_spec.rb | 253 ++++++++++++++++++ 22 files changed, 420 insertions(+), 183 deletions(-) create mode 100644 lib/reporting/report_headers_builder.rb delete mode 100644 lib/reporting/report_object_template.rb rename lib/reporting/{report_grouper.rb => report_rows_builder.rb} (93%) create mode 100644 spec/lib/reports/report_spec.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b5db2cea97..f1ae0553da 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -537,7 +537,7 @@ Metrics/ClassLength: - 'lib/open_food_network/order_cycle_permissions.rb' - 'lib/reporting/reports/payments/payments_report.rb' - 'lib/reporting/reports/xero_invoices/base.rb' - - 'lib/reporting/report_grouper.rb' + - 'lib/reporting/report_rows_builder.rb' # Offense count: 39 # Configuration parameters: IgnoredMethods, Max. diff --git a/app/views/admin/reports/_row_group.haml b/app/views/admin/reports/_row_group.haml index fcf8e01052..b06583637b 100644 --- a/app/views/admin/reports/_row_group.haml +++ b/app/views/admin/reports/_row_group.haml @@ -5,14 +5,14 @@ - data.each do |group_or_row| - if group_or_row[:is_group].present? / Header Row - - if group_or_row[:header].present? && params[:display_header_row].present? + - if group_or_row[:header].present? && report.display_header_row? %tr %td.header-row{ colspan: report.table_headers.count, class: group_or_row[:header_class] } = group_or_row[:header].html_safe / Rows = render partial: 'admin/reports/row_group', locals: { report: report, data: group_or_row[:data] } / Summary Row - - if group_or_row[:summary_row].present? && params[:display_summary_row].present? + - if group_or_row[:summary_row].present? && report.display_summary_row? %tr.summary_row{ class: group_or_row[:summary_row_class] } - group_or_row[:summary_row].to_h.each do |key, value| %td= format_cell(value) diff --git a/lib/reporting/report_headers_builder.rb b/lib/reporting/report_headers_builder.rb new file mode 100644 index 0000000000..7d3a6b8e29 --- /dev/null +++ b/lib/reporting/report_headers_builder.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Reporting + class ReportHeadersBuilder + attr_reader :report + + def initialize(report) + @report = report + end + + def table_headers + report.columns.keys.filter{ |key| !key.in?(fields_to_hide) }.map do |key| + translate_header(key) + end + end + + def available_headers + report.columns.keys.map { |key| [translate_header(key), key] } + end + + def fields_to_hide + if report.display_header_row? + report.formatted_rules.map { |rule| rule[:fields_used_in_header] }.flatten.reject(&:blank?) + else + [] + end.concat(params_fields_to_hide) + end + + private + + def translate_header(key) + # Quite some headers use currency interpolation, so providing it by default + default_params = { currency: currency_symbol, currency_symbol: currency_symbol } + report.custom_headers[key] || I18n.t("report_header_#{key}", **default_params) + end + + def currency_symbol + Spree::Money.currency_symbol + end + + def params_fields_to_hide + report.params[:fields_to_hide]&.map(&:to_sym) || [] + end + end +end diff --git a/lib/reporting/report_object_template.rb b/lib/reporting/report_object_template.rb deleted file mode 100644 index e4e45899ed..0000000000 --- a/lib/reporting/report_object_template.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -# This is the old way of managing report, by loading Models from the DB and building -# The result from those models -module Reporting - class ReportObjectTemplate < ReportTemplate - # rubocop:disable Rails/Delegate - # Not delegating for now cause not all subclasses are ready to use reportGrouper - # so they can implement this method themseves - def table_rows - grouper.table_rows - end - # rubocop:enable Rails/Delegate - - # The search result, an ActiveRecord Array - def query_result - raise NotImplementedError - end - - # Convert the query_result into expected row result (which will be displayed) - # Example - # { - # name: proc { |model| model.display_name }, - # best_friend: proc { |model| model.friends.first.first_name } - # } - def columns - raise NotImplementedError - end - end -end diff --git a/lib/reporting/report_query_template.rb b/lib/reporting/report_query_template.rb index 8dfa488b12..4ab64b130f 100644 --- a/lib/reporting/report_query_template.rb +++ b/lib/reporting/report_query_template.rb @@ -3,30 +3,29 @@ # This is the new report template that use QueryBuilder to directly get the data from the DB module Reporting class ReportQueryTemplate < ReportTemplate - def report_data - @report_data ||= report_query.raw_result - end - alias_method :query_result, :report_data - def report_query raise NotImplementedError end - # ReportQueryTemplate work differently than ReportObjectTemplate + def report_data + @report_data ||= report_query.raw_result + end + + # ReportQueryTemplate work differently than standard reports # Here the query_result is already the expected result, so we just create # a fake columns method to copy the sql result into the row result def columns report_data.columns.map { |field| [field.to_sym, proc { |data| data[field] }] }.to_h end - def table_rows - report_data.rows - end - def search visible_line_items_relation.ransack(ransack_params) end + def query_result + report_data.to_a + end + private def ransacked_line_items_relation diff --git a/lib/reporting/report_renderer.rb b/lib/reporting/report_renderer.rb index d910f5f42e..61999c45c1 100644 --- a/lib/reporting/report_renderer.rb +++ b/lib/reporting/report_renderer.rb @@ -8,6 +8,18 @@ module Reporting @report = report end + def raw_render? + @report.params[:report_format].in?(['json', 'csv']) + end + + def display_header_row? + @report.params[:display_header_row].present? && !raw_render? + end + + def display_summary_row? + @report.params[:display_summary_row].present? && !raw_render? + end + def table_headers @report.table_headers || [] end @@ -16,19 +28,8 @@ module Reporting @report.table_rows || [] end - def as_json - # columns methods give the headers code, but as not reports are implementing it - # we fallback with the translated headers with table_headers - headers = begin - @report.columns.keys - rescue NotImplementedError, NoMethodError - table_headers - end - table_rows.map do |row| - result = {} - headers.zip(row) { |a, b| result[a.to_sym] = b } - result - end.as_json + def as_json(_context_controller = nil) + @report.rows.map(&:to_h).as_json end def as_arrays @@ -39,12 +40,8 @@ module Reporting SpreadsheetArchitect.to_csv(headers: table_headers, data: table_rows) end - def to_ods(_context_controller = nil) - SpreadsheetArchitect.to_ods(headers: table_headers, data: table_rows) - end - def to_xlsx(_context_controller = nil) - SpreadsheetArchitect.to_xlsx(headers: table_headers, data: table_rows) + SpreadsheetArchitect.to_xlsx(spreadsheets_options) end def to_pdf(context_controller) @@ -56,5 +53,33 @@ module Reporting ) ) end + + private + + def spreadsheets_options + { + headers: table_headers, + data: table_rows, + freeze_headers: true, + row_style: spreadsheets_style, + header_style: spreadsheets_style.merge({ bg_color: "f7f6f6", bold: true }), + conditional_row_styles: [ + { + # Detect header_row: the row is nil except for first cell + if: proc { |row_data, _row_index| + row_data.compact.count == 1 && row_data[0].present? + }, + styles: { font_size: 12, bold: true } + }, + ], + } + end + + def spreadsheets_style + { + font_name: 'system-ui', + alignment: { horizontal: :left, vertical: :bottom } + } + end end end diff --git a/lib/reporting/report_grouper.rb b/lib/reporting/report_rows_builder.rb similarity index 93% rename from lib/reporting/report_grouper.rb rename to lib/reporting/report_rows_builder.rb index 4bdd8756c3..28e0c8147c 100644 --- a/lib/reporting/report_grouper.rb +++ b/lib/reporting/report_rows_builder.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Reporting - class ReportGrouper + class ReportRowsBuilder attr_reader :report def initialize(report) @@ -46,8 +46,14 @@ module Reporting 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) - if group_or_row[:summary_row].present? && report.params[:display_summary_row].present? + # Summary Row + if group_or_row[:summary_row].present? && report.display_summary_row? result << group_or_row[:summary_row] end else @@ -99,7 +105,7 @@ module Reporting rule[:sort_by].call(group_key) else # downcase for better comparaison - group_key.is_a?(String) ? group_key.downcase : group_key + group_key.is_a?(String) ? group_key.downcase : group_key.to_s end end.to_h end diff --git a/lib/reporting/report_template.rb b/lib/reporting/report_template.rb index ff0f8d8b4f..899e77db90 100644 --- a/lib/reporting/report_template.rb +++ b/lib/reporting/report_template.rb @@ -6,8 +6,12 @@ module Reporting attr_accessor :user, :params, :ransack_params delegate :as_json, :as_arrays, :to_csv, :to_xlsx, :to_ods, :to_pdf, :to_json, to: :renderer + delegate :raw_render?, :display_header_row?, :display_summary_row?, to: :renderer + + delegate :rows, :table_rows, :grouped_data, to: :rows_builder + delegate :available_headers, :table_headers, :fields_to_hide, to: :headers_builder + delegate :formatted_rules, :header_option?, :summary_row_option?, to: :ruler - delegate :grouped_data, :rows, to: :grouper def initialize(user, params = {}) @user = user @@ -28,24 +32,19 @@ module Reporting Ransack::Search.new(Spree::Order) end - def available_headers - columns.is_a?(Hash) ? columns.keys.map { |key| [translate_header(key), key] } : nil - rescue NotImplementedError - nil + # The search result, usually an ActiveRecord Array + def query_result + raise NotImplementedError end - # Can be re implemented in subclasses if they not use yet the new syntax - # with columns method - def table_headers - columns.keys.filter{ |key| !key.in?(fields_to_hide) }.map do |key| - translate_header(key) - end - end - - def translate_header(key) - # Quite some headers use currency interpolation, so providing it by default - default_params = { currency: currency_symbol, currency_symbol: currency_symbol } - custom_headers[key] || I18n.t("report_header_#{key}", **default_params) + # Convert the query_result into expected row result (which will be displayed) + # Example + # { + # name: proc { |model| model.display_name }, + # best_friend: proc { |model| model.friends.first.first_name } + # } + def columns + raise NotImplementedError end # Headers are automatically translated with table_headers method @@ -54,22 +53,6 @@ module Reporting {} end - def table_rows - raise NotImplementedError - end - - def fields_to_hide - if params[:display_header_row] - formatted_rules.map { |rule| rule[:fields_used_in_header] }.flatten.reject(&:blank?) - else - [] - end.concat(params_fields_to_hide) - end - - def params_fields_to_hide - params[:fields_to_hide]&.map(&:to_sym) || [] - end - # Rules for grouping, ordering, and summary rows # Rule Full Example. In the following item reference the query_result item and # row the transformation of this item into the expected result @@ -101,16 +84,16 @@ module Reporting private - def raw_render? - params[:report_format].in?(['json', 'csv']) - end - def renderer @renderer ||= ReportRenderer.new(self) end - def grouper - @grouper ||= ReportGrouper.new(self) + def rows_builder + @rows_builder ||= ReportRowsBuilder.new(self) + end + + def headers_builder + @headers_builder ||= ReportHeadersBuilder.new(self) end def ruler diff --git a/lib/reporting/reports/bulk_coop/base.rb b/lib/reporting/reports/bulk_coop/base.rb index c09bc656d6..8c8a6501bd 100644 --- a/lib/reporting/reports/bulk_coop/base.rb +++ b/lib/reporting/reports/bulk_coop/base.rb @@ -3,7 +3,7 @@ module Reporting module Reports module BulkCoop - class Base < ReportObjectTemplate + class Base < ReportTemplate def message I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") end diff --git a/lib/reporting/reports/customers/base.rb b/lib/reporting/reports/customers/base.rb index f483d44de8..f926e54b8a 100644 --- a/lib/reporting/reports/customers/base.rb +++ b/lib/reporting/reports/customers/base.rb @@ -3,7 +3,7 @@ module Reporting module Reports module Customers - class Base < ReportObjectTemplate + class Base < ReportTemplate def query_result filter Spree::Order.managed_by(@user) .distributed_by_user(@user) diff --git a/lib/reporting/reports/enterprise_fee_summary/base.rb b/lib/reporting/reports/enterprise_fee_summary/base.rb index 6ffa34a67a..8e144f511b 100644 --- a/lib/reporting/reports/enterprise_fee_summary/base.rb +++ b/lib/reporting/reports/enterprise_fee_summary/base.rb @@ -3,7 +3,7 @@ module Reporting module Reports module EnterpriseFeeSummary - class Base < ReportObjectTemplate + class Base < ReportTemplate attr_accessor :permissions, :parameters def initialize(user, params = {}) @@ -19,8 +19,8 @@ module Reporting @parameters.authorize!(@permissions) end - def translate_header(key) - I18n.t("header.#{key}", scope: i18n_scope) + def custom_headers + data_attributes.map { |attr| [attr, I18n.t("header.#{attr}", scope: i18n_scope)] }.to_h end def i18n_scope diff --git a/lib/reporting/reports/order_cycle_management/base.rb b/lib/reporting/reports/order_cycle_management/base.rb index 5101285beb..095177402d 100644 --- a/lib/reporting/reports/order_cycle_management/base.rb +++ b/lib/reporting/reports/order_cycle_management/base.rb @@ -3,14 +3,14 @@ module Reporting module Reports module OrderCycleManagement - class Base < ReportObjectTemplate + class Base < ReportTemplate DEFAULT_DATE_INTERVAL = { from: -1.month, to: 1.day }.freeze def initialize(user, params = {}) - super(user, params) params[:q] ||= {} params[:q][:completed_at_gt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:from] params[:q][:completed_at_lt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:to] + super(user, params) end def search @@ -19,7 +19,7 @@ module Reporting not_state(:canceled). distributed_by_user(@user). managed_by(@user). - ransack(params[:q]) + ransack(ransack_params) end # This result is used in _order_cucle_management.html so caching it diff --git a/lib/reporting/reports/orders_and_distributors/base.rb b/lib/reporting/reports/orders_and_distributors/base.rb index 92e2b3b06e..d2d6552e1e 100644 --- a/lib/reporting/reports/orders_and_distributors/base.rb +++ b/lib/reporting/reports/orders_and_distributors/base.rb @@ -3,7 +3,7 @@ module Reporting module Reports module OrdersAndDistributors - class Base < ReportObjectTemplate + class Base < ReportTemplate def initialize(user, params = {}) super(user, params) end @@ -38,7 +38,7 @@ module Reporting def search permissions.visible_orders.select("DISTINCT spree_orders.*"). complete.not_state(:canceled). - ransack(@params[:q]) + ransack(ransack_params) end def query_result @@ -55,7 +55,7 @@ module Reporting private def permissions - @permissions ||= ::Permissions::Order.new(user, params[:q]) + @permissions ||= ::Permissions::Order.new(user, ransack_params) end end end diff --git a/lib/reporting/reports/orders_and_fulfillment/base.rb b/lib/reporting/reports/orders_and_fulfillment/base.rb index fbd9725f79..6ea3651e6b 100644 --- a/lib/reporting/reports/orders_and_fulfillment/base.rb +++ b/lib/reporting/reports/orders_and_fulfillment/base.rb @@ -3,7 +3,7 @@ module Reporting module Reports module OrdersAndFulfillment - class Base < ReportObjectTemplate + class Base < ReportTemplate def initialize(user, params = {}) super(user, params) @@ -31,7 +31,7 @@ module Reporting def order_permissions return @order_permissions unless @order_permissions.nil? - @order_permissions = ::Permissions::Order.new(@user, params[:q]) + @order_permissions = ::Permissions::Order.new(@user, ransack_params) end def report_line_items diff --git a/lib/reporting/reports/packing/base.rb b/lib/reporting/reports/packing/base.rb index 132b0d1f18..a27f50784c 100644 --- a/lib/reporting/reports/packing/base.rb +++ b/lib/reporting/reports/packing/base.rb @@ -8,12 +8,8 @@ module Reporting I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") end - def primary_model - Spree::LineItem - end - def report_query - Queries::QueryBuilder.new(primary_model). + Queries::QueryBuilder.new(Spree::LineItem). scoped_to_orders(visible_orders_relation). scoped_to_line_items(ransacked_line_items_relation). with_managed_orders(managed_orders_relation). @@ -29,6 +25,8 @@ module Reporting ordered_by(ordering_fields) end + private + def select_fields lambda do { @@ -47,8 +45,6 @@ module Reporting end end - private - def row_header(row) result = "#{row.last_name} #{row.first_name}" result += " (#{row.customer_code})" if row.customer_code diff --git a/lib/reporting/reports/payments/base.rb b/lib/reporting/reports/payments/base.rb index aa3b74701c..d2024ada6d 100644 --- a/lib/reporting/reports/payments/base.rb +++ b/lib/reporting/reports/payments/base.rb @@ -3,9 +3,9 @@ module Reporting module Reports module Payments - class Base < ReportObjectTemplate + class Base < ReportTemplate def search - Spree::Order.complete.not_state(:canceled).managed_by(@user).ransack(params[:q]) + Spree::Order.complete.not_state(:canceled).managed_by(@user).ransack(ransack_params) end def query_result diff --git a/lib/reporting/reports/products_and_inventory/base.rb b/lib/reporting/reports/products_and_inventory/base.rb index b0928cf0fb..907c7f13f8 100644 --- a/lib/reporting/reports/products_and_inventory/base.rb +++ b/lib/reporting/reports/products_and_inventory/base.rb @@ -5,7 +5,7 @@ require 'open_food_network/scope_variant_to_hub' module Reporting module Reports module ProductsAndInventory - class Base < ReportObjectTemplate + class Base < ReportTemplate def query_result filter(child_variants) end diff --git a/lib/reporting/reports/sales_tax/base.rb b/lib/reporting/reports/sales_tax/base.rb index 3ac4f0e2da..c3b5466870 100644 --- a/lib/reporting/reports/sales_tax/base.rb +++ b/lib/reporting/reports/sales_tax/base.rb @@ -3,10 +3,10 @@ module Reporting module Reports module SalesTax - class Base < ReportObjectTemplate + class Base < ReportTemplate def search permissions = ::Permissions::Order.new(user) - permissions.editable_orders.complete.not_state(:canceled).ransack(params[:q]) + permissions.editable_orders.complete.not_state(:canceled).ransack(ransack_params) end def query_result diff --git a/lib/reporting/reports/users_and_enterprises/base.rb b/lib/reporting/reports/users_and_enterprises/base.rb index f7d7c52610..33dc30f402 100644 --- a/lib/reporting/reports/users_and_enterprises/base.rb +++ b/lib/reporting/reports/users_and_enterprises/base.rb @@ -3,7 +3,7 @@ module Reporting module Reports module UsersAndEnterprises - class Base < ReportObjectTemplate + class Base < ReportTemplate def initialize(user, params = {}) super(user, params) end diff --git a/lib/reporting/reports/xero_invoices/base.rb b/lib/reporting/reports/xero_invoices/base.rb index 3ca44c019c..6b2425c5f9 100644 --- a/lib/reporting/reports/xero_invoices/base.rb +++ b/lib/reporting/reports/xero_invoices/base.rb @@ -3,7 +3,7 @@ module Reporting module Reports module XeroInvoices - class Base < ReportObjectTemplate + class Base < ReportTemplate def initialize(user, params = {}) params.reverse_merge!(report_subtype: 'summary', invoice_date: Time.zone.today, @@ -37,7 +37,7 @@ module Reporting def search permissions = ::Permissions::Order.new(@user) - permissions.editable_orders.complete.not_state(:canceled).ransack(params[:q]) + permissions.editable_orders.complete.not_state(:canceled).ransack(ransack_params) end # In the new way of managing reports, query_result should be an ActiveRecordRelation diff --git a/spec/lib/reports/report_renderer_spec.rb b/spec/lib/reports/report_renderer_spec.rb index b7ad4c0565..269130d3b5 100644 --- a/spec/lib/reports/report_renderer_spec.rb +++ b/spec/lib/reports/report_renderer_spec.rb @@ -9,24 +9,20 @@ describe Reporting::ReportRenderer do { "id" => 2, "name" => "onions", "quantity" => 6 } ] } - let(:report) { OpenStruct.new(table_headers: data.first.keys, table_rows: data.map(&:values)) } + let(:report) { + OpenStruct.new( + columns: { + id: proc { |row| row["id"] }, + name: proc { |row| row["name"] }, + quantity: proc { |row| row["quantity"] }, + }, + rows: data, + table_headers: data.first.keys, + table_rows: data.map(&:values) + ) + } let(:service) { described_class.new(report) } - describe "#table_headers" do - it "returns the report's table headers" do - expect(service.table_headers).to eq ["id", "name", "quantity"] - end - end - - describe "#table_rows" do - it "returns the report's table rows" do - expect(service.table_rows).to eq [ - [1, "carrots", 3], - [2, "onions", 6] - ] - end - end - describe "#as_json" do it "returns the report's data as hashes" do expect(service.as_json).to eq data.as_json @@ -42,40 +38,4 @@ describe Reporting::ReportRenderer do ] end end - - describe "exporting to different formats" do - let(:spreadsheet_architect) { SpreadsheetArchitect } - before do - allow(spreadsheet_architect).to receive(:to_csv) {} - allow(spreadsheet_architect).to receive(:to_ods) {} - allow(spreadsheet_architect).to receive(:to_xlsx) {} - end - - describe "#to_csv" do - it "exports as csv" do - service.to_csv - - expect(spreadsheet_architect).to have_received(:to_csv). - with(headers: service.table_headers, data: service.table_rows) - end - end - - describe "#to_ods" do - it "exports as ods" do - service.to_ods - - expect(spreadsheet_architect).to have_received(:to_ods). - with(headers: service.table_headers, data: service.table_rows) - end - end - - describe "#to_xslx" do - it "exports as xlsx" do - service.to_xlsx - - expect(spreadsheet_architect).to have_received(:to_xlsx). - with(headers: service.table_headers, data: service.table_rows) - end - end - end end diff --git a/spec/lib/reports/report_spec.rb b/spec/lib/reports/report_spec.rb new file mode 100644 index 0000000000..a5258dfb7b --- /dev/null +++ b/spec/lib/reports/report_spec.rb @@ -0,0 +1,253 @@ +# frozen_string_literal: true + +require 'spec_helper' + +# rubocop:disable Metrics/ModuleLength +module Reporting + describe ReportTemplate do + let(:user) { create(:user) } + let(:params) { {} } + subject { described_class.new(user, params) } + + # rubocop:disable Metrics/AbcSize + def check_report + # Mock using instance variables + allow(subject).to receive(:columns).and_return(@columns) + allow(subject).to receive(:query_result).and_return(@query_result) + allow(subject).to receive(:rules).and_return(@rules) if @rules.present? + if @custom_headers.present? + allow(subject).to receive(:custom_headers).and_return(@custom_headers) + end + + # Check result depending on existing instance variables + expect(subject.rows.map(&:to_h)).to eq(@expected_rows) if @expected_rows.present? + expect(subject.table_rows).to eq(@expected_table_rows) if @expected_table_rows.present? + expect(subject.table_headers).to eq(@expected_headers) if @expected_headers.present? + end + # rubocop:enable Metrics/AbcSize + + describe ".columns" do + before do + @query_result = [ + OpenStruct.new(hub: { name: "My Hub" }, product: { name: "Apple", price: 5 }) + ] + end + + it "handle procs" do + @columns = { + hub: proc { |item| item.hub[:name] } + } + @expected_rows = [ + { hub: "My Hub" } + ] + check_report + end + + it "handles symbols" do + @columns = { + hub: :hub_name + } + allow(subject).to receive(:hub_name).and_return("Transformed Hub Name") + @expected_rows = [ + { hub: "Transformed Hub Name" } + ] + check_report + end + end + + describe ".table_headers" do + before do + @columns = { + hub: proc { |item| item.hub[:name] }, + product: proc { |item| item.product[:name] }, + price: proc { |item| item.product[:price] }, + } + end + + it "uses the columns keys" do + @expected_headers = ['Hub', 'Product', 'Price'] + check_report + end + + it "handles custom_headers" do + @custom_headers = { + product: 'Custom Product', + not_existing_key: "My Key" + } + @expected_headers = ['Hub', 'Custom Product', 'Price'] + check_report + end + + describe "fields_to_hide" do + let(:params) { { fields_to_hide: [:product], report_format: 'json' } } + + it "works" do + @expected_headers = ['Hub', 'Price'] + check_report + end + end + end + + describe ".table_rows" do + before do + @columns = { + price: proc { |item| item.product[:price] }, + hub: proc { |item| item.hub[:name] } + } + @query_result = [ + OpenStruct.new(hub: { name: "My Hub" }, product: { name: "Apple", price: 5 }), + OpenStruct.new(hub: { name: "My Other Hub" }, product: { name: "Apple", price: 12 }) + ] + end + + it "get correct data" do + @expected_table_rows = [ + [5, "My Hub"], + [12, "My Other Hub"], + ] + check_report + end + end + + describe ".rules" do + describe "#group_by" do + before do + @columns = { + hub: proc { |item| item.hub }, + customer: proc { |item| item.customer }, + product: proc { |item| item.product }, + quantity: proc { |item| item.quantity }, + } + @query_result = [ + OpenStruct.new(hub: "Hub 1", customer: "John", product: "Apple", quantity: 4), + OpenStruct.new(hub: "Hub 2", customer: "John", product: "Pear", quantity: 3), + OpenStruct.new(hub: "Hub 2", customer: "John", product: "Apple", quantity: 5), + OpenStruct.new(hub: "Hub 1", customer: "Abby", product: "Orange", quantity: 6), + ] + end + + it "works with symbol or proc" do + @rules = [ + { group_by: proc { |_i, row| row.hub }, fields_used_in_header: [:hub], header: true }, + { group_by: :customer, header: true } + ] + allow(subject).to receive(:display_header_row?).and_return(true) + @expected_rows = [ + { header: "Hub 1" }, + { header: "Abby" }, + { product: "Orange", quantity: 6 }, + { header: "John" }, + { product: "Apple", quantity: 4 }, + { header: "Hub 2" }, + { header: "John" }, + { product: "Pear", quantity: 3 }, + { product: "Apple", quantity: 5 }, + ] + check_report + end + end + + describe "#sort_by" do + before do + @columns = { + hub_name: proc { |item| item.hub[:name] } + } + hub1 = { name: "Hub 1", popularity: 5 } + hub2 = { name: "Hub 2", popularity: 2 } + @query_result = [ + OpenStruct.new(hub: hub2), + OpenStruct.new(hub: hub1) + ] + end + + it "use default sort" do + @rules = [{ + group_by: proc { |item, _row| item.hub } + }] + @expected_rows = [ + { hub_name: "Hub 1" }, + { hub_name: "Hub 2" }, + ] + check_report + end + + it "use sort_by proc" do + @rules = [{ + group_by: proc { |item, _row| item.hub }, + sort_by: proc { |hub| hub[:popularity] } + }] + @expected_rows = [ + { hub_name: "Hub 2" }, + { hub_name: "Hub 1" } + ] + check_report + end + end + + describe "#summary_row" do + before do + @query_result = [ + OpenStruct.new(hub: "Hub 1", customer: "John", product: "Apple", quantity: 4), + OpenStruct.new(hub: "Hub 2", customer: "John", product: "Pear", quantity: 3), + OpenStruct.new(hub: "Hub 2", customer: "John", product: "Apple", quantity: 5), + OpenStruct.new(hub: "Hub 1", customer: "Abby", product: "Orange", quantity: 6), + ] + end + + it "groups and sum" do + @columns = { + hub: proc { |item| item.hub }, + quantity: proc { |item| item.quantity }, + count: proc { |_item| "" }, + } + @rules = [{ + group_by: :hub, + summary_row: proc do |group_key, items, rows| + { count: "#{group_key} count=#{items.count}", quantity: rows.sum(&:quantity) } + end, + summary_row_label: "TOTAL" + }] + @expetec_rows = [ + { hub: "Hub 1", quantity: 4, count: "" }, + { hub: "Hub 1", quantity: 6, count: "" }, + { hub: "TOTAL", quantity: 10, count: "Hub 1 count=2" }, + { hub: "Hub 2", quantity: 3, count: "" }, + { hub: "Hub 2", quantity: 5, count: "" }, + { hub: "TOTAL", quantity: 8, count: "Hub 2 count=2" } + ] + check_report + end + end + + describe "should not group when for JSON" do + before do + @query_result = [ + OpenStruct.new(hub: "Hub 1", customer: "John", quantity: 4) + ] + @columns = { + hub: proc { |item| item.hub }, + customer: proc { |item| item.customer }, + quantity: proc { |item| item.quantity }, + } + @rules = [{ + group_by: :hub, + header: true, + summary_row: proc do |_group_key, _items, rows| + { quantity: rows.sum(&:quantity) } + end + }] + end + + let(:params) { { fields_to_hide: [:customer], report_format: 'json' } } + + it "works" do + @expetec_rows = [ + { hub: "Hub 1", quantity: 4 } + ] + check_report + end + end + end + end +end +# rubocop:enable Metrics/ModuleLength From 3b01c44eae250a917dce79443da17ce1e5d34eb0 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Thu, 7 Apr 2022 19:28:37 +0200 Subject: [PATCH 41/54] Reports: Improve Exports Improve PDF style and encoding Include header_row and summary_row, except for CSV and JSON Style spreadsheets Use only Xlsx no more Ods format --- .../reports/_rendering_options.html.haml | 10 +++---- app/views/layouts/pdf.html.haml | 30 +++++++++++++++++-- app/webpacker/css/admin/reports.scss | 6 ++-- config/locales/en.yml | 7 ++--- 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/app/views/admin/reports/_rendering_options.html.haml b/app/views/admin/reports/_rendering_options.html.haml index 9da74a6a11..e51b1d5862 100644 --- a/app/views/admin/reports/_rendering_options.html.haml +++ b/app/views/admin/reports/_rendering_options.html.haml @@ -28,11 +28,9 @@ .alpha.two.columns = label_tag :report_format, t(".generate_report") .omega.fourteen.columns - = select_tag :report_format, options_for_select({ | - t('.on_screen') => '', | - t('.pdf') => 'pdf', | - t('.csv_spreadsheet') => 'csv', | - t('.excel_spreadsheet') => 'xlsx', | - t('.openoffice_spreadsheet') => 'ods'}) + = select_tag :report_format, grouped_options_for_select({ | + t('.formatted_data') => { t('.on_screen') => '', "PDF" => 'pdf', t('.spreadsheet') => 'xlsx' }, | + t('.raw_data') => { "CSV" => 'csv', "JSON" => 'json'}, | + }) diff --git a/app/views/layouts/pdf.html.haml b/app/views/layouts/pdf.html.haml index f1cf20a008..81b43b6588 100644 --- a/app/views/layouts/pdf.html.haml +++ b/app/views/layouts/pdf.html.haml @@ -1,7 +1,7 @@ !!! %html %head - %meta{:content => "charset=UTF-8"} + %meta{charset: 'utf-8'} -# Using wicked_pdf_stylesheet_pack_tag with a new pdf pack was not working when using -# WickedPdf.new.pdf_from_string cause the css file reference was not absolute -# So I ended up putting inline css here, so it's included for sure in the PDF @@ -11,7 +11,8 @@ height: 100%; margin: 0; padding: 0; - font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif; + font-family: system-ui,-apple-system,"Helvetica Neue",Arial,sans-serif; + color: #212529; } table { width: 100%; @@ -21,12 +22,35 @@ text-align: left; } th, td { - padding: 5px; + padding: 7px 5px; + vertical-align: middle; + text-overflow: ellipsis; + padding-top: 12px; + } + tr { + border-bottom: 1px solid #e2e2e2; } thead { background-color: #f6f6f6; border-bottom: 1px solid grey; } + .h1, .h2, .h3 { + font-weight: bold; + padding-top: 15px; + } + .h1 { + font-size: 1.6rem; + padding-top: 20px; + } + .h2 { + font-size: 1.3rem; + } + .h3 { + font-size: 1.15rem; + } + .text-bold { + font-weight: bold; + } %body = yield diff --git a/app/webpacker/css/admin/reports.scss b/app/webpacker/css/admin/reports.scss index fceab15629..d51d54204b 100644 --- a/app/webpacker/css/admin/reports.scss +++ b/app/webpacker/css/admin/reports.scss @@ -2,6 +2,10 @@ table.report__table { margin-top: 1em; @media print { margin: 0; + td, th { + border-left: none; + border-right: none; + } } thead th { text-align: left; @@ -48,8 +52,6 @@ table.report__table { } } - - .customer-names-tip { margin-top: 1em; } diff --git a/config/locales/en.yml b/config/locales/en.yml index 7224a5d9da..82c667fb07 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1325,13 +1325,12 @@ en: rendering_options: generate_report: "Generate report" on_screen: "On screen" - pdf: PDF - csv_spreadsheet: "CSV Spreadsheet" - excel_spreadsheet: "Excel Spreadsheet" - openoffice_spreadsheet: "OpenOffice Spreadsheet" + spreadsheet: "Spreadsheet (Excel, OpenOffice..)" display: Display summary_row: Summary Row header_row: Header Row + raw_data: Raw Data + formatted_data: Formatted Data packing: name: "Packing Reports" subscriptions: From 8a943f50efa96eaf3d324c1e758cf8df3b07bff1 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Thu, 7 Apr 2022 20:43:26 +0200 Subject: [PATCH 42/54] Reports: Format cells for html, pdf, and spreadsheet Currency, number format, dates --- .rubocop_todo.yml | 1 - app/helpers/reports_helper.rb | 12 -- app/views/admin/reports/_row_group.haml | 6 +- lib/reporting/report_renderer.rb | 4 + lib/reporting/report_row_builder.rb | 126 ++++++++++++++++++ lib/reporting/report_rows_builder.rb | 60 +-------- lib/reporting/report_template.rb | 7 +- .../order_cycle_management_report_spec.rb | 2 + .../orders_and_distributors_report_spec.rb | 2 +- ...order_cycle_customer_totals_report_spec.rb | 1 + .../products_and_inventory_report_spec.rb | 1 + spec/lib/reports/report_spec.rb | 2 + spec/system/admin/reports_spec.rb | 16 +-- 13 files changed, 159 insertions(+), 81 deletions(-) create mode 100644 lib/reporting/report_row_builder.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f1ae0553da..3dba2bd123 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -537,7 +537,6 @@ Metrics/ClassLength: - 'lib/open_food_network/order_cycle_permissions.rb' - 'lib/reporting/reports/payments/payments_report.rb' - 'lib/reporting/reports/xero_invoices/base.rb' - - 'lib/reporting/report_rows_builder.rb' # Offense count: 39 # Configuration parameters: IgnoredMethods, Max. diff --git a/app/helpers/reports_helper.rb b/app/helpers/reports_helper.rb index 6cf6cb8c16..953a4bbffa 100644 --- a/app/helpers/reports_helper.rb +++ b/app/helpers/reports_helper.rb @@ -29,16 +29,4 @@ module ReportsHelper def currency_symbol Spree::Money.currency_symbol end - - def format_cell(value) - return "" if value.nil? - - if value.in? [true, false] # Boolean - value ? I18n.t(:yes) : I18n.t(:no) - elsif value.respond_to?(:strftime) # Date - value.to_datetime.in_time_zone.strftime "%Y-%m-%d %H:%M" - else - value - end - end end diff --git a/app/views/admin/reports/_row_group.haml b/app/views/admin/reports/_row_group.haml index b06583637b..6a5bea828b 100644 --- a/app/views/admin/reports/_row_group.haml +++ b/app/views/admin/reports/_row_group.haml @@ -13,10 +13,10 @@ = render partial: 'admin/reports/row_group', locals: { report: report, data: group_or_row[:data] } / Summary Row - if group_or_row[:summary_row].present? && report.display_summary_row? - %tr.summary_row{ class: group_or_row[:summary_row_class] } + %tr.summary-row{ class: group_or_row[:summary_row_class] } - group_or_row[:summary_row].to_h.each do |key, value| - %td= format_cell(value) + %td= value - else %tr - group_or_row.row.to_h.each do |key, value| - %td= format_cell(value) \ No newline at end of file + %td= value \ No newline at end of file diff --git a/lib/reporting/report_renderer.rb b/lib/reporting/report_renderer.rb index 61999c45c1..7eafcb8e3a 100644 --- a/lib/reporting/report_renderer.rb +++ b/lib/reporting/report_renderer.rb @@ -12,6 +12,10 @@ module Reporting @report.params[:report_format].in?(['json', 'csv']) end + def html_render? + @report.params[:report_format].in?(['', 'pdf']) + end + def display_header_row? @report.params[:display_header_row].present? && !raw_render? end diff --git a/lib/reporting/report_row_builder.rb b/lib/reporting/report_row_builder.rb new file mode 100644 index 0000000000..2beee3e631 --- /dev/null +++ b/lib/reporting/report_row_builder.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +module Reporting + class ReportRowBuilder + include ActionView::Helpers::NumberHelper + include ActionView::Helpers::TagHelper + + attr_reader :report + + def initialize(report) + @report = report + 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 + + def slice_and_format_row(row) + result = row.to_h.reject { |k, _v| k.in?(report.fields_to_hide) } + unless report.raw_render? + result = result.map { |k, v| [k, format_cell(v, k)] }.to_h + end + OpenStruct.new(result) + 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_and_format_row(OpenStruct.new(row.reverse_merge!(blank_row))) + add_summary_row_label(row, rule, proc_args) + end + + private + + 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 + + # rubocop:disable Metrics/CyclomaticComplexity + def format_cell(value, column = nil) + return "" if value.nil? + + # Currency + if report.columns_format[column] == :currency || column.to_s.include?("price") + format_currency(value) + # Quantity + elsif report.columns_format[column] == :quantity && report.html_render? + format_quantity(value) + # Boolean + elsif value.in? [true, false] + format_boolean(value) + # Time + elsif value.is_a?(Time) + format_time(value) + # Date + elsif value.is_a?(Date) + format_date(value) + # Numeric + elsif value.is_a?(Numeric) + format_numeric(value) + # Default + else + value + end + end + # rubocop:enable Metrics/CyclomaticComplexity + + def format_currency(value) + number_to_currency(value, unit: Spree::Money.currency_symbol) + end + + def format_quantity(value) + content_tag(value > 1 ? :strong : :span, value) + end + + def format_boolean(value) + value ? I18n.t(:yes) : I18n.t(:no) + end + + def format_time(value) + value.to_datetime.in_time_zone.strftime "%Y-%m-%d %H:%M" + end + + def format_date(value) + value.to_datetime.in_time_zone.strftime "%Y-%m-%d" + end + + def format_numeric(value) + number_with_delimiter(value) + end + end +end diff --git a/lib/reporting/report_rows_builder.rb b/lib/reporting/report_rows_builder.rb index 28e0c8147c..1395aef98d 100644 --- a/lib/reporting/report_rows_builder.rb +++ b/lib/reporting/report_rows_builder.rb @@ -6,6 +6,7 @@ module Reporting def initialize(report) @report = report + @builder = ReportRowBuilder.new(report) end # Structured data by groups. This tree is used to render @@ -38,8 +39,8 @@ module Reporting 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)) + row = @builder.build_row(item) + OpenStruct.new(item: item, full_row: row, row: @builder.slice_and_format_row(row)) } end @@ -78,9 +79,9 @@ module Reporting sorted_groups.each do |group_value, group_datas| result << { is_group: true, - header: build_header(rule, group_value, group_datas), + header: @builder.build_header(rule, group_value, group_datas), header_class: rule[:header_class], - summary_row: build_summary_row(rule, group_value, group_datas), + summary_row: @builder.build_summary_row(rule, group_value, group_datas), summary_row_class: rule[:summary_row_class], data: build_tree(group_datas, remaining_rules) } @@ -109,56 +110,5 @@ module Reporting 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 diff --git a/lib/reporting/report_template.rb b/lib/reporting/report_template.rb index 899e77db90..1582fb323a 100644 --- a/lib/reporting/report_template.rb +++ b/lib/reporting/report_template.rb @@ -6,7 +6,7 @@ module Reporting attr_accessor :user, :params, :ransack_params delegate :as_json, :as_arrays, :to_csv, :to_xlsx, :to_ods, :to_pdf, :to_json, to: :renderer - delegate :raw_render?, :display_header_row?, :display_summary_row?, to: :renderer + delegate :raw_render?, :html_render?, :display_header_row?, :display_summary_row?, to: :renderer delegate :rows, :table_rows, :grouped_data, to: :rows_builder delegate :available_headers, :table_headers, :fields_to_hide, to: :headers_builder @@ -47,6 +47,11 @@ module Reporting raise NotImplementedError end + # Exple { total_price: :currency } + def columns_format + {} + end + # Headers are automatically translated with table_headers method # You can customize some header name if needed def custom_headers diff --git a/spec/lib/reports/order_cycle_management_report_spec.rb b/spec/lib/reports/order_cycle_management_report_spec.rb index 8676d7795f..6fe005beda 100644 --- a/spec/lib/reports/order_cycle_management_report_spec.rb +++ b/spec/lib/reports/order_cycle_management_report_spec.rb @@ -165,6 +165,7 @@ module Reporting end it 'returns rows with payment information' do + allow(subject).to receive(:raw_render?).and_return(true) expect(subject.table_rows).to eq([[ order.billing_address.firstname, order.billing_address.lastname, @@ -191,6 +192,7 @@ module Reporting end it 'returns rows with delivery information' do + allow(subject).to receive(:raw_render?).and_return(true) expect(subject.table_rows).to eq([[ order.ship_address.firstname, order.ship_address.lastname, diff --git a/spec/lib/reports/orders_and_distributors_report_spec.rb b/spec/lib/reports/orders_and_distributors_report_spec.rb index 85a84ab788..243b45005b 100644 --- a/spec/lib/reports/orders_and_distributors_report_spec.rb +++ b/spec/lib/reports/orders_and_distributors_report_spec.rb @@ -46,7 +46,7 @@ module Reporting it 'should denormalise order and distributor details for display as csv' do subject = Base.new create(:admin_user), {} - + allow(subject).to receive(:raw_render?).and_return(true) table = subject.table_rows expect(table.size).to eq 1 diff --git a/spec/lib/reports/orders_and_fulfillment/order_cycle_customer_totals_report_spec.rb b/spec/lib/reports/orders_and_fulfillment/order_cycle_customer_totals_report_spec.rb index 8d39e883a4..40297f34c8 100644 --- a/spec/lib/reports/orders_and_fulfillment/order_cycle_customer_totals_report_spec.rb +++ b/spec/lib/reports/orders_and_fulfillment/order_cycle_customer_totals_report_spec.rb @@ -80,6 +80,7 @@ module Reporting end it "shows the correct payment fee amount for the order" do + allow(report).to receive(:raw_render?).and_return(true) expect(report.rows.last.pay_fee_price).to eq completed_payment.adjustment.amount end end diff --git a/spec/lib/reports/products_and_inventory_report_spec.rb b/spec/lib/reports/products_and_inventory_report_spec.rb index 7ed6034437..87739af47e 100644 --- a/spec/lib/reports/products_and_inventory_report_spec.rb +++ b/spec/lib/reports/products_and_inventory_report_spec.rb @@ -49,6 +49,7 @@ module Reporting double(name: "taxon2")] allow(variant).to receive_message_chain(:product, :group_buy_unit_size).and_return(21) allow(subject).to receive(:query_result).and_return [variant] + allow(subject).to receive(:raw_render?).and_return(true) expect(subject.table_rows).to eq([[ "Supplier", diff --git a/spec/lib/reports/report_spec.rb b/spec/lib/reports/report_spec.rb index a5258dfb7b..e197fd8f97 100644 --- a/spec/lib/reports/report_spec.rb +++ b/spec/lib/reports/report_spec.rb @@ -101,6 +101,7 @@ module Reporting end it "get correct data" do + allow(subject).to receive(:raw_render?).and_return(true) @expected_table_rows = [ [5, "My Hub"], [12, "My Other Hub"], @@ -132,6 +133,7 @@ module Reporting { group_by: :customer, header: true } ] allow(subject).to receive(:display_header_row?).and_return(true) + allow(subject).to receive(:raw_render?).and_return(true) @expected_rows = [ { header: "Hub 1" }, { header: "Abby" }, diff --git a/spec/system/admin/reports_spec.rb b/spec/system/admin/reports_spec.rb index aa9400fa35..91ac68f7bf 100644 --- a/spec/system/admin/reports_spec.rb +++ b/spec/system/admin/reports_spec.rb @@ -182,8 +182,8 @@ describe ' expect(page).to have_content order1.number.to_s # And the totals and sales tax should be correct - expect(page).to have_content "1512.99" # items total - expect(page).to have_content "1500.45" # taxable items total + expect(page).to have_content "1,512.99" # items total + expect(page).to have_content "1,500.45" # taxable items total expect(page).to have_content "250.08" # sales tax expect(page).to have_content "20.0" # enterprise fee tax @@ -310,17 +310,17 @@ describe ' expect(page).to have_table_row [product1.supplier.name, product1.supplier.address.city, "Product Name", product1.properties.map(&:presentation).join(", "), - product1.primary_taxon.name, "Test", "100.0", + product1.primary_taxon.name, "Test", "$100.00", product1.group_buy_unit_size.to_s, "", "sku1"] expect(page).to have_table_row [product1.supplier.name, product1.supplier.address.city, "Product Name", product1.properties.map(&:presentation).join(", "), - product1.primary_taxon.name, "Something", "80.0", + product1.primary_taxon.name, "Something", "$80.00", product1.group_buy_unit_size.to_s, "", "sku2"] expect(page).to have_table_row [product2.supplier.name, product1.supplier.address.city, "Product 2", product1.properties.map(&:presentation).join(", "), - product2.primary_taxon.name, "100g", "99.0", + product2.primary_taxon.name, "100g", "$99.00", product1.group_buy_unit_size.to_s, "", "product_sku"] end @@ -332,7 +332,7 @@ describe ' expect(page).to have_table_row ['PRODUCT', 'Description', 'Qty', 'Pack Size', 'Unit', 'Unit Price', 'Total', 'GST incl.', 'Grower and growing method', 'Taxon'].map(&:upcase) - expect(page).to have_table_row ['Product 2', '100g', '', '100', 'g', '99.0', '', '0', + expect(page).to have_table_row ['Product 2', '100g', '', '100', 'g', '$99.00', '', '0', 'Supplier Name (Organic - NASAA 12345)', 'Taxon Name'] end end @@ -559,7 +559,7 @@ describe ' xero_invoice_header, xero_invoice_summary_row('Total untaxable produce (no tax)', 12.54, 'GST Free Income'), - xero_invoice_summary_row('Total taxable produce (tax inclusive)', 1500.45, + xero_invoice_summary_row('Total taxable produce (tax inclusive)', '1,500.45', 'GST on Income'), xero_invoice_summary_row('Total untaxable fees (no tax)', 10.0, 'GST Free Income'), @@ -589,7 +589,7 @@ describe ' xero_invoice_header, xero_invoice_summary_row('Total untaxable produce (no tax)', 12.54, 'GST Free Income', opts), - xero_invoice_summary_row('Total taxable produce (tax inclusive)', 1500.45, + xero_invoice_summary_row('Total taxable produce (tax inclusive)', '1,500.45', 'GST on Income', opts), xero_invoice_summary_row('Total untaxable fees (no tax)', 10.0, 'GST Free Income', opts), From 2dd31d970e65a5bbcf7b87466b86a3487ee06a98 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Thu, 7 Apr 2022 19:31:52 +0200 Subject: [PATCH 43/54] Reports Format cells: format some reports --- lib/reporting/reports/bulk_coop/base.rb | 2 +- lib/reporting/reports/bulk_coop/supplier_report.rb | 4 ++++ lib/reporting/reports/packing/base.rb | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/reporting/reports/bulk_coop/base.rb b/lib/reporting/reports/bulk_coop/base.rb index 8c8a6501bd..e6d0c0229a 100644 --- a/lib/reporting/reports/bulk_coop/base.rb +++ b/lib/reporting/reports/bulk_coop/base.rb @@ -115,7 +115,7 @@ module Reporting end def variant_product_group_buy_unit_size_f(line_items) - group_buy_unit_size(line_items) + group_buy_unit_size(line_items).to_i end def variant_product_name(line_items) diff --git a/lib/reporting/reports/bulk_coop/supplier_report.rb b/lib/reporting/reports/bulk_coop/supplier_report.rb index 6e797cd389..16257b26c7 100644 --- a/lib/reporting/reports/bulk_coop/supplier_report.rb +++ b/lib/reporting/reports/bulk_coop/supplier_report.rb @@ -24,6 +24,10 @@ module Reporting } end + def columns_format + { sum_total: :currency } + end + def rules [ { diff --git a/lib/reporting/reports/packing/base.rb b/lib/reporting/reports/packing/base.rb index a27f50784c..c5c6f73601 100644 --- a/lib/reporting/reports/packing/base.rb +++ b/lib/reporting/reports/packing/base.rb @@ -25,6 +25,10 @@ module Reporting ordered_by(ordering_fields) end + def columns_format + { price: :currency, quantity: :quantity } + end + private def select_fields From 767afe1fba5f9f40185d4929e25a2de8e7651834 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Thu, 14 Apr 2022 06:19:16 +0200 Subject: [PATCH 44/54] Reports Renderer: whitelist report formats --- app/controllers/admin/reports_controller.rb | 2 +- lib/reporting/report_renderer.rb | 10 ++++++++-- lib/reporting/report_template.rb | 2 +- spec/lib/reports/report_renderer_spec.rb | 16 ++++++---------- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index 04d55e1fc9..a09111f471 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -31,7 +31,7 @@ module Admin private def export_report - send_data @report.public_send("to_#{report_format}", self), filename: report_filename + send_data @report.render_as(report_format, controller: self), filename: report_filename end def render_report diff --git a/lib/reporting/report_renderer.rb b/lib/reporting/report_renderer.rb index 7eafcb8e3a..0c56d6daab 100644 --- a/lib/reporting/report_renderer.rb +++ b/lib/reporting/report_renderer.rb @@ -4,6 +4,8 @@ require 'spreadsheet_architect' module Reporting class ReportRenderer + REPORT_FORMATS = [:csv, :json, :xlsx, :pdf].freeze + def initialize(report) @report = report end @@ -36,8 +38,12 @@ module Reporting @report.rows.map(&:to_h).as_json end - def as_arrays - @as_arrays ||= [table_headers] + table_rows + def render_as(target_format, controller: nil) + unless target_format.to_sym.in?(REPORT_FORMATS) + raise ActionController::BadRequest, "report_format should be in #{REPORT_FORMATS}" + end + + public_send("to_#{target_format}", controller) end def to_csv(_context_controller = nil) diff --git a/lib/reporting/report_template.rb b/lib/reporting/report_template.rb index 1582fb323a..286bb67e93 100644 --- a/lib/reporting/report_template.rb +++ b/lib/reporting/report_template.rb @@ -5,7 +5,7 @@ module Reporting include ReportsHelper attr_accessor :user, :params, :ransack_params - delegate :as_json, :as_arrays, :to_csv, :to_xlsx, :to_ods, :to_pdf, :to_json, to: :renderer + delegate :render_as, :as_json, :to_csv, :to_xlsx, :to_pdf, :to_json, to: :renderer delegate :raw_render?, :html_render?, :display_header_row?, :display_summary_row?, to: :renderer delegate :rows, :table_rows, :grouped_data, to: :rows_builder diff --git a/spec/lib/reports/report_renderer_spec.rb b/spec/lib/reports/report_renderer_spec.rb index 269130d3b5..eda37744ca 100644 --- a/spec/lib/reports/report_renderer_spec.rb +++ b/spec/lib/reports/report_renderer_spec.rb @@ -21,21 +21,17 @@ describe Reporting::ReportRenderer do table_rows: data.map(&:values) ) } - let(:service) { described_class.new(report) } + let(:subject) { described_class.new(report) } - describe "#as_json" do + describe ".as_json" do it "returns the report's data as hashes" do - expect(service.as_json).to eq data.as_json + expect(subject.as_json).to eq data.as_json end end - describe "#as_arrays" do - it "returns the report's data as arrays" do - expect(service.as_arrays).to eq [ - ["id", "name", "quantity"], - [1, "carrots", 3], - [2, "onions", 6] - ] + describe ".render_as" do + it "raise an error if format is not supported" do + expect { subject.render_as("give_me_everything") }.to raise_error end end end From b54ec4354a428a6c2d0347450d572fd4fdfcc777 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Fri, 22 Apr 2022 20:21:59 +0200 Subject: [PATCH 45/54] Report TaxRate fix --- lib/reporting/reports/sales_tax/tax_rates.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/reporting/reports/sales_tax/tax_rates.rb b/lib/reporting/reports/sales_tax/tax_rates.rb index afc62ac3c8..83bd3f2eb0 100644 --- a/lib/reporting/reports/sales_tax/tax_rates.rb +++ b/lib/reporting/reports/sales_tax/tax_rates.rb @@ -11,7 +11,7 @@ module Reporting total_excl_vat: proc { |order| order.total - order.total_tax } } add_key_for_each_rate(result, proc { |rate| - OrderTaxAdjustmentsFetcher.new(order).totals.fetch(rate, 0) + proc { |order| OrderTaxAdjustmentsFetcher.new(order).totals.fetch(rate, 0) } }) other = { total_tax: proc { |order| order.total_tax }, From 0545c9de28c69622e0f61b6c690017a0ceb2b52d Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 27 Apr 2022 07:26:58 +0200 Subject: [PATCH 46/54] Packing Report: revert column order change --- lib/reporting/reports/packing/customer.rb | 2 +- lib/reporting/reports/packing/product.rb | 2 +- lib/reporting/reports/packing/supplier.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/reporting/reports/packing/customer.rb b/lib/reporting/reports/packing/customer.rb index ad660ddbd1..8aa805829d 100644 --- a/lib/reporting/reports/packing/customer.rb +++ b/lib/reporting/reports/packing/customer.rb @@ -6,7 +6,7 @@ module Reporting class Customer < Base def columns # Reorder default columns - super.slice(:hub, :customer_code, :last_name, :first_name, :phone, + super.slice(:hub, :customer_code, :first_name, :last_name, :phone, :supplier, :product, :variant, :quantity, :price, :temp_controlled) end diff --git a/lib/reporting/reports/packing/product.rb b/lib/reporting/reports/packing/product.rb index 639061f875..83f6bd2e1b 100644 --- a/lib/reporting/reports/packing/product.rb +++ b/lib/reporting/reports/packing/product.rb @@ -7,7 +7,7 @@ module Reporting def columns # Reorder default columns super.slice(:hub, :supplier, :product, :variant, - :customer_code, :last_name, :first_name, :phone, + :customer_code, :first_name, :last_name, :phone, :quantity, :price, :temp_controlled) end diff --git a/lib/reporting/reports/packing/supplier.rb b/lib/reporting/reports/packing/supplier.rb index cd0acbb6ab..c8ed63adba 100644 --- a/lib/reporting/reports/packing/supplier.rb +++ b/lib/reporting/reports/packing/supplier.rb @@ -6,7 +6,7 @@ module Reporting class Supplier < Base def columns # Reorder default columns - super.slice(:hub, :supplier, :customer_code, :last_name, :first_name, :phone, + super.slice(:hub, :supplier, :customer_code, :first_name, :last_name, :phone, :product, :variant, :quantity, :price, :temp_controlled) end From 0218f75f34156ba57b218838ef64df9523fb83ce Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 27 Apr 2022 07:30:04 +0200 Subject: [PATCH 47/54] Packing Report: hide by default phone and price column to avoid breaking changes --- app/controllers/admin/reports_controller.rb | 2 +- lib/reporting/report_template.rb | 2 +- .../reports/enterprise_fee_summary/base.rb | 4 ++-- .../reports/order_cycle_management/base.rb | 4 ++-- .../reports/orders_and_distributors/base.rb | 4 ++-- .../reports/orders_and_fulfillment/base.rb | 4 ++-- lib/reporting/reports/packing/base.rb | 6 ++++++ .../reports/users_and_enterprises/base.rb | 4 ++-- lib/reporting/reports/xero_invoices/base.rb | 14 ++++++++------ 9 files changed, 26 insertions(+), 18 deletions(-) diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index a09111f471..f7505208ec 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -19,7 +19,7 @@ module Admin end def show - @report = report_class.new(spree_current_user, params) + @report = report_class.new(spree_current_user, params, request) if report_format.present? export_report diff --git a/lib/reporting/report_template.rb b/lib/reporting/report_template.rb index 286bb67e93..c383e3c577 100644 --- a/lib/reporting/report_template.rb +++ b/lib/reporting/report_template.rb @@ -13,7 +13,7 @@ module Reporting delegate :formatted_rules, :header_option?, :summary_row_option?, to: :ruler - def initialize(user, params = {}) + def initialize(user, params = {}, _request = nil) @user = user @params = params @params = @params.permit!.to_h unless @params.is_a? Hash diff --git a/lib/reporting/reports/enterprise_fee_summary/base.rb b/lib/reporting/reports/enterprise_fee_summary/base.rb index 8e144f511b..99d17747a3 100644 --- a/lib/reporting/reports/enterprise_fee_summary/base.rb +++ b/lib/reporting/reports/enterprise_fee_summary/base.rb @@ -6,8 +6,8 @@ module Reporting class Base < ReportTemplate attr_accessor :permissions, :parameters - def initialize(user, params = {}) - super(user, params) + def initialize(user, params = {}, request = nil) + super(user, params, request) p = params[:q] if p.present? p['start_at'] = p.delete('completed_at_gt') diff --git a/lib/reporting/reports/order_cycle_management/base.rb b/lib/reporting/reports/order_cycle_management/base.rb index 095177402d..337559d82f 100644 --- a/lib/reporting/reports/order_cycle_management/base.rb +++ b/lib/reporting/reports/order_cycle_management/base.rb @@ -6,11 +6,11 @@ module Reporting class Base < ReportTemplate DEFAULT_DATE_INTERVAL = { from: -1.month, to: 1.day }.freeze - def initialize(user, params = {}) + def initialize(user, params = {}, request = nil) params[:q] ||= {} params[:q][:completed_at_gt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:from] params[:q][:completed_at_lt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:to] - super(user, params) + super(user, params, request) end def search diff --git a/lib/reporting/reports/orders_and_distributors/base.rb b/lib/reporting/reports/orders_and_distributors/base.rb index d2d6552e1e..6438b9c895 100644 --- a/lib/reporting/reports/orders_and_distributors/base.rb +++ b/lib/reporting/reports/orders_and_distributors/base.rb @@ -4,8 +4,8 @@ module Reporting module Reports module OrdersAndDistributors class Base < ReportTemplate - def initialize(user, params = {}) - super(user, params) + def initialize(user, params = {}, request = nil) + super(user, params, request) end # rubocop:disable Metrics/AbcSize diff --git a/lib/reporting/reports/orders_and_fulfillment/base.rb b/lib/reporting/reports/orders_and_fulfillment/base.rb index 6ea3651e6b..4a70fb7e2c 100644 --- a/lib/reporting/reports/orders_and_fulfillment/base.rb +++ b/lib/reporting/reports/orders_and_fulfillment/base.rb @@ -4,8 +4,8 @@ module Reporting module Reports module OrdersAndFulfillment class Base < ReportTemplate - def initialize(user, params = {}) - super(user, params) + def initialize(user, params = {}, request = nil) + super(user, params, request) now = Time.zone.now params[:q] ||= { diff --git a/lib/reporting/reports/packing/base.rb b/lib/reporting/reports/packing/base.rb index c5c6f73601..87d8fe639f 100644 --- a/lib/reporting/reports/packing/base.rb +++ b/lib/reporting/reports/packing/base.rb @@ -4,6 +4,12 @@ module Reporting module Reports module Packing class Base < ReportQueryTemplate + def initialize(user, params = {}, request = nil) + # Prevent breaking change in this report by hidding new columns by default + params.reverse_merge!(fields_to_hide: ["phone", "price"]) if request&.get? + super(user, params, request) + end + def message I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") end diff --git a/lib/reporting/reports/users_and_enterprises/base.rb b/lib/reporting/reports/users_and_enterprises/base.rb index 33dc30f402..c29a8f605f 100644 --- a/lib/reporting/reports/users_and_enterprises/base.rb +++ b/lib/reporting/reports/users_and_enterprises/base.rb @@ -4,8 +4,8 @@ module Reporting module Reports module UsersAndEnterprises class Base < ReportTemplate - def initialize(user, params = {}) - super(user, params) + def initialize(user, params = {}, request = nil) + super(user, params, request) end def query_result diff --git a/lib/reporting/reports/xero_invoices/base.rb b/lib/reporting/reports/xero_invoices/base.rb index 6b2425c5f9..e4ad8a40fb 100644 --- a/lib/reporting/reports/xero_invoices/base.rb +++ b/lib/reporting/reports/xero_invoices/base.rb @@ -4,12 +4,14 @@ module Reporting module Reports module XeroInvoices class Base < ReportTemplate - def initialize(user, params = {}) - params.reverse_merge!(report_subtype: 'summary', - invoice_date: Time.zone.today, - due_date: Time.zone.today + 1.month, - account_code: 'food sales') - super(user, params) + def initialize(user, params = {}, request = nil) + if request&.get? + params.reverse_merge!(report_subtype: 'summary', + invoice_date: Time.zone.today, + due_date: Time.zone.today + 1.month, + account_code: 'food sales') + end + super(user, params, request) end def xero_columns From 2008c10c6fd75fe50730cfcbd49e965c5b75f27b Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 27 Apr 2022 08:33:35 +0200 Subject: [PATCH 48/54] Reports: use field name as header instead of translated value for raw renders (csv, json) --- lib/reporting/report_headers_builder.rb | 2 ++ spec/lib/reports/report_spec.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/reporting/report_headers_builder.rb b/lib/reporting/report_headers_builder.rb index 7d3a6b8e29..e20d04c29b 100644 --- a/lib/reporting/report_headers_builder.rb +++ b/lib/reporting/report_headers_builder.rb @@ -29,6 +29,8 @@ module Reporting private def translate_header(key) + return key.to_s if report.raw_render? + # Quite some headers use currency interpolation, so providing it by default default_params = { currency: currency_symbol, currency_symbol: currency_symbol } report.custom_headers[key] || I18n.t("report_header_#{key}", **default_params) diff --git a/spec/lib/reports/report_spec.rb b/spec/lib/reports/report_spec.rb index e197fd8f97..9f7fcc3ad5 100644 --- a/spec/lib/reports/report_spec.rb +++ b/spec/lib/reports/report_spec.rb @@ -79,7 +79,7 @@ module Reporting end describe "fields_to_hide" do - let(:params) { { fields_to_hide: [:product], report_format: 'json' } } + let(:params) { { fields_to_hide: [:product] } } it "works" do @expected_headers = ['Hub', 'Price'] From b7991e5ae853f2bdc8fcfb5c7537f8260caa0003 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 27 Apr 2022 07:50:27 +0200 Subject: [PATCH 49/54] Packing Report: use field name as columns headers in spreadsheet export --- lib/reporting/reports/packing/base.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/reporting/reports/packing/base.rb b/lib/reporting/reports/packing/base.rb index 87d8fe639f..1dbc2b1fa6 100644 --- a/lib/reporting/reports/packing/base.rb +++ b/lib/reporting/reports/packing/base.rb @@ -35,6 +35,13 @@ module Reporting { price: :currency, quantity: :quantity } end + def custom_headers + return {} if html_render? + + # Use non translated headers to avoid breaking changes + @custom_headers ||= report_data.columns.index_by(&:itself).symbolize_keys + end + private def select_fields From 56fab262353a91dcadac8d5939258a1d352a2202 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 27 Apr 2022 08:35:22 +0200 Subject: [PATCH 50/54] Reports: refactor to easily provide default_params --- lib/reporting/report_template.rb | 12 +++++++++- .../reports/order_cycle_management/base.rb | 14 +++++------ .../reports/orders_and_distributors/base.rb | 4 ---- .../reports/orders_and_fulfillment/base.rb | 19 ++++++++------- lib/reporting/reports/packing/base.rb | 11 ++++----- .../reports/users_and_enterprises/base.rb | 4 ---- lib/reporting/reports/xero_invoices/base.rb | 19 ++++++++------- .../api/v0/reports/packing_report_spec.rb | 3 +++ spec/lib/reports/report_spec.rb | 23 +++++++++++++++++++ spec/lib/reports/xero_invoices_report_spec.rb | 3 ++- 10 files changed, 69 insertions(+), 43 deletions(-) diff --git a/lib/reporting/report_template.rb b/lib/reporting/report_template.rb index c383e3c577..de8f291d0f 100644 --- a/lib/reporting/report_template.rb +++ b/lib/reporting/report_template.rb @@ -13,7 +13,12 @@ module Reporting delegate :formatted_rules, :header_option?, :summary_row_option?, to: :ruler - def initialize(user, params = {}, _request = nil) + def initialize(user, params = {}, request = nil) + if request.nil? || request.get? + params.reverse_merge!(default_params) + params[:q] ||= {} + params[:q].reverse_merge!(default_params[:q]) if default_params[:q].present? + end @user = user @params = params @params = @params.permit!.to_h unless @params.is_a? Hash @@ -87,6 +92,11 @@ module Reporting [] end + # Default filters/search params to be used + def default_params + {} + end + private def renderer diff --git a/lib/reporting/reports/order_cycle_management/base.rb b/lib/reporting/reports/order_cycle_management/base.rb index 337559d82f..68c08fe81f 100644 --- a/lib/reporting/reports/order_cycle_management/base.rb +++ b/lib/reporting/reports/order_cycle_management/base.rb @@ -4,13 +4,13 @@ module Reporting module Reports module OrderCycleManagement class Base < ReportTemplate - DEFAULT_DATE_INTERVAL = { from: -1.month, to: 1.day }.freeze - - def initialize(user, params = {}, request = nil) - params[:q] ||= {} - params[:q][:completed_at_gt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:from] - params[:q][:completed_at_lt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:to] - super(user, params, request) + def default_params + { + q: { + completed_at_gt: 1.month.ago.beginning_of_day, + completed_at_lt: 1.day.from_now.beginning_of_day + } + } end def search diff --git a/lib/reporting/reports/orders_and_distributors/base.rb b/lib/reporting/reports/orders_and_distributors/base.rb index 6438b9c895..9225096680 100644 --- a/lib/reporting/reports/orders_and_distributors/base.rb +++ b/lib/reporting/reports/orders_and_distributors/base.rb @@ -4,10 +4,6 @@ module Reporting module Reports module OrdersAndDistributors class Base < ReportTemplate - def initialize(user, params = {}, request = nil) - super(user, params, request) - end - # rubocop:disable Metrics/AbcSize def columns { diff --git a/lib/reporting/reports/orders_and_fulfillment/base.rb b/lib/reporting/reports/orders_and_fulfillment/base.rb index 4a70fb7e2c..1d780a8608 100644 --- a/lib/reporting/reports/orders_and_fulfillment/base.rb +++ b/lib/reporting/reports/orders_and_fulfillment/base.rb @@ -4,20 +4,19 @@ module Reporting module Reports module OrdersAndFulfillment class Base < ReportTemplate - def initialize(user, params = {}, request = nil) - super(user, params, request) - - now = Time.zone.now - params[:q] ||= { - completed_at_gt: (now - 1.month).beginning_of_day, - completed_at_lt: (now + 1.day).beginning_of_day - } - end - def message I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") end + def default_params + { + q: { + completed_at_gt: 1.month.ago.beginning_of_day, + completed_at_lt: 1.day.from_now.beginning_of_day + } + } + end + def search report_line_items.orders end diff --git a/lib/reporting/reports/packing/base.rb b/lib/reporting/reports/packing/base.rb index 1dbc2b1fa6..2513af0d22 100644 --- a/lib/reporting/reports/packing/base.rb +++ b/lib/reporting/reports/packing/base.rb @@ -4,12 +4,6 @@ module Reporting module Reports module Packing class Base < ReportQueryTemplate - def initialize(user, params = {}, request = nil) - # Prevent breaking change in this report by hidding new columns by default - params.reverse_merge!(fields_to_hide: ["phone", "price"]) if request&.get? - super(user, params, request) - end - def message I18n.t("spree.admin.reports.customer_names_message.customer_names_tip") end @@ -42,6 +36,11 @@ module Reporting @custom_headers ||= report_data.columns.index_by(&:itself).symbolize_keys end + def default_params + # Prevent breaking change in this report by hidding new columns by default + { fields_to_hide: ["phone", "price"] } + end + private def select_fields diff --git a/lib/reporting/reports/users_and_enterprises/base.rb b/lib/reporting/reports/users_and_enterprises/base.rb index c29a8f605f..12ab4183a0 100644 --- a/lib/reporting/reports/users_and_enterprises/base.rb +++ b/lib/reporting/reports/users_and_enterprises/base.rb @@ -4,10 +4,6 @@ module Reporting module Reports module UsersAndEnterprises class Base < ReportTemplate - def initialize(user, params = {}, request = nil) - super(user, params, request) - end - def query_result sort(owners_and_enterprises.concat(managers_and_enterprises)) end diff --git a/lib/reporting/reports/xero_invoices/base.rb b/lib/reporting/reports/xero_invoices/base.rb index e4ad8a40fb..5ece80fc9f 100644 --- a/lib/reporting/reports/xero_invoices/base.rb +++ b/lib/reporting/reports/xero_invoices/base.rb @@ -4,16 +4,6 @@ module Reporting module Reports module XeroInvoices class Base < ReportTemplate - def initialize(user, params = {}, request = nil) - if request&.get? - params.reverse_merge!(report_subtype: 'summary', - invoice_date: Time.zone.today, - due_date: Time.zone.today + 1.month, - account_code: 'food sales') - end - super(user, params, request) - end - def xero_columns # These are NOT to be translated, they need to be in this exact format to work with Xero %w(*ContactName EmailAddress POAddressLine1 POAddressLine2 POAddressLine3 POAddressLine4 @@ -37,6 +27,15 @@ module Reporting result end + def default_params + { + report_subtype: 'summary', + invoice_date: Time.zone.today, + due_date: Time.zone.today + 1.month, + account_code: 'food sales' + } + end + def search permissions = ::Permissions::Order.new(@user) permissions.editable_orders.complete.not_state(:canceled).ransack(ransack_params) diff --git a/spec/controllers/api/v0/reports/packing_report_spec.rb b/spec/controllers/api/v0/reports/packing_report_spec.rb index 8600462701..971b3d5832 100644 --- a/spec/controllers/api/v0/reports/packing_report_spec.rb +++ b/spec/controllers/api/v0/reports/packing_report_spec.rb @@ -6,6 +6,9 @@ describe Api::V0::ReportsController, type: :controller do let(:params) { { report_type: 'packing', + # rspec seems to remove empty values to setting something dummy so the + # default_params will not overwritting this params + fields_to_hide: [:none], q: { order_created_at_lt: Time.zone.now } } } diff --git a/spec/lib/reports/report_spec.rb b/spec/lib/reports/report_spec.rb index 9f7fcc3ad5..c5d31dc369 100644 --- a/spec/lib/reports/report_spec.rb +++ b/spec/lib/reports/report_spec.rb @@ -26,6 +26,29 @@ module Reporting end # rubocop:enable Metrics/AbcSize + describe ".default_params" do + it "use correctly the default values" do + default_params = { + filter: "default__filter", + other_filter: "default_other_filter", + q: { hub: "default_hub", customer: "default_customer" } + } + real_params = { + filter: "test_filter", + q: { hub: "test_hub" } + } + expected_params = { + filter: "test_filter", + other_filter: "default_other_filter", + q: { hub: "test_hub", customer: "default_customer" } + } + allow_any_instance_of(described_class).to receive(:default_params) + .and_return(default_params) + report = described_class.new(user, real_params) + expect(report.params).to eq(expected_params) + end + end + describe ".columns" do before do @query_result = [ diff --git a/spec/lib/reports/xero_invoices_report_spec.rb b/spec/lib/reports/xero_invoices_report_spec.rb index 7f4d4cb840..a8670a0f00 100644 --- a/spec/lib/reports/xero_invoices_report_spec.rb +++ b/spec/lib/reports/xero_invoices_report_spec.rb @@ -19,7 +19,8 @@ module Reporting expect(report.params).to eq(invoice_date: Date.civil(2015, 5, 5), due_date: Date.civil(2015, 6, 5), account_code: 'food sales', - report_subtype: 'summary' ) + report_subtype: 'summary', + q: {}) end end From 35b5ca3a51cfef0e3e76e03ceba86191ba96264f Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 4 May 2022 15:10:39 +0200 Subject: [PATCH 51/54] Csv Report: translate headers --- lib/reporting/report_headers_builder.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/reporting/report_headers_builder.rb b/lib/reporting/report_headers_builder.rb index e20d04c29b..7d3a6b8e29 100644 --- a/lib/reporting/report_headers_builder.rb +++ b/lib/reporting/report_headers_builder.rb @@ -29,8 +29,6 @@ module Reporting private def translate_header(key) - return key.to_s if report.raw_render? - # Quite some headers use currency interpolation, so providing it by default default_params = { currency: currency_symbol, currency_symbol: currency_symbol } report.custom_headers[key] || I18n.t("report_header_#{key}", **default_params) From 89212736e96a0c23df72142fe1cba3997b19725d Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 4 May 2022 15:57:31 +0200 Subject: [PATCH 52/54] Reports fix formatting cells --- lib/reporting/report_row_builder.rb | 8 ++++---- lib/reporting/reports/sales_tax/base.rb | 6 +++--- spec/system/admin/reports_spec.rb | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/reporting/report_row_builder.rb b/lib/reporting/report_row_builder.rb index 2beee3e631..96683138e1 100644 --- a/lib/reporting/report_row_builder.rb +++ b/lib/reporting/report_row_builder.rb @@ -80,6 +80,9 @@ module Reporting # Quantity elsif report.columns_format[column] == :quantity && report.html_render? format_quantity(value) + # Numeric + elsif report.columns_format[column] == :numeric + format_numeric(value) # Boolean elsif value.in? [true, false] format_boolean(value) @@ -89,9 +92,6 @@ module Reporting # Date elsif value.is_a?(Date) format_date(value) - # Numeric - elsif value.is_a?(Numeric) - format_numeric(value) # Default else value @@ -100,7 +100,7 @@ module Reporting # rubocop:enable Metrics/CyclomaticComplexity def format_currency(value) - number_to_currency(value, unit: Spree::Money.currency_symbol) + value.present? ? number_to_currency(value, unit: Spree::Money.currency_symbol) : "" end def format_quantity(value) diff --git a/lib/reporting/reports/sales_tax/base.rb b/lib/reporting/reports/sales_tax/base.rb index c3b5466870..399ee86231 100644 --- a/lib/reporting/reports/sales_tax/base.rb +++ b/lib/reporting/reports/sales_tax/base.rb @@ -20,13 +20,13 @@ module Reporting end def order_number_column(order) - if raw_render? - order.number - else + if html_render? url = Spree::Core::Engine.routes.url_helpers.edit_admin_order_path(order.number) <<-HTML #{order.number} HTML + else + order.number end end end diff --git a/spec/system/admin/reports_spec.rb b/spec/system/admin/reports_spec.rb index 91ac68f7bf..0c2676f64c 100644 --- a/spec/system/admin/reports_spec.rb +++ b/spec/system/admin/reports_spec.rb @@ -182,8 +182,8 @@ describe ' expect(page).to have_content order1.number.to_s # And the totals and sales tax should be correct - expect(page).to have_content "1,512.99" # items total - expect(page).to have_content "1,500.45" # taxable items total + expect(page).to have_content "1512.99" # items total + expect(page).to have_content "1500.45" # taxable items total expect(page).to have_content "250.08" # sales tax expect(page).to have_content "20.0" # enterprise fee tax @@ -559,7 +559,7 @@ describe ' xero_invoice_header, xero_invoice_summary_row('Total untaxable produce (no tax)', 12.54, 'GST Free Income'), - xero_invoice_summary_row('Total taxable produce (tax inclusive)', '1,500.45', + xero_invoice_summary_row('Total taxable produce (tax inclusive)', 1500.45, 'GST on Income'), xero_invoice_summary_row('Total untaxable fees (no tax)', 10.0, 'GST Free Income'), @@ -589,7 +589,7 @@ describe ' xero_invoice_header, xero_invoice_summary_row('Total untaxable produce (no tax)', 12.54, 'GST Free Income', opts), - xero_invoice_summary_row('Total taxable produce (tax inclusive)', '1,500.45', + xero_invoice_summary_row('Total taxable produce (tax inclusive)', 1500.45, 'GST on Income', opts), xero_invoice_summary_row('Total untaxable fees (no tax)', 10.0, 'GST Free Income', opts), From 294f7c2fb5345581c3cf88b471c676eb88661a96 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 4 May 2022 15:11:39 +0200 Subject: [PATCH 53/54] Reports restore original columns ordering --- .../order_cycle_distributor_totals_by_supplier.rb | 4 ++-- .../orders_and_fulfillment/order_cycle_supplier_totals.rb | 4 ++-- .../order_cycle_supplier_totals_by_distributor.rb | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/reporting/reports/orders_and_fulfillment/order_cycle_distributor_totals_by_supplier.rb b/lib/reporting/reports/orders_and_fulfillment/order_cycle_distributor_totals_by_supplier.rb index be4c1ecdf1..4c5c093d36 100644 --- a/lib/reporting/reports/orders_and_fulfillment/order_cycle_distributor_totals_by_supplier.rb +++ b/lib/reporting/reports/orders_and_fulfillment/order_cycle_distributor_totals_by_supplier.rb @@ -13,8 +13,8 @@ module Reporting quantity: proc { |line_items| line_items.to_a.sum(&:quantity) }, curr_cost_per_unit: proc { |line_items| line_items.first.price }, total_cost: proc { |line_items| line_items.sum(&:amount) }, - shipping_method: proc { |line_items| line_items.first.order.shipping_method&.name }, - total_shipping_cost: proc { |_line_items| "" } + total_shipping_cost: proc { |_line_items| "" }, + shipping_method: proc { |line_items| line_items.first.order.shipping_method&.name } } end diff --git a/lib/reporting/reports/orders_and_fulfillment/order_cycle_supplier_totals.rb b/lib/reporting/reports/orders_and_fulfillment/order_cycle_supplier_totals.rb index abaf4ad8e4..1a1a085cf0 100644 --- a/lib/reporting/reports/orders_and_fulfillment/order_cycle_supplier_totals.rb +++ b/lib/reporting/reports/orders_and_fulfillment/order_cycle_supplier_totals.rb @@ -9,10 +9,10 @@ module Reporting producer: supplier_name, product: product_name, variant: variant_name, - curr_cost_per_unit: proc { |line_items| line_items.first.price }, quantity: proc { |line_items| line_items.sum(&:quantity) }, total_units: proc { |line_items| total_units(line_items) }, - total_cost: proc { |line_items| line_items.sum(&:amount) }, + curr_cost_per_unit: proc { |line_items| line_items.first.price }, + total_cost: proc { |line_items| line_items.sum(&:amount) } } end diff --git a/lib/reporting/reports/orders_and_fulfillment/order_cycle_supplier_totals_by_distributor.rb b/lib/reporting/reports/orders_and_fulfillment/order_cycle_supplier_totals_by_distributor.rb index 2a64349125..4152731bcf 100644 --- a/lib/reporting/reports/orders_and_fulfillment/order_cycle_supplier_totals_by_distributor.rb +++ b/lib/reporting/reports/orders_and_fulfillment/order_cycle_supplier_totals_by_distributor.rb @@ -9,9 +9,9 @@ module Reporting producer: supplier_name, product: product_name, variant: variant_name, - curr_cost_per_unit: proc { |line_items| line_items.first.price }, hub: hub_name, quantity: proc { |line_items| line_items.to_a.sum(&:quantity) }, + curr_cost_per_unit: proc { |line_items| line_items.first.price }, total_cost: proc { |line_items| line_items.sum(&:amount) }, shipping_method: proc { |line_items| line_items.first.order.shipping_method&.name } } From 1be3b508bf1170e9b5d7663f359a7058d7de2859 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 4 May 2022 15:57:25 +0200 Subject: [PATCH 54/54] Reports restore original results ordering --- lib/reporting/line_items.rb | 3 ++- .../reports/bulk_coop/customer_payments.rb | 18 +----------------- .../reports/orders_and_fulfillment/base.rb | 2 +- 3 files changed, 4 insertions(+), 19 deletions(-) diff --git a/lib/reporting/line_items.rb b/lib/reporting/line_items.rb index d5ee578ad3..2ffd375bc7 100644 --- a/lib/reporting/line_items.rb +++ b/lib/reporting/line_items.rb @@ -14,8 +14,9 @@ module Reporting @orders ||= search_orders end - def list(line_item_includes = nil) + def list(line_item_includes = [variant: [product: :supplier]]) line_items = order_permissions.visible_line_items.in_orders(orders.result) + .order("supplier.name", "product.name", "variant.display_name") if @params[:supplier_id_in].present? line_items = line_items.supplied_by_any(@params[:supplier_id_in]) diff --git a/lib/reporting/reports/bulk_coop/customer_payments.rb b/lib/reporting/reports/bulk_coop/customer_payments.rb index 1935f96a60..97752b3feb 100644 --- a/lib/reporting/reports/bulk_coop/customer_payments.rb +++ b/lib/reporting/reports/bulk_coop/customer_payments.rb @@ -5,7 +5,7 @@ module Reporting module BulkCoop class CustomerPayments < Base def query_result - table_items.group_by(&:order).values + table_items.reorder("spree_orders.completed_at").group_by(&:order).values end def columns @@ -18,22 +18,6 @@ module Reporting } end - def rules - [ - { - group_by: :customer, - header: true, - summary_row: proc do |_key, _items, rows| - { - total_cost: rows.sum(&:total_cost), - amount_owing: rows.sum(&:amount_owing), - amount_paid: rows.sum(&:amount_paid), - } - end - } - ] - end - private def customer_payments_total_cost(line_items) diff --git a/lib/reporting/reports/orders_and_fulfillment/base.rb b/lib/reporting/reports/orders_and_fulfillment/base.rb index 1d780a8608..703f5e12b9 100644 --- a/lib/reporting/reports/orders_and_fulfillment/base.rb +++ b/lib/reporting/reports/orders_and_fulfillment/base.rb @@ -22,7 +22,7 @@ module Reporting end def query_result - report_line_items.list(line_item_includes).group_by(&:variant).values + report_line_items.list(line_item_includes).group_by(&:variant_id).values end private