mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-04-08 07:46:59 +00:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1608a0288 | ||
|
|
e4e7ef395b | ||
|
|
ec24740c3b | ||
|
|
783ac990bc | ||
|
|
a4cc2f17dc | ||
|
|
c19241ddd9 | ||
|
|
eee9f61c38 | ||
|
|
7f711d746f | ||
|
|
732234f1c0 | ||
|
|
450fe4ada1 | ||
|
|
bcecbf9a0f | ||
|
|
15abea51ab | ||
|
|
cacb62f58c | ||
|
|
6048fcb053 | ||
|
|
6013b6be70 | ||
|
|
22a1528ac7 | ||
|
|
ca3c0c98bf | ||
|
|
19006d6c17 | ||
|
|
da69e2c383 | ||
|
|
2e6e4b665f | ||
|
|
e255bcc082 | ||
|
|
51b4dc64cc | ||
|
|
77b6bc15e7 | ||
|
|
9b145da898 | ||
|
|
00d600911d | ||
|
|
10d6dd73f2 | ||
|
|
c74624cd57 | ||
|
|
60edcada2c | ||
|
|
b61f6ab444 | ||
|
|
ccc38367f3 | ||
|
|
80a12db191 | ||
|
|
5beed6f028 | ||
|
|
0a65322594 | ||
|
|
b7f154d289 | ||
|
|
edb8a03436 | ||
|
|
3ee338fa8d | ||
|
|
e3da27ca12 | ||
|
|
4c8e6d8260 | ||
|
|
de28083007 | ||
|
|
cc608adddc | ||
|
|
01bfd72387 | ||
|
|
69d9c52a53 | ||
|
|
5371361a74 | ||
|
|
a6f5f2c10d | ||
|
|
c72976b1e2 | ||
|
|
b7c628dc2a | ||
|
|
5bef61aa2e | ||
|
|
79c346acb1 | ||
|
|
ca10ae2f5c | ||
|
|
8ba0ab6b5a | ||
|
|
044f6131da | ||
|
|
062fcd317c |
6
.env
6
.env
@@ -21,12 +21,6 @@ CHECKOUT_ZONE="Australia"
|
|||||||
# Find currency codes at http://en.wikipedia.org/wiki/ISO_4217.
|
# Find currency codes at http://en.wikipedia.org/wiki/ISO_4217.
|
||||||
CURRENCY="AUD"
|
CURRENCY="AUD"
|
||||||
|
|
||||||
# The whenever gem can set the `MAILTO` variable for our cron jobs.
|
|
||||||
# You can define an email address to notify if any job outputs something.
|
|
||||||
# But you need a working mail server setup so that the message is delivered.
|
|
||||||
# See: config/schedule.rb
|
|
||||||
# SCHEDULE_NOTIFICATIONS="admin@example.com"
|
|
||||||
|
|
||||||
# Mail settings
|
# Mail settings
|
||||||
MAIL_HOST="example.com"
|
MAIL_HOST="example.com"
|
||||||
MAIL_DOMAIN="example.com"
|
MAIL_DOMAIN="example.com"
|
||||||
|
|||||||
15
.github/dependabot.yml
vendored
15
.github/dependabot.yml
vendored
@@ -9,32 +9,30 @@ multi-ecosystem-groups:
|
|||||||
turbo_power:
|
turbo_power:
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
|
|
||||||
updates:
|
updates:
|
||||||
|
# turbo_power: ensure gem and package are updated together
|
||||||
- package-ecosystem: "bundler"
|
- package-ecosystem: "bundler"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
patterns: ["turbo_power"]
|
patterns: ["turbo_power"]
|
||||||
multi-ecosystem-group: "turbo_power"
|
multi-ecosystem-group: "turbo_power"
|
||||||
|
|
||||||
# Only specific requirements are specified in Gemfile, so don't touch it.
|
|
||||||
versioning-strategy: lockfile-only
|
versioning-strategy: lockfile-only
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
patterns: ["turbo_power"]
|
patterns: ["turbo_power"]
|
||||||
multi-ecosystem-group: "turbo_power"
|
multi-ecosystem-group: "turbo_power"
|
||||||
|
|
||||||
# Only specific requirements are specified in package.json, so don't touch it.
|
|
||||||
versioning-strategy: lockfile-only
|
versioning-strategy: lockfile-only
|
||||||
|
|
||||||
|
# All others
|
||||||
- package-ecosystem: "bundler"
|
- package-ecosystem: "bundler"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
cooldown:
|
cooldown:
|
||||||
default-days: 7
|
default-days: 7
|
||||||
|
# Only specific requirements are specified in Gemfile, so don't let Dependabot touch it.
|
||||||
# Only specific requirements are specified in Gemfile, so don't touch it.
|
|
||||||
versioning-strategy: lockfile-only
|
versioning-strategy: lockfile-only
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
@@ -43,6 +41,5 @@ updates:
|
|||||||
interval: "daily"
|
interval: "daily"
|
||||||
cooldown:
|
cooldown:
|
||||||
default-days: 7
|
default-days: 7
|
||||||
|
# Only specific requirements are specified in package.json, so don't let Dependabot touch it.
|
||||||
# Only specific requirements are specified in package.json, so don't touch it.
|
|
||||||
versioning-strategy: lockfile-only
|
versioning-strategy: lockfile-only
|
||||||
|
|||||||
2
Gemfile
2
Gemfile
@@ -118,8 +118,6 @@ gem 'immigrant'
|
|||||||
gem 'roo' # read spreadsheets
|
gem 'roo' # read spreadsheets
|
||||||
gem 'spreadsheet_architect' # write spreadsheets
|
gem 'spreadsheet_architect' # write spreadsheets
|
||||||
|
|
||||||
gem 'whenever', require: false
|
|
||||||
|
|
||||||
gem 'coffee-rails', '~> 5.0.0'
|
gem 'coffee-rails', '~> 5.0.0'
|
||||||
|
|
||||||
gem 'angular_rails_csrf'
|
gem 'angular_rails_csrf'
|
||||||
|
|||||||
36
Gemfile.lock
36
Gemfile.lock
@@ -112,7 +112,7 @@ GEM
|
|||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
active_model_serializers (0.8.4)
|
active_model_serializers (0.8.4)
|
||||||
activemodel (>= 3.0)
|
activemodel (>= 3.0)
|
||||||
active_storage_validations (3.0.3)
|
active_storage_validations (3.0.4)
|
||||||
activejob (>= 6.1.4)
|
activejob (>= 6.1.4)
|
||||||
activemodel (>= 6.1.4)
|
activemodel (>= 6.1.4)
|
||||||
activestorage (>= 6.1.4)
|
activestorage (>= 6.1.4)
|
||||||
@@ -185,8 +185,8 @@ GEM
|
|||||||
ast (2.4.3)
|
ast (2.4.3)
|
||||||
attr_required (1.0.2)
|
attr_required (1.0.2)
|
||||||
aws-eventstream (1.4.0)
|
aws-eventstream (1.4.0)
|
||||||
aws-partitions (1.1227.0)
|
aws-partitions (1.1233.0)
|
||||||
aws-sdk-core (3.243.0)
|
aws-sdk-core (3.244.0)
|
||||||
aws-eventstream (~> 1, >= 1.3.0)
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
aws-partitions (~> 1, >= 1.992.0)
|
aws-partitions (~> 1, >= 1.992.0)
|
||||||
aws-sigv4 (~> 1.9)
|
aws-sigv4 (~> 1.9)
|
||||||
@@ -194,11 +194,11 @@ GEM
|
|||||||
bigdecimal
|
bigdecimal
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
logger
|
logger
|
||||||
aws-sdk-kms (1.122.0)
|
aws-sdk-kms (1.123.0)
|
||||||
aws-sdk-core (~> 3, >= 3.241.4)
|
aws-sdk-core (~> 3, >= 3.244.0)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sdk-s3 (1.215.0)
|
aws-sdk-s3 (1.217.0)
|
||||||
aws-sdk-core (~> 3, >= 3.243.0)
|
aws-sdk-core (~> 3, >= 3.244.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sigv4 (1.12.1)
|
aws-sigv4 (1.12.1)
|
||||||
@@ -210,7 +210,7 @@ GEM
|
|||||||
bigdecimal (3.3.1)
|
bigdecimal (3.3.1)
|
||||||
bindata (2.5.1)
|
bindata (2.5.1)
|
||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
bootsnap (1.22.0)
|
bootsnap (1.23.0)
|
||||||
msgpack (~> 1.2)
|
msgpack (~> 1.2)
|
||||||
bugsnag (6.29.0)
|
bugsnag (6.29.0)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
@@ -245,7 +245,6 @@ GEM
|
|||||||
cgi (0.5.1)
|
cgi (0.5.1)
|
||||||
childprocess (5.0.0)
|
childprocess (5.0.0)
|
||||||
choice (0.2.0)
|
choice (0.2.0)
|
||||||
chronic (0.10.2)
|
|
||||||
coderay (1.1.3)
|
coderay (1.1.3)
|
||||||
coffee-rails (5.0.0)
|
coffee-rails (5.0.0)
|
||||||
coffee-script (>= 2.2.0)
|
coffee-script (>= 2.2.0)
|
||||||
@@ -290,8 +289,8 @@ GEM
|
|||||||
warden (~> 1.2.3)
|
warden (~> 1.2.3)
|
||||||
devise-encryptable (0.2.0)
|
devise-encryptable (0.2.0)
|
||||||
devise (>= 2.1.0)
|
devise (>= 2.1.0)
|
||||||
devise-i18n (1.15.0)
|
devise-i18n (1.16.0)
|
||||||
devise (>= 4.9.0)
|
devise (>= 5.0.0)
|
||||||
rails-i18n
|
rails-i18n
|
||||||
diff-lcs (1.6.2)
|
diff-lcs (1.6.2)
|
||||||
digest (3.2.1)
|
digest (3.2.1)
|
||||||
@@ -591,7 +590,7 @@ GEM
|
|||||||
hashery (~> 2.0)
|
hashery (~> 2.0)
|
||||||
ruby-rc4
|
ruby-rc4
|
||||||
ttfunk
|
ttfunk
|
||||||
pg (1.6.2)
|
pg (1.6.3)
|
||||||
pp (0.6.3)
|
pp (0.6.3)
|
||||||
prettyprint
|
prettyprint
|
||||||
prettyprint (0.2.0)
|
prettyprint (0.2.0)
|
||||||
@@ -621,7 +620,7 @@ GEM
|
|||||||
railties (>= 4.2)
|
railties (>= 4.2)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
rack (2.2.22)
|
rack (2.2.23)
|
||||||
rack-mini-profiler (2.3.4)
|
rack-mini-profiler (2.3.4)
|
||||||
rack (>= 1.2.0)
|
rack (>= 1.2.0)
|
||||||
rack-oauth2 (2.3.0)
|
rack-oauth2 (2.3.0)
|
||||||
@@ -905,7 +904,7 @@ GEM
|
|||||||
thor (1.5.0)
|
thor (1.5.0)
|
||||||
thread-local (1.1.0)
|
thread-local (1.1.0)
|
||||||
tilt (2.7.0)
|
tilt (2.7.0)
|
||||||
timeout (0.6.0)
|
timeout (0.6.1)
|
||||||
tsort (0.2.0)
|
tsort (0.2.0)
|
||||||
ttfunk (1.8.0)
|
ttfunk (1.8.0)
|
||||||
bigdecimal (~> 3.1)
|
bigdecimal (~> 3.1)
|
||||||
@@ -938,9 +937,9 @@ GEM
|
|||||||
validates_lengths_from_database (0.8.0)
|
validates_lengths_from_database (0.8.0)
|
||||||
activerecord (>= 4)
|
activerecord (>= 4)
|
||||||
vcr (6.4.0)
|
vcr (6.4.0)
|
||||||
view_component (4.1.1)
|
view_component (4.5.0)
|
||||||
actionview (>= 7.1.0, < 8.2)
|
actionview (>= 7.1.0)
|
||||||
activesupport (>= 7.1.0, < 8.2)
|
activesupport (>= 7.1.0)
|
||||||
concurrent-ruby (~> 1)
|
concurrent-ruby (~> 1)
|
||||||
view_component_reflex (3.1.14.pre9)
|
view_component_reflex (3.1.14.pre9)
|
||||||
rails (>= 5.2, < 8.0)
|
rails (>= 5.2, < 8.0)
|
||||||
@@ -968,8 +967,6 @@ GEM
|
|||||||
base64
|
base64
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
whenever (1.1.0)
|
|
||||||
chronic (>= 0.6.3)
|
|
||||||
xml-simple (1.1.8)
|
xml-simple (1.1.8)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
@@ -1127,7 +1124,6 @@ DEPENDENCIES
|
|||||||
web!
|
web!
|
||||||
web-console
|
web-console
|
||||||
webmock
|
webmock
|
||||||
whenever
|
|
||||||
wicked_pdf!
|
wicked_pdf!
|
||||||
wkhtmltopdf-binary!
|
wkhtmltopdf-binary!
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,9 @@
|
|||||||
background-color: white;
|
background-color: white;
|
||||||
box-shadow: $box-shadow;
|
box-shadow: $box-shadow;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
width: max-content;
|
||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
|
max-width: 110px;
|
||||||
display: none;
|
display: none;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|
||||||
@@ -27,6 +29,15 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fade out so user can see which option was selected
|
||||||
|
&.selected {
|
||||||
|
transition:
|
||||||
|
opacity 0.2s linear,
|
||||||
|
visibility 0.2s linear;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
& > a {
|
& > a {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ export default class extends Controller {
|
|||||||
connect() {
|
connect() {
|
||||||
super.connect();
|
super.connect();
|
||||||
window.addEventListener("click", this.#hideIfClickedOutside);
|
window.addEventListener("click", this.#hideIfClickedOutside);
|
||||||
|
|
||||||
|
// Close menu when making a selection
|
||||||
|
this.contentTarget.addEventListener("click", this.#selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnect() {
|
disconnect() {
|
||||||
@@ -13,17 +16,22 @@ export default class extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggle() {
|
toggle() {
|
||||||
this.contentTarget.classList.toggle("show");
|
this.#toggleShow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#selected = () => {
|
||||||
|
this.contentTarget.classList.add("selected");
|
||||||
|
};
|
||||||
|
|
||||||
#hideIfClickedOutside = (event) => {
|
#hideIfClickedOutside = (event) => {
|
||||||
if (this.element.contains(event.target)) {
|
if (this.element.contains(event.target)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.#hide();
|
this.#toggleShow(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
#hide() {
|
#toggleShow(force = undefined) {
|
||||||
this.contentTarget.classList.remove("show");
|
this.contentTarget.classList.toggle("show", force);
|
||||||
|
this.contentTarget.classList.remove("selected");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
56
app/controllers/admin/ajax_search_controller.rb
Normal file
56
app/controllers/admin/ajax_search_controller.rb
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Admin
|
||||||
|
class AjaxSearchController < Spree::Admin::BaseController
|
||||||
|
def producers
|
||||||
|
query = OpenFoodNetwork::Permissions.new(spree_current_user)
|
||||||
|
.managed_product_enterprises.is_primary_producer.by_name
|
||||||
|
|
||||||
|
render json: build_search_response(query)
|
||||||
|
end
|
||||||
|
|
||||||
|
def categories
|
||||||
|
query = Spree::Taxon.all
|
||||||
|
|
||||||
|
render json: build_search_response(query)
|
||||||
|
end
|
||||||
|
|
||||||
|
def tax_categories
|
||||||
|
query = Spree::TaxCategory.all
|
||||||
|
|
||||||
|
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}%"
|
||||||
|
|
||||||
|
query.where('name ILIKE ?', pattern)
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginated_items(query, page, per_page)
|
||||||
|
query.order(:name).offset((page - 1) * per_page).limit(per_page).pluck(:name, :id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def format_results(items)
|
||||||
|
items.map { |label, value| { value:, label: } }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -107,7 +107,7 @@ module Spree
|
|||||||
|
|
||||||
def cancel
|
def cancel
|
||||||
@order = Spree::Order.find_by!(number: params[:id])
|
@order = Spree::Order.find_by!(number: params[:id])
|
||||||
authorize! :cancel, @order
|
authorize! :cancel, @order, session[:access_token]
|
||||||
|
|
||||||
if Orders::CustomerCancellationService.new(@order).call
|
if Orders::CustomerCancellationService.new(@order).call
|
||||||
flash[:success] = I18n.t(:orders_your_order_has_been_cancelled)
|
flash[:success] = I18n.t(:orders_your_order_has_been_cancelled)
|
||||||
|
|||||||
@@ -52,5 +52,15 @@ module Admin
|
|||||||
@allowed_source_producers ||= OpenFoodNetwork::Permissions.new(spree_current_user)
|
@allowed_source_producers ||= OpenFoodNetwork::Permissions.new(spree_current_user)
|
||||||
.enterprises_granting_linked_variants
|
.enterprises_granting_linked_variants
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Query only name of the model to avoid loading the whole record
|
||||||
|
def selected_option(id, model)
|
||||||
|
return [] unless id
|
||||||
|
|
||||||
|
name = model.where(id: id).pick(:name)
|
||||||
|
return [] unless name
|
||||||
|
|
||||||
|
[[name, id]]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
15
app/jobs/rake_job.rb
Normal file
15
app/jobs/rake_job.rb
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "rake"
|
||||||
|
|
||||||
|
# Executes a rake task
|
||||||
|
class RakeJob < ApplicationJob
|
||||||
|
def perform(task_string)
|
||||||
|
Rails.application.load_tasks if Rake::Task.tasks.empty?
|
||||||
|
|
||||||
|
Rake.application.invoke_task(task_string)
|
||||||
|
ensure
|
||||||
|
name, _args = Rake.application.parse_task_string(task_string)
|
||||||
|
Rake::Task[name].reenable
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -113,7 +113,11 @@ module Spree
|
|||||||
item.order.changes_allowed?
|
item.order.changes_allowed?
|
||||||
end
|
end
|
||||||
|
|
||||||
can [:cancel, :bulk_cancel], Spree::Order do |order|
|
can :cancel, Spree::Order do |order, token|
|
||||||
|
order.user == user || (order.token && token == order.token)
|
||||||
|
end
|
||||||
|
|
||||||
|
can :bulk_cancel, Spree::Order do |order|
|
||||||
order.user == user
|
order.user == user
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -225,9 +229,17 @@ module Spree
|
|||||||
OpenFoodNetwork::Permissions.new(user).
|
OpenFoodNetwork::Permissions.new(user).
|
||||||
enterprises_granting_linked_variants.include? variant.supplier
|
enterprises_granting_linked_variants.include? variant.supplier
|
||||||
end
|
end
|
||||||
|
can [
|
||||||
|
:admin,
|
||||||
|
:index,
|
||||||
|
:bulk_update,
|
||||||
|
:destroy,
|
||||||
|
:destroy_variant,
|
||||||
|
:clone,
|
||||||
|
:create_linked_variant
|
||||||
|
], :products_v3
|
||||||
|
|
||||||
can [:admin, :index, :bulk_update, :destroy, :destroy_variant, :clone,
|
can [:admin, :producers, :categories, :tax_categories], :ajax_search
|
||||||
:create_linked_variant], :products_v3
|
|
||||||
|
|
||||||
can [:create], Spree::Variant
|
can [:create], Spree::Variant
|
||||||
can [:admin, :index, :read, :edit,
|
can [:admin, :index, :read, :edit,
|
||||||
|
|||||||
@@ -9,14 +9,22 @@
|
|||||||
- if producer_options.many?
|
- if producer_options.many?
|
||||||
.producers
|
.producers
|
||||||
= label_tag :producer_id, t('.producers.label')
|
= label_tag :producer_id, t('.producers.label')
|
||||||
= select_tag :producer_id, options_for_select(producer_options, producer_id),
|
= render(SearchableDropdownComponent.new(name: :producer_id,
|
||||||
include_blank: t('.all_producers'), class: "fullwidth",
|
aria_label: t('.producers.label'),
|
||||||
data: { "controller": "tom-select", 'tom-select-placeholder-value': t('.search_for_producers')}
|
options: selected_option(producer_id, Enterprise),
|
||||||
|
selected_option: producer_id,
|
||||||
|
remote_url: admin_ajax_search_producers_url,
|
||||||
|
include_blank: t('.all_producers'),
|
||||||
|
placeholder_value: t('.search_for_producers')))
|
||||||
.categories
|
.categories
|
||||||
= label_tag :category_id, t('.categories.label')
|
= label_tag :category_id, t('.categories.label')
|
||||||
= select_tag :category_id, options_for_select(category_options, category_id),
|
= render(SearchableDropdownComponent.new(name: :category_id,
|
||||||
include_blank: t('.all_categories'), class: "fullwidth",
|
aria_label: t('.categories.label'),
|
||||||
data: { "controller": "tom-select", 'tom-select-placeholder-value': t('.search_for_categories')}
|
options: selected_option(category_id, Spree::Taxon),
|
||||||
|
selected_option: category_id,
|
||||||
|
remote_url: admin_ajax_search_categories_url,
|
||||||
|
include_blank: t('.all_categories'),
|
||||||
|
placeholder_value: t('.search_for_categories')))
|
||||||
-if variant_tag_enabled?(spree_current_user)
|
-if variant_tag_enabled?(spree_current_user)
|
||||||
.tags
|
.tags
|
||||||
= label_tag :tags_name_in, t('.tags.label')
|
= label_tag :tags_name_in, t('.tags.label')
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
%td.col-image
|
%td.col-image
|
||||||
-# empty
|
-# empty
|
||||||
- variant.source_variants.each do |source_variant|
|
- variant.source_variants.each do |source_variant|
|
||||||
= content_tag(:span, "🔗", title: t('admin.products_page.variant_row.sourced_from', source_name: source_variant.display_name, source_id: source_variant.id, hub_name: variant.hub&.name))
|
= content_tag(:span, "🔗", title: t('admin.products_page.variant_row.sourced_from', source_name: source_variant.full_name, source_id: source_variant.id, hub_name: variant.hub&.name))
|
||||||
%td.col-name.field.naked_inputs
|
%td.col-name.field.naked_inputs
|
||||||
= f.hidden_field :id
|
= f.hidden_field :id
|
||||||
= f.text_field :display_name, 'aria-label': t('admin.products_page.columns.name'), placeholder: variant.product.name
|
= f.text_field :display_name, 'aria-label': t('admin.products_page.columns.name'), placeholder: variant.product.name
|
||||||
@@ -59,27 +59,28 @@
|
|||||||
= render(SearchableDropdownComponent.new(form: f,
|
= render(SearchableDropdownComponent.new(form: f,
|
||||||
name: :supplier_id,
|
name: :supplier_id,
|
||||||
aria_label: t('.producer_field_name'),
|
aria_label: t('.producer_field_name'),
|
||||||
options: producer_options,
|
options: variant.supplier_id ? [[variant.supplier.name, variant.supplier_id]] : [],
|
||||||
selected_option: variant.supplier_id,
|
selected_option: variant.supplier_id,
|
||||||
include_blank: t('admin.products_v3.filters.select_producer'),
|
remote_url: admin_ajax_search_producers_url,
|
||||||
placeholder_value: t('admin.products_v3.filters.select_producer')))
|
placeholder_value: t('admin.products_v3.filters.select_producer')))
|
||||||
= error_message_on variant, :supplier
|
= error_message_on variant, :supplier
|
||||||
%td.col-category.field.naked_inputs
|
%td.col-category.field.naked_inputs
|
||||||
= render(SearchableDropdownComponent.new(form: f,
|
= render(SearchableDropdownComponent.new(form: f,
|
||||||
name: :primary_taxon_id,
|
name: :primary_taxon_id,
|
||||||
options: category_options,
|
options: variant.primary_taxon_id ? [[variant.primary_taxon.name, variant.primary_taxon_id]] : [],
|
||||||
selected_option: variant.primary_taxon_id,
|
selected_option: variant.primary_taxon_id,
|
||||||
aria_label: t('.category_field_name'),
|
aria_label: t('.category_field_name'),
|
||||||
include_blank: t('admin.products_v3.filters.select_category'),
|
remote_url: admin_ajax_search_categories_url,
|
||||||
placeholder_value: t('admin.products_v3.filters.select_category')))
|
placeholder_value: t('admin.products_v3.filters.select_category')))
|
||||||
= error_message_on variant, :primary_taxon
|
= error_message_on variant, :primary_taxon
|
||||||
%td.col-tax_category.field.naked_inputs
|
%td.col-tax_category.field.naked_inputs
|
||||||
= render(SearchableDropdownComponent.new(form: f,
|
= render(SearchableDropdownComponent.new(form: f,
|
||||||
name: :tax_category_id,
|
name: :tax_category_id,
|
||||||
options: tax_category_options,
|
options: variant.tax_category_id ? [[variant.tax_category.name, variant.tax_category_id]] : [],
|
||||||
selected_option: variant.tax_category_id,
|
selected_option: variant.tax_category_id,
|
||||||
include_blank: t('.none_tax_category'),
|
|
||||||
aria_label: t('.tax_category_field_name'),
|
aria_label: t('.tax_category_field_name'),
|
||||||
|
include_blank: t('.none_tax_category'),
|
||||||
|
remote_url: admin_ajax_search_tax_categories_url,
|
||||||
placeholder_value: t('.search_for_tax_categories')))
|
placeholder_value: t('.search_for_tax_categories')))
|
||||||
= error_message_on variant, :tax_category
|
= error_message_on variant, :tax_category
|
||||||
- if variant_tag_enabled?(spree_current_user)
|
- if variant_tag_enabled?(spree_current_user)
|
||||||
@@ -92,7 +93,7 @@
|
|||||||
- if variant.persisted?
|
- if variant.persisted?
|
||||||
= link_to t('admin.products_page.actions.edit'), edit_admin_product_variant_path(variant.product, variant)
|
= link_to t('admin.products_page.actions.edit'), edit_admin_product_variant_path(variant.product, variant)
|
||||||
|
|
||||||
- if allowed_source_producers.include?(variant.supplier)
|
- if variant.source_variants.empty? && allowed_source_producers.include?(variant.supplier)
|
||||||
= link_to t('admin.products_page.actions.create_linked_variant'), admin_create_linked_variant_path(variant_id: variant.id, product_index:), 'data-turbo-method': :post
|
= link_to t('admin.products_page.actions.create_linked_variant'), admin_create_linked_variant_path(variant_id: variant.id, product_index:), 'data-turbo-method': :post
|
||||||
|
|
||||||
- if variant.product.variants.size > 1
|
- if variant.product.variants.size > 1
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ export default class extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#addRemoteOptions(options) {
|
#addRemoteOptions(options) {
|
||||||
|
// by default, for dropdown_input plugin, it's true. Otherwise for multi-select it's false
|
||||||
|
// it should always be true so to invoke the onDropdownOpen to fetch options
|
||||||
|
options.shouldOpen = true;
|
||||||
this.openedByClick = false;
|
this.openedByClick = false;
|
||||||
|
|
||||||
options.firstUrl = (query) => {
|
options.firstUrl = (query) => {
|
||||||
@@ -91,12 +94,9 @@ export default class extends Controller {
|
|||||||
|
|
||||||
options.load = this.#fetchOptions.bind(this);
|
options.load = this.#fetchOptions.bind(this);
|
||||||
|
|
||||||
options.onFocus = function () {
|
|
||||||
this.control.load("", () => {});
|
|
||||||
}.bind(this);
|
|
||||||
|
|
||||||
options.onDropdownOpen = function () {
|
options.onDropdownOpen = function () {
|
||||||
this.openedByClick = true;
|
this.openedByClick = true;
|
||||||
|
this.control.load("", () => {});
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
options.onType = function () {
|
options.onType = function () {
|
||||||
|
|||||||
@@ -375,6 +375,6 @@
|
|||||||
|
|
||||||
.slide-in {
|
.slide-in {
|
||||||
transform-origin: top;
|
transform-origin: top;
|
||||||
animation: slideInTop 0.5s forwards;
|
animation: slideInTop 0.5s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,15 @@ redis_connection_settings = {
|
|||||||
|
|
||||||
Sidekiq.configure_server do |config|
|
Sidekiq.configure_server do |config|
|
||||||
config.redis = redis_connection_settings
|
config.redis = redis_connection_settings
|
||||||
|
config.on(:startup) do
|
||||||
|
# Load schedule file similar to sidekiq/cli.rb loading the main config.
|
||||||
|
path = File.expand_path("../sidekiq_scheduler.yml", __dir__)
|
||||||
|
erb = ERB.new(File.read(path), trim_mode: "-")
|
||||||
|
|
||||||
|
Sidekiq.schedule =
|
||||||
|
YAML.safe_load(erb.result, permitted_classes: [Symbol], aliases: true)
|
||||||
|
SidekiqScheduler::Scheduler.instance.reload_schedule!
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Sidekiq.configure_client do |config|
|
Sidekiq.configure_client do |config|
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ de_DE:
|
|||||||
not_available_to_shop: "ist nicht verfügbar für %{shop}"
|
not_available_to_shop: "ist nicht verfügbar für %{shop}"
|
||||||
card_details: "Kreditkartendaten"
|
card_details: "Kreditkartendaten"
|
||||||
card_type: "Kreditkartentyp"
|
card_type: "Kreditkartentyp"
|
||||||
|
use_new_cc: "Eine neue Kreditkarte verwenden"
|
||||||
cardholder_name: "Kreditkarteninhaber"
|
cardholder_name: "Kreditkarteninhaber"
|
||||||
community_forum_url: "URL des Community-Forums"
|
community_forum_url: "URL des Community-Forums"
|
||||||
customer_instructions: "Informationen für Kunden"
|
customer_instructions: "Informationen für Kunden"
|
||||||
@@ -665,6 +666,7 @@ de_DE:
|
|||||||
bill_address: "Rechnungsadresse"
|
bill_address: "Rechnungsadresse"
|
||||||
ship_address: "Lieferadresse"
|
ship_address: "Lieferadresse"
|
||||||
balance: "Saldo"
|
balance: "Saldo"
|
||||||
|
credit: "Verfügbares Guthaben"
|
||||||
update_address_success: "Adresse wurde erfolgreich aktualisiert."
|
update_address_success: "Adresse wurde erfolgreich aktualisiert."
|
||||||
update_address_error: "Bitte füllen Sie alle erforderlichen Felder aus."
|
update_address_error: "Bitte füllen Sie alle erforderlichen Felder aus."
|
||||||
edit_bill_address: "Rechnungsadresse bearbeiten"
|
edit_bill_address: "Rechnungsadresse bearbeiten"
|
||||||
@@ -683,6 +685,7 @@ de_DE:
|
|||||||
has_associated_subscriptions: "Löschen fehlgeschlagen: Dieser Kunde hat aktive Abonnements. Bitte kündigen Sie diese zuerst."
|
has_associated_subscriptions: "Löschen fehlgeschlagen: Dieser Kunde hat aktive Abonnements. Bitte kündigen Sie diese zuerst."
|
||||||
customer_account_transaction:
|
customer_account_transaction:
|
||||||
index:
|
index:
|
||||||
|
available_credit: "Verfügbares Guthaben: %{available_credit}"
|
||||||
description: Beschreibung
|
description: Beschreibung
|
||||||
amount: Summe
|
amount: Summe
|
||||||
running_balance: Fortlaufender Saldo
|
running_balance: Fortlaufender Saldo
|
||||||
@@ -2203,6 +2206,7 @@ de_DE:
|
|||||||
order_total: 'Gesamtsumme:'
|
order_total: 'Gesamtsumme:'
|
||||||
order_payment: "Zahlungsart:"
|
order_payment: "Zahlungsart:"
|
||||||
no_payment_required: "Keine Zahlung erforderlich."
|
no_payment_required: "Keine Zahlung erforderlich."
|
||||||
|
credit_used: "Verwendetes Guthaben: %{amount}"
|
||||||
customer_credit: Guthaben
|
customer_credit: Guthaben
|
||||||
order_billing_address: Rechnungsadresse
|
order_billing_address: Rechnungsadresse
|
||||||
order_delivery_on: Lieferung am
|
order_delivery_on: Lieferung am
|
||||||
@@ -4363,7 +4367,7 @@ de_DE:
|
|||||||
date_picker:
|
date_picker:
|
||||||
flatpickr_date_format: "d.m.Y"
|
flatpickr_date_format: "d.m.Y"
|
||||||
flatpickr_datetime_format: "d.m.Y H:i"
|
flatpickr_datetime_format: "d.m.Y H:i"
|
||||||
today: "heute"
|
today: "Heute"
|
||||||
now: "Jetzt"
|
now: "Jetzt"
|
||||||
close: "Schließen"
|
close: "Schließen"
|
||||||
orders:
|
orders:
|
||||||
|
|||||||
@@ -208,6 +208,10 @@ hu:
|
|||||||
no_default_card: "^Ennél az ügyfélnél nem áll rendelkezésre alapértelmezett kártya"
|
no_default_card: "^Ennél az ügyfélnél nem áll rendelkezésre alapértelmezett kártya"
|
||||||
shipping_method:
|
shipping_method:
|
||||||
not_available_to_shop: "nem érhető el a %{shop} számára"
|
not_available_to_shop: "nem érhető el a %{shop} számára"
|
||||||
|
user_invitation:
|
||||||
|
attributes:
|
||||||
|
email:
|
||||||
|
is_already_manager: már menedzser
|
||||||
card_details: "A kártya adatai"
|
card_details: "A kártya adatai"
|
||||||
card_type: "Kártyatípus"
|
card_type: "Kártyatípus"
|
||||||
card_type_is: "A kártya típusa: "
|
card_type_is: "A kártya típusa: "
|
||||||
@@ -513,7 +517,7 @@ hu:
|
|||||||
create: "Létrehozás"
|
create: "Létrehozás"
|
||||||
cancel: "Mégsem"
|
cancel: "Mégsem"
|
||||||
cancel_order: "Törlés"
|
cancel_order: "Törlés"
|
||||||
resume: "Összefoglaló"
|
resume: "Visszaállítás"
|
||||||
save: "Mentés"
|
save: "Mentés"
|
||||||
edit: "Szerkesztés"
|
edit: "Szerkesztés"
|
||||||
update: "Frissítés"
|
update: "Frissítés"
|
||||||
@@ -570,8 +574,11 @@ hu:
|
|||||||
delete: Törlés
|
delete: Törlés
|
||||||
remove: Eltávolítás
|
remove: Eltávolítás
|
||||||
preview: Előnézet
|
preview: Előnézet
|
||||||
|
create_linked_variant: Kapcsolt termékváltozat létrehozása
|
||||||
image:
|
image:
|
||||||
edit: Szerkesztés
|
edit: Szerkesztés
|
||||||
|
variant_row:
|
||||||
|
sourced_from: "Forrás: %{source_name} ( %{source_id} ); Elosztó: %{hub_name}"
|
||||||
product_preview:
|
product_preview:
|
||||||
product_preview: Termék előnézet
|
product_preview: Termék előnézet
|
||||||
shop_tab: Átvételi pont
|
shop_tab: Átvételi pont
|
||||||
@@ -1051,7 +1058,7 @@ hu:
|
|||||||
back_to_my_inventory: Vissza a leltárhoz
|
back_to_my_inventory: Vissza a leltárhoz
|
||||||
orders:
|
orders:
|
||||||
edit:
|
edit:
|
||||||
order_sure_want_to: Biztosan %{event} akarod ezt a megrendelést?
|
order_sure_want_to: 'Biztosan végrehajtod a rendelésen ezt a műveletet? %{event} '
|
||||||
voucher_tax_included_in_price: "%{label}(a kupon tartalmazza az adót)"
|
voucher_tax_included_in_price: "%{label}(a kupon tartalmazza az adót)"
|
||||||
tax_on_fees: "Díjak adója"
|
tax_on_fees: "Díjak adója"
|
||||||
invoice_email_sent: 'Számla e-mail elküldve'
|
invoice_email_sent: 'Számla e-mail elküldve'
|
||||||
@@ -1561,7 +1568,7 @@ hu:
|
|||||||
coordinator: Koordinátor
|
coordinator: Koordinátor
|
||||||
orders_close: A rendelések lezárulnak
|
orders_close: A rendelések lezárulnak
|
||||||
row:
|
row:
|
||||||
suppliers: beszállítók
|
suppliers: beszállító
|
||||||
distributors: elosztó
|
distributors: elosztó
|
||||||
variants: változat
|
variants: változat
|
||||||
simple_form:
|
simple_form:
|
||||||
|
|||||||
@@ -83,6 +83,13 @@ Openfoodnetwork::Application.routes.draw do
|
|||||||
delete 'products_v3/destroy_variant/:id', to: 'products_v3#destroy_variant', as: 'destroy_variant'
|
delete 'products_v3/destroy_variant/:id', to: 'products_v3#destroy_variant', as: 'destroy_variant'
|
||||||
post 'clone/:id', to: 'products_v3#clone', as: 'clone_product'
|
post 'clone/:id', to: 'products_v3#clone', as: 'clone_product'
|
||||||
post 'products/create_linked_variant', to: 'products_v3#create_linked_variant', as: 'create_linked_variant'
|
post 'products/create_linked_variant', to: 'products_v3#create_linked_variant', as: 'create_linked_variant'
|
||||||
|
|
||||||
|
scope :ajax_search, as: :ajax_search, controller: :ajax_search do
|
||||||
|
get :producers
|
||||||
|
get :categories
|
||||||
|
get :tax_categories
|
||||||
|
end
|
||||||
|
|
||||||
resources :product_preview, only: [:show]
|
resources :product_preview, only: [:show]
|
||||||
|
|
||||||
resources :variant_overrides do
|
resources :variant_overrides do
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
# Force manual loading of rails application to get all env variables from dotenv-rails when running whenever cmd
|
|
||||||
require File.expand_path('../environment', __FILE__)
|
|
||||||
|
|
||||||
require 'whenever'
|
|
||||||
require 'yaml'
|
|
||||||
|
|
||||||
# Learn more: http://github.com/javan/whenever
|
|
||||||
|
|
||||||
env "MAILTO", ENV["SCHEDULE_NOTIFICATIONS"] if ENV["SCHEDULE_NOTIFICATIONS"]
|
|
||||||
|
|
||||||
# If we use -e with a file containing specs, rspec interprets it and filters out our examples
|
|
||||||
job_type :run_file, "cd :path; :environment_variable=:environment bundle exec script/rails runner :task :output"
|
|
||||||
|
|
||||||
every 1.month, at: '4:30am' do
|
|
||||||
rake 'ofn:data:remove_transient_data'
|
|
||||||
end
|
|
||||||
|
|
||||||
every 1.day, at: '2:45am' do
|
|
||||||
rake 'db2fog:clean' if ENV['S3_BACKUPS_BUCKET']
|
|
||||||
end
|
|
||||||
|
|
||||||
every 4.hours do
|
|
||||||
rake 'db2fog:backup' if ENV['S3_BACKUPS_BUCKET']
|
|
||||||
end
|
|
||||||
@@ -7,15 +7,8 @@
|
|||||||
- default
|
- default
|
||||||
- mailers
|
- mailers
|
||||||
|
|
||||||
:scheduler:
|
# This config is loaded by sidekiq before dotenv is loading our server config.
|
||||||
:schedule:
|
# Therefore we load the schedule later. See:
|
||||||
HeartbeatJob:
|
#
|
||||||
every: ["5m", first_in: "0s"]
|
# - config/initializers/sidekiq.rb
|
||||||
SubscriptionPlacementJob:
|
# - config/sidekiq_scheduler.yml
|
||||||
every: "5m"
|
|
||||||
SubscriptionConfirmJob:
|
|
||||||
every: "5m"
|
|
||||||
TriggerOrderCyclesToOpenJob:
|
|
||||||
every: "5m"
|
|
||||||
OrderCycleClosingJob:
|
|
||||||
every: "5m"
|
|
||||||
|
|||||||
36
config/sidekiq_scheduler.yml
Normal file
36
config/sidekiq_scheduler.yml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Configure sidekiq-scheduler to run jobs.
|
||||||
|
#
|
||||||
|
# - https://github.com/sidekiq-scheduler/sidekiq-scheduler
|
||||||
|
#
|
||||||
|
# > Note that every and interval count from when the Sidekiq process (re)starts.
|
||||||
|
# > So every: '48h' will never run if the Sidekiq process is restarted daily,
|
||||||
|
# > for example. You can do every: ['48h', first_in: '0s'] to make the job run
|
||||||
|
# > immediately after a restart, and then have the worker check when it was
|
||||||
|
# > last run.
|
||||||
|
#
|
||||||
|
# Therefore, we use `cron` for jobs that should run at certain times like backups.
|
||||||
|
HeartbeatJob:
|
||||||
|
every: ["5m", first_in: "0s"]
|
||||||
|
SubscriptionPlacementJob:
|
||||||
|
every: "5m"
|
||||||
|
SubscriptionConfirmJob:
|
||||||
|
every: "5m"
|
||||||
|
TriggerOrderCyclesToOpenJob:
|
||||||
|
every: "5m"
|
||||||
|
OrderCycleClosingJob:
|
||||||
|
every: "5m"
|
||||||
|
|
||||||
|
backup:
|
||||||
|
class: "RakeJob"
|
||||||
|
args: ["db2fog:backup"]
|
||||||
|
cron: "0 */4 * * *" # every 4 hours
|
||||||
|
enabled: <%= ENV.fetch("S3_BACKUPS_BUCKET", false) && true %>
|
||||||
|
backup_clean:
|
||||||
|
class: "RakeJob"
|
||||||
|
args: ["db2fog:clean"]
|
||||||
|
cron: "45 2 * * *" # every day at 2:45am
|
||||||
|
enabled: <%= ENV.fetch("S3_BACKUPS_BUCKET", false) && true %>
|
||||||
|
ofn_clean:
|
||||||
|
class: "RakeJob"
|
||||||
|
args: ["ofn:data:remove_transient_data"]
|
||||||
|
cron: "30 4 1 * *" # every month on the first at 4:30am
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
echo "::group:: Running prettier with reviewdog 🐶 ..."
|
echo -e "\nRunning prettier with reviewdog 🐶 ..."
|
||||||
|
|
||||||
"$(npm root)/.bin/prettier" --check . 2>&1 | sed --regexp-extended 's/(\[warn\].*)$/\1 File is not properly formatted./' \
|
"$(npm root)/.bin/prettier" --check . 2>&1 | sed --regexp-extended 's/(\[warn\].*)$/\1 File is not properly formatted./' \
|
||||||
| reviewdog \
|
| reviewdog \
|
||||||
@@ -25,7 +25,7 @@ echo "::group:: Running prettier with reviewdog 🐶 ..."
|
|||||||
|
|
||||||
prettier=$?
|
prettier=$?
|
||||||
|
|
||||||
echo "::group:: Running rubocop with reviewdog 🐶 ..."
|
echo -e "\nRunning rubocop with reviewdog 🐶 ..."
|
||||||
|
|
||||||
bundle exec rubocop \
|
bundle exec rubocop \
|
||||||
--fail-level info \
|
--fail-level info \
|
||||||
@@ -39,7 +39,7 @@ bundle exec rubocop \
|
|||||||
|
|
||||||
rubocop=$?
|
rubocop=$?
|
||||||
|
|
||||||
echo "::group:: Running haml-lint with reviewdog 🐶 ..."
|
echo -e "\nRunning haml-lint with reviewdog 🐶 ..."
|
||||||
|
|
||||||
bundle exec haml-lint \
|
bundle exec haml-lint \
|
||||||
--fail-level warning \
|
--fail-level warning \
|
||||||
|
|||||||
@@ -461,14 +461,34 @@ RSpec.describe Spree::OrdersController do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "when a guest user has the order token in session" do
|
||||||
|
let(:order) {
|
||||||
|
create(:completed_order_with_totals, user: nil, email: "guest@example.com",
|
||||||
|
distributor: create(:distributor_enterprise))
|
||||||
|
}
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(controller).to receive(:spree_current_user) { nil }
|
||||||
|
session[:access_token] = order.token
|
||||||
|
end
|
||||||
|
|
||||||
|
it "cancels the order and redirects to the order page" do
|
||||||
|
request.env['HTTP_REFERER'] = order_path(order)
|
||||||
|
spree_put :cancel, params
|
||||||
|
|
||||||
|
expect(response.body).to match(order_path(order)).and match("redirect")
|
||||||
|
expect(flash[:success]).to eq 'Your order has been cancelled'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "when the user has permission to cancel the order" do
|
context "when the user has permission to cancel the order" do
|
||||||
before { allow(controller).to receive(:spree_current_user) { user } }
|
before { allow(controller).to receive(:spree_current_user) { user } }
|
||||||
|
|
||||||
context "when the order is not yet complete" do
|
context "when the order is not yet complete" do
|
||||||
it "responds with forbidden" do
|
it "responds with forbidden" do
|
||||||
|
request.env['HTTP_REFERER'] = order_path(order)
|
||||||
spree_put :cancel, params
|
spree_put :cancel, params
|
||||||
|
|
||||||
expect(response).to have_http_status(:found)
|
|
||||||
expect(response.body).to match(order_path(order)).and match("redirect")
|
expect(response.body).to match(order_path(order)).and match("redirect")
|
||||||
expect(flash[:error]).to eq 'Sorry, the order could not be cancelled'
|
expect(flash[:error]).to eq 'Sorry, the order could not be cancelled'
|
||||||
end
|
end
|
||||||
@@ -481,9 +501,9 @@ RSpec.describe Spree::OrdersController do
|
|||||||
}
|
}
|
||||||
|
|
||||||
it "responds with success" do
|
it "responds with success" do
|
||||||
|
request.env['HTTP_REFERER'] = order_path(order)
|
||||||
spree_put :cancel, params
|
spree_put :cancel, params
|
||||||
|
|
||||||
expect(response).to have_http_status(:found)
|
|
||||||
expect(response.body).to match(order_path(order)).and match("redirect")
|
expect(response.body).to match(order_path(order)).and match("redirect")
|
||||||
expect(flash[:success]).to eq 'Your order has been cancelled'
|
expect(flash[:success]).to eq 'Your order has been cancelled'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -166,7 +166,6 @@ describe("TomSelectController", () => {
|
|||||||
expect(settings.searchField).toBe("label");
|
expect(settings.searchField).toBe("label");
|
||||||
expect(settings.load).toEqual(expect.any(Function));
|
expect(settings.load).toEqual(expect.any(Function));
|
||||||
expect(settings.firstUrl).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 () => {
|
it("fetches page 1 on focus", async () => {
|
||||||
|
|||||||
@@ -16,12 +16,13 @@ describe("VerticalEllipsisMenuController test", () => {
|
|||||||
<div data-controller="vertical-ellipsis-menu" id="root">
|
<div data-controller="vertical-ellipsis-menu" id="root">
|
||||||
<div data-action="click->vertical-ellipsis-menu#toggle" id="button">...</div>
|
<div data-action="click->vertical-ellipsis-menu#toggle" id="button">...</div>
|
||||||
<div data-vertical-ellipsis-menu-target="content" id="content">
|
<div data-vertical-ellipsis-menu-target="content" id="content">
|
||||||
|
<a href="#" id="item"></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
const button = document.getElementById("button");
|
const button = document.getElementById("button");
|
||||||
const content = document.getElementById("content");
|
const content = document.getElementById("content");
|
||||||
|
const item = document.getElementById("item");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("add show class to content when toggle is called", () => {
|
it("add show class to content when toggle is called", () => {
|
||||||
@@ -43,4 +44,15 @@ describe("VerticalEllipsisMenuController test", () => {
|
|||||||
document.body.click();
|
document.body.click();
|
||||||
expect(content.classList.contains("show")).toBe(false);
|
expect(content.classList.contains("show")).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("adds selected class to content when clicking a menu item", () => {
|
||||||
|
button.click();
|
||||||
|
expect(content.classList.contains("selected")).toBe(false);
|
||||||
|
item.click();
|
||||||
|
expect(content.classList.contains("selected")).toBe(true);
|
||||||
|
|
||||||
|
// and removes it again when clicking button again
|
||||||
|
button.click();
|
||||||
|
expect(content.classList.contains("selected")).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
18
spec/jobs/rake_job_spec.rb
Normal file
18
spec/jobs/rake_job_spec.rb
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "tasks/data/remove_transient_data"
|
||||||
|
|
||||||
|
RSpec.describe RakeJob do
|
||||||
|
let(:task_string) { "ofn:data:remove_transient_data" }
|
||||||
|
|
||||||
|
it "calls the removal service" do
|
||||||
|
expect(RemoveTransientData).to receive(:new).and_call_original
|
||||||
|
RakeJob.perform_now(task_string)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can be called several times" do
|
||||||
|
expect(RemoveTransientData).to receive(:new).twice.and_call_original
|
||||||
|
RakeJob.perform_now(task_string)
|
||||||
|
RakeJob.perform_now(task_string)
|
||||||
|
end
|
||||||
|
end
|
||||||
285
spec/requests/admin/ajax_search_spec.rb
Normal file
285
spec/requests/admin/ajax_search_spec.rb
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe "/admin/ajax_search" do
|
||||||
|
include AuthenticationHelper
|
||||||
|
|
||||||
|
let(:admin_user) { create(:admin_user) }
|
||||||
|
let(:regular_user) { create(:user) }
|
||||||
|
|
||||||
|
describe "GET /admin/ajax_search/producers" do
|
||||||
|
context "when user is not logged in" do
|
||||||
|
it "redirects to login" do
|
||||||
|
get admin_ajax_search_producers_path
|
||||||
|
|
||||||
|
expect(response).to redirect_to %r|#/login$|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when user is logged in without permissions" do
|
||||||
|
before { login_as regular_user }
|
||||||
|
|
||||||
|
it "redirects to unauthorized" do
|
||||||
|
get admin_ajax_search_producers_path
|
||||||
|
|
||||||
|
expect(response).to redirect_to('/unauthorized')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when user is an admin" do
|
||||||
|
before { login_as admin_user }
|
||||||
|
|
||||||
|
let!(:producer1) { create(:supplier_enterprise, name: "Apple Farm") }
|
||||||
|
let!(:producer2) { create(:supplier_enterprise, name: "Berry Farm") }
|
||||||
|
let!(:producer3) { create(:supplier_enterprise, name: "Cherry Orchard") }
|
||||||
|
let!(:distributor) { create(:distributor_enterprise, name: "Distributor") }
|
||||||
|
|
||||||
|
it "returns producers sorted alphabetically by name" do
|
||||||
|
get admin_ajax_search_producers_path
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
json_response = response.parsed_body
|
||||||
|
|
||||||
|
expect(json_response["results"].pluck("label")).to eq(['Apple Farm', 'Berry Farm',
|
||||||
|
'Cherry Orchard'])
|
||||||
|
expect(json_response["pagination"]["more"]).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "filters producers by search query" do
|
||||||
|
get admin_ajax_search_producers_path, params: { q: "berry" }
|
||||||
|
|
||||||
|
json_response = response.parsed_body
|
||||||
|
expect(json_response["results"].pluck("label")).to eq(['Berry Farm'])
|
||||||
|
expect(json_response["results"].pluck("value")).to eq([producer2.id])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "filters are case insensitive" do
|
||||||
|
get admin_ajax_search_producers_path, params: { q: "BERRY" }
|
||||||
|
|
||||||
|
json_response = response.parsed_body
|
||||||
|
expect(json_response["results"].pluck("label")).to eq(['Berry Farm'])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "filters with partial matches" do
|
||||||
|
get admin_ajax_search_producers_path, params: { q: "Farm" }
|
||||||
|
|
||||||
|
json_response = response.parsed_body
|
||||||
|
expect(json_response["results"].pluck("label")).to eq(['Apple Farm', 'Berry Farm'])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "excludes non-producer enterprises" do
|
||||||
|
get admin_ajax_search_producers_path
|
||||||
|
|
||||||
|
json_response = response.parsed_body
|
||||||
|
expect(json_response["results"].pluck("label")).not_to include('Distributor')
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with more than 30 producers" do
|
||||||
|
before do
|
||||||
|
create_list(:supplier_enterprise, 35) do |enterprise, i|
|
||||||
|
enterprise.update!(name: "Producer #{(i + 1).to_s.rjust(2, '0')}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns first page with 30 results and more flag as true" do
|
||||||
|
get admin_ajax_search_producers_path, params: { 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_ajax_search_producers_path, params: { page: 2 }
|
||||||
|
|
||||||
|
json_response = response.parsed_body
|
||||||
|
expect(json_response["results"].length).to eq(8)
|
||||||
|
expect(json_response["pagination"]["more"]).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when user has enterprise permissions" do
|
||||||
|
let!(:my_producer) { create(:supplier_enterprise, name: "My Producer") }
|
||||||
|
let!(:other_producer) { create(:supplier_enterprise, name: "Other Producer") }
|
||||||
|
let(:user_with_producer) { create(:user, enterprises: [my_producer]) }
|
||||||
|
|
||||||
|
before { login_as user_with_producer }
|
||||||
|
|
||||||
|
it "returns only managed producers" do
|
||||||
|
get admin_ajax_search_producers_path
|
||||||
|
|
||||||
|
json_response = response.parsed_body
|
||||||
|
expect(json_response["results"].pluck("label")).to eq(['My Producer'])
|
||||||
|
expect(json_response["results"].pluck("label")).not_to include('Other Producer')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "GET /admin/ajax_search/categories" do
|
||||||
|
context "when user is not logged in" do
|
||||||
|
it "redirects to login" do
|
||||||
|
get admin_ajax_search_categories_path
|
||||||
|
|
||||||
|
expect(response).to redirect_to %r|#/login$|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when user is logged in without permissions" do
|
||||||
|
before { login_as regular_user }
|
||||||
|
|
||||||
|
it "redirects to unauthorized" do
|
||||||
|
get admin_ajax_search_categories_path
|
||||||
|
|
||||||
|
expect(response).to redirect_to('/unauthorized')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when user is an admin" do
|
||||||
|
before { login_as admin_user }
|
||||||
|
|
||||||
|
let!(:category1) { create(:taxon, name: "Vegetables") }
|
||||||
|
let!(:category2) { create(:taxon, name: "Fruits") }
|
||||||
|
let!(:category3) { create(:taxon, name: "Dairy") }
|
||||||
|
|
||||||
|
it "returns categories sorted alphabetically by name" do
|
||||||
|
get admin_ajax_search_categories_path
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
json_response = response.parsed_body
|
||||||
|
|
||||||
|
expect(json_response["results"].pluck("label")).to eq(['Dairy', 'Fruits', 'Vegetables'])
|
||||||
|
expect(json_response["pagination"]["more"]).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "filters categories by search query" do
|
||||||
|
get admin_ajax_search_categories_path, params: { q: "fruit" }
|
||||||
|
|
||||||
|
json_response = response.parsed_body
|
||||||
|
expect(json_response["results"].pluck("label")).to eq(['Fruits'])
|
||||||
|
expect(json_response["results"].pluck("value")).to eq([category2.id])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "filters are case insensitive" do
|
||||||
|
get admin_ajax_search_categories_path, params: { q: "VEGETABLES" }
|
||||||
|
|
||||||
|
json_response = response.parsed_body
|
||||||
|
expect(json_response["results"].pluck("label")).to eq(['Vegetables'])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "filters with partial matches" do
|
||||||
|
get admin_ajax_search_categories_path, params: { q: "ege" }
|
||||||
|
|
||||||
|
json_response = response.parsed_body
|
||||||
|
expect(json_response["results"].pluck("label")).to eq(['Vegetables'])
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with more than 30 categories" do
|
||||||
|
before do
|
||||||
|
create_list(:taxon, 35) do |taxon, i|
|
||||||
|
taxon.update!(name: "Category #{(i + 1).to_s.rjust(2, '0')}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns first page with 30 results and more flag as true" do
|
||||||
|
get admin_ajax_search_categories_path, params: { 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_ajax_search_categories_path, params: { page: 2 }
|
||||||
|
|
||||||
|
json_response = response.parsed_body
|
||||||
|
expect(json_response["results"].length).to eq(8)
|
||||||
|
expect(json_response["pagination"]["more"]).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "GET /admin/ajax_search/tax_categories" do
|
||||||
|
context "when user is not logged in" do
|
||||||
|
it "redirects to login" do
|
||||||
|
get admin_ajax_search_tax_categories_path
|
||||||
|
|
||||||
|
expect(response).to redirect_to %r|#/login$|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when user is logged in without permissions" do
|
||||||
|
before { login_as regular_user }
|
||||||
|
|
||||||
|
it "redirects to unauthorized" do
|
||||||
|
get admin_ajax_search_tax_categories_path
|
||||||
|
|
||||||
|
expect(response).to redirect_to('/unauthorized')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when user is an admin" do
|
||||||
|
before { login_as admin_user }
|
||||||
|
|
||||||
|
let!(:tax_cat1) { create(:tax_category, name: "GST") }
|
||||||
|
let!(:tax_cat2) { create(:tax_category, name: "VAT") }
|
||||||
|
let!(:tax_cat3) { create(:tax_category, name: "No Tax") }
|
||||||
|
|
||||||
|
it "returns tax categories sorted alphabetically by name" do
|
||||||
|
get admin_ajax_search_tax_categories_path
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
json_response = response.parsed_body
|
||||||
|
|
||||||
|
expect(json_response["results"].pluck("label")).to eq(['GST', 'No Tax', 'VAT'])
|
||||||
|
expect(json_response["pagination"]["more"]).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "filters tax categories by search query" do
|
||||||
|
get admin_ajax_search_tax_categories_path, params: { q: "vat" }
|
||||||
|
|
||||||
|
json_response = response.parsed_body
|
||||||
|
expect(json_response["results"].pluck("label")).to eq(['VAT'])
|
||||||
|
expect(json_response["results"].pluck("value")).to eq([tax_cat2.id])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "filters are case insensitive" do
|
||||||
|
get admin_ajax_search_tax_categories_path, params: { q: "GST" }
|
||||||
|
|
||||||
|
json_response = response.parsed_body
|
||||||
|
expect(json_response["results"].pluck("label")).to eq(['GST'])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "filters with partial matches" do
|
||||||
|
get admin_ajax_search_tax_categories_path, params: { q: "tax" }
|
||||||
|
|
||||||
|
json_response = response.parsed_body
|
||||||
|
expect(json_response["results"].pluck("label")).to eq(['No Tax'])
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with more than 30 tax categories" do
|
||||||
|
before do
|
||||||
|
create_list(:tax_category, 35) do |tax_cat, i|
|
||||||
|
tax_cat.update!(name: "Tax Category #{(i + 1).to_s.rjust(2, '0')}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns first page with 30 results and more flag as true" do
|
||||||
|
get admin_ajax_search_tax_categories_path, params: { 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_ajax_search_tax_categories_path, params: { page: 2 }
|
||||||
|
|
||||||
|
json_response = response.parsed_body
|
||||||
|
expect(json_response["results"].length).to eq(8)
|
||||||
|
expect(json_response["pagination"]["more"]).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -19,12 +19,25 @@ module TomSelectHelper
|
|||||||
tomselect_wrapper.find(:css, '.ts-dropdown div.create').click
|
tomselect_wrapper.find(:css, '.ts-dropdown div.create').click
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Searches for and selects an option in a TomSelect dropdown with search functionality.
|
||||||
|
# @param value [String] The text to search for and select from the dropdown
|
||||||
|
# @param options [Hash] Configuration options
|
||||||
|
# @option options [String] :from The name/id of the select field
|
||||||
|
# @option options [Boolean] :remote_search If true, waits for search loading after interactions
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# tomselect_search_and_select("Apple", from: "fruit_selector")
|
||||||
|
# tomselect_search_and_select("California", from: "state", remote_search: true)
|
||||||
def tomselect_search_and_select(value, options)
|
def tomselect_search_and_select(value, options)
|
||||||
tomselect_wrapper = page.find_field(options[:from]).sibling(".ts-wrapper")
|
tomselect_wrapper = page.find_field(options[:from]).sibling(".ts-wrapper")
|
||||||
tomselect_wrapper.find(".ts-control").click
|
tomselect_wrapper.find(".ts-control").click
|
||||||
|
expect_tomselect_loading_completion(tomselect_wrapper, options)
|
||||||
|
|
||||||
# Use send_keys as setting the value directly doesn't trigger the search
|
# Use send_keys as setting the value directly doesn't trigger the search
|
||||||
tomselect_wrapper.find(".ts-dropdown input.dropdown-input").send_keys(value)
|
tomselect_wrapper.find(".ts-dropdown input.dropdown-input").send_keys(value)
|
||||||
tomselect_wrapper.find(".ts-dropdown .ts-dropdown-content .option.active", text: value).click
|
expect_tomselect_loading_completion(tomselect_wrapper, options)
|
||||||
|
|
||||||
|
tomselect_wrapper.find(".ts-dropdown .ts-dropdown-content .option", text: value).click
|
||||||
end
|
end
|
||||||
|
|
||||||
def tomselect_select(value, options)
|
def tomselect_select(value, options)
|
||||||
@@ -64,4 +77,52 @@ module TomSelectHelper
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Validates both available options and selected options in a TomSelect dropdown.
|
||||||
|
# @param from [String] The name/id of the select field
|
||||||
|
# @param existing_options [Array<String>] List of options that should be available in the dropdown
|
||||||
|
# @param selected_options [Array<String>] List of options that should currently be selected
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# expect_tomselect_existing_with_selected_options(
|
||||||
|
# from: "category_selector",
|
||||||
|
# existing_options: ["Fruit", "Vegetables", "Dairy"],
|
||||||
|
# selected_options: ["Fruit"]
|
||||||
|
# )
|
||||||
|
def expect_tomselect_existing_with_selected_options(from:, existing_options:, selected_options:)
|
||||||
|
tomselect_wrapper = page.find_field(from).sibling(".ts-wrapper")
|
||||||
|
tomselect_control = tomselect_wrapper.find('.ts-control')
|
||||||
|
|
||||||
|
tomselect_control.click # open the dropdown (would work for remote vs non-remote dropdowns)
|
||||||
|
|
||||||
|
# validate existing options are present in the dropdown
|
||||||
|
within(tomselect_wrapper) do
|
||||||
|
existing_options.each do |option|
|
||||||
|
expect(page).to have_css(
|
||||||
|
".ts-dropdown .ts-dropdown-content .option",
|
||||||
|
text: option
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# validate selected options are selected in the dropdown
|
||||||
|
within(tomselect_wrapper) do
|
||||||
|
selected_options.each do |option|
|
||||||
|
expect(page).to have_css(
|
||||||
|
"div[data-ts-item]",
|
||||||
|
text: option
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# close the dropdown by clicking on the already selected option
|
||||||
|
tomselect_wrapper.find(".ts-dropdown .ts-dropdown-content .option.active").click
|
||||||
|
end
|
||||||
|
|
||||||
|
def expect_tomselect_loading_completion(tomselect_wrapper, options)
|
||||||
|
return unless options[:remote_search]
|
||||||
|
|
||||||
|
expect(tomselect_wrapper).to have_css(".spinner")
|
||||||
|
expect(tomselect_wrapper).not_to have_css(".spinner")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,10 +15,6 @@ RSpec.describe 'As an enterprise user, I can perform actions on the products scr
|
|||||||
login_as user
|
login_as user
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:producer_search_selector) { 'input[placeholder="Select producer"]' }
|
|
||||||
let(:categories_search_selector) { 'input[placeholder="Select category"]' }
|
|
||||||
let(:tax_categories_search_selector) { 'input[placeholder="Search for tax categories"]' }
|
|
||||||
|
|
||||||
describe "column selector" do
|
describe "column selector" do
|
||||||
let!(:product) { create(:simple_product) }
|
let!(:product) { create(:simple_product) }
|
||||||
|
|
||||||
@@ -102,54 +98,7 @@ RSpec.describe 'As an enterprise user, I can perform actions on the products scr
|
|||||||
}
|
}
|
||||||
let!(:product_a) { create(:simple_product, name: "Apples", sku: "APL-00") }
|
let!(:product_a) { create(:simple_product, name: "Apples", sku: "APL-00") }
|
||||||
|
|
||||||
context "when they are under 11" do
|
context "when there are products" do
|
||||||
before do
|
|
||||||
create_list(:supplier_enterprise, 9, users: [user])
|
|
||||||
create_list(:tax_category, 9)
|
|
||||||
create_list(:taxon, 2)
|
|
||||||
|
|
||||||
visit admin_products_url
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should not display search input, change the producers, category and tax category" do
|
|
||||||
producer_to_select = random_producer(variant_a1)
|
|
||||||
category_to_select = random_category(variant_a1)
|
|
||||||
tax_category_to_select = random_tax_category
|
|
||||||
|
|
||||||
within row_containing_name(variant_a1.display_name) do
|
|
||||||
validate_tomselect_without_search!(
|
|
||||||
page, "Producer",
|
|
||||||
producer_search_selector
|
|
||||||
)
|
|
||||||
tomselect_select(producer_to_select, from: "Producer")
|
|
||||||
end
|
|
||||||
|
|
||||||
within row_containing_name(variant_a1.display_name) do
|
|
||||||
validate_tomselect_without_search!(
|
|
||||||
page, "Category",
|
|
||||||
categories_search_selector
|
|
||||||
)
|
|
||||||
tomselect_select(category_to_select, from: "Category")
|
|
||||||
|
|
||||||
validate_tomselect_without_search!(
|
|
||||||
page, "Tax Category",
|
|
||||||
tax_categories_search_selector
|
|
||||||
)
|
|
||||||
tomselect_select(tax_category_to_select, from: "Tax Category")
|
|
||||||
end
|
|
||||||
|
|
||||||
click_button "Save changes"
|
|
||||||
|
|
||||||
expect(page).to have_content "Changes saved"
|
|
||||||
|
|
||||||
variant_a1.reload
|
|
||||||
expect(variant_a1.supplier.name).to eq(producer_to_select)
|
|
||||||
expect(variant_a1.primary_taxon.name).to eq(category_to_select)
|
|
||||||
expect(variant_a1.tax_category.name).to eq(tax_category_to_select)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when they are over 11" do
|
|
||||||
before do
|
before do
|
||||||
create_list(:supplier_enterprise, 11, users: [user])
|
create_list(:supplier_enterprise, 11, users: [user])
|
||||||
create_list(:tax_category, 11)
|
create_list(:tax_category, 11)
|
||||||
@@ -167,9 +116,13 @@ RSpec.describe 'As an enterprise user, I can perform actions on the products scr
|
|||||||
tax_category_to_select = random_tax_category
|
tax_category_to_select = random_tax_category
|
||||||
|
|
||||||
within row_containing_name(variant_a1.display_name) do
|
within row_containing_name(variant_a1.display_name) do
|
||||||
tomselect_search_and_select(producer_to_select, from: "Producer")
|
tomselect_search_and_select(producer_to_select, from: "Producer", remote_search: true)
|
||||||
tomselect_search_and_select(category_to_select, from: "Category")
|
tomselect_search_and_select(category_to_select, from: "Category", remote_search: true)
|
||||||
tomselect_search_and_select(tax_category_to_select, from: "Tax Category")
|
tomselect_search_and_select(
|
||||||
|
tax_category_to_select,
|
||||||
|
from: "Tax Category",
|
||||||
|
remote_search: true
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
click_button "Save changes"
|
click_button "Save changes"
|
||||||
@@ -285,8 +238,9 @@ RSpec.describe 'As an enterprise user, I can perform actions on the products scr
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "Create linked variant" do
|
describe "Create linked variant" do
|
||||||
let!(:variant) {
|
let!(:variant) { create(:variant, display_name: "My box", supplier: producer) }
|
||||||
create(:variant, display_name: "My box", supplier: producer)
|
let!(:linked_variant) {
|
||||||
|
variant.create_linked_variant(user).tap{ |v| v.update! display_name: "My linked variant" }
|
||||||
}
|
}
|
||||||
let!(:other_producer) { create(:supplier_enterprise) }
|
let!(:other_producer) { create(:supplier_enterprise) }
|
||||||
let!(:other_variant) {
|
let!(:other_variant) {
|
||||||
@@ -312,6 +266,15 @@ RSpec.describe 'As an enterprise user, I can perform actions on the products scr
|
|||||||
|
|
||||||
expect(page).to have_link "Create linked variant"
|
expect(page).to have_link "Create linked variant"
|
||||||
end
|
end
|
||||||
|
close_action_menu
|
||||||
|
|
||||||
|
# Check my own linked variant
|
||||||
|
within row_containing_name("My linked variant") do
|
||||||
|
page.find(".vertical-ellipsis-menu").click
|
||||||
|
|
||||||
|
expect(page).not_to have_link "Create linked variant"
|
||||||
|
end
|
||||||
|
close_action_menu
|
||||||
|
|
||||||
# Create linked variant sourced from my friend
|
# Create linked variant sourced from my friend
|
||||||
within row_containing_name("My friends box") do
|
within row_containing_name("My friends box") do
|
||||||
@@ -332,12 +295,11 @@ RSpec.describe 'As an enterprise user, I can perform actions on the products scr
|
|||||||
# Close action menu (shouldn't need this, it should close itself)
|
# Close action menu (shouldn't need this, it should close itself)
|
||||||
last_box.click
|
last_box.click
|
||||||
|
|
||||||
# And I can perform actions on the new product
|
# And I can perform actions on the new variant
|
||||||
within last_box do
|
within last_box do
|
||||||
page.find(".vertical-ellipsis-menu").click
|
page.find(".vertical-ellipsis-menu").click
|
||||||
expect(page).to have_link "Edit"
|
expect(page).to have_link "Edit"
|
||||||
# expect(page).to have_link "Clone" # tofix: menu is partially obscured
|
expect(page).to have_selector "a", text: "Delete" # it's not a proper link
|
||||||
# expect(page).to have_link "Delete" # it's not a proper link
|
|
||||||
|
|
||||||
fill_in "Name", with: "My copy of Apples"
|
fill_in "Name", with: "My copy of Apples"
|
||||||
end
|
end
|
||||||
@@ -376,12 +338,10 @@ RSpec.describe 'As an enterprise user, I can perform actions on the products scr
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe "Actions columns (delete)" do
|
describe "Actions columns (delete)" do
|
||||||
before do
|
|
||||||
visit admin_products_url
|
|
||||||
end
|
|
||||||
|
|
||||||
it "shows an actions menu with a delete link when clicking on icon for product. " \
|
it "shows an actions menu with a delete link when clicking on icon for product. " \
|
||||||
"doesn't show delete link for the single variant" do
|
"doesn't show delete link for the single variant" do
|
||||||
|
visit admin_products_url
|
||||||
|
|
||||||
within product_selector do
|
within product_selector do
|
||||||
page.find(".vertical-ellipsis-menu").click
|
page.find(".vertical-ellipsis-menu").click
|
||||||
expect(page).to have_css(delete_option_selector)
|
expect(page).to have_css(delete_option_selector)
|
||||||
|
|||||||
@@ -86,14 +86,14 @@ RSpec.describe 'As an enterprise user, I can manage my products' do
|
|||||||
find('button[aria-label="On Hand"]').click
|
find('button[aria-label="On Hand"]').click
|
||||||
find('input[id$="_price"]').fill_in with: "11.1"
|
find('input[id$="_price"]').fill_in with: "11.1"
|
||||||
|
|
||||||
select supplier.name, from: 'Producer'
|
|
||||||
select taxon.name, from: 'Category'
|
|
||||||
|
|
||||||
if stock == "on_hand"
|
if stock == "on_hand"
|
||||||
find('input[id$="_on_hand_desired"]').fill_in with: "66"
|
find('input[id$="_on_hand_desired"]').fill_in with: "66"
|
||||||
elsif stock == "on_demand"
|
elsif stock == "on_demand"
|
||||||
find('input[id$="_on_demand_desired"]').check
|
find('input[id$="_on_demand_desired"]').check
|
||||||
end
|
end
|
||||||
|
|
||||||
|
tomselect_select supplier.name, from: 'Producer'
|
||||||
|
tomselect_select taxon.name, from: 'Category'
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(page).to have_content "1 product modified."
|
expect(page).to have_content "1 product modified."
|
||||||
|
|||||||
@@ -106,13 +106,19 @@ RSpec.describe 'As an enterprise user, I can browse my products' do
|
|||||||
visit spree.admin_products_path
|
visit spree.admin_products_path
|
||||||
|
|
||||||
within row_containing_name "Variant1" do
|
within row_containing_name "Variant1" do
|
||||||
expect(page).to have_select "Producer", with_options: ["Producer A", "Producer B"],
|
expect_tomselect_existing_with_selected_options(
|
||||||
selected: "Producer A"
|
from: 'Producer',
|
||||||
|
existing_options: ["Producer A", "Producer B"],
|
||||||
|
selected_options: ["Producer A"]
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
within row_containing_name "Variant2a" do
|
within row_containing_name "Variant2a" do
|
||||||
expect(page).to have_select "Producer", with_options: ["Producer A", "Producer B"],
|
expect_tomselect_existing_with_selected_options(
|
||||||
selected: "Producer B"
|
from: 'Producer',
|
||||||
|
existing_options: ["Producer A", "Producer B"],
|
||||||
|
selected_options: ["Producer B"]
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -543,24 +549,21 @@ RSpec.describe 'As an enterprise user, I can browse my products' do
|
|||||||
|
|
||||||
it "shows only suppliers that I manage or have permission to" do
|
it "shows only suppliers that I manage or have permission to" do
|
||||||
visit spree.admin_products_path
|
visit spree.admin_products_path
|
||||||
|
existing_options = [supplier_managed1.name, supplier_managed2.name, supplier_permitted.name]
|
||||||
|
|
||||||
within row_containing_placeholder(product_supplied.name) do
|
within row_containing_placeholder(product_supplied.name) do
|
||||||
expect(page).to have_select(
|
expect_tomselect_existing_with_selected_options(
|
||||||
'_products_0_variants_attributes_0_supplier_id',
|
existing_options:,
|
||||||
options: [
|
from: '_products_0_variants_attributes_0_supplier_id',
|
||||||
'Select producer',
|
selected_options: [supplier_managed1.name]
|
||||||
supplier_managed1.name, supplier_managed2.name, supplier_permitted.name
|
|
||||||
], selected: supplier_managed1.name
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
within row_containing_placeholder(product_supplied_permitted.name) do
|
within row_containing_placeholder(product_supplied_permitted.name) do
|
||||||
expect(page).to have_select(
|
expect_tomselect_existing_with_selected_options(
|
||||||
'_products_1_variants_attributes_0_supplier_id',
|
existing_options:,
|
||||||
options: [
|
from: '_products_1_variants_attributes_0_supplier_id',
|
||||||
'Select producer',
|
selected_options: [supplier_permitted.name]
|
||||||
supplier_managed1.name, supplier_managed2.name, supplier_permitted.name
|
|
||||||
], selected: supplier_permitted.name
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -350,8 +350,8 @@ RSpec.describe 'As an enterprise user, I can update my products' do
|
|||||||
click_on "On Hand" # activate popout
|
click_on "On Hand" # activate popout
|
||||||
fill_in "On Hand", with: "3"
|
fill_in "On Hand", with: "3"
|
||||||
|
|
||||||
select producer.name, from: 'Producer'
|
tomselect_select producer.name, from: 'Producer'
|
||||||
select taxon.name, from: 'Category'
|
tomselect_select taxon.name, from: 'Category'
|
||||||
end
|
end
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
@@ -586,8 +586,8 @@ RSpec.describe 'As an enterprise user, I can update my products' do
|
|||||||
fill_in "Name", with: "Nice box"
|
fill_in "Name", with: "Nice box"
|
||||||
fill_in "SKU", with: "APL-02"
|
fill_in "SKU", with: "APL-02"
|
||||||
|
|
||||||
select producer.name, from: 'Producer'
|
tomselect_select producer.name, from: 'Producer'
|
||||||
select taxon.name, from: 'Category'
|
tomselect_select taxon.name, from: 'Category'
|
||||||
end
|
end
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ RSpec.describe "admin/products_v3/_filters.html.haml" do
|
|||||||
end
|
end
|
||||||
let(:spree_current_user) { build(:enterprise_user) }
|
let(:spree_current_user) { build(:enterprise_user) }
|
||||||
|
|
||||||
it "shows the producer filter when there are options" do
|
it "shows the producer filter with the default option initially" do
|
||||||
allow(view).to receive_messages locals.merge(
|
allow(view).to receive_messages locals.merge(
|
||||||
producer_options: [
|
producer_options: [
|
||||||
["Ada's Apples", 1],
|
["Ada's Apples", 1],
|
||||||
@@ -27,9 +27,7 @@ RSpec.describe "admin/products_v3/_filters.html.haml" do
|
|||||||
|
|
||||||
is_expected.to have_content "Producers"
|
is_expected.to have_content "Producers"
|
||||||
is_expected.to have_select "producer_id", options: [
|
is_expected.to have_select "producer_id", options: [
|
||||||
"All producers",
|
"All producers"
|
||||||
"Ada's Apples",
|
|
||||||
"Ben's Bananas",
|
|
||||||
], selected: nil
|
], selected: nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
12
yarn.lock
12
yarn.lock
@@ -5270,9 +5270,9 @@ node-addon-api@^7.0.0:
|
|||||||
integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
|
integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
|
||||||
|
|
||||||
node-forge@^1:
|
node-forge@^1:
|
||||||
version "1.3.3"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.3.tgz#0ad80f6333b3a0045e827ac20b7f735f93716751"
|
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.4.0.tgz#1c7b7d8bdc2d078739f58287d589d903a11b2fc2"
|
||||||
integrity sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==
|
integrity sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==
|
||||||
|
|
||||||
node-int64@^0.4.0:
|
node-int64@^0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
@@ -7111,9 +7111,9 @@ tr46@^5.1.0:
|
|||||||
punycode "^2.3.1"
|
punycode "^2.3.1"
|
||||||
|
|
||||||
trix@*:
|
trix@*:
|
||||||
version "2.1.17"
|
version "2.1.18"
|
||||||
resolved "https://registry.yarnpkg.com/trix/-/trix-2.1.17.tgz#a47c4ee1925a7abb26aee5c094ec7ef9fe49575a"
|
resolved "https://registry.yarnpkg.com/trix/-/trix-2.1.18.tgz#d87a5be64c10ecdab64f5af47f941b5318c08225"
|
||||||
integrity sha512-nkHg7VgIItGVx1CFA645dDlAoCgah+9gv80Yc+97aS8jkZmO5K4MxkSqmU9t/C8upqZB8uhfJs90epoDfYz/6Q==
|
integrity sha512-DWOdTsz3n9PO3YBc1R6pGh9MG1cXys/2+rouc/qsISncjc2MBew2UOW8nXh3NjUOjobKsXCIPR6LB02abg2EYg==
|
||||||
dependencies:
|
dependencies:
|
||||||
dompurify "^3.2.5"
|
dompurify "^3.2.5"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user