mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-21 00:47:26 +00:00
Merge pull request #13823 from chahmedejaz/bugfix/13625-enterprise-fee-reports-throws-504
Some Enterprise Fee reports are unusable when managing big shops
This commit is contained in:
@@ -1,17 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SearchableDropdownComponent < ViewComponent::Base
|
||||
REMOVED_SEARCH_PLUGIN = { 'tom-select-options-value': '{ "plugins": [] }' }.freeze
|
||||
MINIMUM_OPTIONS_FOR_SEARCH_FIELD = 11 # at least 11 options are required for the search field
|
||||
|
||||
def initialize(
|
||||
form:,
|
||||
name:,
|
||||
options:,
|
||||
selected_option:,
|
||||
placeholder_value:,
|
||||
form: nil,
|
||||
placeholder_value: '',
|
||||
include_blank: false,
|
||||
aria_label: '',
|
||||
multiple: false,
|
||||
remote_url: nil,
|
||||
other_attrs: {}
|
||||
)
|
||||
@f = form
|
||||
@@ -21,13 +22,15 @@ class SearchableDropdownComponent < ViewComponent::Base
|
||||
@placeholder_value = placeholder_value
|
||||
@include_blank = include_blank
|
||||
@aria_label = aria_label
|
||||
@multiple = multiple
|
||||
@remote_url = remote_url
|
||||
@other_attrs = other_attrs
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :f, :name, :options, :selected_option, :placeholder_value, :include_blank,
|
||||
:aria_label, :other_attrs
|
||||
:aria_label, :multiple, :remote_url, :other_attrs
|
||||
|
||||
def classes
|
||||
"fullwidth #{'no-input' if remove_search_plugin?}"
|
||||
@@ -36,11 +39,33 @@ class SearchableDropdownComponent < ViewComponent::Base
|
||||
def data
|
||||
{
|
||||
controller: "tom-select",
|
||||
'tom-select-placeholder-value': placeholder_value
|
||||
}.merge(remove_search_plugin? ? REMOVED_SEARCH_PLUGIN : {})
|
||||
'tom-select-placeholder-value': placeholder_value,
|
||||
'tom-select-options-value': tom_select_options_value,
|
||||
'tom-select-remote-url-value': remote_url,
|
||||
}
|
||||
end
|
||||
|
||||
def tom_select_options_value
|
||||
plugins = []
|
||||
plugins << 'virtual_scroll' if @remote_url.present?
|
||||
plugins << 'dropdown_input' unless remove_search_plugin?
|
||||
plugins << 'remove_button' if multiple
|
||||
|
||||
{
|
||||
plugins:,
|
||||
maxItems: multiple ? nil : 1,
|
||||
}
|
||||
end
|
||||
|
||||
def uses_form_builder?
|
||||
f.present?
|
||||
end
|
||||
|
||||
def remove_search_plugin?
|
||||
@remove_search_plugin ||= options.count < MINIMUM_OPTIONS_FOR_SEARCH_FIELD
|
||||
# Remove the search plugin when:
|
||||
# - the select is multiple (it already includes a search field), or
|
||||
# - there is no remote URL and the options are below the search threshold
|
||||
@remove_search_plugin ||= multiple ||
|
||||
(@remote_url.nil? && options.count < MINIMUM_OPTIONS_FOR_SEARCH_FIELD)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
= f.select name, options_for_select(options, selected_option), { include_blank: }, class: classes, data:, 'aria-label': aria_label, **other_attrs
|
||||
- if uses_form_builder?
|
||||
= f.select name, options, { selected: selected_option, include_blank:, multiple: }, class: classes, data:, 'aria-label': aria_label, **other_attrs
|
||||
- else
|
||||
= select_tag name, options_for_select(options, selected_option), include_blank:, multiple:, class: classes, data:, 'aria-label': aria_label, **other_attrs
|
||||
|
||||
@@ -4,6 +4,7 @@ module Admin
|
||||
class ReportsController < Spree::Admin::BaseController
|
||||
include ActiveStorage::SetCurrent
|
||||
include ReportsActions
|
||||
include Reports::AjaxSearch
|
||||
|
||||
helper ReportsHelper
|
||||
|
||||
|
||||
108
app/controllers/concerns/reports/ajax_search.rb
Normal file
108
app/controllers/concerns/reports/ajax_search.rb
Normal file
@@ -0,0 +1,108 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Reports
|
||||
module AjaxSearch
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def search_enterprise_fees
|
||||
report = report_class.new(spree_current_user, params, render: false)
|
||||
fee_ids = enterprise_fee_ids(report.search.result)
|
||||
query = EnterpriseFee.where(id: fee_ids)
|
||||
|
||||
render json: build_search_response(query)
|
||||
end
|
||||
|
||||
def search_enterprise_fee_owners
|
||||
report = report_class.new(spree_current_user, params, render: false)
|
||||
owner_ids = enterprise_fee_owner_ids(report.search.result)
|
||||
query = Enterprise.where(id: owner_ids)
|
||||
|
||||
render json: build_search_response(query)
|
||||
end
|
||||
|
||||
def search_distributors
|
||||
query = frontend_data.distributors
|
||||
|
||||
render json: build_search_response(query)
|
||||
end
|
||||
|
||||
def search_order_cycles
|
||||
query = frontend_data.order_cycles
|
||||
|
||||
render json: build_search_response(query)
|
||||
end
|
||||
|
||||
def search_order_customers
|
||||
query = frontend_data.order_customers
|
||||
|
||||
render json: build_search_response(query)
|
||||
end
|
||||
|
||||
def search_suppliers
|
||||
query = frontend_data.orders_suppliers
|
||||
|
||||
render json: build_search_response(query)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_search_response(query)
|
||||
page = (params[:page] || 1).to_i
|
||||
per_page = 30
|
||||
|
||||
filtered_query = apply_search_filter(query)
|
||||
total_count = filtered_query.size
|
||||
items = paginated_items(filtered_query, page, per_page)
|
||||
results = format_results(items)
|
||||
|
||||
{ results: results, pagination: { more: (page * per_page) < total_count } }
|
||||
end
|
||||
|
||||
def apply_search_filter(query)
|
||||
search_term = params[:q]
|
||||
return query if search_term.blank?
|
||||
|
||||
escaped_search_term = ActiveRecord::Base.sanitize_sql_like(search_term)
|
||||
pattern = "%#{escaped_search_term}%"
|
||||
|
||||
# Handle different model types
|
||||
if query.model == OrderCycle
|
||||
query.where("order_cycles.name ILIKE ?", pattern)
|
||||
elsif query.model == Customer
|
||||
query.where("customers.email ILIKE ?", pattern)
|
||||
else
|
||||
query.where("name ILIKE ?", pattern)
|
||||
end
|
||||
end
|
||||
|
||||
def paginated_items(query, page, per_page)
|
||||
if query.model == Customer
|
||||
query.order(:email).offset((page - 1) * per_page).limit(per_page).pluck(:email, :id)
|
||||
elsif query.model == OrderCycle
|
||||
query.order('order_cycles.orders_close_at DESC')
|
||||
.offset((page - 1) * per_page)
|
||||
.limit(per_page).pluck(
|
||||
:name, :id
|
||||
)
|
||||
else
|
||||
query.order(:name).offset((page - 1) * per_page).limit(per_page).pluck(:name, :id)
|
||||
end
|
||||
end
|
||||
|
||||
def format_results(items)
|
||||
items.map { |label, value| { value:, label: } }
|
||||
end
|
||||
|
||||
def frontend_data
|
||||
@frontend_data ||= Reporting::FrontendData.new(spree_current_user)
|
||||
end
|
||||
|
||||
def enterprise_fee_owner_ids(orders)
|
||||
EnterpriseFee.where(id: enterprise_fee_ids(orders)).select(:enterprise_id)
|
||||
end
|
||||
|
||||
def enterprise_fee_ids(orders)
|
||||
Spree::Adjustment.enterprise_fee.where(order_id: orders.select(:id)).select(:originator_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -34,29 +34,8 @@ module ReportsHelper
|
||||
end
|
||||
end
|
||||
|
||||
def fee_name_options(orders)
|
||||
EnterpriseFee.where(id: enterprise_fee_ids(orders))
|
||||
.pluck(:name, :id)
|
||||
end
|
||||
|
||||
def fee_owner_options(orders)
|
||||
Enterprise.where(id: enterprise_fee_owner_ids(orders))
|
||||
.pluck(:name, :id)
|
||||
end
|
||||
|
||||
delegate :currency_symbol, to: :'Spree::Money'
|
||||
|
||||
def enterprise_fee_owner_ids(orders)
|
||||
EnterpriseFee.where(id: enterprise_fee_ids(orders))
|
||||
.pluck(:enterprise_id)
|
||||
end
|
||||
|
||||
def enterprise_fee_ids(orders)
|
||||
Spree::Adjustment.enterprise_fee
|
||||
.where(order_id: orders.map(&:id))
|
||||
.pluck(:originator_id)
|
||||
end
|
||||
|
||||
def datepicker_time(datetime)
|
||||
datetime = Time.zone.parse(datetime) if datetime.is_a? String
|
||||
datetime.strftime('%Y-%m-%d %H:%M')
|
||||
|
||||
@@ -6,6 +6,11 @@ module Spree
|
||||
class Ability
|
||||
include CanCan::Ability
|
||||
|
||||
REPORTS_SEARCH_ACTIONS = [
|
||||
:search_enterprise_fees, :search_enterprise_fee_owners, :search_distributors,
|
||||
:search_suppliers, :search_order_cycles, :search_order_customers
|
||||
].freeze
|
||||
|
||||
def initialize(user)
|
||||
clear_aliased_actions
|
||||
|
||||
@@ -260,7 +265,8 @@ module Spree
|
||||
can [:admin, :index, :import], ::Admin::DfcProductImportsController
|
||||
|
||||
# Reports page
|
||||
can [:admin, :index, :show, :create], ::Admin::ReportsController
|
||||
can [:admin, :index, :show, :create, *REPORTS_SEARCH_ACTIONS],
|
||||
::Admin::ReportsController
|
||||
can [:admin, :show, :create, :customers, :orders_and_distributors, :group_buys, :payments,
|
||||
:orders_and_fulfillment, :products_and_inventory, :order_cycle_management,
|
||||
:packing, :enterprise_fee_summary, :bulk_coop, :suppliers], :report
|
||||
@@ -392,7 +398,7 @@ module Spree
|
||||
end
|
||||
|
||||
# Reports page
|
||||
can [:admin, :index, :show, :create], ::Admin::ReportsController
|
||||
can [:admin, :index, :show, :create, *REPORTS_SEARCH_ACTIONS], ::Admin::ReportsController
|
||||
can [:admin, :customers, :group_buys, :sales_tax, :payments,
|
||||
:orders_and_distributors, :orders_and_fulfillment, :products_and_inventory,
|
||||
:order_cycle_management, :xero_invoices, :enterprise_fee_summary, :bulk_coop], :report
|
||||
|
||||
@@ -1,23 +1,50 @@
|
||||
- search_url_query = {report_type: :enterprise_fee_summary, report_subtype: :enterprise_fees_with_tax_report_by_order}
|
||||
.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})
|
||||
.omega.fourteen.columns
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :distributor_id_in,
|
||||
options: [],
|
||||
selected_option: params.dig(:q, :distributor_id_in),
|
||||
multiple: true,
|
||||
remote_url: admin_reports_search_distributors_url))
|
||||
|
||||
.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})
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :order_cycle_id_in,
|
||||
options: [],
|
||||
selected_option: params.dig(:q, :order_cycle_id_in),
|
||||
multiple: true,
|
||||
remote_url: admin_reports_search_order_cycles_url))
|
||||
|
||||
.row
|
||||
.alpha.two.columns= label_tag nil, t(:fee_name)
|
||||
.omega.fourteen.columns
|
||||
= f.select(:enterprise_fee_id_in, fee_name_options(@report.search.result), {selected: params.dig(:q, :enterprise_fee_id_in)}, {class: "select2 fullwidth", multiple: true})
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :enterprise_fee_id_in,
|
||||
options: [],
|
||||
selected_option: params.dig(:q, :enterprise_fee_id_in),
|
||||
multiple: true,
|
||||
remote_url: admin_reports_search_enterprise_fees_url(search_url_query)))
|
||||
|
||||
.row
|
||||
.alpha.two.columns= label_tag nil, t(:fee_owner)
|
||||
.omega.fourteen.columns
|
||||
= f.select(:enterprise_fee_owner_id_in, fee_owner_options(@report.search.result), {selected: params.dig(:q, :enterprise_fee_owner_id_in)}, {class: "select2 fullwidth", multiple: true})
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :enterprise_fee_owner_id_in,
|
||||
options: [],
|
||||
selected_option: params.dig(:q, :enterprise_fee_owner_id_in),
|
||||
multiple: true,
|
||||
remote_url: admin_reports_search_enterprise_fee_owners_url(search_url_query)))
|
||||
|
||||
.row
|
||||
.alpha.two.columns= label_tag nil, t(:report_customers)
|
||||
.omega.fourteen.columns
|
||||
= f.select(:customer_id_in, customer_email_options(@data.order_customers), {selected: params.dig(:q, :customer_id_in)}, {class: "select2 fullwidth", multiple: true})
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :customer_id_in,
|
||||
options: [],
|
||||
selected_option: params.dig(:q, :customer_id_in),
|
||||
multiple: true,
|
||||
remote_url: admin_reports_search_order_customers_url))
|
||||
|
||||
@@ -1,27 +1,57 @@
|
||||
- search_url_query = {report_type: :enterprise_fee_summary, report_subtype: :enterprise_fees_with_tax_report_by_producer}
|
||||
.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})
|
||||
|
||||
.omega.fourteen.columns
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :distributor_id_in,
|
||||
options: [],
|
||||
selected_option: params.dig(:q, :distributor_id_in),
|
||||
multiple: true,
|
||||
remote_url: admin_reports_search_distributors_url))
|
||||
.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})
|
||||
.omega.fourteen.columns
|
||||
= render(SearchableDropdownComponent.new(name: :supplier_id_in,
|
||||
options: [],
|
||||
selected_option: params.dig(:supplier_id_in),
|
||||
multiple: true,
|
||||
remote_url: admin_reports_search_suppliers_url))
|
||||
|
||||
.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})
|
||||
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :order_cycle_id_in,
|
||||
options: [],
|
||||
selected_option: params.dig(:q, :order_cycle_id_in),
|
||||
multiple: true,
|
||||
remote_url: admin_reports_search_order_cycles_url))
|
||||
.row
|
||||
.alpha.two.columns= label_tag nil, t(:fee_name)
|
||||
.omega.fourteen.columns
|
||||
= f.select(:enterprise_fee_id_in, fee_name_options(@report.search.result), {selected: params.dig(:q, :enterprise_fee_id_in)}, {class: "select2 fullwidth", multiple: true})
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :enterprise_fee_id_in,
|
||||
options: [],
|
||||
selected_option: params.dig(:q, :enterprise_fee_id_in),
|
||||
multiple: true,
|
||||
remote_url: admin_reports_search_enterprise_fees_url(search_url_query)))
|
||||
|
||||
.row
|
||||
.alpha.two.columns= label_tag nil, t(:fee_owner)
|
||||
.omega.fourteen.columns
|
||||
= f.select(:enterprise_fee_owner_id_in, fee_owner_options(@report.search.result), {selected: params.dig(:q, :enterprise_fee_owner_id_in)}, {class: "select2 fullwidth", multiple: true})
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :enterprise_fee_owner_id_in,
|
||||
options: [],
|
||||
selected_option: params.dig(:q, :enterprise_fee_owner_id_in),
|
||||
multiple: true,
|
||||
remote_url: admin_reports_search_enterprise_fee_owners_url(search_url_query)))
|
||||
|
||||
.row
|
||||
.alpha.two.columns= label_tag nil, t(:report_customers)
|
||||
.omega.fourteen.columns
|
||||
= f.select(:customer_id_in, customer_email_options(@data.order_customers), {selected: params.dig(:q, :customer_id_in)}, {class: "select2 fullwidth", multiple: true})
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :customer_id_in,
|
||||
options: [],
|
||||
selected_option: params.dig(:q, :customer_id_in),
|
||||
multiple: true,
|
||||
remote_url: admin_reports_search_order_customers_url))
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { Controller } from "stimulus";
|
||||
import TomSelect from "tom-select/dist/esm/tom-select.complete";
|
||||
import showHttpError from "../../webpacker/js/services/show_http_error";
|
||||
|
||||
export default class extends Controller {
|
||||
static values = { options: Object, placeholder: String };
|
||||
static values = {
|
||||
options: Object,
|
||||
placeholder: String,
|
||||
remoteUrl: String,
|
||||
};
|
||||
|
||||
connect(options = {}) {
|
||||
this.control = new TomSelect(this.element, {
|
||||
let tomSelectOptions = {
|
||||
maxItems: 1,
|
||||
maxOptions: null,
|
||||
plugins: ["dropdown_input"],
|
||||
@@ -16,7 +21,13 @@ export default class extends Controller {
|
||||
},
|
||||
...this.optionsValue,
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
if (this.remoteUrlValue) {
|
||||
this.#addRemoteOptions(tomSelectOptions);
|
||||
}
|
||||
|
||||
this.control = new TomSelect(this.element, tomSelectOptions);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
@@ -29,4 +40,78 @@ export default class extends Controller {
|
||||
const optionsArray = [...this.element.options];
|
||||
return optionsArray.find((option) => [null, ""].includes(option.value))?.text;
|
||||
}
|
||||
|
||||
#buildUrl(query, page = 1) {
|
||||
const url = new URL(this.remoteUrlValue, window.location.origin);
|
||||
url.searchParams.set("q", query);
|
||||
url.searchParams.set("page", page);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
#fetchOptions(query, callback) {
|
||||
const url = this.control.getUrl(query);
|
||||
|
||||
fetch(url)
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
showHttpError(response.status);
|
||||
throw response;
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((json) => {
|
||||
/**
|
||||
* Expected API shape:
|
||||
* {
|
||||
* results: [{ value, label }],
|
||||
* pagination: { more: boolean }
|
||||
* }
|
||||
*/
|
||||
if (json.pagination?.more) {
|
||||
const currentUrl = new URL(url);
|
||||
const currentPage = parseInt(currentUrl.searchParams.get("page") || "1");
|
||||
const nextUrl = this.#buildUrl(query, currentPage + 1);
|
||||
this.control.setNextUrl(query, nextUrl);
|
||||
}
|
||||
|
||||
callback(json.results || []);
|
||||
})
|
||||
.catch((error) => {
|
||||
callback();
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
#addRemoteOptions(options) {
|
||||
this.openedByClick = false;
|
||||
|
||||
options.firstUrl = (query) => {
|
||||
return this.#buildUrl(query, 1);
|
||||
};
|
||||
|
||||
options.load = this.#fetchOptions.bind(this);
|
||||
|
||||
options.onFocus = function () {
|
||||
this.control.load("", () => {});
|
||||
}.bind(this);
|
||||
|
||||
options.onDropdownOpen = function () {
|
||||
this.openedByClick = true;
|
||||
}.bind(this);
|
||||
|
||||
options.onType = function () {
|
||||
this.openedByClick = false;
|
||||
}.bind(this);
|
||||
|
||||
// As per TomSelect source code, Loading state is shown on the UI when this function returns true.
|
||||
// By default it shows loading state only when there is some input in the search box.
|
||||
// We want to show loading state on focus as well (when there is no input) to indicate that options are being loaded.
|
||||
options.shouldLoad = function (query) {
|
||||
return this.openedByClick || query.length > 0;
|
||||
}.bind(this);
|
||||
|
||||
options.valueField = "value";
|
||||
options.labelField = "label";
|
||||
options.searchField = "label";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,15 @@ Openfoodnetwork::Application.routes.draw do
|
||||
put :resume, on: :member, format: :json
|
||||
end
|
||||
|
||||
get '/reports', to: 'reports#index', as: :reports
|
||||
scope :reports, as: :reports do
|
||||
get '/', to: 'reports#index'
|
||||
get '/search_enterprise_fees', to: 'reports#search_enterprise_fees', as: :search_enterprise_fees
|
||||
get '/search_enterprise_fee_owners', to: 'reports#search_enterprise_fee_owners', as: :search_enterprise_fee_owners
|
||||
get '/search_distributors', to: 'reports#search_distributors', as: :search_distributors
|
||||
get '/search_suppliers', to: 'reports#search_suppliers', as: :search_suppliers
|
||||
get '/search_order_cycles', to: 'reports#search_order_cycles', as: :search_order_cycles
|
||||
get '/search_order_customers', to: 'reports#search_order_customers', as: :search_order_customers
|
||||
end
|
||||
match '/reports/:report_type(/:report_subtype)', to: 'reports#show', via: :get, as: :report
|
||||
match '/reports/:report_type(/:report_subtype)', to: 'reports#create', via: :post
|
||||
end
|
||||
|
||||
@@ -179,7 +179,7 @@ module.exports = {
|
||||
// transform: { "\\.[jt]sx?$": "babel-jest" },
|
||||
|
||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||
transformIgnorePatterns: ["/node_modules/(?!stimulus)/"],
|
||||
transformIgnorePatterns: ["/node_modules/(?!(stimulus.+|tom-select)/)"],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||
// unmockedModulePathPatterns: undefined,
|
||||
|
||||
@@ -46,7 +46,7 @@ module Reporting
|
||||
end
|
||||
|
||||
def order_customers
|
||||
Customer.where(id: visible_order_customer_ids).select("customers.id, customers.email")
|
||||
Customer.where(id: visible_order_customer_ids_query).select("customers.id, customers.email")
|
||||
end
|
||||
|
||||
private
|
||||
@@ -57,8 +57,8 @@ module Reporting
|
||||
@permissions ||= OpenFoodNetwork::Permissions.new(current_user)
|
||||
end
|
||||
|
||||
def visible_order_customer_ids
|
||||
Permissions::Order.new(current_user).visible_orders.pluck(:customer_id)
|
||||
def visible_order_customer_ids_query
|
||||
Permissions::Order.new(current_user).visible_orders.select(:customer_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
296
spec/javascripts/stimulus/tom_select_controller_test.js
Normal file
296
spec/javascripts/stimulus/tom_select_controller_test.js
Normal file
@@ -0,0 +1,296 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { Application } from "stimulus";
|
||||
import { fireEvent, waitFor } from "@testing-library/dom";
|
||||
import tom_select_controller from "controllers/tom_select_controller";
|
||||
import showHttpError from "js/services/show_http_error";
|
||||
|
||||
jest.mock("js/services/show_http_error", () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Helpers
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
const buildResults = (count, start = 1) =>
|
||||
Array.from({ length: count }, (_, i) => ({
|
||||
value: String(start + i),
|
||||
label: `Option ${start + i}`,
|
||||
}));
|
||||
|
||||
const setupDOM = (html) => {
|
||||
document.body.innerHTML = html;
|
||||
};
|
||||
|
||||
const getSelect = () => document.getElementById("select");
|
||||
const getTomSelect = () => getSelect().tomselect;
|
||||
|
||||
const openDropdown = () => fireEvent.click(document.getElementById("select-ts-control"));
|
||||
|
||||
const mockRemoteFetch = (...responses) => {
|
||||
responses.forEach((response) => {
|
||||
fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => response,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const mockDropdownScroll = (
|
||||
dropdown,
|
||||
{ scrollHeight = 1000, clientHeight = 300, scrollTop = 700 } = {},
|
||||
) => {
|
||||
Object.defineProperty(dropdown, "scrollHeight", {
|
||||
configurable: true,
|
||||
value: scrollHeight,
|
||||
});
|
||||
|
||||
Object.defineProperty(dropdown, "clientHeight", {
|
||||
configurable: true,
|
||||
value: clientHeight,
|
||||
});
|
||||
|
||||
Object.defineProperty(dropdown, "scrollTop", {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: scrollTop,
|
||||
});
|
||||
|
||||
fireEvent.scroll(dropdown);
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Expectation helpers
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
const expectOptionsCount = (count) => {
|
||||
expect(document.querySelectorAll('.ts-dropdown-content [role="option"]').length).toBe(count);
|
||||
};
|
||||
|
||||
const expectDropdownToContain = (text) => {
|
||||
expect(document.querySelector(".ts-dropdown-content")?.textContent).toContain(text);
|
||||
};
|
||||
|
||||
const expectDropdownWithNoResults = () => {
|
||||
expect(document.querySelector(".ts-dropdown-content")?.textContent).toBe("No results found");
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Specs
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
describe("TomSelectController", () => {
|
||||
let application;
|
||||
|
||||
beforeAll(() => {
|
||||
application = Application.start();
|
||||
application.register("tom-select", tom_select_controller);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
global.fetch = jest.fn();
|
||||
global.I18n = { t: (key) => key };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = "";
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("connect()", () => {
|
||||
beforeEach(() => {
|
||||
setupDOM(`
|
||||
<select id="select" data-controller="tom-select">
|
||||
<option value="">Default Option</option>
|
||||
<option value="1">Option 1</option>
|
||||
<option value="2">Option 2</option>
|
||||
</select>
|
||||
`);
|
||||
});
|
||||
|
||||
it("initializes TomSelect with default options", () => {
|
||||
const settings = getTomSelect().settings;
|
||||
|
||||
expect(settings.placeholder).toBe("Default Option");
|
||||
expect(settings.maxItems).toBe(1);
|
||||
expect(settings.plugins).toEqual(["dropdown_input"]);
|
||||
expect(settings.allowEmptyOption).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("connect() with custom values", () => {
|
||||
beforeEach(() => {
|
||||
setupDOM(`
|
||||
<select
|
||||
id="select"
|
||||
data-controller="tom-select"
|
||||
data-tom-select-placeholder-value="Choose an option"
|
||||
data-tom-select-options-value='{"maxItems": 3, "plugins": ["remove_button"]}'
|
||||
>
|
||||
<option value="1">Option 1</option>
|
||||
<option value="2">Option 2</option>
|
||||
</select>
|
||||
`);
|
||||
});
|
||||
|
||||
it("applies custom placeholder and options", () => {
|
||||
const settings = getTomSelect().settings;
|
||||
|
||||
expect(settings.placeholder).toBe("Choose an option");
|
||||
expect(settings.maxItems).toBe(3);
|
||||
expect(settings.plugins).toEqual(["remove_button"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("connect() with remoteUrl", () => {
|
||||
beforeEach(() => {
|
||||
setupDOM(`
|
||||
<select
|
||||
id="select"
|
||||
data-controller="tom-select"
|
||||
data-tom-select-options-value='{"plugins":["virtual_scroll"]}'
|
||||
data-tom-select-remote-url-value="https://ofn-tests.com/api/search"
|
||||
></select>
|
||||
`);
|
||||
});
|
||||
|
||||
it("configures remote loading callbacks", () => {
|
||||
const settings = getTomSelect().settings;
|
||||
|
||||
expect(settings.valueField).toBe("value");
|
||||
expect(settings.labelField).toBe("label");
|
||||
expect(settings.searchField).toBe("label");
|
||||
expect(settings.load).toEqual(expect.any(Function));
|
||||
expect(settings.firstUrl).toEqual(expect.any(Function));
|
||||
expect(settings.onFocus).toEqual(expect.any(Function));
|
||||
});
|
||||
|
||||
it("fetches page 1 on focus", async () => {
|
||||
mockRemoteFetch({
|
||||
results: buildResults(1),
|
||||
pagination: { more: false },
|
||||
});
|
||||
|
||||
openDropdown();
|
||||
|
||||
await waitFor(() => expect(fetch).toHaveBeenCalledTimes(1));
|
||||
|
||||
expect(fetch).toHaveBeenCalledWith(expect.stringContaining("q=&page=1"));
|
||||
|
||||
await waitFor(() => {
|
||||
expectOptionsCount(1);
|
||||
expectDropdownToContain("Option 1");
|
||||
});
|
||||
});
|
||||
|
||||
it("fetches remote options using search query", async () => {
|
||||
const appleOption = { value: "apple", label: "Apple" };
|
||||
mockRemoteFetch({
|
||||
results: [...buildResults(1), appleOption],
|
||||
pagination: { more: false },
|
||||
});
|
||||
|
||||
openDropdown();
|
||||
|
||||
await waitFor(() => {
|
||||
expectOptionsCount(2);
|
||||
});
|
||||
|
||||
mockRemoteFetch({
|
||||
results: [appleOption],
|
||||
pagination: { more: false },
|
||||
});
|
||||
|
||||
fireEvent.input(document.getElementById("select-ts-control"), {
|
||||
target: { value: "apple" },
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(fetch).toHaveBeenCalledWith(expect.stringContaining("q=apple&page=1")),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expectOptionsCount(1);
|
||||
expectDropdownToContain("Apple");
|
||||
});
|
||||
});
|
||||
|
||||
it("loads next page on scroll (infinite scroll)", async () => {
|
||||
mockRemoteFetch(
|
||||
{
|
||||
results: buildResults(30),
|
||||
pagination: { more: true },
|
||||
},
|
||||
{
|
||||
results: buildResults(1, 31),
|
||||
pagination: { more: false },
|
||||
},
|
||||
);
|
||||
|
||||
openDropdown();
|
||||
|
||||
await waitFor(() => {
|
||||
expectOptionsCount(30);
|
||||
});
|
||||
|
||||
const dropdown = document.querySelector(".ts-dropdown-content");
|
||||
mockDropdownScroll(dropdown);
|
||||
|
||||
await waitFor(() => {
|
||||
expectOptionsCount(31);
|
||||
});
|
||||
|
||||
expect(fetch).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("handles fetch errors gracefully", async () => {
|
||||
fetch.mockRejectedValueOnce(new Error("Fetch error"));
|
||||
|
||||
openDropdown();
|
||||
|
||||
await waitFor(() => {
|
||||
expectDropdownWithNoResults();
|
||||
});
|
||||
|
||||
expect(showHttpError).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("displays HTTP error on failure", async () => {
|
||||
fetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 500,
|
||||
json: async () => ({}),
|
||||
});
|
||||
|
||||
openDropdown();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(showHttpError).toHaveBeenCalledWith(500);
|
||||
});
|
||||
|
||||
expectDropdownWithNoResults();
|
||||
});
|
||||
|
||||
it("controls loading behavior based on user interaction", () => {
|
||||
const settings = getTomSelect().settings;
|
||||
|
||||
// Initial state: openedByClick is false, query is empty
|
||||
expect(settings.shouldLoad("")).toBe(false);
|
||||
|
||||
// Simulating opening the dropdown
|
||||
settings.onDropdownOpen();
|
||||
expect(settings.shouldLoad("")).toBe(true);
|
||||
|
||||
// Simulating typing
|
||||
settings.onType();
|
||||
expect(settings.shouldLoad("")).toBe(false);
|
||||
|
||||
// Query present
|
||||
expect(settings.shouldLoad("a")).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
328
spec/requests/admin/reports_ajax_api_spec.rb
Normal file
328
spec/requests/admin/reports_ajax_api_spec.rb
Normal file
@@ -0,0 +1,328 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe "Admin Reports AJAX Search API" do
|
||||
let(:bill_address) { create(:address) }
|
||||
let(:ship_address) { create(:address) }
|
||||
let(:instructions) { "pick up on thursday please" }
|
||||
let(:coordinator1) { create(:distributor_enterprise) }
|
||||
let(:supplier1) { create(:supplier_enterprise) }
|
||||
let(:supplier2) { create(:supplier_enterprise) }
|
||||
let(:supplier3) { create(:supplier_enterprise) }
|
||||
let(:distributor1) { create(:distributor_enterprise) }
|
||||
let(:distributor2) { create(:distributor_enterprise) }
|
||||
let(:product1) { create(:product, price: 12.34, supplier_id: supplier1.id) }
|
||||
let(:product2) { create(:product, price: 23.45, supplier_id: supplier2.id) }
|
||||
let(:product3) { create(:product, price: 34.56, supplier_id: supplier3.id) }
|
||||
|
||||
let(:enterprise_fee1) { create(:enterprise_fee, name: "Delivery Fee", enterprise: distributor1) }
|
||||
let(:enterprise_fee2) { create(:enterprise_fee, name: "Admin Fee", enterprise: distributor2) }
|
||||
|
||||
let(:ocA) {
|
||||
create(:simple_order_cycle, coordinator: coordinator1,
|
||||
distributors: [distributor1, distributor2],
|
||||
suppliers: [supplier1, supplier2, supplier3],
|
||||
variants: [product1.variants.first, product3.variants.first])
|
||||
}
|
||||
let(:ocB) {
|
||||
create(:simple_order_cycle, coordinator: coordinator1,
|
||||
distributors: [distributor1, distributor2],
|
||||
suppliers: [supplier1, supplier2, supplier3],
|
||||
variants: [product2.variants.first])
|
||||
}
|
||||
|
||||
let(:orderA1) do
|
||||
order = create(:order, distributor: distributor1, bill_address:,
|
||||
ship_address:, special_instructions: instructions,
|
||||
order_cycle: ocA)
|
||||
order.line_items << create(:line_item, variant: product1.variants.first)
|
||||
order.line_items << create(:line_item, variant: product3.variants.first)
|
||||
order.finalize!
|
||||
order.save
|
||||
order
|
||||
end
|
||||
|
||||
let(:orderA2) do
|
||||
order = create(:order, distributor: distributor2, bill_address:,
|
||||
ship_address:, special_instructions: instructions,
|
||||
order_cycle: ocA)
|
||||
order.line_items << create(:line_item, variant: product2.variants.first)
|
||||
order.finalize!
|
||||
order.save
|
||||
order
|
||||
end
|
||||
|
||||
let(:orderB1) do
|
||||
order = create(:order, distributor: distributor1, bill_address:,
|
||||
ship_address:, special_instructions: instructions,
|
||||
order_cycle: ocB)
|
||||
order.line_items << create(:line_item, variant: product1.variants.first)
|
||||
order.line_items << create(:line_item, variant: product3.variants.first)
|
||||
order.finalize!
|
||||
order.save
|
||||
order
|
||||
end
|
||||
|
||||
let(:base_params) do
|
||||
{
|
||||
report_type: :enterprise_fee_summary,
|
||||
report_subtype: :enterprise_fees_with_tax_report_by_order
|
||||
}
|
||||
end
|
||||
|
||||
def create_adjustment(order, fee, amount)
|
||||
order.adjustments.create!(
|
||||
originator: fee,
|
||||
label: fee.name,
|
||||
amount:,
|
||||
state: "finalized",
|
||||
order:
|
||||
)
|
||||
end
|
||||
|
||||
context "when user is an admin" do
|
||||
before do
|
||||
login_as create(:admin_user)
|
||||
create_adjustment(orderA1, enterprise_fee1, 5.0)
|
||||
create_adjustment(orderB1, enterprise_fee2, 3.0)
|
||||
end
|
||||
|
||||
describe "GET /admin/reports/search_enterprise_fees" do
|
||||
it "returns enterprise fees sorted alphabetically by name" do
|
||||
get "/admin/reports/search_enterprise_fees", params: base_params
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
json_response = response.parsed_body
|
||||
|
||||
expect(json_response["results"].pluck("label")).to eq(['Admin Fee', 'Delivery Fee'])
|
||||
expect(json_response["pagination"]["more"]).to be false
|
||||
end
|
||||
|
||||
context "with more than 30 records" do
|
||||
before do
|
||||
create_list(:enterprise_fee, 35, enterprise: distributor1) do |fee, i|
|
||||
index = (i + 1).to_s.rjust(2, "0")
|
||||
fee.update!(name: "Fee #{index}")
|
||||
create_adjustment(orderA1, fee, 1.0)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns first page with 30 results and more flag as true" do
|
||||
get "/admin/reports/search_enterprise_fees", params: base_params.merge(page: 1)
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(json_response["results"].length).to eq(30)
|
||||
expect(json_response["pagination"]["more"]).to be true
|
||||
end
|
||||
|
||||
it "returns remaining results on second page with more flag as false" do
|
||||
get "/admin/reports/search_enterprise_fees", params: base_params.merge(page: 2)
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(json_response["results"].length).to eq(7)
|
||||
expect(json_response["pagination"]["more"]).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /admin/reports/search_enterprise_fee_owners" do
|
||||
it "returns unique enterprise fee owners sorted alphabetically by name" do
|
||||
distributor1.update!(name: "Zebra Farm")
|
||||
distributor2.update!(name: "Alpha Market")
|
||||
|
||||
get "/admin/reports/search_enterprise_fee_owners", params: base_params
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
json_response = response.parsed_body
|
||||
|
||||
expect(json_response["results"].pluck("label")).to eq(['Alpha Market', 'Zebra Farm'])
|
||||
expect(json_response["pagination"]["more"]).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /admin/reports/search_order_customers" do
|
||||
let!(:customer1) { create(:customer, email: "alice@example.com", enterprise: distributor1) }
|
||||
let!(:customer2) { create(:customer, email: "bob@example.com", enterprise: distributor1) }
|
||||
|
||||
before do
|
||||
orderA1.update!(customer: customer1)
|
||||
orderA2.update!(customer: customer2)
|
||||
end
|
||||
|
||||
it "returns all customers sorted by email" do
|
||||
get "/admin/reports/search_order_customers", params: base_params
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(json_response["results"].pluck("label")).to eq(["alice@example.com",
|
||||
"bob@example.com"])
|
||||
expect(json_response["pagination"]["more"]).to be false
|
||||
end
|
||||
|
||||
it "filters customers by email query" do
|
||||
get "/admin/reports/search_order_customers", params: base_params.merge(q: "alice")
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(json_response["results"].pluck("label")).to eq(["alice@example.com"])
|
||||
expect(json_response["pagination"]["more"]).to be false
|
||||
end
|
||||
|
||||
context "with more than 30 customers" do
|
||||
before do
|
||||
create_list(:customer, 35, enterprise: distributor1) do |customer, i|
|
||||
customer.update!(email: "customer#{(i + 1).to_s.rjust(2, '0')}@example.com")
|
||||
order = create(:order, distributor: distributor1, order_cycle: ocA, customer:)
|
||||
order.line_items << create(:line_item, variant: product1.variants.first)
|
||||
order.finalize!
|
||||
end
|
||||
end
|
||||
|
||||
it "returns first page with 30 results and more flag as true" do
|
||||
get "/admin/reports/search_order_customers", params: base_params.merge(page: 1)
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(json_response["results"].length).to eq(30)
|
||||
expect(json_response["pagination"]["more"]).to be true
|
||||
end
|
||||
|
||||
it "returns remaining results on second page with more flag as false" do
|
||||
get "/admin/reports/search_order_customers", params: base_params.merge(page: 2)
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(json_response["results"].length).to eq(7)
|
||||
expect(json_response["pagination"]["more"]).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /admin/reports/search_order_cycles" do
|
||||
before do
|
||||
ocA.update!(name: "Winter Market")
|
||||
ocB.update!(name: "Summer Market")
|
||||
end
|
||||
|
||||
it "returns order cycles sorted by close date" do
|
||||
get "/admin/reports/search_order_cycles", params: base_params
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(json_response["results"].pluck("label")).to eq(["Summer Market", "Winter Market"])
|
||||
expect(json_response["pagination"]["more"]).to be false
|
||||
end
|
||||
|
||||
it "filters order cycles by name query" do
|
||||
get "/admin/reports/search_order_cycles", params: base_params.merge(q: "Winter")
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(json_response["results"].pluck("label")).to eq(["Winter Market"])
|
||||
expect(json_response["pagination"]["more"]).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /admin/reports/search_distributors" do
|
||||
before do
|
||||
distributor1.update!(name: "Alpha Farm")
|
||||
distributor2.update!(name: "Beta Market")
|
||||
end
|
||||
|
||||
it "filters distributors by name query" do
|
||||
get "/admin/reports/search_distributors", params: base_params.merge(q: "Alpha")
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(json_response["results"].pluck("label")).to eq(["Alpha Farm"])
|
||||
expect(json_response["pagination"]["more"]).to be false
|
||||
end
|
||||
|
||||
context "with more than 30 distributors" do
|
||||
before { create_list(:distributor_enterprise, 35) }
|
||||
|
||||
it "returns first page with 30 results and more flag as true" do
|
||||
get "/admin/reports/search_distributors", params: base_params.merge(page: 1)
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(json_response["results"].length).to eq(30)
|
||||
expect(json_response["pagination"]["more"]).to be true
|
||||
end
|
||||
|
||||
it "returns remaining results on subsequent pages with more flag as false" do
|
||||
get "/admin/reports/search_distributors", params: base_params.merge(page: 2)
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(json_response["results"].length).to be > 0
|
||||
expect(json_response["pagination"]["more"]).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is not an admin" do
|
||||
before do
|
||||
login_as distributor1.users.first
|
||||
create_adjustment(orderA1, enterprise_fee1, 5.0)
|
||||
create_adjustment(orderA2, enterprise_fee2, 3.0)
|
||||
end
|
||||
|
||||
describe "GET /admin/reports/search_enterprise_fees" do
|
||||
it "returns only enterprise fees for user's managed enterprises" do
|
||||
get "/admin/reports/search_enterprise_fees", params: base_params
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
json_response = response.parsed_body
|
||||
|
||||
expect(json_response["results"].pluck("label")).to eq(['Delivery Fee'])
|
||||
expect(json_response["pagination"]["more"]).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /admin/reports/search_enterprise_fee_owners" do
|
||||
it "returns only enterprise fee owners for user's managed enterprises" do
|
||||
get "/admin/reports/search_enterprise_fee_owners", params: base_params
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
json_response = response.parsed_body
|
||||
|
||||
expect(json_response["results"].pluck("label")).to eq([distributor1.name])
|
||||
expect(json_response["pagination"]["more"]).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /admin/reports/search_order_customers" do
|
||||
it "returns only customers from user's managed enterprises" do
|
||||
customer1 = create(:customer, email: "alice@example.com", enterprise: distributor1)
|
||||
customer2 = create(:customer, email: "bob@example.com", enterprise: distributor1)
|
||||
orderA1.update!(customer: customer1)
|
||||
orderA2.update!(customer: customer2)
|
||||
|
||||
get "/admin/reports/search_order_customers", params: base_params
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(json_response["results"].pluck("label")).to eq(["alice@example.com"])
|
||||
expect(json_response["pagination"]["more"]).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /admin/reports/search_order_cycles" do
|
||||
it "returns only order cycles accessible to user's managed enterprises" do
|
||||
ocA.update!(name: "Winter Market")
|
||||
ocB.update!(name: "Summer Market")
|
||||
create(:simple_order_cycle, name: 'Autumn Market', coordinator: coordinator1,
|
||||
distributors: [distributor2],
|
||||
suppliers: [supplier1, supplier2, supplier3],
|
||||
variants: [product2.variants.first])
|
||||
|
||||
get "/admin/reports/search_order_cycles", params: base_params
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(json_response["results"].pluck("label")).to eq(["Summer Market", "Winter Market"])
|
||||
expect(json_response["pagination"]["more"]).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /admin/reports/search_distributors" do
|
||||
it "returns only user's managed distributors" do
|
||||
get "/admin/reports/search_distributors", params: base_params
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(json_response["results"].pluck("label")).to eq([distributor1.name])
|
||||
expect(json_response["pagination"]["more"]).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -296,11 +296,8 @@ RSpec.describe "Enterprise Summary Fee with Tax Report By Producer" do
|
||||
end
|
||||
|
||||
it "should filter by distributor and order cycle" do
|
||||
page.find("#s2id_autogen1").click
|
||||
find('li', text: distributor.name).click # selects Distributor
|
||||
|
||||
page.find("#s2id_q_order_cycle_id_in").click
|
||||
find('li', text: order_cycle.name).click
|
||||
tomselect_multiselect distributor.name, from: 'q[distributor_id_in][]'
|
||||
tomselect_multiselect order_cycle.name, from: 'q[order_cycle_id_in][]'
|
||||
|
||||
run_report
|
||||
expect(page.find("table.report__table thead tr")).to have_content(table_header)
|
||||
@@ -455,9 +452,6 @@ RSpec.describe "Enterprise Summary Fee with Tax Report By Producer" do
|
||||
}
|
||||
|
||||
context "filtering" do
|
||||
let(:fee_name_selector){ "#s2id_q_enterprise_fee_id_in" }
|
||||
let(:fee_owner_selector){ "#s2id_q_enterprise_fee_owner_id_in" }
|
||||
|
||||
let(:summary_row_after_filtering_by_fee_name){
|
||||
[cost_of_produce1, "TOTAL", "120.0", "4.8", "124.8"].join(" ")
|
||||
}
|
||||
@@ -471,11 +465,8 @@ RSpec.describe "Enterprise Summary Fee with Tax Report By Producer" do
|
||||
end
|
||||
|
||||
it "should filter by distributor and order cycle" do
|
||||
page.find("#s2id_autogen1").click
|
||||
find('li', text: distributor.name).click # selects Distributor
|
||||
|
||||
page.find("#s2id_q_order_cycle_id_in").click
|
||||
find('li', text: order_cycle3.name).click
|
||||
tomselect_multiselect distributor.name, from: 'q[distributor_id_in][]'
|
||||
tomselect_multiselect order_cycle3.name, from: 'q[order_cycle_id_in][]'
|
||||
|
||||
run_report
|
||||
expect(page.find("table.report__table thead tr")).to have_content(table_header)
|
||||
@@ -504,8 +495,7 @@ RSpec.describe "Enterprise Summary Fee with Tax Report By Producer" do
|
||||
end
|
||||
|
||||
it "should filter by producer" do
|
||||
page.find("#s2id_supplier_id_in").click
|
||||
find('li', text: supplier2.name).click
|
||||
tomselect_multiselect supplier2.name, from: 'supplier_id_in[]'
|
||||
|
||||
run_report
|
||||
expect(page.find("table.report__table thead tr")).to have_content(table_header)
|
||||
@@ -528,8 +518,7 @@ RSpec.describe "Enterprise Summary Fee with Tax Report By Producer" do
|
||||
end
|
||||
|
||||
it "should filter by fee name" do
|
||||
page.find(fee_name_selector).click
|
||||
find('li', text: supplier_fees.name).click
|
||||
tomselect_multiselect supplier_fees.name, from: 'q[enterprise_fee_id_in][]'
|
||||
|
||||
run_report
|
||||
|
||||
@@ -557,8 +546,7 @@ RSpec.describe "Enterprise Summary Fee with Tax Report By Producer" do
|
||||
end
|
||||
|
||||
it "should filter by fee owner" do
|
||||
page.find(fee_owner_selector).click
|
||||
find('li', text: supplier.name).click
|
||||
tomselect_multiselect supplier.name, from: 'q[enterprise_fee_owner_id_in][]'
|
||||
|
||||
run_report
|
||||
expect(page.find("table.report__table thead tr")).to have_content(table_header)
|
||||
|
||||
Reference in New Issue
Block a user