diff --git a/.gitignore b/.gitignore index 4c0d217d06..21ff15e147 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ libpeerconnection.log node_modules vendor/bundle/ coverage +/reports/ +!/reports/README.md +bin/ diff --git a/.rubocop_styleguide.yml b/.rubocop_styleguide.yml index 61419eb02f..13214b0aed 100644 --- a/.rubocop_styleguide.yml +++ b/.rubocop_styleguide.yml @@ -187,7 +187,7 @@ Metrics/AbcSize: Max: 15 Metrics/BlockLength: - ExcludedMethods: ["context", "describe", "it"] + ExcludedMethods: ["collection", "context", "describe", "it", "member", "namespace", "resource", "resources"] Metrics/BlockNesting: Max: 3 diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ba7bb23eef..63e730db24 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -840,7 +840,6 @@ Layout/SpaceInsideHashLiteralBraces: - 'spec/features/admin/reports_spec.rb' - 'spec/features/consumer/shopping/checkout_spec.rb' - 'spec/helpers/checkout_helper_spec.rb' - - 'spec/helpers/i18n_helper_spec.rb' - 'spec/helpers/order_cycles_helper_spec.rb' - 'spec/lib/open_food_network/enterprise_fee_calculator_spec.rb' - 'spec/lib/open_food_network/feature_toggle_spec.rb' diff --git a/.travis.yml b/.travis.yml index 952e999999..db818fad23 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,9 +9,6 @@ addons: # Set the timezone for phantomjs with TZ # Set the timezone for karma with TIMEZONE -# -# The test cases are roughly split according to their test times. -# It would be better to use https://github.com/ArturT/knapsack. env: global: - TZ="Australia/Melbourne" @@ -21,7 +18,7 @@ env: - CI_NODE_INDEX=0 - CI_NODE_INDEX=1 - CI_NODE_INDEX=2 - - CI_NODE_INDEX=3 + - CI_NODE_INDEX=3 RSPEC_ENGINES="true" - CI_NODE_INDEX=4 KARMA="true" GITHUB_DEPLOY="true" before_script: @@ -42,6 +39,7 @@ before_script: script: - 'if [ "$KARMA" = "true" ]; then bundle exec rake karma:run; else echo "Skipping karma run"; fi' + - 'if [ "$RSPEC_ENGINES" = "true" ]; then bundle exec rake openfoodnetwork:specs:engines:rspec; else echo "Skipping RSpec run in engines"; fi' - "bundle exec rake 'knapsack:rspec[--format progress --tag ~performance]'" after_success: diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index 93cff8dad0..5a2e7eea54 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -69,10 +69,14 @@ Tests, both unit and integration, are based on RSpec. To run the test suite, fir bundle exec rake db:test:prepare -Then the tests can be run with: +Then the main application tests can be run with: bundle exec rspec spec +The tests of all custom engines can be run with: + + bundle exec rake openfoodnetwork:specs:engines:rspec + Note: If your OS is not explicitly supported in the setup guides then not all tests may pass. However, you may still be able to develop. Get in touch with the [#dev][slack-dev] channel on Slack to troubleshoot issues and determine if they will preclude you from contributing to OFN. Note: The time zone on your machine should match the one defined in `config/application.yml`. diff --git a/Gemfile b/Gemfile index 6d2f898c9e..cc244119de 100644 --- a/Gemfile +++ b/Gemfile @@ -67,7 +67,7 @@ gem 'spinjs-rails' gem 'rack-ssl', require: 'rack/ssl' gem 'rack-rewrite' gem 'custom_error_message', github: 'jeremydurham/custom-err-msg' -gem 'angularjs-file-upload-rails', '~> 1.1.6' +gem 'angularjs-file-upload-rails', '~> 2.4.1' gem 'roadie-rails', '~> 1.1.1' gem 'figaro' gem 'blockenspiel' @@ -149,6 +149,8 @@ group :development do gem 'guard-rails' gem 'guard-rspec', '~> 4.7.3' gem 'rubocop', '>= 0.49.1' + gem 'spring', '=1.1.3' + gem 'spring-commands-rspec' # 1.0.9 fixed openssl issues on macOS https://github.com/eventmachine/eventmachine/issues/602 # While we don't require this gem directly, no dependents forced the upgrade to a version diff --git a/Gemfile.lock b/Gemfile.lock index 4714af396a..177a69a026 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -172,7 +172,7 @@ GEM railties (>= 3.1) sprockets (~> 2.0) tilt - angularjs-file-upload-rails (1.1.6) + angularjs-file-upload-rails (2.4.1) angularjs-rails (1.5.5) arel (3.0.3) ast (2.4.0) @@ -444,10 +444,10 @@ GEM ruby-progressbar (~> 1.4) geocoder (1.1.8) gmaps4rails (1.5.6) - guard (2.14.1) + guard (2.15.0) formatador (>= 0.2.4) listen (>= 2.7, < 4.0) - lumberjack (~> 1.0) + lumberjack (>= 1.0.12, < 2.0) nenv (~> 0.1) notiffany (~> 0.0) pry (>= 0.9.12) @@ -507,17 +507,17 @@ GEM listen (3.0.8) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - lumberjack (1.0.12) + lumberjack (1.0.13) mail (2.5.5) mime-types (~> 1.16) treetop (~> 1.4.8) - method_source (0.9.0) + method_source (0.9.2) mime-types (1.25.1) mini_mime (1.0.1) mini_portile2 (2.1.0) mini_racer (0.1.15) libv8 (~> 6.3) - momentjs-rails (2.5.1) + momentjs-rails (2.20.1) railties (>= 3.1) money (5.1.1) i18n (~> 0.6.0) @@ -566,7 +566,7 @@ GEM activerecord (~> 3.0) polyglot (0.3.5) powerpack (0.1.1) - pry (0.11.2) + pry (0.12.2) coderay (~> 1.1.0) method_source (~> 0.9.0) pry-byebug (3.4.3) @@ -605,7 +605,7 @@ GEM thor (>= 0.14.6, < 2.0) rainbow (3.0.0) raindrops (0.19.0) - rake (12.3.1) + rake (12.3.2) ransack (0.7.2) actionpack (~> 3.0) activerecord (~> 3.0) @@ -691,6 +691,9 @@ GEM rails (>= 3.1) spreadsheet (1.1.7) ruby-ole (>= 1.0) + spring (1.1.3) + spring-commands-rspec (1.0.4) + spring (>= 0.9.1) sprockets (2.2.3) hike (~> 1.2) multi_json (~> 1.0) @@ -728,7 +731,7 @@ GEM railties (>= 3.0) warden (1.2.7) rack (>= 1.0) - webmock (3.4.2) + webmock (3.5.1) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff @@ -739,7 +742,7 @@ GEM activesupport (>= 2.3.4) chronic (>= 0.6.3) wicked_pdf (1.1.0) - wkhtmltopdf-binary (0.12.3.1) + wkhtmltopdf-binary (0.12.4) xml-simple (1.1.5) xpath (2.1.0) nokogiri (~> 1.3) @@ -753,7 +756,7 @@ DEPENDENCIES acts-as-taggable-on (~> 3.4) andand angular-rails-templates (~> 0.3.0) - angularjs-file-upload-rails (~> 1.1.6) + angularjs-file-upload-rails (~> 2.4.1) angularjs-rails (= 1.5.5) atomic awesome_print @@ -834,6 +837,8 @@ DEPENDENCIES spree_auth_devise! spree_i18n! spree_paypal_express! + spring (= 1.1.3) + spring-commands-rspec stripe (~> 3.3.2) timecop truncate_html diff --git a/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee b/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee index 0fe051f496..6b7cea8243 100644 --- a/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee +++ b/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee @@ -13,6 +13,11 @@ angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl", $scope.RequestMonitor = RequestMonitor $scope.selectView = Views.selectView $scope.currentView = -> Views.currentView + $scope.onDemandOptions = [ + { description: t('js.variant_overrides.on_demand.use_producer_settings'), value: null }, + { description: t('js.variant_overrides.on_demand.yes'), value: true }, + { description: t('js.variant_overrides.on_demand.no'), value: false } + ] $scope.views = Views.setViews inventory: { name: t('js.variant_overrides.inventory_products'), visible: true } @@ -105,3 +110,35 @@ angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl", StatusMessage.display 'success', t('js.variant_overrides.stock_reset') .error (data, status) -> $timeout -> StatusMessage.display 'failure', $scope.updateError(data, status) + + # Variant override count_on_hand field placeholder logic: + # on_demand true -- Show "On Demand" + # on_demand false -- Show empty value to be set by the user + # on_demand nil -- Show producer on_hand value + $scope.countOnHandPlaceholder = (variant, hubId) -> + variantOverride = $scope.variantOverrides[hubId][variant.id] + + if variantOverride.on_demand + t('js.variants.on_demand.yes') + else if variantOverride.on_demand == false + '' + else + variant.on_hand + + # This method should only be used when the variant override on_demand is changed. + # + # Change the count_on_hand value to a suggested value. + $scope.updateCountOnHand = (variant, hubId) -> + variantOverride = $scope.variantOverrides[hubId][variant.id] + + suggested = $scope.countOnHandSuggestion(variant, hubId) + return if suggested == variantOverride.count_on_hand + variantOverride.count_on_hand = suggested + DirtyVariantOverrides.set hubId, variant.id, variantOverride.id, 'count_on_hand', suggested + + # Suggest producer count_on_hand if variant has limited stock and variant override forces limited + # stock. Otherwise, clear whatever value is set. + $scope.countOnHandSuggestion = (variant, hubId) -> + variantOverride = $scope.variantOverrides[hubId][variant.id] + return null unless !variant.on_demand && variantOverride.on_demand == false + variant.on_hand diff --git a/app/assets/stylesheets/admin/components/input.css.scss b/app/assets/stylesheets/admin/components/input.css.scss new file mode 100644 index 0000000000..5b4cf8ed05 --- /dev/null +++ b/app/assets/stylesheets/admin/components/input.css.scss @@ -0,0 +1,10 @@ +@import '../../darkswarm/branding'; + +.container { + input { + &[readonly] { + background-color: $disabled-light; + cursor: default; + } + } +} diff --git a/app/controllers/user_registrations_controller.rb b/app/controllers/user_registrations_controller.rb index cc43badbb8..e8e75dc5ee 100644 --- a/app/controllers/user_registrations_controller.rb +++ b/app/controllers/user_registrations_controller.rb @@ -5,25 +5,29 @@ class UserRegistrationsController < Spree::UserRegistrationsController before_filter :set_checkout_redirect, only: :create + include I18nHelper + before_filter :set_locale + # POST /resource/sign_up def create @user = build_resource(params[:spree_user]) - if resource.save - session[:spree_user_signup] = true - session[:confirmation_return_url] = params[:return_url] - associate_user + @user.locale = I18n.locale.to_s + unless resource.save + return render_error(@user.errors) + end - respond_to do |format| - format.html do - set_flash_message(:success, :signed_up_but_unconfirmed) - redirect_to after_sign_in_path_for(@user) - end - format.js do - render json: { email: @user.email } - end + session[:spree_user_signup] = true + session[:confirmation_return_url] = params[:return_url] + associate_user + + respond_to do |format| + format.html do + set_flash_message(:success, :signed_up_but_unconfirmed) + redirect_to after_sign_in_path_for(@user) + end + format.js do + render json: { email: @user.email } end - else - render_error(@user.errors) end rescue StandardError => error OpenFoodNetwork::ErrorLogger.notify(error) diff --git a/app/helpers/i18n_helper.rb b/app/helpers/i18n_helper.rb index 4c93bdc0bc..3293d42d98 100644 --- a/app/helpers/i18n_helper.rb +++ b/app/helpers/i18n_helper.rb @@ -1,7 +1,7 @@ module I18nHelper def set_locale # Save a given locale - if params[:locale] && Rails.application.config.i18n.available_locales.include?(params[:locale]) + if params[:locale] && available_locale?(params[:locale]) spree_current_user.update_attributes!(locale: params[:locale]) if spree_current_user cookies[:locale] = params[:locale] end @@ -13,4 +13,20 @@ module I18nHelper I18n.locale = spree_current_user.andand.locale || cookies[:locale] || I18n.default_locale end + + def valid_locale(user) + if user.present? && + user.locale.present? && + available_locale?(user.locale) + user.locale + else + I18n.default_locale + end + end + + private + + def available_locale?(locale) + Rails.application.config.i18n.available_locales.include?(locale) + end end diff --git a/app/mailers/enterprise_mailer.rb b/app/mailers/enterprise_mailer.rb index 03d9279c93..36a1b071a9 100644 --- a/app/mailers/enterprise_mailer.rb +++ b/app/mailers/enterprise_mailer.rb @@ -1,15 +1,18 @@ require 'devise/mailers/helpers' class EnterpriseMailer < Spree::BaseMailer include Devise::Mailers::Helpers + include I18nHelper def welcome(enterprise) @enterprise = enterprise - subject = t('enterprise_mailer.welcome.subject', - enterprise: @enterprise.name, - sitename: Spree::Config[:site_name]) - mail(:to => enterprise.contact.email, - :from => from_address, - :subject => subject) + I18n.with_locale valid_locale(@enterprise.owner) do + subject = t('enterprise_mailer.welcome.subject', + enterprise: @enterprise.name, + sitename: Spree::Config[:site_name]) + mail(:to => enterprise.contact.email, + :from => from_address, + :subject => subject) + end end def manager_invitation(enterprise, user) @@ -17,11 +20,12 @@ class EnterpriseMailer < Spree::BaseMailer @instance = Spree::Config[:site_name] @instance_email = from_address - subject = t('enterprise_mailer.invite_manager.subject', enterprise: @enterprise.name) - - mail(to: user.email, - from: from_address, - subject: subject) + I18n.with_locale valid_locale(@enterprise.owner) do + subject = t('enterprise_mailer.invite_manager.subject', enterprise: @enterprise.name) + mail(to: user.email, + from: from_address, + subject: subject) + end end private diff --git a/app/mailers/producer_mailer.rb b/app/mailers/producer_mailer.rb index ad7569dad2..058d5403ba 100644 --- a/app/mailers/producer_mailer.rb +++ b/app/mailers/producer_mailer.rb @@ -1,4 +1,5 @@ class ProducerMailer < Spree::BaseMailer + include I18nHelper def order_cycle_report(producer, order_cycle) @producer = producer @@ -10,9 +11,11 @@ class ProducerMailer < Spree::BaseMailer @total = total_from_line_items(line_items) @tax_total = tax_total_from_line_items(line_items) - subject = "[#{Spree::Config.site_name}] #{I18n.t('producer_mailer.order_cycle.subject', producer: producer.name)}" + I18n.with_locale valid_locale(@producer.owner) do + order_cycle_subject = I18n.t('producer_mailer.order_cycle.subject', producer: producer.name) + subject = "[#{Spree::Config.site_name}] #{order_cycle_subject}" - if has_orders?(order_cycle, producer) + return unless has_orders?(order_cycle, producer) mail( to: @producer.contact.email, from: from_address, @@ -23,7 +26,6 @@ class ProducerMailer < Spree::BaseMailer end end - private def has_orders?(order_cycle, producer) diff --git a/app/mailers/spree/order_mailer_decorator.rb b/app/mailers/spree/order_mailer_decorator.rb index 0ff7e21558..b0d4deef89 100644 --- a/app/mailers/spree/order_mailer_decorator.rb +++ b/app/mailers/spree/order_mailer_decorator.rb @@ -2,32 +2,47 @@ Spree::OrderMailer.class_eval do helper HtmlHelper helper CheckoutHelper helper SpreeCurrencyHelper + include I18nHelper + + def cancel_email(order_or_order_id, resend = false) + @order = find_order(order_or_order_id) + I18n.with_locale valid_locale(@order.user) do + mail(to: order.email, + from: from_address, + subject: mail_subject(t('order_mailer.cancel_email.subject'), resend)) + end + end def confirm_email_for_customer(order_or_order_id, resend = false) @order = find_order(order_or_order_id) - subject = build_subject(t('order_mailer.confirm_email.subject'), resend) - mail(:to => @order.email, - :from => from_address, - :subject => subject, - :reply_to => @order.distributor.contact.email) + I18n.with_locale valid_locale(@order.user) do + subject = mail_subject(t('order_mailer.confirm_email.subject'), resend) + mail(:to => @order.email, + :from => from_address, + :subject => subject, + :reply_to => @order.distributor.contact.email) + end end def confirm_email_for_shop(order_or_order_id, resend = false) @order = find_order(order_or_order_id) - subject = build_subject(t('order_mailer.confirm_email.subject'), resend) - mail(:to => @order.distributor.contact.email, - :from => from_address, - :subject => subject) + I18n.with_locale valid_locale(@order.user) do + subject = mail_subject(t('order_mailer.confirm_email.subject'), resend) + mail(:to => @order.distributor.contact.email, + :from => from_address, + :subject => subject) + end end def invoice_email(order_or_order_id, pdf) @order = find_order(order_or_order_id) - subject = build_subject(t(:invoice)) - attachments["invoice-#{@order.number}.pdf"] = pdf if pdf.present? - mail(:to => @order.email, - :from => from_address, - :subject => subject, - :reply_to => @order.distributor.contact.email) + attach_file("invoice-#{@order.number}.pdf", pdf) + I18n.with_locale valid_locale(@order.user) do + mail(to: @order.email, + from: from_address, + subject: mail_subject(t(:invoice), false), + reply_to: @order.distributor.contact.email) + end end private @@ -37,8 +52,12 @@ Spree::OrderMailer.class_eval do order_or_order_id.respond_to?(:id) ? order_or_order_id : Spree::Order.find(order_or_order_id) end - def build_subject( subject_text, resend = false ) - subject = (resend ? "[#{t(:resend).upcase}] " : "") - subject += "#{Spree::Config[:site_name]} #{subject_text} ##{@order.number}" + def mail_subject(base_subject, resend) + resend_prefix = (resend ? "[#{t(:resend).upcase}] " : '') + "#{resend_prefix}#{Spree::Config[:site_name]} #{base_subject} ##{@order.number}" + end + + def attach_file(filename, file) + attachments[filename] = file if file.present? end end diff --git a/app/mailers/spree/user_mailer_decorator.rb b/app/mailers/spree/user_mailer_decorator.rb index dced31e7fd..f9e618e348 100644 --- a/app/mailers/spree/user_mailer_decorator.rb +++ b/app/mailers/spree/user_mailer_decorator.rb @@ -1,8 +1,12 @@ Spree::UserMailer.class_eval do + include I18nHelper + def signup_confirmation(user) @user = user - mail(:to => user.email, :from => from_address, - :subject => t(:welcome_to) + Spree::Config[:site_name]) + I18n.with_locale valid_locale(@user) do + mail(:to => user.email, :from => from_address, + :subject => t(:welcome_to) + Spree::Config[:site_name]) + end end # Overriding `Spree::UserMailer.confirmation_instructions` which is @@ -12,10 +16,12 @@ Spree::UserMailer.class_eval do @instance = Spree::Config[:site_name] @contact = ContentConfig.footer_email - subject = t('spree.user_mailer.confirmation_instructions.subject') - mail(to: confirmation_email_address, - from: from_address, - subject: subject) + I18n.with_locale valid_locale(@user) do + subject = t('spree.user_mailer.confirmation_instructions.subject') + mail(to: confirmation_email_address, + from: from_address, + subject: subject) + end end private diff --git a/app/mailers/subscription_mailer.rb b/app/mailers/subscription_mailer.rb index 426d94f847..0046155499 100644 --- a/app/mailers/subscription_mailer.rb +++ b/app/mailers/subscription_mailer.rb @@ -1,6 +1,7 @@ class SubscriptionMailer < Spree::BaseMailer helper CheckoutHelper helper ShopMailHelper + include I18nHelper def confirmation_email(order) @type = 'confirmation' @@ -46,10 +47,13 @@ class SubscriptionMailer < Spree::BaseMailer private def send_mail(order) - subject = "#{Spree::Config[:site_name]} #{t('order_mailer.confirm_email.subject')} ##{order.number}" - mail(to: order.email, - from: from_address, - subject: subject, - reply_to: order.distributor.contact.email) + I18n.with_locale valid_locale(order.user) do + confirm_email_subject = t('order_mailer.confirm_email.subject') + subject = "#{Spree::Config[:site_name]} #{confirm_email_subject} ##{order.number}" + mail(to: order.email, + from: from_address, + subject: subject, + reply_to: order.distributor.contact.email) + end end end diff --git a/app/models/concerns/stock_settings_override_validation.rb b/app/models/concerns/stock_settings_override_validation.rb new file mode 100644 index 0000000000..b9e3624aec --- /dev/null +++ b/app/models/concerns/stock_settings_override_validation.rb @@ -0,0 +1,41 @@ +module StockSettingsOverrideValidation + extend ActiveSupport::Concern + + included do + before_validation :require_compatible_on_demand_and_count_on_hand + end + + def require_compatible_on_demand_and_count_on_hand + disallow_count_on_hand_if_using_producer_stock_settings + disallow_count_on_hand_if_on_demand + require_count_on_hand_if_limited_stock + end + + def disallow_count_on_hand_if_using_producer_stock_settings + return unless on_demand.nil? && count_on_hand.present? + + error_message = I18n.t("count_on_hand.using_producer_stock_settings_but_count_on_hand_set", + scope: i18n_scope_for_stock_settings_override_validation_error) + errors.add(:count_on_hand, error_message) + end + + def disallow_count_on_hand_if_on_demand + return unless on_demand? && count_on_hand.present? + + error_message = I18n.t("count_on_hand.on_demand_but_count_on_hand_set", + scope: i18n_scope_for_stock_settings_override_validation_error) + errors.add(:count_on_hand, error_message) + end + + def require_count_on_hand_if_limited_stock + return unless on_demand == false && count_on_hand.blank? + + error_message = I18n.t("count_on_hand.limited_stock_but_no_count_on_hand", + scope: i18n_scope_for_stock_settings_override_validation_error) + errors.add(:count_on_hand, error_message) + end + + def i18n_scope_for_stock_settings_override_validation_error + "activerecord.errors.models.#{self.class.name.underscore}" + end +end diff --git a/app/models/product_import/entry_validator.rb b/app/models/product_import/entry_validator.rb index b7f055e445..11c0aab7dd 100644 --- a/app/models/product_import/entry_validator.rb +++ b/app/models/product_import/entry_validator.rb @@ -68,6 +68,18 @@ module ProductImport private + def find_or_initialize_variant_override(entry, existing_variant) + existing_variant_override = VariantOverride.where( + variant_id: existing_variant.id, + hub_id: entry.enterprise_id + ).first + + existing_variant_override || VariantOverride.new( + variant_id: existing_variant.id, + hub_id: entry.enterprise_id + ) + end + def enterprise_validation(entry) return if name_presence_error entry return if enterprise_not_found_error entry @@ -310,21 +322,12 @@ module ProductImport end def create_inventory_item(entry, existing_variant) - existing_variant_override = VariantOverride.where( - variant_id: existing_variant.id, - hub_id: entry.enterprise_id - ).first + find_or_initialize_variant_override(entry, existing_variant).tap do |variant_override| + check_variant_override_stock_settings(entry, variant_override) - variant_override = existing_variant_override || VariantOverride.new( - variant_id: existing_variant.id, - hub_id: entry.enterprise_id - ) - - variant_override.assign_attributes(count_on_hand: entry.on_hand, import_date: @import_time) - check_on_hand_nil(entry, variant_override) - variant_override.assign_attributes(entry.attributes.slice('price', 'on_demand')) - - variant_override + variant_override.assign_attributes(import_date: @import_time) + variant_override.assign_attributes(entry.attributes.slice('price', 'on_demand')) + end end def mark_as_inventory_item(entry, variant_override) @@ -355,5 +358,11 @@ module ProductImport object.count_on_hand = 0 if object.respond_to?(:count_on_hand) entry.on_hand_nil = true end + + def check_variant_override_stock_settings(entry, object) + object.count_on_hand = entry.on_hand.presence + object.on_demand = object.count_on_hand.blank? if entry.on_demand.blank? + entry.on_hand_nil = object.count_on_hand.blank? + end end end diff --git a/app/models/variant_override.rb b/app/models/variant_override.rb index 1e421c7adf..5f8004f317 100644 --- a/app/models/variant_override.rb +++ b/app/models/variant_override.rb @@ -1,5 +1,6 @@ class VariantOverride < ActiveRecord::Base extend Spree::LocalizedNumber + include StockSettingsOverrideValidation acts_as_taggable @@ -58,7 +59,7 @@ class VariantOverride < ActiveRecord::Base def reset_stock! if resettable if default_stock? - self.attributes = { count_on_hand: default_stock } + self.attributes = { on_demand: false, count_on_hand: default_stock } save else Bugsnag.notify RuntimeError.new "Attempting to reset stock level for a variant with no default stock level." diff --git a/app/views/admin/enterprises/form/_social.html.haml b/app/views/admin/enterprises/form/_social.html.haml index 11939739c1..0df88d7d46 100644 --- a/app/views/admin/enterprises/form/_social.html.haml +++ b/app/views/admin/enterprises/form/_social.html.haml @@ -2,17 +2,17 @@ .alpha.three.columns = f.label :facebook, 'Facebook' .omega.eight.columns - = f.text_field :facebook + = f.text_field :facebook, { placeholder: t('.facebook_placeholder') } .row .alpha.three.columns = f.label :instagram, 'Instagram' .omega.eight.columns - = f.text_field :instagram + = f.text_field :instagram, { placeholder: t('.instagram_placeholder') } .row .alpha.three.columns = f.label :linkedin, 'LinkedIn' .omega.eight.columns - = f.text_field :linkedin + = f.text_field :linkedin, { placeholder: t('.linkedin_placeholder') } .row .alpha.three.columns = f.label :twitter diff --git a/app/views/admin/variant_overrides/_products_variants.html.haml b/app/views/admin/variant_overrides/_products_variants.html.haml index bda9725142..ff8e5ab186 100644 --- a/app/views/admin/variant_overrides/_products_variants.html.haml +++ b/app/views/admin/variant_overrides/_products_variants.html.haml @@ -8,9 +8,9 @@ %td.price{ ng: { show: 'columns.price.visible' } } %input{name: 'variant-overrides-{{ variant.id }}-price', type: 'text', ng: {model: 'variantOverrides[hub_id][variant.id].price'}, placeholder: '{{ variant.price }}', 'ofn-track-variant-override' => 'price'} %td.on_hand{ ng: { show: 'columns.on_hand.visible' } } - %input{name: 'variant-overrides-{{ variant.id }}-count_on_hand', type: 'text', ng: {model: 'variantOverrides[hub_id][variant.id].count_on_hand'}, placeholder: '{{ variant.on_hand }}', 'ofn-track-variant-override' => 'count_on_hand'} + %input{name: 'variant-overrides-{{ variant.id }}-count_on_hand', type: 'text', ng: { model: 'variantOverrides[hub_id][variant.id].count_on_hand', readonly: 'variantOverrides[hub_id][variant.id].on_demand != false' }, placeholder: '{{ countOnHandPlaceholder(variant, hub_id) }}', 'ofn-track-variant-override' => 'count_on_hand'} %td.on_demand{ ng: { show: 'columns.on_demand.visible' } } - %input.field{ :type => 'checkbox', name: 'variant-overrides-{{ variant.id }}-on_demand', ng: { model: 'variantOverrides[hub_id][variant.id].on_demand' }, 'ofn-track-variant-override' => 'on_demand' } + %select{ name: 'variant-overrides-{{ variant.id }}-on_demand', ng: { model: 'variantOverrides[hub_id][variant.id].on_demand', change: 'updateCountOnHand(variant, hub_id)', options: 'option.value as option.description for option in onDemandOptions' }, 'ofn-track-variant-override' => 'on_demand' } %td.reset{ ng: { show: 'columns.reset.visible' } } %input{name: 'variant-overrides-{{ variant.id }}-resettable', type: 'checkbox', ng: {model: 'variantOverrides[hub_id][variant.id].resettable'}, placeholder: '{{ variant.resettable }}', 'ofn-track-variant-override' => 'resettable'} %td.reset{ ng: { show: 'columns.reset.visible' } } @@ -24,4 +24,4 @@ %button.icon-remove.fullwidth{ :type => 'button', ng: { click: "setVisibility(hub_id,variant.id,false)" } } = t('admin.variant_overrides.index.hide') %td.import_date{ ng: { show: 'columns.import_date.visible' } } - %span {{variantOverrides[hub_id][variant.id].import_date | date:"MMMM dd, yyyy HH:mm"}} \ No newline at end of file + %span {{variantOverrides[hub_id][variant.id].import_date | date:"MMMM dd, yyyy HH:mm"}} diff --git a/config/application.rb b/config/application.rb index 14c0b82b86..f80c91aee1 100644 --- a/config/application.rb +++ b/config/application.rb @@ -88,6 +88,7 @@ module Openfoodnetwork # Custom directories with classes and modules you want to be autoloadable. config.autoload_paths += %W( + #{config.root}/app/models/concerns #{config.root}/app/presenters #{config.root}/app/jobs ) diff --git a/config/locales/en.yml b/config/locales/en.yml index e78a5f8ddc..4b472ea154 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -47,6 +47,11 @@ en: attributes: orders_close_at: after_orders_open_at: must be after open date + variant_override: + count_on_hand: + using_producer_stock_settings_but_count_on_hand_set: "must be blank because using producer stock settings" + on_demand_but_count_on_hand_set: "must be blank if on demand" + limited_stock_but_no_count_on_hand: "must be specified because forcing limited stock" activemodel: errors: models: @@ -772,6 +777,9 @@ en: close_date: Close Date social: twitter_placeholder: eg. @the_prof + instagram_placeholder: eg. the_prof + facebook_placeholder: eg. www.facebook.com/PageNameHere + linkedin_placeholder: eg. www.linkedin.com/in/YourNameHere stripe_connect: connect_with_stripe: "Connect with Stripe" stripe_connect_intro: "To accept payments using credit card, you will need to connect your stripe account to the Open Food Network. Use the button to the right to get started." @@ -2601,7 +2609,14 @@ See the %{link} to find out more about %{sitename}'s features and to start using in your cart have reduced. Here's what's changed: now_out_of_stock: is now out of stock. only_n_remainging: "now only has %{num} remaining." + variants: + on_demand: + "yes": "On demand" variant_overrides: + on_demand: + use_producer_settings: "Use producer stock settings" + "yes": "Yes" + "no": "No" inventory_products: "Inventory Products" hidden_products: "Hidden Products" new_products: "New Products" diff --git a/config/locales/en_US.yml b/config/locales/en_US.yml index 3ede2a5dc4..4da7c84c1f 100644 --- a/config/locales/en_US.yml +++ b/config/locales/en_US.yml @@ -24,6 +24,11 @@ en_US: attributes: orders_close_at: after_orders_open_at: must be after open date + variant_override: + count_on_hand: + using_producer_stock_settings_but_count_on_hand_set: "must be blank because using producer stock settings" + on_demand_but_count_on_hand_set: "must be blank if on demand" + limited_stock_but_no_count_on_hand: "must be specified because forcing limited stock" activemodel: errors: models: @@ -195,6 +200,7 @@ en_US: admin_and_handling: Admin & Handling profile: Profile supplier_only: Supplier Only + has_shopfront: Has Shopfront weight: Weight volume: Volume items: Items @@ -219,6 +225,7 @@ en_US: password_confirmation: Password Confirmation reset_password_token: Reset password token expired: has expired, please request a new one + back_to_payments_list: "Back to Payments List" actions: create_and_add_another: "Create and Add Another" admin: @@ -572,6 +579,7 @@ en_US: tip: "Use this page to alter product quantities across multiple orders. Products may also be removed from orders entirely, if required." shared: "Shared Resource?" order_no: "Order No." + order_date: "Completed at" max: "Max" product_unit: "Product: Unit" weight_volume: "Weight/Volume" @@ -712,6 +720,9 @@ en_US: close_date: Close Date social: twitter_placeholder: eg. @the_prof + instagram_placeholder: eg. the_prof + facebook_placeholder: eg. www.facebook.com/PageNameHere + linkedin_placeholder: eg. www.linkedin.com/in/YourNameHere stripe_connect: connect_with_stripe: "Connect with Stripe" stripe_connect_intro: "To accept payments using credit card, you will need to connect your stripe account to the Open Food Network. Use the button to the right to get started." @@ -1702,6 +1713,11 @@ en_US: update_and_recalculate_fees: "Update And Recalculate Fees" registration: steps: + images: + continue: "Continue" + back: "Back" + headline: "Thanks!" + description: "Let's upload some pretty pictures so your profile looks great! :)" type: headline: "Last step to add %{enterprise}!" question: "Are you a producer?" @@ -1836,8 +1852,6 @@ en_US: registration_type_error: "Please choose one. Are you are producer?" registration_type_producer_help: "Producers make yummy things to eat and/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it." registration_type_no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other." - registration_images_headline: "Thanks!" - registration_images_description: "Let's upload some pretty pictures so your profile looks great! :)" registration_detail_headline: "Let's get started" registration_detail_enterprise: "Hooray! First we need to know a little bit about your enterprise:" registration_detail_producer: "Oh Yeah! First we need to know a little bit about your farm:" @@ -2234,6 +2248,7 @@ en_US: 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_product_category_cant_be_blank: "^Product Category cant be blank" + validation_msg_tax: "^Tax Category is required" validation_msg_tax_category_cant_be_blank: "^Tax Category can't be blank" validation_msg_is_associated_with_an_exising_customer: "is associated with an existing customer" content_configuration_pricing_table: "(TODO: Pricing table)" @@ -2434,7 +2449,14 @@ en_US: in your cart have reduced. Here's what's changed: now_out_of_stock: is now out of stock. only_n_remainging: "now only has %{num} remaining." + variants: + on_demand: + 'yes': "On demand" variant_overrides: + on_demand: + use_producer_settings: "Use producer stock settings" + 'yes': "Yes" + 'no': "No" inventory_products: "Inventory Products" hidden_products: "Hidden Products" new_products: "New Products" @@ -2538,6 +2560,12 @@ en_US: other: "You have %{count} active order cycles." manage_order_cycles: "MANAGE ORDER CYCLES" payment_methods: + new: + new_payment_method: "New Payment Method" + back_to_payment_methods_list: "Back To Payment Methods List" + edit: + editing_payment_method: "Editing Payment Method" + back_to_payment_methods_list: "Back To Payment Methods List" stripe_connect: enterprise_select_placeholder: Choose... loading_account_information_msg: Loading account information from stripe, please wait... @@ -2598,14 +2626,21 @@ en_US: bulk_coop_customer_payments: 'Bulk Co-op - Customer Payments' users: index: + listing_users: "Listing Users" + new_user: "New User" user: "User" enterprise_limit: "Enterprise Limit" search: "Search" email: "Email" + edit: + editing_user: "Editing User" + back_to_users_list: "Back To Users List" + general_settings: "General Settings" form: email: "Email" roles: "Roles" enterprise_limit: "Enterprise Limit" + confirm_password: "Confirm Password" password: "Password" email_confirmation: confirmation_pending: "Email confirmation is pending. We've sent a confirmation email to %{address}." diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 16c118266a..1fa30bd680 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -24,6 +24,11 @@ fr: attributes: orders_close_at: after_orders_open_at: doit être postérieure à Date d'ouverture + variant_override: + count_on_hand: + using_producer_stock_settings_but_count_on_hand_set: "doit être vide car utilise les informations de stock du producteur" + on_demand_but_count_on_hand_set: "doit être vide si \"à volonté\"" + limited_stock_but_no_count_on_hand: "doit être spécifié car pas \"à volonté\"" activemodel: errors: models: @@ -117,7 +122,7 @@ fr: explainer: Ces commandes ont été traitées mais pour certains produits, le stock était insuffisant empty: title: Pas de stock (%{count} commandes) - explainer: Ces commandes n'ont pas pu être traitées car les produits souhaités étaient en rupture de stok + explainer: Ces commandes n'ont pas pu être traitées car les produits souhaités étaient en rupture de stock complete: title: Déjà traité (%{count} commandes) explainer: Ces commandes étaient déjà marquées comme passées, et n'ont donc pas été retouchées @@ -716,6 +721,9 @@ fr: close_date: Date de fermeture social: twitter_placeholder: ex. @OpenFoodNet_fr + instagram_placeholder: 'ex: OpenFoodNet_fr' + facebook_placeholder: 'ex: www.facebook.com/NomDeLaPage' + linkedin_placeholder: 'ex: www.linkedin.com/in/VotreNom' stripe_connect: connect_with_stripe: "Connecter avec Stripe" stripe_connect_intro: "Pour accepter des paiements utilisant la carte bancaire, vous devez connecter votre compte Stripe à Open Food France. Cliquez sur le bouton à droite pour commencer." @@ -1706,6 +1714,11 @@ fr: update_and_recalculate_fees: "Mettre à jour et recalculer les frais" registration: steps: + images: + continue: "Suivant" + back: "Retour" + headline: "Merci!" + description: "Ajoutez maintenant de jolies photos pour que votre profil soit attractif! :)" type: headline: "Dernière étape pour ajouter %{enterprise} !" question: "Etes-vous un producteur ?" @@ -1840,8 +1853,6 @@ fr: registration_type_error: "Veuillez faire un choix. Etes vous un producteur?" registration_type_producer_help: "Un producteur fabrique de bonnes choses à boire et à manger. Vous êtes un producteur si vous les faites pousser, les élevez, les pétrissez, transformez, fermentez, les réduisez en grains, etc." registration_type_no_producer_help: "Si vous n'êtes pas un producteur, vous êtes probablement un revendeur ou distributeur alimentaire: un \"hub\", une coopérative, un groupement d'achat, un revendeur, un grossiste, ou autre." - registration_images_headline: "Merci!" - registration_images_description: "Ajoutez maintenant de jolies photos pour que votre profil soit attractif! :)" registration_detail_headline: "Commençons" registration_detail_enterprise: "Woohoo! Dites-nous déjà quelques mots à propos de votre entreprise:" registration_detail_producer: "Woohoo! Dites-nous déjà quelques mots à propos de votre ferme:" @@ -2450,7 +2461,14 @@ fr: à votre demande. Voilà les modifications opérées: now_out_of_stock: est maintenant en rupture de stock. only_n_remainging: "plus que %{num} en stock." + variants: + on_demand: + 'yes': "A volonté" variant_overrides: + on_demand: + use_producer_settings: "Utiliser les infos de stock producteur" + 'yes': "Oui" + 'no': "Non" inventory_products: "Produits du Catalogue Boutique" hidden_products: "Produits Masqués" new_products: "Nouveaux Produits" @@ -2554,6 +2572,12 @@ fr: other: "Vous avez %{count} cycles de vente actifs." manage_order_cycles: "GERER LES CYCLES DE VENTE" payment_methods: + new: + new_payment_method: "Nouvelle méthode de paiement" + back_to_payment_methods_list: "Retour à la liste des méthodes de paiement" + edit: + editing_payment_method: "Modification de la méthode de paiement" + back_to_payment_methods_list: "Retour à la liste des méthodes de paiement" stripe_connect: enterprise_select_placeholder: Choisir... loading_account_information_msg: Informations de compte en cours de chargement depuis Stripe, veuillez patienter... diff --git a/config/locales/fr_BE.yml b/config/locales/fr_BE.yml index 7a2990aeda..97fb8d6710 100644 --- a/config/locales/fr_BE.yml +++ b/config/locales/fr_BE.yml @@ -1144,7 +1144,7 @@ fr_BE: ticket_column_unit_price: "Prix unitaire" ticket_column_total_price: "Prix total" menu_1_title: "Comptoir" - menu_1_url: "/Comptoir" + menu_1_url: "/shops" menu_2_title: "Carte" menu_2_url: "/map" menu_3_title: "Producteurs" diff --git a/db/migrate/20181008201815_update_instagram_data.rb b/db/migrate/20181008201815_update_instagram_data.rb new file mode 100644 index 0000000000..d1a45dc155 --- /dev/null +++ b/db/migrate/20181008201815_update_instagram_data.rb @@ -0,0 +1,8 @@ +class UpdateInstagramData < ActiveRecord::Migration + def change + Enterprise.where("instagram like ?", "%instagram.com%").find_each do |e| + e.instagram = e.instagram.split('/').last + e.save + end + end +end diff --git a/db/migrate/20181128054803_simplify_variant_override_stock_settings.rb b/db/migrate/20181128054803_simplify_variant_override_stock_settings.rb new file mode 100644 index 0000000000..6627dca015 --- /dev/null +++ b/db/migrate/20181128054803_simplify_variant_override_stock_settings.rb @@ -0,0 +1,165 @@ +# This simplifies variant overrides to have only the following combinations: +# +# on_demand | count_on_hand +# -----------+--------------- +# true | nil +# false | set +# nil | nil +# +# Refer to the table {here}[https://github.com/openfoodfoundation/openfoodnetwork/issues/3067] for +# the effect of different variant and variant override stock configurations. +# +# Furthermore, this will allow all existing variant overrides to satisfy the newly added model +# validation rules. +class SimplifyVariantOverrideStockSettings < ActiveRecord::Migration + class VariantOverride < ActiveRecord::Base + belongs_to :variant + belongs_to :hub, class_name: "Enterprise" + + scope :with_count_on_hand, -> { where("count_on_hand IS NOT NULL") } + scope :without_count_on_hand, -> { where(count_on_hand: nil) } + end + + class Variant < ActiveRecord::Base + self.table_name = "spree_variants" + + belongs_to :product + + def name + namer = OpenFoodNetwork::OptionValueNamer.new(self) + namer.name + end + end + + class Product < ActiveRecord::Base + self.table_name = "spree_products" + + belongs_to :supplier, class_name: "Enterprise" + end + + class Enterprise < ActiveRecord::Base; end + + def up + ensure_reports_path_exists + + CSV.open(csv_path, "w") do |csv| + csv << csv_header_row + + update_use_producer_stock_settings_with_count_on_hand(csv) + update_on_demand_with_count_on_hand(csv) + update_limited_stock_without_count_on_hand(csv) + end + + split_csv_by_distributor + end + + def down + CSV.foreach(csv_path, headers: true) do |row| + VariantOverride.where(id: row["variant_override_id"]) + .update_all(on_demand: row["previous_on_demand"], + count_on_hand: row["previous_count_on_hand"]) + end + end + + private + + def reports_path + Rails.root.join("reports", "SimplifyVariantOverrideStockSettings") + end + + def ensure_reports_path_exists + Dir.mkdir(reports_path) unless File.exist?(reports_path) + end + + def csv_path + reports_path.join("changed_variant_overrides.csv") + end + + def distributor_csv_path(name, id) + reports_path.join("changed_variant_overrides-#{name.parameterize('_')}-#{id}.csv") + end + + # When on_demand is nil but count_on_hand is set, force limited stock. + def update_use_producer_stock_settings_with_count_on_hand(csv) + variant_overrides = VariantOverride.where(on_demand: nil).with_count_on_hand + update_variant_overrides_and_log(csv, variant_overrides) do |variant_override| + variant_override.update_attributes!(on_demand: false) + end + end + + # Clear count_on_hand if forcing on demand. + def update_on_demand_with_count_on_hand(csv) + variant_overrides = VariantOverride.where(on_demand: true).with_count_on_hand + update_variant_overrides_and_log(csv, variant_overrides) do |variant_override| + variant_override.update_attributes!(count_on_hand: nil) + end + end + + # When on_demand is false but count on hand is not specified, set this to use producer stock + # settings. + def update_limited_stock_without_count_on_hand(csv) + variant_overrides = VariantOverride.where(on_demand: false).without_count_on_hand + update_variant_overrides_and_log(csv, variant_overrides) do |variant_override| + variant_override.update_attributes!(on_demand: nil) + end + end + + def update_variant_overrides_and_log(csv, variant_overrides) + variant_overrides.find_each do |variant_override| + csv << variant_override_log_row(variant_override) do + yield variant_override + end + end + end + + def csv_header_row + %w( + variant_override_id + distributor_name distributor_id + producer_name producer_id + product_name product_id + variant_description variant_id + previous_on_demand previous_count_on_hand + updated_on_demand updated_count_on_hand + ) + end + + def variant_override_log_row(variant_override) + variant = variant_override.variant + distributor = variant_override.hub + product = variant.andand.product + supplier = product.andand.supplier + + row = [ + variant_override.id, + distributor.andand.name, distributor.andand.id, + supplier.andand.name, supplier.andand.id, + product.andand.name, product.andand.id, + variant.andand.name, variant.andand.id, + variant_override.on_demand, variant_override.count_on_hand + ] + + yield variant_override + + row + [variant_override.on_demand, variant_override.count_on_hand] + end + + def split_csv_by_distributor + table = CSV.read(csv_path) + distributor_ids = table[1..-1].map { |row| row[2] }.uniq # Don't use the header row. + + distributor_ids.each do |distributor_id| + distributor_data_rows = filter_data_rows_for_distributor(table[1..-1], distributor_id) + distributor_name = distributor_data_rows.first[1] + + CSV.open(distributor_csv_path(distributor_name, distributor_id), "w") do |csv| + csv << table[0] # Header row + distributor_data_rows.each { |row| csv << row } + end + end + end + + def filter_data_rows_for_distributor(data_rows, distributor_id) + data_rows.select { |row| row[2] == distributor_id } + end +end diff --git a/db/schema.rb b/db/schema.rb index 92214c8281..696182139d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20181123012635) do +ActiveRecord::Schema.define(:version => 20181128054803) do create_table "account_invoices", :force => true do |t| t.integer "user_id", :null => false diff --git a/engines/web/spec/spec_helper.rb b/engines/web/spec/spec_helper.rb index 3306063166..9cfd0bc717 100644 --- a/engines/web/spec/spec_helper.rb +++ b/engines/web/spec/spec_helper.rb @@ -1,8 +1,3 @@ -ENV["RAILS_ENV"] = "test" - -require File.expand_path("dummy/config/environment.rb", __dir__) -require "rails/test_help" - -Rails.backtrace_cleaner.remove_silencers! +require "../../spec/spec_helper.rb" Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } diff --git a/lib/tasks/specs.rake b/lib/tasks/specs.rake new file mode 100644 index 0000000000..dddb5ffb54 --- /dev/null +++ b/lib/tasks/specs.rake @@ -0,0 +1,47 @@ +namespace :openfoodnetwork do + namespace :specs do + namespace :engines do + def detect_engine_paths + Pathname("engines/").children.select(&:directory?) + end + + def engine_name_for_engine(engine_path) + engine_path.basename.to_path + end + + def execute_rspec_for_engine(engine_path) + system "cd #{engine_path.expand_path} && DISABLE_KNAPSACK=true bundle exec rspec" + end + + engine_paths = detect_engine_paths + + engine_paths.each do |engine_path| + engine_name = engine_name_for_engine(engine_path) + + namespace engine_name do + desc "Run RSpec tests for engine \"#{engine_name}\"" + task :rspec do + success = execute_rspec_for_engine(engine_path) + abort "Failure when running tests for engine \"#{engine_name}\"" unless success + end + end + end + + namespace :all do + desc "Run RSpec tests for all engines" + task :rspec do + success = true + + engine_paths.each do |engine_path| + success = !!execute_rspec_for_engine(engine_path) && success + end + + abort "Failure encountered when running tests for engines" unless success + end + end + + desc "Alias for openfoodnetwork:specs:engines:all:rspec" + task rspec: "all:rspec" + end + end +end diff --git a/reports/README.md b/reports/README.md new file mode 100644 index 0000000000..84c8566c33 --- /dev/null +++ b/reports/README.md @@ -0,0 +1,4 @@ +## `reports/` Directory + +This directory may be used for reports generated for the OFN instance. For example, a database +migration may save in this directory a spreadsheet of changes it made during execution. diff --git a/spec/controllers/user_registrations_controller_spec.rb b/spec/controllers/user_registrations_controller_spec.rb index 7ed7951c50..22d14b383f 100644 --- a/spec/controllers/user_registrations_controller_spec.rb +++ b/spec/controllers/user_registrations_controller_spec.rb @@ -48,6 +48,16 @@ describe UserRegistrationsController, type: :controller do expect(json).to eq({"email" => "test@test.com"}) expect(controller.spree_current_user).to be_nil end + + it "sets user.locale from cookie on create" do + original_locale_cookie = cookies[:locale] + cookies[:locale] = "pt" + + xhr :post, :create, spree_user: user_params, use_route: :spree + + expect(assigns[:user].locale).to eq("pt") + cookies[:locale] = original_locale_cookie + end end context "when registration fails" do diff --git a/spec/factories.rb b/spec/factories.rb index 6c58fc8d81..58273f7cb5 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -189,9 +189,20 @@ FactoryBot.define do factory :variant_override, :class => VariantOverride do price 77.77 + on_demand false count_on_hand 11111 default_stock 2000 resettable false + + trait :on_demand do + on_demand true + count_on_hand nil + end + + trait :use_producer_stock_settings do + on_demand nil + count_on_hand nil + end end factory :inventory_item, :class => InventoryItem do diff --git a/spec/features/admin/variant_overrides_spec.rb b/spec/features/admin/variant_overrides_spec.rb index 60130ddc6d..f2b0bb916b 100644 --- a/spec/features/admin/variant_overrides_spec.rb +++ b/spec/features/admin/variant_overrides_spec.rb @@ -140,8 +140,8 @@ feature %q{ fill_in "variant-overrides-#{variant.id}-sku", with: 'NEWSKU' fill_in "variant-overrides-#{variant.id}-price", with: '777.77' + select_on_demand variant, :no fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '123' - check "variant-overrides-#{variant.id}-on_demand" page.should have_content "Changes to one override remain unsaved." expect do @@ -154,14 +154,15 @@ feature %q{ vo.hub_id.should == hub.id vo.sku.should == "NEWSKU" vo.price.should == 777.77 + expect(vo.on_demand).to eq(false) vo.count_on_hand.should == 123 - vo.on_demand.should == true end describe "creating and then updating the new override" do it "updates the same override instead of creating a duplicate" do # When I create a new override fill_in "variant-overrides-#{variant.id}-price", with: '777.77' + select_on_demand variant, :no fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '123' page.should have_content "Changes to one override remain unsaved." @@ -186,6 +187,7 @@ feature %q{ vo.variant_id.should == variant.id vo.hub_id.should == hub.id vo.price.should == 111.11 + expect(vo.on_demand).to eq(false) vo.count_on_hand.should == 111 end end @@ -218,7 +220,7 @@ feature %q{ end context "with overrides" do - let!(:vo) { create(:variant_override, variant: variant, hub: hub, price: 77.77, count_on_hand: 11111, default_stock: 1000, resettable: true, tag_list: ["tag1","tag2","tag3"]) } + let!(:vo) { create(:variant_override, :on_demand, variant: variant, hub: hub, price: 77.77, default_stock: 1000, resettable: true, tag_list: ["tag1","tag2","tag3"]) } let!(:vo_no_auth) { create(:variant_override, variant: variant, hub: hub2, price: 1, count_on_hand: 2) } let!(:product2) { create(:simple_product, supplier: producer, variant_unit: 'weight', variant_unit_scale: 1) } let!(:variant2) { create(:variant, product: product2, unit_value: 8, price: 1.00, on_hand: 12) } @@ -235,11 +237,15 @@ feature %q{ it "product values are affected by overrides" do page.should have_input "variant-overrides-#{variant.id}-price", with: '77.77', placeholder: '1.23' - page.should have_input "variant-overrides-#{variant.id}-count_on_hand", with: '11111', placeholder: '12' + expect(page).to have_input "variant-overrides-#{variant.id}-count_on_hand", with: "", placeholder: I18n.t("js.variants.on_demand.yes") + expect(page).to have_select "variant-overrides-#{variant.id}-on_demand", selected: I18n.t("js.variant_overrides.on_demand.yes") + + expect(page).to have_input "variant-overrides-#{variant2.id}-count_on_hand", with: "40", placeholder: "" end it "updates existing overrides" do fill_in "variant-overrides-#{variant.id}-price", with: '22.22' + select_on_demand variant, :no fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '8888' page.should have_content "Changes to one override remain unsaved." @@ -252,13 +258,36 @@ feature %q{ vo.variant_id.should == variant.id vo.hub_id.should == hub.id vo.price.should == 22.22 + expect(vo.on_demand).to eq(false) vo.count_on_hand.should == 8888 end + it "updates on_demand settings" do + select_on_demand variant, :no + click_button I18n.t("save_changes") + expect(page).to have_content I18n.t("js.changes_saved") + + vo.reload + expect(vo.on_demand).to eq(false) + + select_on_demand variant, :yes + click_button I18n.t("save_changes") + expect(page).to have_content I18n.t("js.changes_saved") + + vo.reload + expect(vo.on_demand).to eq(true) + + select_on_demand variant, :use_producer_settings + click_button I18n.t("save_changes") + expect(page).to have_content I18n.t("js.changes_saved") + + vo.reload + expect(vo.on_demand).to be_nil + end + # Any new fields added to the VO model need to be added to this test it "deletes overrides when values are cleared" do first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "On Demand").click first("div#columns-dropdown div.menu div.menu_item", text: "Enable Stock Reset?").click first("div#columns-dropdown div.menu div.menu_item", text: "Tags").click first("div#columns-dropdown", :text => "COLUMNS").click @@ -272,6 +301,7 @@ feature %q{ # Clearing values manually fill_in "variant-overrides-#{variant.id}-price", with: '' fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '' + select_on_demand variant, :use_producer_settings fill_in "variant-overrides-#{variant.id}-default_stock", with: '' within "tr#v_#{variant.id}" do vo.tag_list.each do |tag| @@ -297,7 +327,7 @@ feature %q{ first("div#bulk-actions-dropdown div.menu div.menu_item", text: "Reset Stock Levels To Defaults").click page.should have_content 'Stocks reset to defaults.' vo.reload - page.should have_input "variant-overrides-#{variant.id}-count_on_hand", with: '1000', placeholder: '12' + expect(page).to have_input "variant-overrides-#{variant.id}-count_on_hand", with: "1000", placeholder: "" vo.count_on_hand.should == 1000 end @@ -305,7 +335,7 @@ feature %q{ first("div#bulk-actions-dropdown").click first("div#bulk-actions-dropdown div.menu div.menu_item", text: "Reset Stock Levels To Defaults").click vo_no_reset.reload - page.should have_input "variant-overrides-#{variant2.id}-count_on_hand", with: '40', placeholder: '12' + expect(page).to have_input "variant-overrides-#{variant2.id}-count_on_hand", with: "40", placeholder: "" vo_no_reset.count_on_hand.should == 40 end @@ -315,9 +345,52 @@ feature %q{ first("div#bulk-actions-dropdown div.menu div.menu_item", text: "Reset Stock Levels To Defaults").click page.should have_content "Save changes first" end + + describe "ensuring that on demand and count on hand settings are compatible" do + it "clears count on hand when not limited stock" do + # It clears count_on_hand when selecting true on_demand. + select_on_demand variant, :no + fill_in "variant-overrides-#{variant.id}-count_on_hand", with: "200" + select_on_demand variant, :yes + expect(page).to have_input "variant-overrides-#{variant.id}-count_on_hand", with: "" + + # It clears count_on_hand when selecting nil on_demand. + select_on_demand variant, :no + fill_in "variant-overrides-#{variant.id}-count_on_hand", with: "200" + select_on_demand variant, :use_producer_settings + expect(page).to have_input "variant-overrides-#{variant.id}-count_on_hand", with: "" + + # It saves the changes. + click_button I18n.t("save_changes") + expect(page).to have_content I18n.t("js.changes_saved") + vo.reload + expect(vo.count_on_hand).to be_nil + expect(vo.on_demand).to be_nil + end + + it "provides explanation when attempting to save variant override with incompatible stock settings" do + # Successfully change stock settings. + select_on_demand variant, :no + fill_in "variant-overrides-#{variant.id}-count_on_hand", with: "1111" + click_button I18n.t("save_changes") + expect(page).to have_content I18n.t("js.changes_saved") + + # Make stock settings incompatible. + select_on_demand variant, :no + fill_in "variant-overrides-#{variant.id}-count_on_hand", with: "" + + # It does not save the changes. + click_button I18n.t("save_changes") + expect(page).to have_content I18n.t("activerecord.errors.models.variant_override.count_on_hand.limited_stock_but_no_count_on_hand") + expect(page).to have_no_content I18n.t("js.changes_saved") + + vo.reload + expect(vo.count_on_hand).to eq(1111) + expect(vo.on_demand).to eq(false) + end + end end end - end describe "when manually placing an order" do @@ -382,4 +455,9 @@ feature %q{ end end end + + def select_on_demand(variant, value_sym) + option_label = I18n.t(value_sym, scope: "js.variant_overrides.on_demand") + select option_label, from: "variant-overrides-#{variant.id}-on_demand" + end end diff --git a/spec/features/consumer/shopping/variant_overrides_spec.rb b/spec/features/consumer/shopping/variant_overrides_spec.rb index 0e2404252a..1349335132 100644 --- a/spec/features/consumer/shopping/variant_overrides_spec.rb +++ b/spec/features/consumer/shopping/variant_overrides_spec.rb @@ -22,7 +22,7 @@ feature "shopping with variant overrides defined", js: true, retry: 3 do let(:product1_variant3) { create(:variant, product: product1, price: 44.44, unit_value: 4) } let(:product3_variant1) { create(:variant, product: product3, price: 55.55, unit_value: 5, on_demand: true) } let(:product3_variant2) { create(:variant, product: product3, price: 66.66, unit_value: 6, on_demand: true) } - let!(:product1_variant1_override) { create(:variant_override, hub: hub, variant: product1_variant1, price: 55.55, count_on_hand: nil, default_stock: nil, resettable: false) } + let!(:product1_variant1_override) { create(:variant_override, :use_producer_stock_settings, hub: hub, variant: product1_variant1, price: 55.55, count_on_hand: nil, default_stock: nil, resettable: false) } let!(:product1_variant2_override) { create(:variant_override, hub: hub, variant: product1_variant2, count_on_hand: 0, default_stock: nil, resettable: false) } let!(:product2_variant1_override) { create(:variant_override, hub: hub, variant: product2_variant1, count_on_hand: 0, default_stock: nil, resettable: false) } let!(:product1_variant3_override) { create(:variant_override, hub: hub, variant: product1_variant3, count_on_hand: 3, default_stock: nil, resettable: false) } diff --git a/spec/helpers/i18n_helper_spec.rb b/spec/helpers/i18n_helper_spec.rb index 62c58e2345..fba8ab882b 100644 --- a/spec/helpers/i18n_helper_spec.rb +++ b/spec/helpers/i18n_helper_spec.rb @@ -24,13 +24,13 @@ describe I18nHelper, type: :helper do end it "sets the chosen locale" do - allow(helper).to receive(:params) { {locale: "es"} } + allow(helper).to receive(:params) { { locale: "es" } } helper.set_locale expect(I18n.locale).to eq :es end it "remembers the chosen locale" do - allow(helper).to receive(:params) { {locale: "es"} } + allow(helper).to receive(:params) { { locale: "es" } } helper.set_locale allow(helper).to receive(:params) { {} } @@ -39,16 +39,16 @@ describe I18nHelper, type: :helper do end it "ignores unavailable locales" do - allow(helper).to receive(:params) { {locale: "xx"} } + allow(helper).to receive(:params) { { locale: "xx" } } helper.set_locale expect(I18n.locale).to eq :en end it "remembers the last chosen locale" do - allow(helper).to receive(:params) { {locale: "en"} } + allow(helper).to receive(:params) { { locale: "en" } } helper.set_locale - allow(helper).to receive(:params) { {locale: "es"} } + allow(helper).to receive(:params) { { locale: "es" } } helper.set_locale allow(helper).to receive(:params) { {} } @@ -57,7 +57,7 @@ describe I18nHelper, type: :helper do end it "remembers the chosen locale after logging in" do - allow(helper).to receive(:params) { {locale: "es"} } + allow(helper).to receive(:params) { { locale: "es" } } helper.set_locale # log in @@ -68,7 +68,7 @@ describe I18nHelper, type: :helper do end it "forgets the chosen locale without cookies" do - allow(helper).to receive(:params) { {locale: "es"} } + allow(helper).to receive(:params) { { locale: "es" } } helper.set_locale # clean up cookies @@ -91,14 +91,14 @@ describe I18nHelper, type: :helper do end it "sets the chosen locale" do - allow(helper).to receive(:params) { {locale: "es"} } + allow(helper).to receive(:params) { { locale: "es" } } helper.set_locale expect(I18n.locale).to eq :es expect(user.locale).to eq "es" end it "remembers the chosen locale" do - allow(helper).to receive(:params) { {locale: "es"} } + allow(helper).to receive(:params) { { locale: "es" } } helper.set_locale allow(helper).to receive(:params) { {} } @@ -107,10 +107,10 @@ describe I18nHelper, type: :helper do end it "remembers the last chosen locale" do - allow(helper).to receive(:params) { {locale: "en"} } + allow(helper).to receive(:params) { { locale: "en" } } helper.set_locale - allow(helper).to receive(:params) { {locale: "es"} } + allow(helper).to receive(:params) { { locale: "es" } } helper.set_locale allow(helper).to receive(:params) { {} } @@ -119,7 +119,7 @@ describe I18nHelper, type: :helper do end it "remembers the chosen locale after logging out" do - allow(helper).to receive(:params) { {locale: "es"} } + allow(helper).to receive(:params) { { locale: "es" } } helper.set_locale # log out @@ -130,7 +130,7 @@ describe I18nHelper, type: :helper do end it "remembers the chosen locale on another computer" do - allow(helper).to receive(:params) { {locale: "es"} } + allow(helper).to receive(:params) { { locale: "es" } } helper.set_locale expect(cookies[:locale]).to eq "es" @@ -142,4 +142,36 @@ describe I18nHelper, type: :helper do expect(I18n.locale).to eq :es end end + + context "#valid_locale" do + around do |example| + original_default_locale = I18n.default_locale + original_available_locales = Rails.application.config.i18n.available_locales + I18n.default_locale = "es" + Rails.application.config.i18n.available_locales = ["es", "pt"] + example.run + I18n.default_locale = original_default_locale + Rails.application.config.i18n.available_locales = original_available_locales + end + + let(:user) { build(:user) } + + it "returns default locale if given user is nil" do + expect(helper.valid_locale(nil)).to eq I18n.default_locale + end + + it "returns default locale if locale of given user is nil" do + expect(helper.valid_locale(user)).to eq I18n.default_locale + end + + it "returns default locale if locale of given user is not available" do + user.locale = "cn" + expect(helper.valid_locale(user)).to eq I18n.default_locale + end + + it "returns the locale of the given user if available" do + user.locale = "pt" + expect(helper.valid_locale(user)).to eq "pt" + end + end end diff --git a/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee b/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee index 450041a417..3ff397eb18 100644 --- a/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee @@ -87,3 +87,115 @@ describe "VariantOverridesCtrl", -> $httpBackend.flush() expect(VariantOverrides.updateData).toHaveBeenCalledWith variant_overrides_mock expect(StatusMessage.display).toHaveBeenCalledWith 'success', 'Stocks reset to defaults.' + + describe "suggesting count_on_hand when on_demand is changed", -> + variant = null + + beforeEach -> + scope.variantOverrides = {123: {}} + + describe "when variant is on demand", -> + beforeEach -> + # Ideally, count_on_hand is blank when the variant is on demand. However, this rule is not + # enforced. + variant = {id: 2, on_demand: true, count_on_hand: 20, on_hand: "On demand"} + + it "clears count_on_hand when variant override uses producer stock settings", -> + scope.variantOverrides[123][2] = {on_demand: null, count_on_hand: 1} + scope.updateCountOnHand(variant, 123) + + expect(scope.variantOverrides[123][2].count_on_hand).toBeNull() + dirtyVariantOverride = DirtyVariantOverrides.dirtyVariantOverrides[123][2] + expect(dirtyVariantOverride.count_on_hand).toBeNull() + + it "clears count_on_hand when variant override forces on demand", -> + scope.variantOverrides[123][2] = {on_demand: true, count_on_hand: 1} + scope.updateCountOnHand(variant, 123) + + expect(scope.variantOverrides[123][2].count_on_hand).toBeNull() + dirtyVariantOverride = DirtyVariantOverrides.dirtyVariantOverrides[123][2] + expect(dirtyVariantOverride.count_on_hand).toBeNull() + + it "clears count_on_hand when variant override forces limited stock", -> + scope.variantOverrides[123][2] = {on_demand: false, count_on_hand: 1} + scope.updateCountOnHand(variant, 123) + + expect(scope.variantOverrides[123][2].count_on_hand).toBeNull() + dirtyVariantOverride = DirtyVariantOverrides.dirtyVariantOverrides[123][2] + expect(dirtyVariantOverride.count_on_hand).toBeNull() + + describe "when variant has limited stock", -> + beforeEach -> + variant = {id: 2, on_demand: false, count_on_hand: 20, on_hand: 20} + + it "clears count_on_hand when variant override uses producer stock settings", -> + scope.variantOverrides[123][2] = {on_demand: null, count_on_hand: 1} + scope.updateCountOnHand(variant, 123) + + expect(scope.variantOverrides[123][2].count_on_hand).toBeNull() + dirtyVariantOverride = DirtyVariantOverrides.dirtyVariantOverrides[123][2] + expect(dirtyVariantOverride.count_on_hand).toBeNull() + + it "clears count_on_hand when variant override forces on demand", -> + scope.variantOverrides[123][2] = {on_demand: true, count_on_hand: 1} + scope.updateCountOnHand(variant, 123) + + expect(scope.variantOverrides[123][2].count_on_hand).toBeNull() + dirtyVariantOverride = DirtyVariantOverrides.dirtyVariantOverrides[123][2] + expect(dirtyVariantOverride.count_on_hand).toBeNull() + + it "sets to producer count_on_hand when variant override forces limited stock", -> + scope.variantOverrides[123][2] = {on_demand: false, count_on_hand: 1} + scope.updateCountOnHand(variant, 123) + + expect(scope.variantOverrides[123][2].count_on_hand).toBe(20) + dirtyVariantOverride = DirtyVariantOverrides.dirtyVariantOverrides[123][2] + expect(dirtyVariantOverride.count_on_hand).toBe(20) + + describe "count on hand placeholder", -> + beforeEach -> + scope.variantOverrides = {123: {}} + + describe "when variant is on demand", -> + variant = null + + beforeEach -> + # Ideally, count_on_hand is blank when the variant is on demand. However, this rule is not + # enforced. + variant = {id: 2, on_demand: true, count_on_hand: 20, on_hand: t("on_demand")} + + it "is 'On demand' when variant override uses producer stock settings", -> + scope.variantOverrides[123][2] = {on_demand: null, count_on_hand: 1} + placeholder = scope.countOnHandPlaceholder(variant, 123) + expect(placeholder).toBe(t("on_demand")) + + it "is 'On demand' when variant override is on demand", -> + scope.variantOverrides[123][2] = {on_demand: true, count_on_hand: 1} + placeholder = scope.countOnHandPlaceholder(variant, 123) + expect(placeholder).toBe(t("js.variants.on_demand.yes")) + + it "is blank when variant override is limited stock", -> + scope.variantOverrides[123][2] = {on_demand: false, count_on_hand: 1} + placeholder = scope.countOnHandPlaceholder(variant, 123) + expect(placeholder).toBe('') + + describe "when variant is limited stock", -> + variant = null + + beforeEach -> + variant = {id: 2, on_demand: false, count_on_hand: 20, on_hand: 20} + + it "is variant count on hand when variant override uses producer stock settings", -> + scope.variantOverrides[123][2] = {on_demand: null, count_on_hand: 1} + placeholder = scope.countOnHandPlaceholder(variant, 123) + expect(placeholder).toBe(20) + + it "is 'On demand' when variant override is on demand", -> + scope.variantOverrides[123][2] = {on_demand: true, count_on_hand: 1} + placeholder = scope.countOnHandPlaceholder(variant, 123) + expect(placeholder).toBe(t("js.variants.on_demand.yes")) + + it "is blank when variant override is limited stock", -> + scope.variantOverrides[123][2] = {on_demand: false, count_on_hand: 1} + placeholder = scope.countOnHandPlaceholder(variant, 123) + expect(placeholder).toBe('') diff --git a/spec/lib/open_food_network/scope_variant_to_hub_spec.rb b/spec/lib/open_food_network/scope_variant_to_hub_spec.rb index ac79d943ac..b2cdf48d04 100644 --- a/spec/lib/open_food_network/scope_variant_to_hub_spec.rb +++ b/spec/lib/open_food_network/scope_variant_to_hub_spec.rb @@ -5,7 +5,7 @@ module OpenFoodNetwork let(:hub) { create(:distributor_enterprise) } let(:v) { create(:variant, price: 11.11, on_hand: 1, on_demand: true, sku: "VARIANTSKU") } let(:vo) { create(:variant_override, hub: hub, variant: v, price: 22.22, count_on_hand: 2, on_demand: false, sku: "VOSKU") } - let(:vo_price_only) { create(:variant_override, hub: hub, variant: v, price: 22.22, count_on_hand: nil) } + let(:vo_price_only) { create(:variant_override, :use_producer_stock_settings, hub: hub, variant: v, price: 22.22) } let(:scoper) { ScopeVariantToHub.new(hub) } describe "overriding price" do diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 8082d0f6a6..9b4790e5ad 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -17,9 +17,32 @@ describe Spree::UserMailer do setup_email end - it "sends an email when given a user" do - Spree::UserMailer.signup_confirmation(user).deliver - ActionMailer::Base.deliveries.count.should == 1 + describe '#signup_confirmation' do + it "sends email when given a user" do + Spree::UserMailer.signup_confirmation(user).deliver + expect(ActionMailer::Base.deliveries.count).to eq(1) + end + + describe "user locale" do + around do |example| + original_default_locale = I18n.default_locale + I18n.default_locale = 'pt' + example.run + I18n.default_locale = original_default_locale + end + + it "sends email in user locale when user locale is defined" do + user.locale = 'es' + Spree::UserMailer.signup_confirmation(user).deliver + expect(ActionMailer::Base.deliveries.first.body).to include "Gracias por unirte" + end + + it "sends email in default locale when user locale is not available" do + user.locale = 'cn' + Spree::UserMailer.signup_confirmation(user).deliver + expect(ActionMailer::Base.deliveries.first.body).to include "Obrigada por juntar-se" + end + end end # adapted from https://github.com/spree/spree_auth_devise/blob/70737af/spec/mailers/user_mailer_spec.rb diff --git a/spec/models/variant_override_spec.rb b/spec/models/variant_override_spec.rb index 1add54698e..2c91fbddea 100644 --- a/spec/models/variant_override_spec.rb +++ b/spec/models/variant_override_spec.rb @@ -35,6 +35,82 @@ describe VariantOverride do end end + describe "validation" do + describe "ensuring that on_demand and count_on_hand are compatible" do + let(:variant_override) { build(:variant_override, hub: hub, variant: variant, + on_demand: on_demand, count_on_hand: count_on_hand) } + + context "when using producer stock settings" do + let(:on_demand) { nil } + + context "when count_on_hand is blank" do + let(:count_on_hand) { nil } + + it "is valid" do + expect(variant_override.save).to be_truthy + end + end + + context "when count_on_hand is set" do + let(:count_on_hand) { 1 } + + it "is invalid" do + expect(variant_override.save).to be_falsey + error_message = I18n.t("using_producer_stock_settings_but_count_on_hand_set", + scope: [i18n_scope_for_error, "count_on_hand"]) + expect(variant_override.errors[:count_on_hand]).to eq([error_message]) + end + end + end + + context "when on demand" do + let(:on_demand) { true } + + context "when count_on_hand is blank" do + let(:count_on_hand) { nil } + + it "is valid" do + expect(variant_override.save).to be_truthy + end + end + + context "when count_on_hand is set" do + let(:count_on_hand) { 1 } + + it "is invalid" do + expect(variant_override.save).to be_falsey + error_message = I18n.t("on_demand_but_count_on_hand_set", + scope: [i18n_scope_for_error, "count_on_hand"]) + expect(variant_override.errors[:count_on_hand]).to eq([error_message]) + end + end + end + + context "when limited stock" do + let(:on_demand) { false } + + context "when count_on_hand is blank" do + let(:count_on_hand) { nil } + + it "is invalid" do + expect(variant_override.save).to be_falsey + error_message = I18n.t("limited_stock_but_no_count_on_hand", + scope: [i18n_scope_for_error, "count_on_hand"]) + expect(variant_override.errors[:count_on_hand]).to eq([error_message]) + end + end + + context "when count_on_hand is set" do + let(:count_on_hand) { 1 } + + it "is valid" do + expect(variant_override.save).to be_truthy + end + end + end + end + end + describe "callbacks" do let!(:vo) { create(:variant_override, hub: hub, variant: variant) } @@ -119,17 +195,42 @@ describe VariantOverride do end describe "resetting stock levels" do - it "resets the on hand level to the value in the default_stock field" do - vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock: 20, resettable: true) - vo.reset_stock! - expect(vo.reload.count_on_hand).to eq(20) + describe "forcing the on hand level to the value in the default_stock field" do + it "succeeds for variant override that forces limited stock" do + vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock: 20, resettable: true) + vo.reset_stock! + + vo.reload + expect(vo.on_demand).to eq(false) + expect(vo.count_on_hand).to eq(20) + end + + it "succeeds for variant override that forces unlimited stock" do + vo = create(:variant_override, :on_demand, variant: variant, hub: hub, default_stock: 20, resettable: true) + vo.reset_stock! + + vo.reload + expect(vo.on_demand).to eq(false) + expect(vo.count_on_hand).to eq(20) + end + + it "succeeds for variant override that uses producer stock settings" do + vo = create(:variant_override, :use_producer_stock_settings, variant: variant, hub: hub, default_stock: 20, resettable: true) + vo.reset_stock! + + vo.reload + expect(vo.on_demand).to eq(false) + expect(vo.count_on_hand).to eq(20) + end end + it "silently logs an error if the variant override doesn't have a default stock level" do vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock: nil, resettable: true) expect(Bugsnag).to receive(:notify) vo.reset_stock! expect(vo.reload.count_on_hand).to eq(12) end + it "doesn't reset the level if the behaviour is disabled" do vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock: 10, resettable: false) vo.reset_stock! @@ -140,4 +241,8 @@ describe VariantOverride do context "extends LocalizedNumber" do it_behaves_like "a model using the LocalizedNumber module", [:price] end + + def i18n_scope_for_error + "activerecord.errors.models.variant_override" + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b0ae2a685c..211b995ac2 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -6,9 +6,13 @@ require 'rubygems' # Require pry when we're not inside Travis-CI require 'pry' unless ENV['CI'] -require 'knapsack' -Knapsack.tracker.config({enable_time_offset_warning: false}) unless ENV['CI'] -Knapsack::Adapters::RSpecAdapter.bind +# This spec_helper.rb is being used by the custom engines in engines/. The engines are not set up to +# use Knapsack, and this provides the option to disable it when running the tests in CI services. +unless ENV['DISABLE_KNAPSACK'] + require 'knapsack' + Knapsack.tracker.config({enable_time_offset_warning: false}) unless ENV['CI'] + Knapsack::Adapters::RSpecAdapter.bind +end ENV["RAILS_ENV"] ||= 'test' require_relative "../config/environment"