mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-03-01 02:03:22 +00:00
Compare commits
1 Commits
v5.4.3
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34867fb85a |
50
Gemfile.lock
50
Gemfile.lock
@@ -207,7 +207,7 @@ GEM
|
||||
bcp47_spec (0.2.1)
|
||||
bcrypt (3.1.20)
|
||||
benchmark (0.5.0)
|
||||
bigdecimal (3.3.1)
|
||||
bigdecimal (4.0.1)
|
||||
bindata (2.5.1)
|
||||
bindex (0.8.1)
|
||||
bootsnap (1.22.0)
|
||||
@@ -326,21 +326,19 @@ GEM
|
||||
factory_bot_rails (6.2.0)
|
||||
factory_bot (~> 6.2.0)
|
||||
railties (>= 5.0.0)
|
||||
faraday (2.14.1)
|
||||
faraday-net_http (>= 2.0, < 3.5)
|
||||
json
|
||||
logger
|
||||
faraday (2.9.0)
|
||||
faraday-net_http (>= 2.0, < 3.2)
|
||||
faraday-follow_redirects (0.4.0)
|
||||
faraday (>= 1, < 3)
|
||||
faraday-net_http (3.4.2)
|
||||
net-http (~> 0.5)
|
||||
faraday-net_http (3.1.1)
|
||||
net-http
|
||||
ferrum (0.14)
|
||||
addressable (~> 2.5)
|
||||
concurrent-ruby (~> 1.1)
|
||||
webrick (~> 1.7)
|
||||
websocket-driver (>= 0.6, < 0.8)
|
||||
ffaker (2.25.0)
|
||||
ffi (1.17.3)
|
||||
ffi (1.17.2)
|
||||
flipper (1.3.6)
|
||||
concurrent-ruby (< 2)
|
||||
flipper-active_record (1.3.6)
|
||||
@@ -442,7 +440,7 @@ GEM
|
||||
thor (>= 0.14, < 2.0)
|
||||
jquery-ui-rails (4.2.1)
|
||||
railties (>= 3.2.16)
|
||||
json (2.18.1)
|
||||
json (2.15.2)
|
||||
json-canonicalization (1.0.0)
|
||||
json-jwt (1.17.0)
|
||||
activesupport (>= 4.2)
|
||||
@@ -468,7 +466,7 @@ GEM
|
||||
activesupport (>= 4.2)
|
||||
jwt (2.10.2)
|
||||
base64
|
||||
knapsack_pro (9.2.2)
|
||||
knapsack_pro (8.4.0)
|
||||
rake
|
||||
language_server-protocol (3.17.0.5)
|
||||
launchy (3.0.0)
|
||||
@@ -478,8 +476,7 @@ GEM
|
||||
launchy (>= 2.2, < 4)
|
||||
link_header (0.0.8)
|
||||
lint_roller (1.1.0)
|
||||
listen (3.10.0)
|
||||
logger
|
||||
listen (3.9.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
logger (1.7.0)
|
||||
@@ -516,8 +513,8 @@ GEM
|
||||
multi_json (1.17.0)
|
||||
multi_xml (0.6.0)
|
||||
mutex_m (0.3.0)
|
||||
net-http (0.9.1)
|
||||
uri (>= 0.11.1)
|
||||
net-http (0.7.0)
|
||||
uri
|
||||
net-imap (0.5.12)
|
||||
date
|
||||
net-protocol
|
||||
@@ -575,7 +572,7 @@ GEM
|
||||
parallel (1.27.0)
|
||||
paranoia (2.6.4)
|
||||
activerecord (>= 5.1, < 7.2)
|
||||
parser (3.3.10.2)
|
||||
parser (3.3.10.1)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
paypal-sdk-core (0.3.4)
|
||||
@@ -618,7 +615,7 @@ GEM
|
||||
railties (>= 4.2)
|
||||
raabro (1.4.0)
|
||||
racc (1.8.1)
|
||||
rack (2.2.22)
|
||||
rack (2.2.21)
|
||||
rack-mini-profiler (2.3.4)
|
||||
rack (>= 1.2.0)
|
||||
rack-oauth2 (2.3.0)
|
||||
@@ -692,21 +689,15 @@ GEM
|
||||
activesupport (>= 6.1.5)
|
||||
i18n
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.11.1)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rdf (3.3.4)
|
||||
rdf (3.3.1)
|
||||
bcp47_spec (~> 0.2)
|
||||
bigdecimal (~> 3.1, >= 3.1.5)
|
||||
link_header (~> 0.0, >= 0.0.8)
|
||||
logger (~> 1.5)
|
||||
ostruct (~> 0.6)
|
||||
readline (~> 0.0)
|
||||
rdoc (7.2.0)
|
||||
erb
|
||||
psych (>= 4.0.0)
|
||||
tsort
|
||||
readline (0.0.4)
|
||||
reline
|
||||
redcarpet (3.6.1)
|
||||
redis (5.4.1)
|
||||
redis-client (>= 0.22.0)
|
||||
@@ -774,7 +765,7 @@ GEM
|
||||
rswag-ui (2.17.0)
|
||||
actionpack (>= 5.2, < 8.2)
|
||||
railties (>= 5.2, < 8.2)
|
||||
rubocop (1.84.2)
|
||||
rubocop (1.81.7)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.1.0)
|
||||
@@ -782,7 +773,7 @@ GEM
|
||||
parser (>= 3.3.0.2)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 2.9.3, < 3.0)
|
||||
rubocop-ast (>= 1.49.0, < 2.0)
|
||||
rubocop-ast (>= 1.47.1, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 4.0)
|
||||
rubocop-ast (1.49.0)
|
||||
@@ -794,7 +785,7 @@ GEM
|
||||
rubocop-factory_bot (2.28.0)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.72, >= 1.72.1)
|
||||
rubocop-rails (2.34.3)
|
||||
rubocop-rails (2.34.2)
|
||||
activesupport (>= 4.2.0)
|
||||
lint_roller (~> 1.1)
|
||||
rack (>= 1.1)
|
||||
@@ -852,7 +843,7 @@ GEM
|
||||
caxlsx (<= 4.0)
|
||||
csv
|
||||
rodf
|
||||
spring (4.4.2)
|
||||
spring (4.4.0)
|
||||
spring-commands-rspec (1.0.4)
|
||||
spring (>= 0.9.1)
|
||||
spring-commands-rubocop (0.4.0)
|
||||
@@ -902,8 +893,7 @@ GEM
|
||||
tilt (2.6.1)
|
||||
timeout (0.6.0)
|
||||
tsort (0.2.0)
|
||||
ttfunk (1.8.0)
|
||||
bigdecimal (~> 3.1)
|
||||
ttfunk (1.7.0)
|
||||
turbo-rails (2.0.20)
|
||||
actionpack (>= 7.1.0)
|
||||
railties (>= 7.1.0)
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
# 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:,
|
||||
form: nil,
|
||||
placeholder_value: '',
|
||||
placeholder_value:,
|
||||
include_blank: false,
|
||||
aria_label: '',
|
||||
multiple: false,
|
||||
remote_url: nil,
|
||||
other_attrs: {}
|
||||
)
|
||||
@f = form
|
||||
@@ -22,15 +21,13 @@ 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, :multiple, :remote_url, :other_attrs
|
||||
:aria_label, :other_attrs
|
||||
|
||||
def classes
|
||||
"fullwidth #{'no-input' if remove_search_plugin?}"
|
||||
@@ -39,33 +36,11 @@ class SearchableDropdownComponent < ViewComponent::Base
|
||||
def data
|
||||
{
|
||||
controller: "tom-select",
|
||||
'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?
|
||||
'tom-select-placeholder-value': placeholder_value
|
||||
}.merge(remove_search_plugin? ? REMOVED_SEARCH_PLUGIN : {})
|
||||
end
|
||||
|
||||
def remove_search_plugin?
|
||||
# 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)
|
||||
@remove_search_plugin ||= options.count < MINIMUM_OPTIONS_FOR_SEARCH_FIELD
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
- 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
|
||||
= f.select name, options_for_select(options, selected_option), { include_blank: }, class: classes, data:, 'aria-label': aria_label, **other_attrs
|
||||
|
||||
@@ -4,7 +4,6 @@ module Admin
|
||||
class ReportsController < Spree::Admin::BaseController
|
||||
include ActiveStorage::SetCurrent
|
||||
include ReportsActions
|
||||
include Reports::AjaxSearch
|
||||
|
||||
helper ReportsHelper
|
||||
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
# 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
|
||||
@@ -138,7 +138,7 @@ module Spree
|
||||
|
||||
providers.delete("Spree::Gateway::StripeSCA") unless show_stripe?
|
||||
|
||||
providers
|
||||
providers.map(&:constantize)
|
||||
end
|
||||
|
||||
# Show Stripe as an option if enabled, or if the
|
||||
|
||||
@@ -95,7 +95,8 @@ module Spree
|
||||
private
|
||||
|
||||
def load_payment_source
|
||||
if @payment.payment_method.is_a?(Gateway::StripeSCA) &&
|
||||
if @payment.payment_method.is_a?(Spree::Gateway) &&
|
||||
@payment.payment_method.payment_profiles_supported? &&
|
||||
params[:card].present? &&
|
||||
(params[:card] != 'new')
|
||||
@payment.source = CreditCard.find_by(id: params[:card])
|
||||
|
||||
@@ -34,8 +34,29 @@ 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')
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
module Admin
|
||||
module PaymentMethodsHelper
|
||||
def payment_method_type_name(class_name)
|
||||
scope = "spree.admin.payment_methods.providers"
|
||||
key = class_name.demodulize.downcase
|
||||
|
||||
I18n.t(key, scope:)
|
||||
end
|
||||
|
||||
def payment_method_type_options(providers)
|
||||
providers.map { |p| [payment_method_type_name(p), p] }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -6,11 +6,6 @@ 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
|
||||
|
||||
@@ -265,8 +260,7 @@ module Spree
|
||||
can [:admin, :index, :import], ::Admin::DfcProductImportsController
|
||||
|
||||
# Reports page
|
||||
can [:admin, :index, :show, :create, *REPORTS_SEARCH_ACTIONS],
|
||||
::Admin::ReportsController
|
||||
can [:admin, :index, :show, :create], ::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
|
||||
@@ -398,7 +392,7 @@ module Spree
|
||||
end
|
||||
|
||||
# Reports page
|
||||
can [:admin, :index, :show, :create, *REPORTS_SEARCH_ACTIONS], ::Admin::ReportsController
|
||||
can [:admin, :index, :show, :create], ::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
|
||||
|
||||
@@ -5,7 +5,7 @@ module Spree
|
||||
acts_as_taggable
|
||||
include PaymentMethodDistributors
|
||||
|
||||
delegate :authorize, :purchase, :capture, :void, :credit, :refund, to: :provider
|
||||
delegate :authorize, :purchase, :capture, :void, :credit, to: :provider
|
||||
|
||||
validates :name, :type, presence: true
|
||||
|
||||
@@ -35,10 +35,6 @@ module Spree
|
||||
end
|
||||
|
||||
def method_missing(method, *)
|
||||
message = "Deprecated delegation of Gateway##{method}"
|
||||
Alert.raise(message)
|
||||
raise message if Rails.env.local?
|
||||
|
||||
if @provider.nil? || !@provider.respond_to?(method)
|
||||
super
|
||||
else
|
||||
@@ -46,6 +42,10 @@ module Spree
|
||||
end
|
||||
end
|
||||
|
||||
def payment_profiles_supported?
|
||||
false
|
||||
end
|
||||
|
||||
def method_type
|
||||
'gateway'
|
||||
end
|
||||
|
||||
@@ -35,6 +35,10 @@ module Spree
|
||||
ActiveMerchant::Billing::StripePaymentIntentsGateway
|
||||
end
|
||||
|
||||
def payment_profiles_supported?
|
||||
true
|
||||
end
|
||||
|
||||
def stripe_account_id
|
||||
StripeAccount.find_by(enterprise_id: preferred_enterprise_id)&.stripe_user_id
|
||||
end
|
||||
@@ -80,7 +84,7 @@ module Spree
|
||||
end
|
||||
|
||||
# NOTE: this method is required by Spree::Payment::Processing
|
||||
def void(payment_intent_id, gateway_options)
|
||||
def void(payment_intent_id, _creditcard, gateway_options)
|
||||
payment_intent_response = Stripe::PaymentIntent.retrieve(
|
||||
payment_intent_id, stripe_account: stripe_account_id
|
||||
)
|
||||
@@ -97,13 +101,7 @@ module Spree
|
||||
end
|
||||
|
||||
# NOTE: this method is required by Spree::Payment::Processing
|
||||
def credit(money, payment_intent_id, gateway_options)
|
||||
gateway_options[:stripe_account] = stripe_account_id
|
||||
provider.refund(money, payment_intent_id, gateway_options)
|
||||
end
|
||||
|
||||
# NOTE: this method is required by Spree::Payment::Processing
|
||||
def refund(money, payment_intent_id, gateway_options)
|
||||
def credit(money, _creditcard, payment_intent_id, gateway_options)
|
||||
gateway_options[:stripe_account] = stripe_account_id
|
||||
provider.refund(money, payment_intent_id, gateway_options)
|
||||
end
|
||||
|
||||
@@ -34,7 +34,7 @@ module Spree
|
||||
|
||||
# invalidate previously entered payments
|
||||
after_create :invalidate_old_payments
|
||||
after_save :create_payment_profile
|
||||
after_save :create_payment_profile, if: :profiles_supported?
|
||||
|
||||
# update the order totals, etc.
|
||||
after_save :ensure_correct_adjustment, :update_order
|
||||
@@ -217,13 +217,18 @@ module Spree
|
||||
errors.blank?
|
||||
end
|
||||
|
||||
def profiles_supported?
|
||||
payment_method.respond_to?(:payment_profiles_supported?) &&
|
||||
payment_method.payment_profiles_supported?
|
||||
end
|
||||
|
||||
def create_payment_profile
|
||||
return unless source.is_a?(CreditCard)
|
||||
return unless source.try(:save_requested_by_customer?)
|
||||
return unless source.number || source.gateway_payment_profile_id
|
||||
return unless source.gateway_customer_profile_id.nil?
|
||||
|
||||
payment_method.try(:create_profile, self)
|
||||
payment_method.create_profile(self)
|
||||
rescue ActiveMerchant::ConnectionError => e
|
||||
gateway_error e
|
||||
end
|
||||
|
||||
@@ -58,7 +58,16 @@ module Spree
|
||||
protect_from_connection_error do
|
||||
check_environment
|
||||
|
||||
response = payment_method.void(response_code, gateway_options)
|
||||
response = if payment_method.payment_profiles_supported?
|
||||
# Gateways supporting payment profiles will need access to credit
|
||||
# card object because this stores the payment profile information
|
||||
# so supply the authorization itself as well as the credit card,
|
||||
# rather than just the authorization code
|
||||
payment_method.void(response_code, source, gateway_options)
|
||||
else
|
||||
# Standard ActiveMerchant void usage
|
||||
payment_method.void(response_code, gateway_options)
|
||||
end
|
||||
|
||||
record_response(response)
|
||||
|
||||
@@ -77,11 +86,20 @@ module Spree
|
||||
|
||||
credit_amount = calculate_refund_amount(credit_amount)
|
||||
|
||||
response = payment_method.credit(
|
||||
(credit_amount * 100).round,
|
||||
response_code,
|
||||
gateway_options
|
||||
)
|
||||
response = if payment_method.payment_profiles_supported?
|
||||
payment_method.credit(
|
||||
(credit_amount * 100).round,
|
||||
source,
|
||||
response_code,
|
||||
gateway_options
|
||||
)
|
||||
else
|
||||
payment_method.credit(
|
||||
(credit_amount * 100).round,
|
||||
response_code,
|
||||
gateway_options
|
||||
)
|
||||
end
|
||||
|
||||
record_response(response)
|
||||
|
||||
@@ -107,11 +125,20 @@ module Spree
|
||||
|
||||
refund_amount = calculate_refund_amount(refund_amount)
|
||||
|
||||
response = payment_method.refund(
|
||||
(refund_amount * 100).round,
|
||||
response_code,
|
||||
gateway_options
|
||||
)
|
||||
response = if payment_method.payment_profiles_supported?
|
||||
payment_method.refund(
|
||||
(refund_amount * 100).round,
|
||||
source,
|
||||
response_code,
|
||||
gateway_options
|
||||
)
|
||||
else
|
||||
payment_method.refund(
|
||||
(refund_amount * 100).round,
|
||||
response_code,
|
||||
gateway_options
|
||||
)
|
||||
end
|
||||
|
||||
record_response(response)
|
||||
|
||||
|
||||
@@ -93,6 +93,10 @@ module Spree
|
||||
unscoped { find(*) }
|
||||
end
|
||||
|
||||
def payment_profiles_supported?
|
||||
false
|
||||
end
|
||||
|
||||
def source_required?
|
||||
true
|
||||
end
|
||||
@@ -109,6 +113,11 @@ module Spree
|
||||
distributors.include?(distributor)
|
||||
end
|
||||
|
||||
def self.clean_name
|
||||
scope = "spree.admin.payment_methods.providers"
|
||||
I18n.t(name.demodulize.downcase, scope:)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def distributor_validation
|
||||
|
||||
@@ -251,7 +251,7 @@ module Spree
|
||||
transaction do
|
||||
ExchangeVariant.
|
||||
where(exchange_variants: { variant_id: variants.with_deleted.
|
||||
select(:id) }).destroy_all
|
||||
select(:id) }).destroy_all
|
||||
|
||||
yield
|
||||
end
|
||||
|
||||
@@ -35,8 +35,8 @@ module Spree
|
||||
taxons
|
||||
.pluck('spree_taxons.id, enterprises.id AS enterprise_id')
|
||||
.each_with_object({}) do |(taxon_id, enterprise_id), collection|
|
||||
collection[enterprise_id.to_i] ||= Set.new
|
||||
collection[enterprise_id.to_i] << taxon_id
|
||||
collection[enterprise_id.to_i] ||= Set.new
|
||||
collection[enterprise_id.to_i] << taxon_id
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -167,8 +167,8 @@ module Spree
|
||||
# In Rails 3, merging two scopes on the same column will consider only the last scope.
|
||||
def self.in_distributor(distributor)
|
||||
where(id: ExchangeVariant.select(:variant_id).
|
||||
joins(:exchange).
|
||||
where('exchanges.incoming = ? AND exchanges.receiver_id = ?', false, distributor))
|
||||
joins(:exchange).
|
||||
where('exchanges.incoming = ? AND exchanges.receiver_id = ?', false, distributor))
|
||||
end
|
||||
|
||||
def self.indexed
|
||||
@@ -179,11 +179,11 @@ module Spree
|
||||
# "where(id:" is necessary so that the returned relation has no includes
|
||||
# The relation without includes will not be readonly and allow updates on it
|
||||
where(spree_variants: { id: joins(:prices).
|
||||
where(deleted_at: nil).
|
||||
where('spree_prices.currency' =>
|
||||
where(deleted_at: nil).
|
||||
where('spree_prices.currency' =>
|
||||
currency || CurrentConfig.get(:currency)).
|
||||
where.not(spree_prices: { amount: nil }).
|
||||
select("spree_variants.id") })
|
||||
where.not(spree_prices: { amount: nil }).
|
||||
select("spree_variants.id") })
|
||||
end
|
||||
|
||||
def self.linked_to(semantic_id)
|
||||
|
||||
@@ -60,8 +60,8 @@ class ProductScopeQuery
|
||||
|
||||
def product_query_includes
|
||||
[
|
||||
{ image: { attachment_attachment: :blob },
|
||||
variants: [:default_price, :stock_items, :variant_overrides] }
|
||||
image: { attachment_attachment: :blob },
|
||||
variants: [:default_price, :stock_items, :variant_overrides]
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
@@ -42,10 +42,10 @@ class DfcCatalogImporter
|
||||
.includes(:semantic_links).references(:semantic_links)
|
||||
.where.not(semantic_links: { semantic_id: present_ids })
|
||||
.select do |variant|
|
||||
# Variants that were in the same catalog before:
|
||||
variant.semantic_links.map(&:semantic_id).any? do |semantic_id|
|
||||
FdcUrlBuilder.new(semantic_id).catalog_url == catalog_url
|
||||
end
|
||||
# Variants that were in the same catalog before:
|
||||
variant.semantic_links.map(&:semantic_id).any? do |semantic_id|
|
||||
FdcUrlBuilder.new(semantic_id).catalog_url == catalog_url
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,8 +51,8 @@ class LineItemSyncer
|
||||
def destroy_obsolete_items(order)
|
||||
order.line_items.
|
||||
where(variant_id: subscription_line_items.
|
||||
select(&:marked_for_destruction?).
|
||||
map(&:variant_id)).
|
||||
select(&:marked_for_destruction?).
|
||||
map(&:variant_id)).
|
||||
destroy_all
|
||||
end
|
||||
|
||||
|
||||
@@ -130,11 +130,12 @@ module Orders
|
||||
|
||||
def order_cycle_fees
|
||||
return @order_cycle_fees if defined? @order_cycle_fees
|
||||
return [] unless order_cycle && distributor
|
||||
|
||||
@order_cycle_fees = begin
|
||||
fees = []
|
||||
|
||||
return fees unless order_cycle && distributor
|
||||
|
||||
order_cycle.exchanges.supplying_to(distributor).each do |exchange|
|
||||
exchange.enterprise_fees.per_item.each do |enterprise_fee|
|
||||
fee_value = FeeValue.new(fee: enterprise_fee, role: exchange.role)
|
||||
|
||||
@@ -14,12 +14,12 @@ module PermittedAttributes
|
||||
|
||||
def self.attributes
|
||||
basic_permitted_attributes + [
|
||||
{ group_ids: [], user_ids: [],
|
||||
shipping_method_ids: [], payment_method_ids: [],
|
||||
address_attributes: PermittedAttributes::Address.attributes,
|
||||
business_address_attributes: PermittedAttributes::BusinessAddress.attributes,
|
||||
producer_properties_attributes: [:id, :property_name, :value, :_destroy],
|
||||
custom_tab_attributes: PermittedAttributes::CustomTab.attributes },
|
||||
group_ids: [], user_ids: [],
|
||||
shipping_method_ids: [], payment_method_ids: [],
|
||||
address_attributes: PermittedAttributes::Address.attributes,
|
||||
business_address_attributes: PermittedAttributes::BusinessAddress.attributes,
|
||||
producer_properties_attributes: [:id, :property_name, :value, :_destroy],
|
||||
custom_tab_attributes: PermittedAttributes::CustomTab.attributes,
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ module PermittedAttributes
|
||||
private
|
||||
|
||||
def attributes
|
||||
self.class.basic_attributes + [{ incoming_exchanges: permitted_exchange_attributes,
|
||||
outgoing_exchanges: permitted_exchange_attributes }]
|
||||
self.class.basic_attributes + [incoming_exchanges: permitted_exchange_attributes,
|
||||
outgoing_exchanges: permitted_exchange_attributes]
|
||||
end
|
||||
|
||||
def permitted_exchange_attributes
|
||||
|
||||
@@ -26,11 +26,11 @@ module PermittedAttributes
|
||||
|
||||
def other_permitted_attributes
|
||||
[
|
||||
{ subscription_line_items_attributes: [
|
||||
:id, :quantity, :variant_id, :price_estimate, :_destroy
|
||||
],
|
||||
bill_address_attributes: PermittedAttributes::Address.attributes,
|
||||
ship_address_attributes: PermittedAttributes::Address.attributes }
|
||||
subscription_line_items_attributes: [
|
||||
:id, :quantity, :variant_id, :price_estimate, :_destroy
|
||||
],
|
||||
bill_address_attributes: PermittedAttributes::Address.attributes,
|
||||
ship_address_attributes: PermittedAttributes::Address.attributes
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,50 +1,23 @@
|
||||
- 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
|
||||
= 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))
|
||||
.omega.fourteen.columns= f.collection_select(:distributor_id_in, @data.distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true})
|
||||
|
||||
.row
|
||||
.alpha.two.columns= label_tag nil, t(:report_customers_cycle)
|
||||
.omega.fourteen.columns
|
||||
= 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))
|
||||
= f.select(:order_cycle_id_in, report_order_cycle_options(@data.order_cycles), {selected: params.dig(:q, :order_cycle_id_in)}, {class: "select2 fullwidth", multiple: true})
|
||||
|
||||
.row
|
||||
.alpha.two.columns= label_tag nil, t(:fee_name)
|
||||
.omega.fourteen.columns
|
||||
= 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)))
|
||||
= 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})
|
||||
|
||||
.row
|
||||
.alpha.two.columns= label_tag nil, t(:fee_owner)
|
||||
.omega.fourteen.columns
|
||||
= 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)))
|
||||
= 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})
|
||||
|
||||
.row
|
||||
.alpha.two.columns= label_tag nil, t(:report_customers)
|
||||
.omega.fourteen.columns
|
||||
= 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))
|
||||
= f.select(:customer_id_in, customer_email_options(@data.order_customers), {selected: params.dig(:q, :customer_id_in)}, {class: "select2 fullwidth", multiple: true})
|
||||
|
||||
@@ -1,57 +1,27 @@
|
||||
- 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
|
||||
= 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))
|
||||
.omega.fourteen.columns= f.collection_select(:distributor_id_in, @data.distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true})
|
||||
|
||||
.row
|
||||
.alpha.two.columns= label_tag nil, t(:report_producers)
|
||||
.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))
|
||||
.omega.fourteen.columns= select_tag(:supplier_id_in, options_from_collection_for_select(@data.orders_suppliers, :id, :name, params[:supplier_id_in]), {class: "select2 fullwidth", multiple: true})
|
||||
|
||||
.row
|
||||
.alpha.two.columns= label_tag nil, t(:report_customers_cycle)
|
||||
.omega.fourteen.columns
|
||||
= 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))
|
||||
= f.select(:order_cycle_id_in, report_order_cycle_options(@data.order_cycles), {selected: params.dig(:q, :order_cycle_id_in)}, {class: "select2 fullwidth", multiple: true})
|
||||
|
||||
.row
|
||||
.alpha.two.columns= label_tag nil, t(:fee_name)
|
||||
.omega.fourteen.columns
|
||||
= 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)))
|
||||
= 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})
|
||||
|
||||
.row
|
||||
.alpha.two.columns= label_tag nil, t(:fee_owner)
|
||||
.omega.fourteen.columns
|
||||
= 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)))
|
||||
= 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})
|
||||
|
||||
.row
|
||||
.alpha.two.columns= label_tag nil, t(:report_customers)
|
||||
.omega.fourteen.columns
|
||||
= 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))
|
||||
= f.select(:customer_id_in, customer_email_options(@data.order_customers), {selected: params.dig(:q, :customer_id_in)}, {class: "select2 fullwidth", multiple: true})
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
.alpha.four.columns
|
||||
= label :payment_method, :type, t('.provider')
|
||||
.omega.twelve.columns
|
||||
= select(:payment_method, :type, payment_method_type_options(@providers), {}, { class: 'select2 fullwidth', required: true, placeholder: t("admin.choose"), 'provider-prefs-for' => "#{@object.id}"})
|
||||
= collection_select(:payment_method, :type, @providers, :to_s, :clean_name, {}, { class: 'select2 fullwidth', 'provider-prefs-for' => "#{@object.id}"})
|
||||
|
||||
%div{"ng-include" => "include_html" }
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
- method.distributors.each do |distributor|
|
||||
= distributor.name
|
||||
%br/
|
||||
%td= payment_method_type_name(method.class.name)
|
||||
%td= method.class.clean_name
|
||||
- if spree_current_user.admin?
|
||||
%td.align-center= method.environment.to_s.titleize
|
||||
%td.align-center= method.display_on.blank? ? t('.both') : t('.' + method.display_on.to_s)
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
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,
|
||||
remoteUrl: String,
|
||||
};
|
||||
static values = { options: Object, placeholder: String };
|
||||
|
||||
connect(options = {}) {
|
||||
let tomSelectOptions = {
|
||||
this.control = new TomSelect(this.element, {
|
||||
maxItems: 1,
|
||||
maxOptions: null,
|
||||
plugins: ["dropdown_input"],
|
||||
@@ -21,13 +16,7 @@ export default class extends Controller {
|
||||
},
|
||||
...this.optionsValue,
|
||||
...options,
|
||||
};
|
||||
|
||||
if (this.remoteUrlValue) {
|
||||
this.#addRemoteOptions(tomSelectOptions);
|
||||
}
|
||||
|
||||
this.control = new TomSelect(this.element, tomSelectOptions);
|
||||
});
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
@@ -40,78 +29,4 @@ 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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,9 +246,6 @@ en_CA:
|
||||
disconnect_failure: "Failed to disconnecct Stripe."
|
||||
success_code:
|
||||
disconnected: "Stripe account disconnected."
|
||||
taler:
|
||||
order_status:
|
||||
claimed: "The payment request expired. Please try again."
|
||||
activemodel:
|
||||
errors:
|
||||
messages:
|
||||
@@ -3361,8 +3358,6 @@ en_CA:
|
||||
payment_processing_failed: "Payment could not be processed, please check the details you entered."
|
||||
payment_method_not_supported: "That payment method is unsupported. Please choose another one."
|
||||
payment_updated: "Payment Updated"
|
||||
payment_method_taler:
|
||||
order_summary: "Open Food Network order"
|
||||
cannot_perform_operation: "Could not update the payment"
|
||||
action_required: "Action required"
|
||||
tag_rules: "Tag Rules"
|
||||
@@ -4176,7 +4171,6 @@ en_CA:
|
||||
alt_text: "Alternative Text"
|
||||
thumbnail: "Thumbnail"
|
||||
back_to_images_list: "Back to Images List"
|
||||
backend_url: "Backend URL"
|
||||
api_key: "API key"
|
||||
email: Email
|
||||
account_updated: "Account updated!"
|
||||
@@ -4496,7 +4490,6 @@ en_CA:
|
||||
check: "Cash/EFT/Bank Transfer etc. (payments for which automatic validation is not required)"
|
||||
paypalexpress: "PayPal Express"
|
||||
stripesca: "Stripe SCA"
|
||||
taler: "Taler"
|
||||
payments:
|
||||
source_forms:
|
||||
stripe:
|
||||
|
||||
@@ -115,67 +115,6 @@ en_GB:
|
||||
blank: "can't be blank"
|
||||
too_short: "is too short (minimum is %{count} characters)"
|
||||
errors:
|
||||
messages:
|
||||
content_type_invalid:
|
||||
one: "has an invalid content type (authorised content type is %{authorized_human_content_types})"
|
||||
other: "has an invalid content type (authorised content types are\n%{authorized_human_content_types})"
|
||||
content_type_spoofed:
|
||||
one: "has a content type that is not equivalent to the one that is detected through its content (authorised content type is %{authorized_human_content_types})"
|
||||
other: "has a content type that is not equivalent to the one that is detected through its content (authorised content types are %{authorized_human_content_types})"
|
||||
file_size_not_less_than: "file size must be less than %{max} (current size is %{file_size})"
|
||||
file_size_not_less_than_or_equal_to: "file size must be less than or equal to %{max} (current size is %{file_size})"
|
||||
file_size_not_greater_than: "file size must be greater than %{min} (current size is %{file_size})"
|
||||
file_size_not_greater_than_or_equal_to: "file size must be greater than or equal to %{min} (current size is %{file_size})"
|
||||
file_size_not_between: "file size must be between %{min} and %{max} (current size is %{file_size})"
|
||||
file_size_not_equal_to: "file size must be equal to %{exact} (current size is %{file_size})"
|
||||
total_file_size_not_less_than: "total file size must be less than %{max} (current size is %{total_file_size})"
|
||||
total_file_size_not_less_than_or_equal_to: "total file size must be less than or equal to %{max} (current size is %{total_file_size})"
|
||||
total_file_size_not_greater_than: "total file size must be greater than %{min} (current size is %{total_file_size})"
|
||||
total_file_size_not_greater_than_or_equal_to: "total file size must be greater than or equal to %{min} (current size is %{total_file_size})"
|
||||
total_file_size_not_between: "total file size must be between %{min} and %{max} (current size is %{total_file_size})"
|
||||
total_file_size_not_equal_to: "total file size must be equal to %{exact} (current size is %{total_file_size})"
|
||||
duration_not_less_than: "duration must be less than %{max} (current duration is %{duration})"
|
||||
duration_not_less_than_or_equal_to: "duration must be less than or equal to %{max} (current duration is %{duration})"
|
||||
duration_not_greater_than: "duration must be greater than %{min} (current duration is %{duration})"
|
||||
duration_not_greater_than_or_equal_to: "duration must be greater than or equal to %{min} (current duration is %{duration})"
|
||||
duration_not_between: "duration must be between %{min} and %{max} (current duration is %{duration})"
|
||||
duration_not_equal_to: "duration must be equal to %{exact} (current duration is %{duration})"
|
||||
limit_out_of_range:
|
||||
zero: "no files attached (must have between %{min} and %{max}files)"
|
||||
one: "only 1 file attached (must have between %{min} and %{max}files)"
|
||||
other: "total number of files must be between %{min} and %{max} files (there are %{count}files attached)"
|
||||
limit_min_not_reached:
|
||||
zero: "no files attached (must have at least %{min} files)"
|
||||
one: "only 1 file attached (must have at least %{min} files)"
|
||||
other: "%{count} files attached (must have at least %{min} files)"
|
||||
limit_max_exceeded:
|
||||
zero: "no files attached (maximum is %{max} files)"
|
||||
one: "too many files attached (maximum is %{max} files, got %{count})"
|
||||
other: "too many files attached (maximum is %{max} files, got %{count})"
|
||||
attachment_missing: "is missing its attachment"
|
||||
media_metadata_missing: "is not a valid media file"
|
||||
dimension_min_not_included_in: "must be greater than or equal to %{width} x %{height} pixels"
|
||||
dimension_max_not_included_in: "must be less than or equal to %{width} x %{height} pixels"
|
||||
dimension_width_not_included_in: "width is not included between %{min} and %{max} pixels"
|
||||
dimension_height_not_included_in: "height is not included between %{min} and %{max} pixels"
|
||||
dimension_width_not_greater_than_or_equal_to: "width must be greater than or equal to %{length} pixels"
|
||||
dimension_height_not_greater_than_or_equal_to: "height must be greater than or equal to %{length} pixels"
|
||||
dimension_width_not_less_than_or_equal_to: "width must be less than or equal to %{length} pixels"
|
||||
dimension_height_not_less_than_or_equal_to: "height must be less than or equal to %{length} pixels"
|
||||
dimension_width_not_equal_to: "width must be equal to %{length} pixels"
|
||||
dimension_height_not_equal_to: "height must be equal to %{length} pixels"
|
||||
aspect_ratio_not_square: "must be square (current file is %{width}x%{height}px)"
|
||||
aspect_ratio_not_portrait: "must be portrait (current file is %{width}x%{height}px)"
|
||||
aspect_ratio_not_landscape: "must be landscape (current file is %{width}x%{height}px)"
|
||||
aspect_ratio_not_x_y: "must be %{authorized_aspect_ratios} (current file is %{width}x%{height}px)"
|
||||
aspect_ratio_invalid: "has an invalid aspect ratio (valid aspect ratios are %{authorized_aspect_ratios})"
|
||||
file_not_processable: "is not identified as a valid media file"
|
||||
pages_not_less_than: "page count must be less than %{max} (current page count is %{pages})"
|
||||
pages_not_less_than_or_equal_to: "page count must be less than or equal to %{max} (current page count is %{pages})"
|
||||
pages_not_greater_than: "page count must be greater than %{min} (current page count is %{pages})"
|
||||
pages_not_greater_than_or_equal_to: "page count must be greater than or equal to %{min} (current page count is %{pages})"
|
||||
pages_not_between: "page count must be between %{min} and %{max} (current page count is %{pages})"
|
||||
pages_not_equal_to: "page count must be equal to %{exact} (current page count is %{pages})"
|
||||
not_found:
|
||||
title: "The page you were looking for doesn't exist (404)"
|
||||
message_html: "<b>Please try again</b> <p>This might be a temporary problem. Please click the back button to return to the previous screen or go back to <a href='/'>Home</a> and try again.</p> <b>Contact support</b> <p>If the problem persists or is urgent, please tell us about it. Find our contact details from the global <a href='https://openfoodnetwork.org/ofn-local/' target='blank'>Open Food Network Local page</a>.</p> <p>It really helps us if you can give as much detail as possible about what the missing page is about.</p>"
|
||||
@@ -246,9 +185,6 @@ en_GB:
|
||||
disconnect_failure: "Failed to disconnect Stripe."
|
||||
success_code:
|
||||
disconnected: "Stripe account disconnected."
|
||||
taler:
|
||||
order_status:
|
||||
claimed: "The payment request expired. Please try again."
|
||||
activemodel:
|
||||
errors:
|
||||
messages:
|
||||
@@ -584,7 +520,6 @@ en_GB:
|
||||
errors:
|
||||
vine_api: "There was an error communicating with the API, please try again later."
|
||||
invalid_voucher: "The voucher is not valid"
|
||||
expired: "The voucher has expired"
|
||||
not_found_voucher: "Sorry, we couldn't find that voucher, please check the code."
|
||||
vine_voucher_redeemer_service:
|
||||
errors:
|
||||
@@ -999,10 +934,6 @@ en_GB:
|
||||
clone:
|
||||
success: Successfully cloned the product
|
||||
error: Unable to clone the product
|
||||
tag_rules:
|
||||
rules_per_tag:
|
||||
one: "%{tag} has 1 rule"
|
||||
other: "%{tag} has %{count} rules"
|
||||
product_import:
|
||||
title: Product Import
|
||||
file_not_found: File not found or could not be opened
|
||||
@@ -2443,9 +2374,9 @@ en_GB:
|
||||
order_includes_tax: (includes tax)
|
||||
order_payment_paypal_successful: Your payment via PayPal has been processed successfully.
|
||||
order_hub_info: Hub info
|
||||
order_back_to_store: Back to shop
|
||||
order_back_to_cart: Back to cart
|
||||
order_back_to_website: Back to website
|
||||
order_back_to_store: Back To Store
|
||||
order_back_to_cart: Back To Cart
|
||||
order_back_to_website: Back To Website
|
||||
checkout_details_title: Checkout Details
|
||||
checkout_payment_title: Checkout Payment
|
||||
checkout_summary_title: Checkout Summary
|
||||
@@ -3361,13 +3292,11 @@ en_GB:
|
||||
payment_processing_failed: "Payment could not be processed, please check the details you entered"
|
||||
payment_method_not_supported: "That payment method is unsupported. Please choose another one."
|
||||
payment_updated: "Payment Updated"
|
||||
payment_method_taler:
|
||||
order_summary: "Open Food Network order"
|
||||
cannot_perform_operation: "Could not update the payment"
|
||||
action_required: "Action required"
|
||||
tag_rules: "Tag Rules"
|
||||
enterprise_fee_whole_order: Whole order
|
||||
enterprise_fee_by_name: "%{name} fee by %{role} %{enterprise_name}"
|
||||
enterprise_fee_by_name: "%{name} fee by %{role}%{enterprise_name}"
|
||||
validation_msg_relationship_already_established: "^That relationship is already established."
|
||||
validation_msg_at_least_one_hub: "^At least one hub must be selected"
|
||||
validation_msg_tax_category_cant_be_blank: "^Tax Category can't be blank"
|
||||
@@ -3408,7 +3337,6 @@ en_GB:
|
||||
order_cycles_no_permission_to_coordinate_error: "None of your enterprises have permission to coordinate an order cycle"
|
||||
order_cycles_no_permission_to_create_error: "You don't have permission to create an order cycle coordinated by that enterprise"
|
||||
order_cycle_closed: "The order cycle you've selected has just closed."
|
||||
order_cycle_closed_next_steps: "The order cycle you've selected has just closed. Please contact us to complete your order #\n%{order_number}!"
|
||||
back_to_orders_list: "Back To Orders List"
|
||||
no_orders_found: "No Orders Found"
|
||||
order_information: "Order Information"
|
||||
@@ -3983,8 +3911,6 @@ en_GB:
|
||||
destroy:
|
||||
success: Webhook endpoint successfully deleted
|
||||
error: Webhook endpoint failed to delete
|
||||
test:
|
||||
success: Some test data will be sent to the webhook url
|
||||
spree:
|
||||
order_updated: "Order Updated"
|
||||
cannot_perform_operation: "Can not perform this operation"
|
||||
@@ -4091,7 +4017,6 @@ en_GB:
|
||||
logourl: "Logo url"
|
||||
are_you_sure_delete: "Are you sure you want to delete this record?"
|
||||
confirm_delete: "Confirm Deletion"
|
||||
tag_rule: "Tag Rule"
|
||||
voucher: "Voucher"
|
||||
configurations: "Configurations"
|
||||
general_settings: "General Settings"
|
||||
@@ -4183,7 +4108,6 @@ en_GB:
|
||||
alt_text: "Alternative Text"
|
||||
thumbnail: "Thumbnail"
|
||||
back_to_images_list: "Back To Images List"
|
||||
backend_url: "Backend URL"
|
||||
api_key: "API key"
|
||||
email: Email
|
||||
account_updated: "Account updated!"
|
||||
@@ -4503,7 +4427,6 @@ en_GB:
|
||||
check: "Cash/EFT/etc. (payments for which automatic validation is not required)"
|
||||
paypalexpress: "PayPal Express"
|
||||
stripesca: "Stripe SCA "
|
||||
taler: "Taler"
|
||||
payments:
|
||||
source_forms:
|
||||
stripe:
|
||||
@@ -4960,7 +4883,6 @@ en_GB:
|
||||
order_cycle_tagged_bottom: "are:"
|
||||
inventory_tagged_top: "Inventory variants tagged"
|
||||
inventory_tagged_bottom: "are:"
|
||||
variant_tagged_top: "Variants tagged"
|
||||
variant_tagged_bottom: "are:"
|
||||
visible: VISIBLE
|
||||
not_visible: NOT VISIBLE
|
||||
@@ -4976,8 +4898,6 @@ en_GB:
|
||||
create_placeholder: Enter the URL of the remote webhook endpoint
|
||||
event_types:
|
||||
order_cycle_opened: Order Cycle Opened
|
||||
payment_status_changed: Post webhook on Payment status change
|
||||
test_endpoint: Test webhook endpoint
|
||||
invisible_captcha:
|
||||
sentence_for_humans: "Please leave empty"
|
||||
timestamp_error_message: "Please try again after 5 seconds."
|
||||
|
||||
@@ -18,7 +18,7 @@ fi:
|
||||
phone: "Puhelinnumero"
|
||||
firstname: "Etunimi"
|
||||
lastname: "Sukunimi"
|
||||
zipcode: "Toimitusosoitteen postinumero"
|
||||
zipcode: "Toimituspostinumero"
|
||||
spree/order/bill_address:
|
||||
address1: "Laskutusosoite (Katu ja talonumero)"
|
||||
zipcode: "Laskutuspostinumero"
|
||||
@@ -115,67 +115,6 @@ fi:
|
||||
blank: "ei voi olla tyhjä"
|
||||
too_short: "on liian lyhyt (vähintään %{count} merkkiä)"
|
||||
errors:
|
||||
messages:
|
||||
content_type_invalid:
|
||||
one: "sisältää virheellisen sisältötyypin (valtuutettu sisältötyyppi on %{authorized_human_content_types} )"
|
||||
other: "sisältää virheellisen sisältötyypin (valtuutetut sisältötyypit ovat %{authorized_human_content_types} )"
|
||||
content_type_spoofed:
|
||||
one: "sisältötyyppi ei vastaa sisällöstä havaittua tyyppiä (valtuutettu sisältötyyppi on %{authorized_human_content_types} )"
|
||||
other: "sisältötyyppi ei vastaa sisällöstä havaittua tyyppiä (valtuutetut sisältötyypit ovat %{authorized_human_content_types} )"
|
||||
file_size_not_less_than: "tiedoston koko on oltava pienempi kuin %{max} (nykyinen koko on %{file_size} )"
|
||||
file_size_not_less_than_or_equal_to: "tiedoston koko on oltava pienempi tai yhtä suuri kuin %{max} (nykyinen koko on %{file_size} )"
|
||||
file_size_not_greater_than: "tiedoston koko on oltava suurempi kuin %{min} (nykyinen koko on %{file_size} )"
|
||||
file_size_not_greater_than_or_equal_to: "tiedoston koko on oltava suurempi tai yhtä suuri kuin %{min} (nykyinen koko on %{file_size} )"
|
||||
file_size_not_between: "koko on oltava %{min} ja %{max} välillä (nykyinen koko on %{file_size} )"
|
||||
file_size_not_equal_to: "tiedoston koko on oltava yhtä suuri kuin %{exact} (nykyinen koko on %{file_size} )"
|
||||
total_file_size_not_less_than: "yhteensä tiedoston koko on oltava pienempi kuin %{max} (nykyinen koko on %{total_file_size} )"
|
||||
total_file_size_not_less_than_or_equal_to: "yhteensä tiedoston koko on oltava pienempi tai yhtä suuri kuin %{max} (nykyinen koko on %{total_file_size} )"
|
||||
total_file_size_not_greater_than: "yhteensä tiedoston koko on oltava suurempi kuin %{min} (nykyinen koko on %{total_file_size} )"
|
||||
total_file_size_not_greater_than_or_equal_to: "yhteensä tiedoston koko on oltava suurempi tai yhtä suuri kuin %{min} (nykyinen koko on %{total_file_size} )"
|
||||
total_file_size_not_between: "yhteensä tiedoston koko on oltava %{min} ja %{max} välillä (nykyinen koko on %{total_file_size} )"
|
||||
total_file_size_not_equal_to: "yhteensä tiedoston koko on oltava yhtä suuri kuin %{exact} (nykyinen koko on %{total_file_size} )"
|
||||
duration_not_less_than: "keston on oltava pienempi kuin %{max} (nykyinen kesto on %{duration} )"
|
||||
duration_not_less_than_or_equal_to: "keston on oltava pienempi tai yhtä suuri kuin %{max} (nykyinen kesto on %{duration} )"
|
||||
duration_not_greater_than: "keston on oltava suurempi kuin %{min} (nykyinen kesto on %{duration} )"
|
||||
duration_not_greater_than_or_equal_to: "keston on oltava suurempi tai yhtä suuri kuin %{min} (nykyinen kesto on %{duration} )"
|
||||
duration_not_between: "keston on oltava välillä %{min} ja %{max} (nykyinen kesto on %{duration} )"
|
||||
duration_not_equal_to: "keston on oltava yhtä suuri kuin %{exact} (nykyinen kesto on %{duration} )"
|
||||
limit_out_of_range:
|
||||
zero: "ei liitettyjä tiedostoja (tiedostojen on oltava %{min} ja %{max} välillä)"
|
||||
one: "vain yksi liitetty tiedosto (tiedostojen on oltava %{min} ja %{max} välillä)"
|
||||
other: "Tiedostojen yhteensä on oltava %{min} ja %{max} välillä (liitteenä on %{count} tiedostot)"
|
||||
limit_min_not_reached:
|
||||
zero: "ei liitettyjä tiedostoja (tiedostoissa on oltava vähintään %{min} )"
|
||||
one: "vain yksi tiedosto liitettynä (tiedostojen on oltava vähintään %{min} )"
|
||||
other: "%{count} tiedostot liitteenä (vähintään %{min} tiedostoja on oltava)"
|
||||
limit_max_exceeded:
|
||||
zero: "ei liitettyjä tiedostoja (enintään %{max} tiedostoja)"
|
||||
one: "liian monta tiedostoa liitettynä (suurin sallittu määrä on %{max} , sain tulokseksi %{count} )"
|
||||
other: "liian monta tiedostoa liitettynä (suurin sallittu määrä on %{max} , sain tulokseksi %{count} )"
|
||||
attachment_missing: "puuttuu sen liite"
|
||||
media_metadata_missing: "ei ole kelvollinen mediatiedosto"
|
||||
dimension_min_not_included_in: "on oltava suurempi tai yhtä suuri kuin %{width} x %{height} pikseliä"
|
||||
dimension_max_not_included_in: "on oltava pienempi tai yhtä suuri kuin %{width} x %{height} pikseliä"
|
||||
dimension_width_not_included_in: "Leveys ei sisälly %{min} ja %{max} pikselien väliin"
|
||||
dimension_height_not_included_in: "Korkeutta ei ole %{min} ja %{max} pikselien välillä."
|
||||
dimension_width_not_greater_than_or_equal_to: "leveyden on oltava suurempi tai yhtä suuri kuin %{length} pikseliä"
|
||||
dimension_height_not_greater_than_or_equal_to: "korkeuden on oltava suurempi tai yhtä suuri kuin %{length} pikseliä"
|
||||
dimension_width_not_less_than_or_equal_to: "leveyden on oltava pienempi tai yhtä suuri kuin %{length} pikseliä"
|
||||
dimension_height_not_less_than_or_equal_to: "korkeuden on oltava pienempi tai yhtä suuri kuin %{length} pikseliä"
|
||||
dimension_width_not_equal_to: "leveyden on oltava yhtä suuri kuin %{length} pikseliä"
|
||||
dimension_height_not_equal_to: "korkeuden on oltava yhtä suuri kuin %{length} pikselit"
|
||||
aspect_ratio_not_square: "on oltava neliön muotoinen (nykyinen tiedosto on %{width} x %{height} px)"
|
||||
aspect_ratio_not_portrait: "tiedoston on oltava pystysuuntainen (nykyinen tiedosto on %{width} x %{height} px)"
|
||||
aspect_ratio_not_landscape: "tiedoston on oltava vaakasuuntainen (nykyinen tiedosto on %{width} x %{height} px)"
|
||||
aspect_ratio_not_x_y: "tiedoston on oltava %{authorized_aspect_ratios} (nykyinen tiedosto on %{width} x %{height} px)"
|
||||
aspect_ratio_invalid: "kuvasuhde on virheellinen (kelvolliset kuvasuhteet ovat %{authorized_aspect_ratios} )"
|
||||
file_not_processable: "ei ole tunnistettu kelvolliseksi mediatiedostoksi"
|
||||
pages_not_less_than: "sivumäärän on oltava pienempi kuin %{max} (nykyinen sivumäärä on %{pages} )"
|
||||
pages_not_less_than_or_equal_to: "sivumäärän on oltava pienempi tai yhtä suuri kuin %{max} (nykyinen sivumäärä on %{pages} )"
|
||||
pages_not_greater_than: "sivumäärän on oltava suurempi kuin %{min} (nykyinen sivumäärä on %{pages} )"
|
||||
pages_not_greater_than_or_equal_to: "sivumäärän on oltava suurempi tai yhtä suuri kuin %{min} (nykyinen sivumäärä on %{pages} )"
|
||||
pages_not_between: "sivumäärän on oltava välillä %{min} ja %{max} (nykyinen sivumäärä on %{pages} )"
|
||||
pages_not_equal_to: "sivumäärän on oltava yhtä suuri kuin %{exact} (nykyinen sivumäärä on %{pages} )"
|
||||
not_found:
|
||||
title: "Etsimääsi sivua ei löytynyt (404)"
|
||||
message_html: "<b>Yritä uudelleen</b> <p>Tämä voi olla tilapäinen ongelma. Palaa edelliselle sivulle tai <a href='/'>etusivulle</a> ja yritä uudelleen.</p> <b>Ota yhteyttä tukeen</b> <p>Jos ongelma jatkuu tai on kiireellinen, kerro siitä meille. Löydä yhteystiedot globaalilta <a href='https://openfoodnetwork.org/ofn-local/' target='blank'>Open Food Network Local -sivulta</a>.</p> <p>Autat meitä paljon, jos annat mahdollisimman paljon yksityiskohtia puuttuvasta sivusta.</p>"
|
||||
@@ -246,9 +185,6 @@ fi:
|
||||
disconnect_failure: "Stripe-tilin irrottaminen epäonnistui."
|
||||
success_code:
|
||||
disconnected: "Stripe-tili irrotettu."
|
||||
taler:
|
||||
order_status:
|
||||
claimed: "Maksupyyntö vanheni. Yritä uudelleen."
|
||||
activemodel:
|
||||
errors:
|
||||
messages:
|
||||
@@ -582,7 +518,6 @@ fi:
|
||||
errors:
|
||||
vine_api: "API-yhteydessä tapahtui virhe, yritä myöhemmin uudelleen."
|
||||
invalid_voucher: "Alennuskuponki ei ole kelvollinen"
|
||||
expired: "Kuponki on vanhentunut"
|
||||
not_found_voucher: "Anteeksi, emme löytäneet tätä alennuskuponkia, tarkista koodi."
|
||||
vine_voucher_redeemer_service:
|
||||
errors:
|
||||
@@ -997,10 +932,6 @@ fi:
|
||||
clone:
|
||||
success: Tuote kloonattiin onnistuneesti
|
||||
error: Tuotteen kloonaaminen epäonnistui
|
||||
tag_rules:
|
||||
rules_per_tag:
|
||||
one: "%{tag} llä on 1 sääntö"
|
||||
other: "%{tag} llä on %{count} säännöt."
|
||||
product_import:
|
||||
title: Tuontituotteet
|
||||
file_not_found: Tiedostoa ei löytynyt tai sitä ei voitu avata
|
||||
@@ -1395,9 +1326,6 @@ fi:
|
||||
add_new_button: '+ Lisää uusi oletussääntö'
|
||||
no_tags_yet: Tähän yritykseen ei vielä liity tägejä
|
||||
add_new_tag: '+ Lisää uusi tägi'
|
||||
show_hide_variants: 'Näytä tai Piilota variantit myymälästäni'
|
||||
show_hide_shipping: 'Näytä tai piilota toimitustavat kassalla'
|
||||
show_hide_payment: 'Näytä tai Piilota maksutavat kassalla'
|
||||
show_hide_order_cycles: 'Näytä tai piilota tilausjaksot verkkokaupassani'
|
||||
users:
|
||||
legend: "Käyttäjät"
|
||||
@@ -1551,10 +1479,6 @@ fi:
|
||||
invite_manager:
|
||||
user_already_exists: "Käyttäjä on jo olemassa"
|
||||
error: "Jotain meni pieleen"
|
||||
tag_rules:
|
||||
not_supported_type: tägi -sääntötyyppiä ei tueta
|
||||
confirm_delete: Haluatko varmasti poistaa tämän säännön?
|
||||
destroy_error: tägi -säännön poistamisessa oli ongelma.
|
||||
order_cycles:
|
||||
loading_flash:
|
||||
loading_order_cycles: LADATAAN TILAUSJAKSOJA
|
||||
@@ -1778,8 +1702,6 @@ fi:
|
||||
not_visible: "%{enterprise} ei ole näkyvissä, joten sitä ei löydy kartalta tai hauista"
|
||||
reports:
|
||||
none: ei yhtään
|
||||
metadata:
|
||||
report_title: Ilmianna otsikko
|
||||
deprecated: "Tämä raportti on vanhentunut ja se poistetaan tulevasta julkaisusta."
|
||||
hidden_field: "< Piilotettu >"
|
||||
unitsize: YKSIKKÖKOKO
|
||||
@@ -1882,7 +1804,6 @@ fi:
|
||||
display: Näyttö
|
||||
summary_row: Yhteenvetorivi
|
||||
header_row: Otsikkorivi
|
||||
metadata_rows: Metadata-rivit
|
||||
raw_data: Raakadata
|
||||
formatted_data: Muotoiltu data
|
||||
packing:
|
||||
@@ -2458,15 +2379,8 @@ fi:
|
||||
email_confirmed: "Kiitos sähköpostiosoitteesi vahvistamisesta."
|
||||
email_confirmation_activate_account: "Ennen kuin voimme aktivoida uuden tilisi, meidän on vahvistettava sähköpostiosoitteesi."
|
||||
email_confirmation_greeting: "Hei, %{contact} !"
|
||||
email_confirmation_profile_created: >
|
||||
Profiili käyttäjälle %{name} on luotu onnistuneesti! Profiilisi aktivoimiseksi
|
||||
meidän on vahvistettava tämä sähköpostiosoite.
|
||||
email_confirmation_click_link: "Vahvista sähköpostiosoitteesi ja jatka profiilisi luomista napsauttamalla alla olevaa linkkiä."
|
||||
email_confirmation_link_label: "Vahvista tämä sähköpostiosoite »"
|
||||
email_confirmation_help_html: >
|
||||
Kun olet vahvistanut sähköpostiosoitteesi, voit käyttää ylläpitäjän tiliäsi
|
||||
tässä yrityksessä. Katso %{link} saadaksesi about %{sitename} :n ominaisuuksista
|
||||
ja aloittaaksesi profiilisi tai verkkokauppasi käytön.
|
||||
email_confirmation_notice_unexpected: "Sait tämän viestin, koska rekisteröidyit palvelussa %{sitename} tai joku luultavasti tuntemasi henkilö kutsui sinut rekisteröitymään. Jos et ymmärrä, miksi saat tämän sähköpostin, kirjoita osoitteeseen %{contact} ."
|
||||
email_social: "Ota yhteyttä:"
|
||||
email_contact: "Lähetä meille sähköpostia:"
|
||||
@@ -3362,8 +3276,6 @@ fi:
|
||||
payment_processing_failed: "Payment could not be processed, please check the details you entered"
|
||||
payment_method_not_supported: "Maksutapaa ei tueta. Valitse toinen."
|
||||
payment_updated: "Maksu päivitetty"
|
||||
payment_method_taler:
|
||||
order_summary: "Open Food Network tilaus"
|
||||
cannot_perform_operation: "Maksua ei voitu päivittää"
|
||||
action_required: "Toimenpide vaaditaan"
|
||||
tag_rules: "Tägisäännöt"
|
||||
@@ -3409,7 +3321,6 @@ fi:
|
||||
order_cycles_no_permission_to_coordinate_error: "Yhdelläkään yritykselläsi ei ole lupaa koordinoida tilauskiertoa."
|
||||
order_cycles_no_permission_to_create_error: "Sinulla ei ole oikeutta luoda kyseisen yrityksen koordinoimaa tilausjaksoa."
|
||||
order_cycle_closed: "Valitsemasi tilausjakso on juuri päättynyt. Yritä uudelleen!"
|
||||
order_cycle_closed_next_steps: "Valitsemasi tilausjakso on juuri sulkeutunut. Ota meihin yhteyttä täydentääksesi tilaustasi# %{order_number} !"
|
||||
back_to_orders_list: "Takaisin tilauslistaan"
|
||||
no_orders_found: "Ei tilaukset löytynyt"
|
||||
order_information: "Tilaustiedot"
|
||||
@@ -3977,8 +3888,6 @@ fi:
|
||||
destroy:
|
||||
success: Webhook-päätepiste poistettu onnistuneesti
|
||||
error: Webhook-päätepisteen poistaminen epäonnistui
|
||||
test:
|
||||
success: Joitakin testitietoja lähetetään webhookin URL-osoitteeseen
|
||||
spree:
|
||||
order_updated: "Tilaus päivitetty"
|
||||
cannot_perform_operation: "Tätä toimintoa ei voida suorittaa"
|
||||
@@ -4085,7 +3994,6 @@ fi:
|
||||
logourl: "Logourl"
|
||||
are_you_sure_delete: "Haluatko varmasti poistaa tämän tietueen?"
|
||||
confirm_delete: "Vahvista poisto"
|
||||
tag_rule: "tägi Rule"
|
||||
voucher: "Alennuskuponki"
|
||||
configurations: "Asetukset"
|
||||
general_settings: "Yleiset asetukset"
|
||||
@@ -4177,8 +4085,6 @@ fi:
|
||||
alt_text: "Vaihtoehtoinen teksti"
|
||||
thumbnail: "Pienoiskuva"
|
||||
back_to_images_list: "Takaisin kuvaluetteloon"
|
||||
backend_url: "Taustapalvelun URL-osoite"
|
||||
api_key: "API-avain"
|
||||
email: Sähköposti
|
||||
account_updated: "Tili päivitetty!"
|
||||
email_updated: "Tili päivitetään, kun uusi sähköpostiosoite on vahvistettu."
|
||||
@@ -4497,7 +4403,6 @@ fi:
|
||||
check: "Käteinen/sähköinen maksu/jne. (maksut, joille ei vaadita automaattista vahvistusta)"
|
||||
paypalexpress: "PayPal Express"
|
||||
stripesca: "Stripe SCA"
|
||||
taler: "Taleri"
|
||||
payments:
|
||||
source_forms:
|
||||
stripe:
|
||||
@@ -4674,8 +4579,8 @@ fi:
|
||||
or_enter_new_card: "Tai anna uuden kortin tiedot:"
|
||||
remember_this_card: Muistatko tämän kortin?
|
||||
date_picker:
|
||||
flatpickr_date_format: "Vuosi"
|
||||
flatpickr_datetime_format: "Vuosi H:i"
|
||||
flatpickr_date_format: "d.m.Y"
|
||||
flatpickr_datetime_format: "d.m.Y H:i"
|
||||
today: "Tänään"
|
||||
now: "Nyt"
|
||||
close: "Sulje"
|
||||
@@ -4947,31 +4852,13 @@ fi:
|
||||
tag_rule_form:
|
||||
tag_rules:
|
||||
shipping_method_tagged_top: "Toimitustavat merkitty tägillä"
|
||||
shipping_method_tagged_bottom: "ovat:"
|
||||
payment_method_tagged_top: "Maksutavat merkitty tägillä"
|
||||
payment_method_tagged_bottom: "ovat:"
|
||||
order_cycle_tagged_top: "Tilausjaksot merkitty tägillä"
|
||||
order_cycle_tagged_bottom: "ovat:"
|
||||
inventory_tagged_top: "Tägätyt varastomuunnelmat"
|
||||
inventory_tagged_bottom: "ovat:"
|
||||
variant_tagged_top: "Variantit -tunnisteella"
|
||||
variant_tagged_bottom: "ovat:"
|
||||
visible: NÄKYVÄ
|
||||
not_visible: EI NÄKYVÄ
|
||||
tag_rule_group_form:
|
||||
for_customers_tagged: 'Asiakkaille, jotka on tägätty:'
|
||||
add_new_rule: '+ Lisää uusi sääntö'
|
||||
no_rules_yet: Tähän tägiin ei vielä sovelleta sääntöjä
|
||||
add_tag_rule_modal:
|
||||
select_rule_type: "Valitse säännön tyyppi:"
|
||||
add_rule: "lisää sääntö"
|
||||
webhook_endpoint_form:
|
||||
url:
|
||||
create_placeholder: Anna etäwebhook-päätepisteen URL-osoite
|
||||
event_types:
|
||||
order_cycle_opened: Tilausjakso avattu
|
||||
payment_status_changed: Lähetä webhook maksun tilan muutoksesta
|
||||
test_endpoint: Testaa webhookin päätepiste
|
||||
invisible_captcha:
|
||||
sentence_for_humans: "Jätä tyhjäksi"
|
||||
timestamp_error_message: "Yritä uudelleen 5 sekunnin kuluttua."
|
||||
|
||||
@@ -2945,7 +2945,7 @@ fr:
|
||||
shipping_method_destroy_error: "Cette méthode de livraison ne peut pas être supprimée car elle est référencée dans une commande : %{number}."
|
||||
fees: "Frais"
|
||||
fee_name: "Nom de la marge/commission"
|
||||
fee_owner: "Propriétaire de la marge/commission"
|
||||
fee_owner: "Propriétaire des droits"
|
||||
item_cost: "Coût du produit"
|
||||
bulk: "Vrac"
|
||||
shop_variant_quantity_min: "min"
|
||||
|
||||
@@ -115,69 +115,6 @@ fr_CA:
|
||||
blank: "Champ obligatoire"
|
||||
too_short: "est trop court (minimum %{count} caractère)"
|
||||
errors:
|
||||
messages:
|
||||
content_type_invalid:
|
||||
one: "a un contenu invalide (le type de contenu autorisé est %{authorized_human_content_types})"
|
||||
many: "a un contenu invalide (les types de contenus autorisés sont %{authorized_human_content_types})"
|
||||
other: "a un contenu invalide (les types de contenus autorisés sont %{authorized_human_content_types})"
|
||||
content_type_spoofed:
|
||||
one: "a un type de contenu qui n'est pas équivalent avec celui détecté (le type de contenu autorisé est %{authorized_human_content_types})"
|
||||
many: "a un type de contenu qui n'est pas équivalent avec celui détecté (les types de contenus autorisés sont %{authorized_human_content_types})"
|
||||
other: "a un type de contenu qui n'est pas équivalent avec celui détecté (les types de contenus autorisés sont %{authorized_human_content_types})"
|
||||
file_size_not_less_than: "la taille du fichier doit être inférieure à %{max} (la taille actuelle est %{file_size})"
|
||||
file_size_not_less_than_or_equal_to: "la taille du fichier doit être inférieure ou égale à %{max} (la taille actuelle est %{file_size})"
|
||||
file_size_not_greater_than: "la taille du fichier doit être supérieure à %{min} (la taille actuelle est%{file_size})"
|
||||
file_size_not_greater_than_or_equal_to: "la taille du fichier doit être supérieure ou égale à %{min} (la taille actuelle est %{file_size})"
|
||||
file_size_not_between: "la taille du fichier doit être comprise entre%{min} et %{max} (la taille actuelle est %{file_size})"
|
||||
file_size_not_equal_to: "la taille du fichier doit être égale à %{exact} (la taille actuelle est %{file_size})"
|
||||
total_file_size_not_less_than: "La taille totale du fichier doit être inférieure à %{max} (la taille actuelle est%{total_file_size})"
|
||||
total_file_size_not_less_than_or_equal_to: "La taille totale du fichier doit être inférieure ou égale à %{max} (la taille actuelle est %{total_file_size})"
|
||||
total_file_size_not_greater_than: "La taille totale du fichier doit être supérieure à %{min} (la taille actuelle est%{total_file_size})"
|
||||
total_file_size_not_greater_than_or_equal_to: "La taille totale du fichier doit être supérieure ou égale à %{min} (la taille actuelle est %{total_file_size})"
|
||||
total_file_size_not_between: "La taille totale du fichier doit être comprise entre %{min} et %{max} (la taille actuelle est %{total_file_size})"
|
||||
total_file_size_not_equal_to: "La taille totale du fichier doit être égale à %{exact} (la taille actuelle est%{total_file_size})"
|
||||
duration_not_less_than: "La durée doit être inférieure à %{max} (la durée actuelle est %{duration})"
|
||||
duration_not_less_than_or_equal_to: "La durée doit être inférieure ou égale à %{max} (la durée actuelle est%{duration})"
|
||||
duration_not_greater_than: "La durée doit être supérieure à %{min} (la durée actuelle est %{duration})"
|
||||
duration_not_greater_than_or_equal_to: "La durée doit être supérieure ou égale à %{min} (la durée actuelle est%{duration})"
|
||||
duration_not_between: "La durée doit être comprise entre %{min} et %{max} (la durée actuelle est %{duration})"
|
||||
duration_not_equal_to: "La durée doit être égale à %{exact} (la durée actuelle est %{duration})"
|
||||
limit_out_of_range:
|
||||
zero: "Aucun fichier joint (doit contenir entre %{min} et %{max} fichiers)"
|
||||
one: "Seulement 1 fichier joint (doit contenir entre%{min} et %{max} fichiers)"
|
||||
other: "Le nombre total de fichiers doit être compris entre %{min} et %{max} fichiers (il y a %{count} fichiers joints)"
|
||||
limit_min_not_reached:
|
||||
zero: "Aucun fichier joint (doit contenir au moins %{min} fichiers)"
|
||||
one: "Seulement 1 fichier joint (doit contenir au moins %{min} fichiers)"
|
||||
other: "%{count} Aucun fichier joint (doit contenir au moins %{min} fichiers)"
|
||||
limit_max_exceeded:
|
||||
zero: "Aucun fichier joint (au maximum %{max} fichiers)"
|
||||
one: "Trop de fichiers joints (le maximum est %{max} fichiers, il y en a %{count})"
|
||||
other: "Trop de fichiers joints (le maximum est %{max} fichiers, il y en a%{count})"
|
||||
attachment_missing: "une pièce jointe est manquante"
|
||||
media_metadata_missing: "n'est pas un fichier multimédia valide"
|
||||
dimension_min_not_included_in: "doit être plus grand ou égal à %{width} x %{height} pixels"
|
||||
dimension_max_not_included_in: "doit être plus petit ou égal à %{width} x %{height} pixels"
|
||||
dimension_width_not_included_in: "la largeur n'est pas comprise entre %{min} et %{max} pixels"
|
||||
dimension_height_not_included_in: "la hauteur n'est pas comprise entre %{min} et %{max} pixels"
|
||||
dimension_width_not_greater_than_or_equal_to: "la largeur doit être supérieure ou égale à %{length} pixels"
|
||||
dimension_height_not_greater_than_or_equal_to: "la hauteur doit être supérieure ou égale à %{length} pixels"
|
||||
dimension_width_not_less_than_or_equal_to: "la largeur doit être inférieure ou égale à %{length} pixels"
|
||||
dimension_height_not_less_than_or_equal_to: "la hauteur doit être inférieure ou égale à %{length} pixels"
|
||||
dimension_width_not_equal_to: "la largeur doit être égale à %{length} pixels"
|
||||
dimension_height_not_equal_to: "La hauteur doit être égale à %{length} pixels"
|
||||
aspect_ratio_not_square: "doit être carré (le fichier actuel est %{width}x%{height}px)"
|
||||
aspect_ratio_not_portrait: "doit être au format portrait (le fichier actuel mesure %{width}x%{height}px)"
|
||||
aspect_ratio_not_landscape: "doit être au format paysage (le fichier actuel mesure %{width}x%{height}px)"
|
||||
aspect_ratio_not_x_y: "doit être %{authorized_aspect_ratios} (le fichier actuel est %{width}x%{height}px)"
|
||||
aspect_ratio_invalid: "possède un ratio d'image invalide (les ratios d'image valides sont %{authorized_aspect_ratios})"
|
||||
file_not_processable: "n'est pas identifié comme un fichier multimédia valide"
|
||||
pages_not_less_than: "La durée doit être inférieure à %{max} (la durée actuelle est %{pages})"
|
||||
pages_not_less_than_or_equal_to: "La durée doit être inférieure ou égale à %{max} (la durée actuelle est %{pages})"
|
||||
pages_not_greater_than: "La durée doit être supérieure à %{min}(la durée actuelle est %{pages})"
|
||||
pages_not_greater_than_or_equal_to: "La durée doit être supérieure ou égale à %{min} (la durée actuelle %{pages})"
|
||||
pages_not_between: "La durée doit être comprise entre %{min} et %{max} (la durée actuelle est %{pages})"
|
||||
pages_not_equal_to: "La durée doit être égale à %{exact} (la durée actuelle est %{pages})"
|
||||
not_found:
|
||||
title: "La page que vous recherchez n'existe pas (erreur 404)"
|
||||
message_html: "<b>Veuillez essayer à nouveau </b> <p> Il s'agit peut-être d'un problème temporaire. Veuillez cliquer sur le bouton retour de votre navigateur ou retourner à l'<a href='/'> Accueil </a> et recommencez. <b> Contacter notre support </b> <p> Si le problème persiste ou si c'est urgent, veuillez nous contacter. </p>"
|
||||
@@ -248,9 +185,6 @@ fr_CA:
|
||||
disconnect_failure: "Déconnecter Stripe a échoué."
|
||||
success_code:
|
||||
disconnected: "Le compte Stripe est déconnecté."
|
||||
taler:
|
||||
order_status:
|
||||
claimed: "La demande de paiement a expiré. Merci d'essayer à nouveau."
|
||||
activemodel:
|
||||
errors:
|
||||
messages:
|
||||
@@ -586,7 +520,6 @@ fr_CA:
|
||||
errors:
|
||||
vine_api: "There was an error communicating with the API, please try again later."
|
||||
invalid_voucher: "The voucher is not valid"
|
||||
expired: "Le bon de réduction a expiré."
|
||||
not_found_voucher: "Désolé, nous n'avons pas trouvé ce bon de réduction. Merci de vérifier le code qui vous a été transmis."
|
||||
vine_voucher_redeemer_service:
|
||||
errors:
|
||||
@@ -3372,8 +3305,6 @@ fr_CA:
|
||||
payment_processing_failed: "Le paiement n'a pas pu être traité, veuillez vérifier les informations saisies"
|
||||
payment_method_not_supported: "Cette méthode de paiement n'est pas maintenue. Veuillez en sélectionner une autre."
|
||||
payment_updated: "Paiement mis à jour"
|
||||
payment_method_taler:
|
||||
order_summary: "Commande Open Food Network"
|
||||
cannot_perform_operation: "Le paiement n'a pas pu être mis à jour."
|
||||
action_required: "Une action est requise"
|
||||
tag_rules: "Règles de tag"
|
||||
@@ -4034,8 +3965,6 @@ fr_CA:
|
||||
destroy:
|
||||
success: Le webhook a bien été supprimé.
|
||||
error: Le webhook n'a pas pu être supprimé.
|
||||
test:
|
||||
success: Des données test vont être envoyées à l'URL du webhook
|
||||
spree:
|
||||
order_updated: "La commande a été mise à jour"
|
||||
cannot_perform_operation: "Cette opération ne peut pas être réalisée"
|
||||
@@ -4142,7 +4071,6 @@ fr_CA:
|
||||
logourl: "URL du logo"
|
||||
are_you_sure_delete: "Etes-vous certain de vouloir supprimer cet élément ?"
|
||||
confirm_delete: "Confirmer la suppression"
|
||||
tag_rule: "Règle de tag"
|
||||
voucher: "Bon de réduction"
|
||||
configurations: "Configurations"
|
||||
general_settings: "Configurations générales"
|
||||
@@ -4234,7 +4162,6 @@ fr_CA:
|
||||
alt_text: "Texte alternatif"
|
||||
thumbnail: "Miniature"
|
||||
back_to_images_list: "Retour à la liste des images"
|
||||
backend_url: "URL du backend"
|
||||
api_key: "Clé API"
|
||||
email: Email
|
||||
account_updated: "Compte mis à jour!"
|
||||
@@ -4554,7 +4481,6 @@ fr_CA:
|
||||
check: "Espèces / chèques / virements / autres "
|
||||
paypalexpress: "PayPal Express"
|
||||
stripesca: "Stripe SCA"
|
||||
taler: "Taler"
|
||||
payments:
|
||||
source_forms:
|
||||
stripe:
|
||||
@@ -5041,8 +4967,6 @@ fr_CA:
|
||||
create_placeholder: Entrez l'URL du point de terminaison du webhook
|
||||
event_types:
|
||||
order_cycle_opened: Cycle de vente ouvert
|
||||
payment_status_changed: Poster webhook sur le changement du statut de paiement
|
||||
test_endpoint: Tester le point de terminaison du webhook
|
||||
invisible_captcha:
|
||||
sentence_for_humans: "Merci de laisser ce champ libre"
|
||||
timestamp_error_message: "S'il vous plaît réessayez après 5 secondes."
|
||||
|
||||
@@ -136,15 +136,7 @@ Openfoodnetwork::Application.routes.draw do
|
||||
put :resume, on: :member, format: :json
|
||||
end
|
||||
|
||||
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
|
||||
get '/reports', to: 'reports#index', as: :reports
|
||||
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
|
||||
|
||||
@@ -36,7 +36,7 @@ RSpec.describe "CatalogItems", swagger_doc: "dfc.yaml" do
|
||||
|
||||
get "List CatalogItems" do
|
||||
produces "application/json"
|
||||
security [{ oidc_token: [] }]
|
||||
security [oidc_token: []]
|
||||
|
||||
response "404", "not found" do
|
||||
context "as platform user" do
|
||||
|
||||
@@ -35,7 +35,7 @@ RSpec.describe "ProductGroups", swagger_doc: "dfc.yaml" do
|
||||
|
||||
get "Show ProductGroup" do
|
||||
produces "application/json"
|
||||
security [{ oidc_token: [] }]
|
||||
security [oidc_token: []]
|
||||
|
||||
response "200", "success" do
|
||||
let(:id) { product.id }
|
||||
|
||||
@@ -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.+|tom-select)/)"],
|
||||
transformIgnorePatterns: ["/node_modules/(?!stimulus)/"],
|
||||
|
||||
// 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_query).select("customers.id, customers.email")
|
||||
Customer.where(id: visible_order_customer_ids).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_query
|
||||
Permissions::Order.new(current_user).visible_orders.select(:customer_id)
|
||||
def visible_order_customer_ids
|
||||
Permissions::Order.new(current_user).visible_orders.pluck(:customer_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,7 +15,7 @@ module Reporting
|
||||
@orders ||= search_orders
|
||||
end
|
||||
|
||||
def list(line_item_includes = [{ variant: [:supplier, :product] }])
|
||||
def list(line_item_includes = [variant: [:supplier, :product]])
|
||||
line_items = order_permissions.visible_line_items.in_orders(orders.result)
|
||||
.order(
|
||||
"supplier.name",
|
||||
|
||||
@@ -10,10 +10,10 @@ module Reporting
|
||||
.complete.not_state(:canceled)
|
||||
.order(:id))
|
||||
.group_by do |order|
|
||||
{
|
||||
customer_id: order.customer_id || order.email,
|
||||
hub_id: order.distributor_id,
|
||||
}
|
||||
{
|
||||
customer_id: order.customer_id || order.email,
|
||||
hub_id: order.distributor_id,
|
||||
}
|
||||
end.values
|
||||
end
|
||||
|
||||
|
||||
@@ -44,11 +44,11 @@ module Reporting
|
||||
.filter(&method(:filter_enterprise_fee_by_id))
|
||||
.filter(&method(:filter_enterprise_fee_by_owner))
|
||||
.map do |enterprise_fee_id, enterprise_fee_adjustment_ids|
|
||||
{
|
||||
enterprise_fee_id:,
|
||||
enterprise_fee_adjustment_ids:,
|
||||
order:
|
||||
}
|
||||
{
|
||||
enterprise_fee_id:,
|
||||
enterprise_fee_adjustment_ids:,
|
||||
order:
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,296 +0,0 @@
|
||||
/**
|
||||
* @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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -109,7 +109,7 @@ RSpec.describe Spree::Gateway::StripeSCA, :vcr, :stripe_version do
|
||||
end
|
||||
|
||||
it "refunds the payment" do
|
||||
response = subject.void(payment_intent.id, {})
|
||||
response = subject.void(payment_intent.id, nil, {})
|
||||
|
||||
expect(response.success?).to eq true
|
||||
end
|
||||
@@ -131,7 +131,7 @@ RSpec.describe Spree::Gateway::StripeSCA, :vcr, :stripe_version do
|
||||
end
|
||||
|
||||
it "void the payment" do
|
||||
response = subject.void(payment_intent.id, {})
|
||||
response = subject.void(payment_intent.id, nil, {})
|
||||
|
||||
expect(response.success?).to eq true
|
||||
end
|
||||
@@ -162,7 +162,7 @@ RSpec.describe Spree::Gateway::StripeSCA, :vcr, :stripe_version do
|
||||
stripe_account: stripe_test_account
|
||||
)
|
||||
|
||||
response = subject.credit(1000, payment_intent.id, {})
|
||||
response = subject.credit(1000, nil, payment_intent.id, {})
|
||||
|
||||
expect(response.success?).to eq true
|
||||
end
|
||||
|
||||
@@ -14,14 +14,8 @@ RSpec.describe Spree::Gateway do
|
||||
end
|
||||
|
||||
it "passes through all arguments on a method_missing call" do
|
||||
expect(Rails.env).to receive(:local?).and_return(false)
|
||||
gateway = test_gateway.new
|
||||
expect(gateway.provider).to receive(:imaginary_method).with('foo')
|
||||
gateway.imaginary_method('foo')
|
||||
end
|
||||
|
||||
it "raises an error in test env" do
|
||||
gateway = test_gateway.new
|
||||
expect { gateway.imaginary_method('foo') }.to raise_error StandardError
|
||||
end
|
||||
end
|
||||
|
||||
@@ -86,11 +86,11 @@ RSpec.describe Spree::Order do
|
||||
|
||||
(Spree::Shipment.state_machine.states.keys - [:pending, :backorder, :ready])
|
||||
.each do |shipment_state|
|
||||
it "should be false if shipment_state is #{shipment_state}" do
|
||||
allow(order).to receive_messages completed?: true
|
||||
order.shipment_state = shipment_state
|
||||
expect(order.can_cancel?).to be_falsy
|
||||
end
|
||||
it "should be false if shipment_state is #{shipment_state}" do
|
||||
allow(order).to receive_messages completed?: true
|
||||
order.shipment_state = shipment_state
|
||||
expect(order.can_cancel?).to be_falsy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -131,6 +131,13 @@ RSpec.describe Spree::PaymentMethod do
|
||||
expect(pm.errors.to_a).to eq(["Name can't be blank", "At least one hub must be selected"])
|
||||
end
|
||||
|
||||
it "generates a clean name for known Payment Method types" do
|
||||
expect(Spree::PaymentMethod::Check.clean_name)
|
||||
.to eq('Cash/EFT/etc. (payments for which automatic validation is not required)')
|
||||
expect(Spree::Gateway::PayPalExpress.clean_name).to eq('PayPal Express')
|
||||
expect(Spree::Gateway::StripeSCA.clean_name).to eq('Stripe SCA')
|
||||
end
|
||||
|
||||
it "computes the amount of fees" do
|
||||
order = create(:order)
|
||||
|
||||
|
||||
@@ -345,6 +345,24 @@ RSpec.describe Spree::Payment do
|
||||
allow(payment_method).to receive(:void).and_return(success_response)
|
||||
end
|
||||
|
||||
context "when profiles are supported" do
|
||||
it "should call payment_enterprise.void with the payment's response_code" do
|
||||
allow(payment_method).to receive(:payment_profiles_supported) { true }
|
||||
expect(payment_method).to receive(:void).with('123', card,
|
||||
anything).and_return(success_response)
|
||||
payment.void_transaction!
|
||||
end
|
||||
end
|
||||
|
||||
context "when profiles are not supported" do
|
||||
it "should call payment_gateway.void with the payment's response_code" do
|
||||
allow(payment_method).to receive(:payment_profiles_supported) { false }
|
||||
expect(payment_method).to receive(:void).with('123', card,
|
||||
anything).and_return(success_response)
|
||||
payment.void_transaction!
|
||||
end
|
||||
end
|
||||
|
||||
it "should log the response" do
|
||||
payment.void_transaction!
|
||||
expect(payment).to have_received(:record_response)
|
||||
@@ -419,7 +437,7 @@ RSpec.describe Spree::Payment do
|
||||
end
|
||||
|
||||
it "should call credit on the gateway with the credit amount and response_code" do
|
||||
expect(payment_method).to receive(:credit).with(1000, '123',
|
||||
expect(payment_method).to receive(:credit).with(1000, card, '123',
|
||||
anything).and_return(success_response)
|
||||
payment.credit!
|
||||
end
|
||||
@@ -445,7 +463,7 @@ RSpec.describe Spree::Payment do
|
||||
|
||||
it "should call credit on the gateway with the credit amount and response_code" do
|
||||
expect(payment_method).to receive(:credit).with(
|
||||
amount_in_cents, '123', anything
|
||||
amount_in_cents, card, '123', anything
|
||||
).and_return(success_response)
|
||||
payment.credit!
|
||||
end
|
||||
@@ -458,7 +476,7 @@ RSpec.describe Spree::Payment do
|
||||
|
||||
it "should call credit on the gateway with original payment amount and response_code" do
|
||||
expect(payment_method).to receive(:credit).with(
|
||||
amount_in_cents.to_f, '123', anything
|
||||
amount_in_cents.to_f, card, '123', anything
|
||||
).and_return(success_response)
|
||||
payment.credit!
|
||||
end
|
||||
@@ -640,6 +658,7 @@ RSpec.describe Spree::Payment do
|
||||
|
||||
context "when profiles are supported" do
|
||||
before do
|
||||
allow(payment_method).to receive(:payment_profiles_supported?) { true }
|
||||
allow(payment.source).to receive(:has_payment_profile?) { false }
|
||||
end
|
||||
|
||||
@@ -681,6 +700,26 @@ RSpec.describe Spree::Payment do
|
||||
end
|
||||
end
|
||||
|
||||
context "when profiles are not supported" do
|
||||
before do
|
||||
allow(payment_method).to receive(:payment_profiles_supported?) { false }
|
||||
end
|
||||
|
||||
it "should not create a payment profile" do
|
||||
payment_method.name = 'Gateway'
|
||||
payment_method.distributors << create(:distributor_enterprise)
|
||||
payment_method.save!
|
||||
|
||||
expect(payment_method).not_to receive :create_profile
|
||||
payment = Spree::Payment.create(
|
||||
amount: 100,
|
||||
order: create(:order),
|
||||
source: card,
|
||||
payment_method:
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the payment was completed but now void' do
|
||||
let(:payment) { create(:payment, :completed, amount: 100, order:) }
|
||||
|
||||
@@ -838,6 +877,23 @@ RSpec.describe Spree::Payment do
|
||||
end
|
||||
end
|
||||
|
||||
describe "performing refunds" do
|
||||
before do
|
||||
allow(payment).to receive(:calculate_refund_amount) { 123 }
|
||||
expect(payment.payment_method).to receive(:refund).and_return(success)
|
||||
end
|
||||
|
||||
it "performs the refund without payment profiles" do
|
||||
allow(payment.payment_method).to receive(:payment_profiles_supported?) { false }
|
||||
payment.refund!
|
||||
end
|
||||
|
||||
it "performs the refund with payment profiles" do
|
||||
allow(payment.payment_method).to receive(:payment_profiles_supported?) { true }
|
||||
payment.refund!
|
||||
end
|
||||
end
|
||||
|
||||
it "records the response" do
|
||||
allow(payment).to receive(:calculate_refund_amount) { 123 }
|
||||
allow(payment.payment_method).to receive(:refund).and_return(success)
|
||||
|
||||
@@ -17,7 +17,7 @@ RSpec.describe Spree::User do
|
||||
bill_address_attributes: new_bill_address.dup.attributes.merge(
|
||||
'id' => old_bill_address.id
|
||||
)
|
||||
.except!('created_at', 'updated_at')
|
||||
.except!('created_at', 'updated_at')
|
||||
)
|
||||
|
||||
expect(user.bill_address.id).to eq old_bill_address.id
|
||||
|
||||
@@ -10,7 +10,7 @@ RSpec.describe OutstandingBalanceQuery do
|
||||
let(:normalized_sql_statement) { normalize(query.statement) }
|
||||
|
||||
it 'returns the CASE statement necessary to compute the order balance' do
|
||||
expect(normalized_sql_statement).to eq(normalize(<<~SQL.squish))
|
||||
expect(normalized_sql_statement).to eq(normalize(<<-SQL.squish))
|
||||
CASE WHEN "spree_orders"."state" IN ('canceled', 'returned') THEN "spree_orders"."payment_total"
|
||||
WHEN "spree_orders"."state" IS NOT NULL THEN "spree_orders"."payment_total" - "spree_orders"."total"
|
||||
ELSE 0 END
|
||||
|
||||
@@ -1,328 +0,0 @@
|
||||
# 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
|
||||
@@ -38,8 +38,8 @@ RSpec.shared_examples "attribute changes - tax total changes" do |boolean, type,
|
||||
create(:order_with_taxes, product_price: 110, tax_rate_amount: 0.1,
|
||||
included_in_price: included_boolean)
|
||||
.tap do |order|
|
||||
order.create_tax_charge!
|
||||
order.update_shipping_fees!
|
||||
order.create_tax_charge!
|
||||
order.update_shipping_fees!
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -92,7 +92,9 @@ RSpec::Matchers.define :have_select2 do |id, options = {}|
|
||||
end
|
||||
|
||||
def selected_option_is(from, text)
|
||||
find("#{from} a.select2-choice", text:)
|
||||
within find(from) do
|
||||
find("a.select2-choice").text == text
|
||||
end
|
||||
end
|
||||
|
||||
def with_select2_open(from)
|
||||
|
||||
@@ -144,7 +144,7 @@ module StripeStubs
|
||||
customer_id = options[:customer_id] || "cus_A123"
|
||||
{ status: 200,
|
||||
body: JSON.generate(id: customer_id,
|
||||
sources: { data: [{ id: customer_id }] }) }
|
||||
sources: { data: [id: customer_id] }) }
|
||||
end
|
||||
|
||||
def payment_successful_refund_mock
|
||||
|
||||
@@ -69,6 +69,8 @@ module WebHelper
|
||||
.find(:css, '.select2-drop-active .select2-result-label',
|
||||
text: options[:select_text] || value)
|
||||
.click
|
||||
|
||||
expect(page).to have_select2 options[:from], selected: options[:select_text] || value
|
||||
end
|
||||
|
||||
def open_select2(selector)
|
||||
|
||||
@@ -152,7 +152,7 @@ RSpec.describe '
|
||||
click_link 'Adjustments'
|
||||
page.find('tr', text: 'Extra Adjustment').find('a.icon-edit').click
|
||||
|
||||
expect(page).to have_select2 :adjustment_tax_category_id, selected: "None"
|
||||
expect(page).to have_select2 :adjustment_tax_category_id, selected: []
|
||||
|
||||
# When I edit the adjustment, setting a tax rate
|
||||
select2_select 'GST', from: :adjustment_tax_category_id
|
||||
|
||||
@@ -1044,10 +1044,10 @@ RSpec.describe '
|
||||
page.driver
|
||||
.dismiss_modal :confirm,
|
||||
text: "Unsaved changes exist and will be lost if you continue." do
|
||||
within "tr#li_#{li1.id}" do
|
||||
fill_in "quantity", with: (li1.quantity + 1)
|
||||
find("a.edit-order").click
|
||||
end
|
||||
within "tr#li_#{li1.id}" do
|
||||
fill_in "quantity", with: (li1.quantity + 1)
|
||||
find("a.edit-order").click
|
||||
end
|
||||
end
|
||||
|
||||
# So we save the changes
|
||||
|
||||
@@ -20,11 +20,7 @@ RSpec.describe '
|
||||
click_link 'Payment Methods'
|
||||
click_link 'New Payment Method'
|
||||
|
||||
expect(page).to have_select2 "payment_method_type", selected: "Choose..."
|
||||
|
||||
fill_in 'payment_method_name', with: 'Cheque payment method'
|
||||
cash_name = "Cash/EFT/etc. (payments for which automatic validation is not required)"
|
||||
select2_select cash_name, from: "payment_method_type"
|
||||
|
||||
check "payment_method_distributor_ids_#{@distributors[0].id}"
|
||||
click_button 'Create'
|
||||
@@ -247,9 +243,6 @@ RSpec.describe '
|
||||
it "creates payment methods" do
|
||||
visit spree.new_admin_payment_method_path
|
||||
fill_in 'payment_method_name', with: 'Cheque payment method'
|
||||
cash_name = "Cash/EFT/etc. (payments for which automatic validation is not required)"
|
||||
select2_select cash_name, from: "payment_method_type"
|
||||
|
||||
expect(page).to have_field 'payment_method_description'
|
||||
expect(page).to have_select 'payment_method_display_on'
|
||||
|
||||
|
||||
@@ -296,8 +296,11 @@ RSpec.describe "Enterprise Summary Fee with Tax Report By Producer" do
|
||||
end
|
||||
|
||||
it "should filter by distributor and order cycle" do
|
||||
tomselect_multiselect distributor.name, from: 'q[distributor_id_in][]'
|
||||
tomselect_multiselect order_cycle.name, from: 'q[order_cycle_id_in][]'
|
||||
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
|
||||
|
||||
run_report
|
||||
expect(page.find("table.report__table thead tr")).to have_content(table_header)
|
||||
@@ -452,6 +455,9 @@ 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(" ")
|
||||
}
|
||||
@@ -465,8 +471,11 @@ RSpec.describe "Enterprise Summary Fee with Tax Report By Producer" do
|
||||
end
|
||||
|
||||
it "should filter by distributor and order cycle" do
|
||||
tomselect_multiselect distributor.name, from: 'q[distributor_id_in][]'
|
||||
tomselect_multiselect order_cycle3.name, from: 'q[order_cycle_id_in][]'
|
||||
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
|
||||
|
||||
run_report
|
||||
expect(page.find("table.report__table thead tr")).to have_content(table_header)
|
||||
@@ -495,7 +504,8 @@ RSpec.describe "Enterprise Summary Fee with Tax Report By Producer" do
|
||||
end
|
||||
|
||||
it "should filter by producer" do
|
||||
tomselect_multiselect supplier2.name, from: 'supplier_id_in[]'
|
||||
page.find("#s2id_supplier_id_in").click
|
||||
find('li', text: supplier2.name).click
|
||||
|
||||
run_report
|
||||
expect(page.find("table.report__table thead tr")).to have_content(table_header)
|
||||
@@ -518,7 +528,8 @@ RSpec.describe "Enterprise Summary Fee with Tax Report By Producer" do
|
||||
end
|
||||
|
||||
it "should filter by fee name" do
|
||||
tomselect_multiselect supplier_fees.name, from: 'q[enterprise_fee_id_in][]'
|
||||
page.find(fee_name_selector).click
|
||||
find('li', text: supplier_fees.name).click
|
||||
|
||||
run_report
|
||||
|
||||
@@ -546,7 +557,8 @@ RSpec.describe "Enterprise Summary Fee with Tax Report By Producer" do
|
||||
end
|
||||
|
||||
it "should filter by fee owner" do
|
||||
tomselect_multiselect supplier.name, from: 'q[enterprise_fee_owner_id_in][]'
|
||||
page.find(fee_owner_selector).click
|
||||
find('li', text: supplier.name).click
|
||||
|
||||
run_report
|
||||
expect(page.find("table.report__table thead tr")).to have_content(table_header)
|
||||
|
||||
@@ -360,10 +360,10 @@ RSpec.describe "As a consumer, I want to checkout my order" do
|
||||
# And fake the payment status to avoid user interaction.
|
||||
allow_any_instance_of(Taler::Client)
|
||||
.to receive(:fetch_order) do
|
||||
payment = Spree::Payment.last
|
||||
url = payment_gateways_confirm_taler_path(payment_id: payment.id)
|
||||
payment = Spree::Payment.last
|
||||
url = payment_gateways_confirm_taler_path(payment_id: payment.id)
|
||||
|
||||
{ "order_status_url" => url, "order_status" => "paid" }
|
||||
{ "order_status_url" => url, "order_status" => "paid" }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
18
yarn.lock
18
yarn.lock
@@ -4005,9 +4005,9 @@ hasown@^2.0.2:
|
||||
function-bind "^1.1.2"
|
||||
|
||||
hotkeys-js@*:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/hotkeys-js/-/hotkeys-js-4.0.0.tgz#75336c0ac610ad384d286c61c519909dcd4bdf6b"
|
||||
integrity sha512-gIoeqMWYqPIItc4HaseVbtTRpEpBbeufZMUcoWtN62JZdDq3KadS1ijN6wpaDjTzRK7PjT3QOPUcx+yNT0rrZQ==
|
||||
version "3.13.15"
|
||||
resolved "https://registry.yarnpkg.com/hotkeys-js/-/hotkeys-js-3.13.15.tgz#2d394bd6bd78857d4b24dc86bdba2fa1cf7012fc"
|
||||
integrity sha512-gHh8a/cPTCpanraePpjRxyIlxDFrIhYqjuh01UHWEwDpglJKCnvLW8kqSx5gQtOuSsJogNZXLhOdbSExpgUiqg==
|
||||
|
||||
hpack.js@^2.1.6:
|
||||
version "2.1.6"
|
||||
@@ -7064,9 +7064,9 @@ toidentifier@1.0.1, toidentifier@~1.0.1:
|
||||
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
|
||||
|
||||
tom-select@*:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/tom-select/-/tom-select-2.5.1.tgz#8c8d3f11e5c1780b5f26c9e90f4e650842ff9596"
|
||||
integrity sha512-63D5/Qf6bb6kLSgksEuas/60oawDcuUHrD90jZofeOpF6bkQFYriKrvtpJBQQ4xIA5dUGcjhBbk/yrlfOQsy3g==
|
||||
version "2.4.6"
|
||||
resolved "https://registry.yarnpkg.com/tom-select/-/tom-select-2.4.6.tgz#23acdfc09ee235eb752706d418c9c9ae6ccf67f0"
|
||||
integrity sha512-Hhqi15AiTl0+FjaHVTXvUkF3t7x4W5LXUHxLYlzp7r8bcIgGJyz9M+3ZvrHdTRvEmV4EmNyJPbHJJnZOjr5Iig==
|
||||
dependencies:
|
||||
"@orchidjs/sifter" "^1.1.0"
|
||||
"@orchidjs/unicode-variants" "^1.1.2"
|
||||
@@ -7376,9 +7376,9 @@ webpack-sources@^3.3.3:
|
||||
integrity sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==
|
||||
|
||||
webpack@^5.104.0:
|
||||
version "5.105.1"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.105.1.tgz#c05cb3621196c76fa3b3a9bea446d14616b83778"
|
||||
integrity sha512-Gdj3X74CLJJ8zy4URmK42W7wTZUJrqL+z8nyGEr4dTN0kb3nVs+ZvjbTOqRYPD7qX4tUmwyHL9Q9K6T1seW6Yw==
|
||||
version "5.105.0"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.105.0.tgz#38b5e6c5db8cbe81debbd16e089335ada05ea23a"
|
||||
integrity sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==
|
||||
dependencies:
|
||||
"@types/eslint-scope" "^3.7.7"
|
||||
"@types/estree" "^1.0.8"
|
||||
|
||||
Reference in New Issue
Block a user