diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index 94fdd0bd31..446be30aec 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -54,7 +54,6 @@ Layout/LineLength: - app/models/concerns/variant_stock.rb - app/models/content_configuration.rb - app/models/customer.rb - - app/models/enterprise_fee.rb - app/models/enterprise_group.rb - app/models/enterprise_role.rb - app/models/inventory_item.rb @@ -109,7 +108,6 @@ Layout/LineLength: - lib/open_food_network/scope_variants_for_search.rb - lib/open_food_network/variant_and_line_item_naming.rb - lib/open_food_network/xero_invoices_report.rb - - lib/spree/core/controller_helpers/respond_with_decorator.rb - lib/spree/localized_number.rb - lib/spree/product_filters.rb - lib/tasks/data.rake @@ -391,6 +389,7 @@ Metrics/AbcSize: - app/models/spree/order_decorator.rb - app/models/spree/payment_decorator.rb - app/models/spree/product_decorator.rb + - app/models/spree/shipment.rb - app/models/spree/taxon_decorator.rb - app/models/spree/tax_rate_decorator.rb - app/serializers/api/admin/enterprise_serializer.rb @@ -400,6 +399,9 @@ Metrics/AbcSize: - app/services/create_order_cycle.rb - app/services/order_cycle_form.rb - app/services/order_syncer.rb + - engines/order_management/app/services/order_management/stock/estimator.rb + - engines/order_management/app/services/order_management/stock/package.rb + - engines/order_management/app/services/order_management/stock/packer.rb - engines/order_management/app/services/order_management/subscriptions/validator.rb - lib/active_merchant/billing/gateways/stripe_decorator.rb - lib/active_merchant/billing/gateways/stripe_payment_intents.rb @@ -424,7 +426,9 @@ Metrics/AbcSize: - lib/open_food_network/variant_and_line_item_naming.rb - lib/open_food_network/xero_invoices_report.rb - lib/spree/api/controller_setup.rb - - lib/spree/core/controller_helpers/respond_with_decorator.rb + - lib/spree/core/controller_helpers/order.rb + - lib/spree/core/controller_helpers/respond_with.rb + - lib/spree/core/controller_helpers/ssl.rb - lib/spree/localized_number.rb - lib/stripe/account_connector.rb - lib/tasks/enterprises.rake @@ -457,6 +461,9 @@ Metrics/BlockLength: "scenario" ] Exclude: + - app/models/spree/shipment.rb + - lib/spree/core/controller_helpers/common.rb + - lib/spree/core/controller_helpers/ssl.rb - lib/tasks/data.rake - spec/controllers/spree/admin/invoices_controller_spec.rb - spec/factories/enterprise_factory.rb @@ -496,12 +503,14 @@ Metrics/CyclomaticComplexity: - app/models/spree/product_decorator.rb - app/models/variant_override_set.rb - app/services/cart_service.rb + - engines/order_management/app/services/order_management/stock/estimator.rb - lib/active_merchant/billing/gateways/stripe_payment_intents.rb - lib/discourse/single_sign_on.rb - lib/open_food_network/bulk_coop_report.rb - lib/open_food_network/enterprise_issue_validator.rb - - lib/spree/core/controller_helpers/order_decorator.rb - - lib/spree/core/controller_helpers/respond_with_decorator.rb + - lib/spree/core/controller_helpers/order.rb + - lib/spree/core/controller_helpers/respond_with.rb + - lib/spree/core/controller_helpers/ssl.rb - lib/spree/localized_number.rb - spec/models/product_importer_spec.rb @@ -520,12 +529,14 @@ Metrics/PerceivedComplexity: - app/models/spree/ability_decorator.rb - app/models/spree/order_decorator.rb - app/models/spree/product_decorator.rb + - engines/order_management/app/services/order_management/stock/estimator.rb - lib/active_merchant/billing/gateways/stripe_payment_intents.rb - lib/discourse/single_sign_on.rb - lib/open_food_network/bulk_coop_report.rb - lib/open_food_network/enterprise_issue_validator.rb - - lib/spree/core/controller_helpers/order_decorator.rb - - lib/spree/core/controller_helpers/respond_with_decorator.rb + - lib/spree/core/controller_helpers/order.rb + - lib/spree/core/controller_helpers/respond_with.rb + - lib/spree/core/controller_helpers/ssl.rb - lib/spree/localized_number.rb - spec/models/product_importer_spec.rb @@ -585,11 +596,14 @@ Metrics/MethodLength: - app/models/spree/payment_decorator.rb - app/models/spree/payment_method_decorator.rb - app/models/spree/product_decorator.rb + - app/models/spree/shipment.rb - app/serializers/api/admin/order_cycle_serializer.rb - app/serializers/api/cached_enterprise_serializer.rb - app/services/order_cycle_form.rb - app/services/permitted_attributes/checkout.rb - engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb + - engines/order_management/app/services/order_management/stock/estimator.rb + - engines/order_management/app/services/order_management/stock/package.rb - lib/active_merchant/billing/gateways/stripe_payment_intents.rb - lib/discourse/single_sign_on.rb - lib/open_food_network/bulk_coop_report.rb @@ -617,7 +631,10 @@ Metrics/MethodLength: - lib/open_food_network/users_and_enterprises_report.rb - lib/open_food_network/xero_invoices_report.rb - lib/spree/api/controller_setup.rb - - lib/spree/core/controller_helpers/respond_with_decorator.rb + - lib/spree/core/controller_helpers/auth.rb + - lib/spree/core/controller_helpers/order.rb + - lib/spree/core/controller_helpers/respond_with.rb + - lib/spree/core/controller_helpers/ssl.rb - lib/spree/localized_number.rb - lib/stripe/profile_storer.rb - lib/tasks/data/truncate_data.rb @@ -652,6 +669,7 @@ Metrics/ClassLength: - app/models/product_import/entry_validator.rb - app/models/product_import/product_importer.rb - app/models/spree/ability_decorator.rb + - app/models/spree/shipment.rb - app/models/spree/user.rb - app/serializers/api/cached_enterprise_serializer.rb - app/serializers/api/enterprise_shopfront_serializer.rb @@ -676,6 +694,7 @@ Metrics/ModuleLength: - app/helpers/injection_helper.rb - app/helpers/spree/admin/base_helper.rb - app/helpers/spree/admin/navigation_helper.rb + - engines/order_management/spec/services/order_management/stock/package_spec.rb - engines/order_management/spec/services/order_management/subscriptions/estimator_spec.rb - engines/order_management/spec/services/order_management/subscriptions/form_spec.rb - engines/order_management/spec/services/order_management/subscriptions/proxy_order_syncer_spec.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 753aaee48e..a0c6a0b30c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -363,6 +363,13 @@ Rails/UniqueValidationWithoutIndex: - 'app/models/customer.rb' - 'app/models/exchange.rb' +# Offense count: 2 +# Configuration parameters: Include. +# Include: app/models/**/*.rb +Rails/UniqueValidationWithoutIndex: + Exclude: + - 'app/models/spree/stock_item.rb' + # Offense count: 1 # Configuration parameters: Environments. # Environments: development, test, production diff --git a/Gemfile b/Gemfile index 2faf6002e5..0c442ca2c6 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ ruby "2.3.7" git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" } gem 'i18n', '~> 0.6.11' -gem 'i18n-js', '~> 3.7.0' +gem 'i18n-js', '~> 3.7.1' gem 'rails', '~> 4.0.13' gem 'rails-i18n', '~> 4.0' gem 'rails_safe_tasks', '~> 1.0' @@ -33,7 +33,7 @@ gem 'httparty', '~> 0.18' # Used to check alerts in spree_core, this is not used gem 'json', '>= 1.7.7' gem 'money', '5.1.1' gem 'paranoia', '~> 2.0' -gem 'ransack', '~> 1.2.3' +gem 'ransack', '~> 1.8.10' gem 'state_machine', '1.2.0' gem 'stringex', '~> 1.5.1' @@ -75,13 +75,12 @@ gem 'truncate_html', '0.9.2' gem 'unicorn' gem 'actionpack-action_caching' -# AMS is pinned to 0.8.4 because 0.9.x is a complete re-write, as is 0.10.x -# Once Rails is updated to 5.x we should bump directly to 0.10.x +# AMS 0.9.x and 0.10.x are very different from 0.8.4 and the upgrade is not straight forward +# AMS is deprecated, we will introduce an alternative at some point gem "active_model_serializers", "0.8.4" gem 'activerecord-session_store' gem 'acts-as-taggable-on', '~> 4.0' gem 'angularjs-file-upload-rails', '~> 2.4.1' -gem 'blockenspiel' gem 'custom_error_message', github: 'jeremydurham/custom-err-msg' gem 'dalli' gem 'diffy' @@ -110,7 +109,7 @@ gem 'test-unit', '~> 3.3' gem 'coffee-rails', '~> 4.2.2' gem 'compass-rails' -gem 'mini_racer', '0.2.14' +gem 'mini_racer', '0.2.15' gem 'uglifier', '>= 1.0.3' @@ -145,6 +144,7 @@ group :test, :development do gem 'letter_opener', '>= 1.4.1' gem 'rspec-rails', ">= 3.5.2" gem 'rspec-retry' + gem 'rswag' gem 'selenium-webdriver' gem 'shoulda-matchers' gem 'timecop' diff --git a/Gemfile.lock b/Gemfile.lock index 6a48563de8..422ecd984c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -150,8 +150,7 @@ GEM bcrypt (3.1.13) bcrypt-ruby (3.1.5) bcrypt (>= 3.1.3) - blockenspiel (0.5.0) - bugsnag (6.13.1) + bugsnag (6.14.0) concurrent-ruby (~> 1.0) builder (3.1.4) byebug (11.0.1) @@ -202,12 +201,12 @@ GEM addressable daemons (1.3.1) dalli (2.7.10) - database_cleaner (1.7.0) + database_cleaner (1.8.5) db2fog (0.9.0) activerecord (>= 3.2.0, < 5.0) fog (~> 1.0) rails (>= 3.2.0, < 5.0) - ddtrace (0.36.0) + ddtrace (0.38.0) msgpack debugger-linecache (1.2.0) delayed_job (4.1.8) @@ -421,7 +420,7 @@ GEM mime-types (~> 3.0) multi_xml (>= 0.5.2) i18n (0.6.11) - i18n-js (3.7.0) + i18n-js (3.7.1) i18n (>= 0.6.6) immigrant (0.3.6) activerecord (>= 3.0) @@ -434,6 +433,8 @@ GEM jquery-ui-rails (4.2.1) railties (>= 3.2.16) json (1.8.6) + json-schema (2.8.1) + addressable (>= 2.4) json_spec (1.1.5) multi_json (~> 1.0) rspec (>= 2.0, < 4.0) @@ -457,7 +458,7 @@ GEM mime-types-data (3.2020.0512) mini_mime (1.0.2) mini_portile2 (2.4.0) - mini_racer (0.2.14) + mini_racer (0.2.15) libv8 (> 7.3) minitest (4.7.5) momentjs-rails (2.20.1) @@ -477,7 +478,7 @@ GEM multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) - oj (3.10.6) + oj (3.10.7) optimist (3.0.0) orm_adapter (0.5.0) paper_trail (5.2.3) @@ -500,8 +501,6 @@ GEM paypal-sdk-merchant (1.106.1) paypal-sdk-core (~> 0.2.3) pg (0.21.0) - polyamorous (1.0.0) - activerecord (>= 3.0) power_assert (1.2.0) pry (0.12.2) coderay (~> 1.1.0) @@ -540,12 +539,11 @@ GEM rainbow (3.0.0) raindrops (0.19.1) rake (13.0.1) - ransack (1.2.3) - actionpack (>= 3.0) - activerecord (>= 3.0) - activesupport (>= 3.0) + ransack (1.8.10) + actionpack (>= 3.0, < 5.2) + activerecord (>= 3.0, < 5.2) + activesupport (>= 3.0, < 5.2) i18n - polyamorous (~> 1.0.0) rb-fsevent (0.10.3) rb-inotify (0.10.1) ffi (~> 1.0) @@ -590,6 +588,19 @@ GEM rspec-retry (0.6.2) rspec-core (> 3.3) rspec-support (3.9.2) + rswag (2.2.0) + rswag-api (= 2.2.0) + rswag-specs (= 2.2.0) + rswag-ui (= 2.2.0) + rswag-api (2.2.0) + railties (>= 3.1, < 6.1) + rswag-specs (2.2.0) + activesupport (>= 3.1, < 6.1) + json-schema (~> 2.2) + railties (>= 3.1, < 6.1) + rswag-ui (2.2.0) + actionpack (>= 3.1, < 6.1) + railties (>= 3.1, < 6.1) rubocop (0.81.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) @@ -679,7 +690,7 @@ GEM hashdiff (>= 0.4.0, < 2.0.0) whenever (1.0.0) chronic (>= 0.6.3) - wicked_pdf (1.4.0) + wicked_pdf (2.1.0) activesupport wkhtmltopdf-binary (0.12.5) xml-simple (1.1.5) @@ -707,7 +718,6 @@ DEPENDENCIES awesome_nested_set (~> 3.0.0.rc.1) awesome_print aws-sdk (= 1.11.1) - blockenspiel bugsnag byebug (~> 11.0.0) cancan (~> 1.6.10) @@ -743,7 +753,7 @@ DEPENDENCIES highline (= 1.6.18) httparty (~> 0.18) i18n (~> 0.6.11) - i18n-js (~> 3.7.0) + i18n-js (~> 3.7.1) immigrant jquery-migrate-rails jquery-rails (= 3.1.5) @@ -754,7 +764,7 @@ DEPENDENCIES kaminari (~> 0.14.1) knapsack letter_opener (>= 1.4.1) - mini_racer (= 0.2.14) + mini_racer (= 0.2.15) momentjs-rails money (= 5.1.1) newrelic_rpm (~> 3.0) @@ -774,12 +784,13 @@ DEPENDENCIES rails (~> 4.0.13) rails-i18n (~> 4.0) rails_safe_tasks (~> 1.0) - ransack (~> 1.2.3) + ransack (~> 1.8.10) redcarpet roadie-rails (~> 1.3.0) roo (~> 2.8.3) rspec-rails (>= 3.5.2) rspec-retry + rswag rubocop rubocop-rails sass diff --git a/public/OFN-v2.eot b/app/assets/fonts/OFN-v2.eot similarity index 100% rename from public/OFN-v2.eot rename to app/assets/fonts/OFN-v2.eot diff --git a/public/OFN-v2.svg b/app/assets/fonts/OFN-v2.svg similarity index 100% rename from public/OFN-v2.svg rename to app/assets/fonts/OFN-v2.svg diff --git a/public/OFN-v2.ttf b/app/assets/fonts/OFN-v2.ttf similarity index 100% rename from public/OFN-v2.ttf rename to app/assets/fonts/OFN-v2.ttf diff --git a/public/OFN-v2.woff b/app/assets/fonts/OFN-v2.woff similarity index 100% rename from public/OFN-v2.woff rename to app/assets/fonts/OFN-v2.woff diff --git a/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee b/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee index 751ddf8866..4afb7c1c22 100644 --- a/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee +++ b/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee @@ -23,10 +23,13 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, $timeout, Reque $scope.fetchResults() $scope.fetchResults = (page=1) -> + startDateWithTime = $scope.appendStringIfNotEmpty($scope['q']['completed_at_gteq'], ' 00:00:00') + endDateWithTime = $scope.appendStringIfNotEmpty($scope['q']['completed_at_lteq'], ' 23:59:59') + $scope.resetSelected() params = { - 'q[completed_at_lt]': $scope['q']['completed_at_lt'], - 'q[completed_at_gt]': $scope['q']['completed_at_gt'], + 'q[completed_at_gteq]': startDateWithTime, + 'q[completed_at_lteq]': endDateWithTime, 'q[state_eq]': $scope['q']['state_eq'], 'q[number_cont]': $scope['q']['number_cont'], 'q[email_cont]': $scope['q']['email_cont'], @@ -43,6 +46,11 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, $timeout, Reque } RequestMonitor.load(Orders.index(params).$promise) + $scope.appendStringIfNotEmpty = (baseString, stringToAppend) -> + return baseString unless baseString + + baseString + stringToAppend + $scope.resetSelected = -> $scope.selected_orders.length = 0 $scope.selected = false diff --git a/app/assets/javascripts/darkswarm/filters/dates.js.coffee b/app/assets/javascripts/darkswarm/filters/dates.js.coffee index 8793ddefd8..cef73413a7 100644 --- a/app/assets/javascripts/darkswarm/filters/dates.js.coffee +++ b/app/assets/javascripts/darkswarm/filters/dates.js.coffee @@ -12,4 +12,4 @@ Darkswarm.filter "sensible_timeframe", (date_in_wordsFilter)-> if moment().add(2, 'days') < moment(date, dateFormat) t 'orders_open' else - t('closing') + date_in_wordsFilter(date) + t('closing') + ' ' + date_in_wordsFilter(date) diff --git a/app/assets/javascripts/darkswarm/services/products.js.coffee b/app/assets/javascripts/darkswarm/services/products.js.coffee index 59bd19efb4..83b19251bb 100644 --- a/app/assets/javascripts/darkswarm/services/products.js.coffee +++ b/app/assets/javascripts/darkswarm/services/products.js.coffee @@ -34,7 +34,7 @@ Darkswarm.factory 'Products', (OrderCycleResource, OrderCycle, Shopfront, curren product.price = Math.min.apply(null, prices) product.hasVariants = product.variants?.length > 0 product.primaryImage = product.images[0]?.small_url if product.images - product.primaryImageOrMissing = product.primaryImage || "/assets/noimage/small.png" + product.primaryImageOrMissing = product.primaryImage || "/noimage/small.png" product.largeImage = product.images[0]?.large_url if product.images dereference: -> diff --git a/app/assets/javascripts/templates/product_modal.html.haml b/app/assets/javascripts/templates/product_modal.html.haml index 9c65bbbad7..95c74c5f72 100644 --- a/app/assets/javascripts/templates/product_modal.html.haml +++ b/app/assets/javascripts/templates/product_modal.html.haml @@ -15,6 +15,6 @@ .columns.small-12.medium-6.large-6.product-img %img{"ng-src" => "{{::product.largeImage}}", "ng-if" => "::product.largeImage"} - %img.placeholder{ src: "/assets/noimage/large.png", "ng-if" => "::!product.largeImage"} + %img.placeholder{ src: "/noimage/large.png", "ng-if" => "::!product.largeImage"} %ng-include{src: "'partials/close.html'"} diff --git a/app/assets/stylesheets/admin/advanced_settings.css.scss b/app/assets/stylesheets/admin/advanced_settings.scss similarity index 100% rename from app/assets/stylesheets/admin/advanced_settings.css.scss rename to app/assets/stylesheets/admin/advanced_settings.scss diff --git a/app/assets/stylesheets/admin/alert.css.scss b/app/assets/stylesheets/admin/alert.scss similarity index 100% rename from app/assets/stylesheets/admin/alert.css.scss rename to app/assets/stylesheets/admin/alert.scss diff --git a/app/assets/stylesheets/admin/animations.css.scss b/app/assets/stylesheets/admin/animations.scss similarity index 100% rename from app/assets/stylesheets/admin/animations.css.scss rename to app/assets/stylesheets/admin/animations.scss diff --git a/app/assets/stylesheets/admin/change_type_form.css.scss b/app/assets/stylesheets/admin/change_type_form.scss similarity index 100% rename from app/assets/stylesheets/admin/change_type_form.css.scss rename to app/assets/stylesheets/admin/change_type_form.scss diff --git a/app/assets/stylesheets/admin/components/alert-box.css.scss b/app/assets/stylesheets/admin/components/alert-box.scss similarity index 100% rename from app/assets/stylesheets/admin/components/alert-box.css.scss rename to app/assets/stylesheets/admin/components/alert-box.scss diff --git a/app/assets/stylesheets/admin/components/alert_row.css.scss b/app/assets/stylesheets/admin/components/alert_row.scss similarity index 100% rename from app/assets/stylesheets/admin/components/alert_row.css.scss rename to app/assets/stylesheets/admin/components/alert_row.scss diff --git a/app/assets/stylesheets/admin/components/dialogs.css.scss b/app/assets/stylesheets/admin/components/dialogs.scss similarity index 100% rename from app/assets/stylesheets/admin/components/dialogs.css.scss rename to app/assets/stylesheets/admin/components/dialogs.scss diff --git a/app/assets/stylesheets/admin/components/input.css.scss b/app/assets/stylesheets/admin/components/input.scss similarity index 100% rename from app/assets/stylesheets/admin/components/input.css.scss rename to app/assets/stylesheets/admin/components/input.scss diff --git a/app/assets/stylesheets/admin/components/ng-cloak.css.scss b/app/assets/stylesheets/admin/components/ng-cloak.scss similarity index 100% rename from app/assets/stylesheets/admin/components/ng-cloak.css.scss rename to app/assets/stylesheets/admin/components/ng-cloak.scss diff --git a/app/assets/stylesheets/admin/components/page_actions.css.scss b/app/assets/stylesheets/admin/components/page_actions.scss similarity index 100% rename from app/assets/stylesheets/admin/components/page_actions.css.scss rename to app/assets/stylesheets/admin/components/page_actions.scss diff --git a/app/assets/stylesheets/admin/components/simple_modal.css.scss b/app/assets/stylesheets/admin/components/simple_modal.scss similarity index 100% rename from app/assets/stylesheets/admin/components/simple_modal.css.scss rename to app/assets/stylesheets/admin/components/simple_modal.scss diff --git a/app/assets/stylesheets/admin/components/stripe_connect_button.css.scss b/app/assets/stylesheets/admin/components/stripe_connect_button.scss similarity index 100% rename from app/assets/stylesheets/admin/components/stripe_connect_button.css.scss rename to app/assets/stylesheets/admin/components/stripe_connect_button.scss diff --git a/app/assets/stylesheets/admin/components/table_loading.css.scss b/app/assets/stylesheets/admin/components/table_loading.scss similarity index 100% rename from app/assets/stylesheets/admin/components/table_loading.css.scss rename to app/assets/stylesheets/admin/components/table_loading.scss diff --git a/app/assets/stylesheets/admin/components/timepicker.css.scss b/app/assets/stylesheets/admin/components/timepicker.scss similarity index 100% rename from app/assets/stylesheets/admin/components/timepicker.css.scss rename to app/assets/stylesheets/admin/components/timepicker.scss diff --git a/app/assets/stylesheets/admin/components/tooltip.css.scss b/app/assets/stylesheets/admin/components/tooltip.scss similarity index 100% rename from app/assets/stylesheets/admin/components/tooltip.css.scss rename to app/assets/stylesheets/admin/components/tooltip.scss diff --git a/app/assets/stylesheets/admin/components/wizard_progress.css.scss b/app/assets/stylesheets/admin/components/wizard_progress.scss similarity index 100% rename from app/assets/stylesheets/admin/components/wizard_progress.css.scss rename to app/assets/stylesheets/admin/components/wizard_progress.scss diff --git a/app/assets/stylesheets/admin/customers.css.scss b/app/assets/stylesheets/admin/customers.scss similarity index 100% rename from app/assets/stylesheets/admin/customers.css.scss rename to app/assets/stylesheets/admin/customers.scss diff --git a/app/assets/stylesheets/admin/dashboard-single-ent.css.scss b/app/assets/stylesheets/admin/dashboard-single-ent.scss similarity index 100% rename from app/assets/stylesheets/admin/dashboard-single-ent.css.scss rename to app/assets/stylesheets/admin/dashboard-single-ent.scss diff --git a/app/assets/stylesheets/admin/dashboard_item.css.scss b/app/assets/stylesheets/admin/dashboard_item.scss similarity index 100% rename from app/assets/stylesheets/admin/dashboard_item.css.scss rename to app/assets/stylesheets/admin/dashboard_item.scss diff --git a/app/assets/stylesheets/admin/datepicker.css.scss b/app/assets/stylesheets/admin/datepicker.scss similarity index 100% rename from app/assets/stylesheets/admin/datepicker.css.scss rename to app/assets/stylesheets/admin/datepicker.scss diff --git a/app/assets/stylesheets/admin/dialog.css.scss b/app/assets/stylesheets/admin/dialog.scss similarity index 100% rename from app/assets/stylesheets/admin/dialog.css.scss rename to app/assets/stylesheets/admin/dialog.scss diff --git a/app/assets/stylesheets/admin/disabled.css.scss b/app/assets/stylesheets/admin/disabled.scss similarity index 100% rename from app/assets/stylesheets/admin/disabled.css.scss rename to app/assets/stylesheets/admin/disabled.scss diff --git a/app/assets/stylesheets/admin/dropdown.css.scss b/app/assets/stylesheets/admin/dropdown.scss similarity index 100% rename from app/assets/stylesheets/admin/dropdown.css.scss rename to app/assets/stylesheets/admin/dropdown.scss diff --git a/app/assets/stylesheets/admin/enterprise_console.css.scss b/app/assets/stylesheets/admin/enterprise_console.scss similarity index 100% rename from app/assets/stylesheets/admin/enterprise_console.css.scss rename to app/assets/stylesheets/admin/enterprise_console.scss diff --git a/app/assets/stylesheets/admin/enterprise_index_panels.css.scss b/app/assets/stylesheets/admin/enterprise_index_panels.scss similarity index 100% rename from app/assets/stylesheets/admin/enterprise_index_panels.css.scss rename to app/assets/stylesheets/admin/enterprise_index_panels.scss diff --git a/app/assets/stylesheets/admin/enterprises.css.scss b/app/assets/stylesheets/admin/enterprises.scss similarity index 100% rename from app/assets/stylesheets/admin/enterprises.css.scss rename to app/assets/stylesheets/admin/enterprises.scss diff --git a/app/assets/stylesheets/admin/filters_and_controls.css.scss b/app/assets/stylesheets/admin/filters_and_controls.scss similarity index 100% rename from app/assets/stylesheets/admin/filters_and_controls.css.scss rename to app/assets/stylesheets/admin/filters_and_controls.scss diff --git a/app/assets/stylesheets/admin/icons.css.scss b/app/assets/stylesheets/admin/icons.scss similarity index 100% rename from app/assets/stylesheets/admin/icons.css.scss rename to app/assets/stylesheets/admin/icons.scss diff --git a/app/assets/stylesheets/admin/index_panel_buttons.css.scss b/app/assets/stylesheets/admin/index_panel_buttons.scss similarity index 100% rename from app/assets/stylesheets/admin/index_panel_buttons.css.scss rename to app/assets/stylesheets/admin/index_panel_buttons.scss diff --git a/app/assets/stylesheets/admin/index_panels.css.scss b/app/assets/stylesheets/admin/index_panels.scss similarity index 100% rename from app/assets/stylesheets/admin/index_panels.css.scss rename to app/assets/stylesheets/admin/index_panels.scss diff --git a/app/assets/stylesheets/admin/modals.css.scss b/app/assets/stylesheets/admin/modals.scss similarity index 100% rename from app/assets/stylesheets/admin/modals.css.scss rename to app/assets/stylesheets/admin/modals.scss diff --git a/app/assets/stylesheets/admin/offsets.css.scss b/app/assets/stylesheets/admin/offsets.scss similarity index 100% rename from app/assets/stylesheets/admin/offsets.css.scss rename to app/assets/stylesheets/admin/offsets.scss diff --git a/app/assets/stylesheets/admin/openfoodnetwork.css.scss b/app/assets/stylesheets/admin/openfoodnetwork.scss similarity index 100% rename from app/assets/stylesheets/admin/openfoodnetwork.css.scss rename to app/assets/stylesheets/admin/openfoodnetwork.scss diff --git a/app/assets/stylesheets/admin/orders.css.scss b/app/assets/stylesheets/admin/orders.scss similarity index 100% rename from app/assets/stylesheets/admin/orders.css.scss rename to app/assets/stylesheets/admin/orders.scss diff --git a/app/assets/stylesheets/admin/pages/subscription_form.css.scss b/app/assets/stylesheets/admin/pages/subscription_form.scss similarity index 100% rename from app/assets/stylesheets/admin/pages/subscription_form.css.scss rename to app/assets/stylesheets/admin/pages/subscription_form.scss diff --git a/app/assets/stylesheets/admin/pages/subscription_line_items.css.scss b/app/assets/stylesheets/admin/pages/subscription_line_items.scss similarity index 100% rename from app/assets/stylesheets/admin/pages/subscription_line_items.css.scss rename to app/assets/stylesheets/admin/pages/subscription_line_items.scss diff --git a/app/assets/stylesheets/admin/pages/subscription_review.css.scss b/app/assets/stylesheets/admin/pages/subscription_review.scss similarity index 100% rename from app/assets/stylesheets/admin/pages/subscription_review.css.scss rename to app/assets/stylesheets/admin/pages/subscription_review.scss diff --git a/app/assets/stylesheets/admin/product_import.css.scss b/app/assets/stylesheets/admin/product_import.scss similarity index 100% rename from app/assets/stylesheets/admin/product_import.css.scss rename to app/assets/stylesheets/admin/product_import.scss diff --git a/app/assets/stylesheets/admin/products.css.scss b/app/assets/stylesheets/admin/products.scss similarity index 100% rename from app/assets/stylesheets/admin/products.css.scss rename to app/assets/stylesheets/admin/products.scss diff --git a/app/assets/stylesheets/admin/relationships.css.scss b/app/assets/stylesheets/admin/relationships.scss similarity index 100% rename from app/assets/stylesheets/admin/relationships.css.scss rename to app/assets/stylesheets/admin/relationships.scss diff --git a/app/assets/stylesheets/admin/reports.css.scss b/app/assets/stylesheets/admin/reports.scss similarity index 100% rename from app/assets/stylesheets/admin/reports.css.scss rename to app/assets/stylesheets/admin/reports.scss diff --git a/app/assets/stylesheets/admin/select2.css.scss b/app/assets/stylesheets/admin/select2.scss similarity index 100% rename from app/assets/stylesheets/admin/select2.css.scss rename to app/assets/stylesheets/admin/select2.scss diff --git a/app/assets/stylesheets/admin/side_menu.css.scss b/app/assets/stylesheets/admin/side_menu.scss similarity index 100% rename from app/assets/stylesheets/admin/side_menu.css.scss rename to app/assets/stylesheets/admin/side_menu.scss diff --git a/app/assets/stylesheets/admin/sidebar-item.css.scss b/app/assets/stylesheets/admin/sidebar-item.scss similarity index 100% rename from app/assets/stylesheets/admin/sidebar-item.css.scss rename to app/assets/stylesheets/admin/sidebar-item.scss diff --git a/app/assets/stylesheets/admin/tables.css.scss b/app/assets/stylesheets/admin/tables.scss similarity index 100% rename from app/assets/stylesheets/admin/tables.css.scss rename to app/assets/stylesheets/admin/tables.scss diff --git a/app/assets/stylesheets/admin/tag_rules.css.scss b/app/assets/stylesheets/admin/tag_rules.scss similarity index 100% rename from app/assets/stylesheets/admin/tag_rules.css.scss rename to app/assets/stylesheets/admin/tag_rules.scss diff --git a/app/assets/stylesheets/admin/typography.css.scss b/app/assets/stylesheets/admin/typography.scss similarity index 100% rename from app/assets/stylesheets/admin/typography.css.scss rename to app/assets/stylesheets/admin/typography.scss diff --git a/app/assets/stylesheets/admin/validation.css.scss b/app/assets/stylesheets/admin/validation.scss similarity index 100% rename from app/assets/stylesheets/admin/validation.css.scss rename to app/assets/stylesheets/admin/validation.scss diff --git a/app/assets/stylesheets/admin/variables.css.scss b/app/assets/stylesheets/admin/variables.scss similarity index 100% rename from app/assets/stylesheets/admin/variables.css.scss rename to app/assets/stylesheets/admin/variables.scss diff --git a/app/assets/stylesheets/admin/variant_overrides.css.scss b/app/assets/stylesheets/admin/variant_overrides.scss similarity index 100% rename from app/assets/stylesheets/admin/variant_overrides.css.scss rename to app/assets/stylesheets/admin/variant_overrides.scss diff --git a/app/assets/stylesheets/admin/welcome.css.scss b/app/assets/stylesheets/admin/welcome.scss similarity index 100% rename from app/assets/stylesheets/admin/welcome.css.scss rename to app/assets/stylesheets/admin/welcome.scss diff --git a/app/assets/stylesheets/darkswarm/_shop-filters.css.scss b/app/assets/stylesheets/darkswarm/_shop-filters.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/_shop-filters.css.scss rename to app/assets/stylesheets/darkswarm/_shop-filters.scss diff --git a/app/assets/stylesheets/darkswarm/_shop-inputs.css.scss b/app/assets/stylesheets/darkswarm/_shop-inputs.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/_shop-inputs.css.scss rename to app/assets/stylesheets/darkswarm/_shop-inputs.scss diff --git a/app/assets/stylesheets/darkswarm/_shop-modals.css.scss b/app/assets/stylesheets/darkswarm/_shop-modals.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/_shop-modals.css.scss rename to app/assets/stylesheets/darkswarm/_shop-modals.scss diff --git a/app/assets/stylesheets/darkswarm/_shop-navigation.css.scss b/app/assets/stylesheets/darkswarm/_shop-navigation.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/_shop-navigation.css.scss rename to app/assets/stylesheets/darkswarm/_shop-navigation.scss diff --git a/app/assets/stylesheets/darkswarm/_shop-popovers.css.scss b/app/assets/stylesheets/darkswarm/_shop-popovers.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/_shop-popovers.css.scss rename to app/assets/stylesheets/darkswarm/_shop-popovers.scss diff --git a/app/assets/stylesheets/darkswarm/_shop-product-rows.css.scss b/app/assets/stylesheets/darkswarm/_shop-product-rows.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/_shop-product-rows.css.scss rename to app/assets/stylesheets/darkswarm/_shop-product-rows.scss diff --git a/app/assets/stylesheets/darkswarm/_shop-product-thumb.css.scss b/app/assets/stylesheets/darkswarm/_shop-product-thumb.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/_shop-product-thumb.css.scss rename to app/assets/stylesheets/darkswarm/_shop-product-thumb.scss diff --git a/app/assets/stylesheets/darkswarm/_shop-taxon-flag.css.scss b/app/assets/stylesheets/darkswarm/_shop-taxon-flag.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/_shop-taxon-flag.css.scss rename to app/assets/stylesheets/darkswarm/_shop-taxon-flag.scss diff --git a/app/assets/stylesheets/darkswarm/account.css.scss b/app/assets/stylesheets/darkswarm/account.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/account.css.scss rename to app/assets/stylesheets/darkswarm/account.scss diff --git a/app/assets/stylesheets/darkswarm/active_table.css.scss b/app/assets/stylesheets/darkswarm/active_table.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/active_table.css.scss rename to app/assets/stylesheets/darkswarm/active_table.scss diff --git a/app/assets/stylesheets/darkswarm/active_table_search.css.scss b/app/assets/stylesheets/darkswarm/active_table_search.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/active_table_search.css.scss rename to app/assets/stylesheets/darkswarm/active_table_search.scss diff --git a/app/assets/stylesheets/darkswarm/angular.css.scss b/app/assets/stylesheets/darkswarm/angular.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/angular.css.scss rename to app/assets/stylesheets/darkswarm/angular.scss diff --git a/app/assets/stylesheets/darkswarm/base/colors.css.scss b/app/assets/stylesheets/darkswarm/base/colors.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/base/colors.css.scss rename to app/assets/stylesheets/darkswarm/base/colors.scss diff --git a/app/assets/stylesheets/darkswarm/branding.css.scss b/app/assets/stylesheets/darkswarm/branding.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/branding.css.scss rename to app/assets/stylesheets/darkswarm/branding.scss diff --git a/app/assets/stylesheets/darkswarm/cart-dropdown.css.scss b/app/assets/stylesheets/darkswarm/cart-dropdown.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/cart-dropdown.css.scss rename to app/assets/stylesheets/darkswarm/cart-dropdown.scss diff --git a/app/assets/stylesheets/darkswarm/cart-page.css.scss b/app/assets/stylesheets/darkswarm/cart-page.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/cart-page.css.scss rename to app/assets/stylesheets/darkswarm/cart-page.scss diff --git a/app/assets/stylesheets/darkswarm/checkout.css.scss b/app/assets/stylesheets/darkswarm/checkout.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/checkout.css.scss rename to app/assets/stylesheets/darkswarm/checkout.scss diff --git a/app/assets/stylesheets/darkswarm/collapsible.css.scss b/app/assets/stylesheets/darkswarm/collapsible.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/collapsible.css.scss rename to app/assets/stylesheets/darkswarm/collapsible.scss diff --git a/app/assets/stylesheets/darkswarm/distributor_header.css.scss b/app/assets/stylesheets/darkswarm/distributor_header.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/distributor_header.css.scss rename to app/assets/stylesheets/darkswarm/distributor_header.scss diff --git a/app/assets/stylesheets/darkswarm/embedded_shopfront.css.scss b/app/assets/stylesheets/darkswarm/embedded_shopfront.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/embedded_shopfront.css.scss rename to app/assets/stylesheets/darkswarm/embedded_shopfront.scss diff --git a/app/assets/stylesheets/darkswarm/expanding-sidebar.css.scss b/app/assets/stylesheets/darkswarm/expanding-sidebar.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/expanding-sidebar.css.scss rename to app/assets/stylesheets/darkswarm/expanding-sidebar.scss diff --git a/app/assets/stylesheets/darkswarm/forms.css.scss b/app/assets/stylesheets/darkswarm/forms.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/forms.css.scss rename to app/assets/stylesheets/darkswarm/forms.scss diff --git a/app/assets/stylesheets/darkswarm/groups.css.scss b/app/assets/stylesheets/darkswarm/groups.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/groups.css.scss rename to app/assets/stylesheets/darkswarm/groups.scss diff --git a/app/assets/stylesheets/darkswarm/help-modal.css.scss b/app/assets/stylesheets/darkswarm/help-modal.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/help-modal.css.scss rename to app/assets/stylesheets/darkswarm/help-modal.scss diff --git a/app/assets/stylesheets/darkswarm/home_panes.css.scss b/app/assets/stylesheets/darkswarm/home_panes.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/home_panes.css.scss rename to app/assets/stylesheets/darkswarm/home_panes.scss diff --git a/app/assets/stylesheets/darkswarm/home_tagline.css.scss b/app/assets/stylesheets/darkswarm/home_tagline.scss similarity index 89% rename from app/assets/stylesheets/darkswarm/home_tagline.css.scss rename to app/assets/stylesheets/darkswarm/home_tagline.scss index c6cc15c9e8..7ad1ea0241 100644 --- a/app/assets/stylesheets/darkswarm/home_tagline.css.scss +++ b/app/assets/stylesheets/darkswarm/home_tagline.scss @@ -17,10 +17,12 @@ position: fixed; left: 0; right: 0; - bottom: 0; + top: 0; z-index: -1; width: 100%; height: 100%; + // Use vh units for new browsers - fixed issue 1253 + height: 100vh; } h1 { diff --git a/app/assets/stylesheets/darkswarm/hub_node.css.scss b/app/assets/stylesheets/darkswarm/hub_node.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/hub_node.css.scss rename to app/assets/stylesheets/darkswarm/hub_node.scss diff --git a/app/assets/stylesheets/darkswarm/hubs.css.scss b/app/assets/stylesheets/darkswarm/hubs.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/hubs.css.scss rename to app/assets/stylesheets/darkswarm/hubs.scss diff --git a/app/assets/stylesheets/darkswarm/images.css.scss b/app/assets/stylesheets/darkswarm/images.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/images.css.scss rename to app/assets/stylesheets/darkswarm/images.scss diff --git a/app/assets/stylesheets/darkswarm/layout/offcanvas.css.scss b/app/assets/stylesheets/darkswarm/layout/offcanvas.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/layout/offcanvas.css.scss rename to app/assets/stylesheets/darkswarm/layout/offcanvas.scss diff --git a/app/assets/stylesheets/darkswarm/lists.css.scss b/app/assets/stylesheets/darkswarm/lists.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/lists.css.scss rename to app/assets/stylesheets/darkswarm/lists.scss diff --git a/app/assets/stylesheets/darkswarm/map.css.scss b/app/assets/stylesheets/darkswarm/map.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/map.css.scss rename to app/assets/stylesheets/darkswarm/map.scss diff --git a/app/assets/stylesheets/darkswarm/menu.css.scss b/app/assets/stylesheets/darkswarm/menu.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/menu.css.scss rename to app/assets/stylesheets/darkswarm/menu.scss diff --git a/app/assets/stylesheets/darkswarm/modal-enterprises.css.scss b/app/assets/stylesheets/darkswarm/modal-enterprises.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/modal-enterprises.css.scss rename to app/assets/stylesheets/darkswarm/modal-enterprises.scss diff --git a/app/assets/stylesheets/darkswarm/modals.css.scss b/app/assets/stylesheets/darkswarm/modals.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/modals.css.scss rename to app/assets/stylesheets/darkswarm/modals.scss diff --git a/app/assets/stylesheets/darkswarm/overrides.css.scss b/app/assets/stylesheets/darkswarm/overrides.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/overrides.css.scss rename to app/assets/stylesheets/darkswarm/overrides.scss diff --git a/app/assets/stylesheets/darkswarm/page_alert.css.scss b/app/assets/stylesheets/darkswarm/page_alert.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/page_alert.css.scss rename to app/assets/stylesheets/darkswarm/page_alert.scss diff --git a/app/assets/stylesheets/darkswarm/pages/login_modal.css.scss b/app/assets/stylesheets/darkswarm/pages/login_modal.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/pages/login_modal.css.scss rename to app/assets/stylesheets/darkswarm/pages/login_modal.scss diff --git a/app/assets/stylesheets/darkswarm/producer_node.css.scss b/app/assets/stylesheets/darkswarm/producer_node.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/producer_node.css.scss rename to app/assets/stylesheets/darkswarm/producer_node.scss diff --git a/app/assets/stylesheets/darkswarm/producers.css.scss b/app/assets/stylesheets/darkswarm/producers.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/producers.css.scss rename to app/assets/stylesheets/darkswarm/producers.scss diff --git a/app/assets/stylesheets/darkswarm/product_table.css.scss b/app/assets/stylesheets/darkswarm/product_table.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/product_table.css.scss rename to app/assets/stylesheets/darkswarm/product_table.scss diff --git a/app/assets/stylesheets/darkswarm/registration.css.scss b/app/assets/stylesheets/darkswarm/registration.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/registration.css.scss rename to app/assets/stylesheets/darkswarm/registration.scss diff --git a/app/assets/stylesheets/darkswarm/shop.css.scss b/app/assets/stylesheets/darkswarm/shop.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/shop.css.scss rename to app/assets/stylesheets/darkswarm/shop.scss diff --git a/app/assets/stylesheets/darkswarm/shop_search.css.scss b/app/assets/stylesheets/darkswarm/shop_search.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/shop_search.css.scss rename to app/assets/stylesheets/darkswarm/shop_search.scss diff --git a/app/assets/stylesheets/darkswarm/shop_tabs.css.scss b/app/assets/stylesheets/darkswarm/shop_tabs.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/shop_tabs.css.scss rename to app/assets/stylesheets/darkswarm/shop_tabs.scss diff --git a/app/assets/stylesheets/darkswarm/sidebar.css.scss b/app/assets/stylesheets/darkswarm/sidebar.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/sidebar.css.scss rename to app/assets/stylesheets/darkswarm/sidebar.scss diff --git a/app/assets/stylesheets/darkswarm/signup.css.scss b/app/assets/stylesheets/darkswarm/signup.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/signup.css.scss rename to app/assets/stylesheets/darkswarm/signup.scss diff --git a/app/assets/stylesheets/darkswarm/stripe-elements.css.scss b/app/assets/stylesheets/darkswarm/stripe-elements.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/stripe-elements.css.scss rename to app/assets/stylesheets/darkswarm/stripe-elements.scss diff --git a/app/assets/stylesheets/darkswarm/style.css.scss b/app/assets/stylesheets/darkswarm/style.scss similarity index 94% rename from app/assets/stylesheets/darkswarm/style.css.scss rename to app/assets/stylesheets/darkswarm/style.scss index e9f018445b..9228b0bbe9 100644 --- a/app/assets/stylesheets/darkswarm/style.css.scss +++ b/app/assets/stylesheets/darkswarm/style.scss @@ -1,10 +1,10 @@ @font-face { font-family: 'OFN'; - src:url('/OFN-v2.eot?eslsji'); - src:url('/OFN-v2.eot?#iefixeslsji') format('embedded-opentype'), - url('/OFN-v2.woff?eslsji') format('woff'), - url('/OFN-v2.ttf?eslsji') format('truetype'), - url('/OFN-v2.svg?eslsji#OFN') format('svg'); + src: font-url('OFN-v2.eot'); + src: font-url('OFN-v2.eot') format('embedded-opentype'), + font-url('OFN-v2.woff') format('woff'), + font-url('OFN-v2.ttf') format('truetype'), + font-url('OFN-v2.svg') format('svg'); font-weight: normal; font-style: normal; } diff --git a/app/assets/stylesheets/darkswarm/tables.css.scss b/app/assets/stylesheets/darkswarm/tables.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/tables.css.scss rename to app/assets/stylesheets/darkswarm/tables.scss diff --git a/app/assets/stylesheets/darkswarm/tabset.css.scss b/app/assets/stylesheets/darkswarm/tabset.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/tabset.css.scss rename to app/assets/stylesheets/darkswarm/tabset.scss diff --git a/app/assets/stylesheets/darkswarm/taxons.css.scss b/app/assets/stylesheets/darkswarm/taxons.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/taxons.css.scss rename to app/assets/stylesheets/darkswarm/taxons.scss diff --git a/app/assets/stylesheets/darkswarm/typography.css.scss b/app/assets/stylesheets/darkswarm/typography.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/typography.css.scss rename to app/assets/stylesheets/darkswarm/typography.scss diff --git a/app/assets/stylesheets/darkswarm/ui.css.scss b/app/assets/stylesheets/darkswarm/ui.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/ui.css.scss rename to app/assets/stylesheets/darkswarm/ui.scss diff --git a/app/assets/stylesheets/darkswarm/variables.css.scss b/app/assets/stylesheets/darkswarm/variables.scss similarity index 100% rename from app/assets/stylesheets/darkswarm/variables.css.scss rename to app/assets/stylesheets/darkswarm/variables.scss diff --git a/app/assets/stylesheets/mail/email.css.scss b/app/assets/stylesheets/mail/email.scss similarity index 100% rename from app/assets/stylesheets/mail/email.css.scss rename to app/assets/stylesheets/mail/email.scss diff --git a/app/controllers/admin/enterprise_groups_controller.rb b/app/controllers/admin/enterprise_groups_controller.rb index 9d5bc6c665..d5dbccda3a 100644 --- a/app/controllers/admin/enterprise_groups_controller.rb +++ b/app/controllers/admin/enterprise_groups_controller.rb @@ -60,8 +60,8 @@ module Admin def permitted_resource_params params.require(:enterprise_group).permit( - :name, :description, :long_description, :on_front_page, :owner_id, :permalink, - :email, :website, :facebook, :instagram, :linkedin, :twitter, + :name, :description, :long_description, :logo, :promo_image, :on_front_page, + :owner_id, :permalink, :email, :website, :facebook, :instagram, :linkedin, :twitter, enterprise_ids: [], address_attributes: PermittedAttributes::Address.attributes ) end diff --git a/app/controllers/admin/enterprises_controller.rb b/app/controllers/admin/enterprises_controller.rb index 333926a728..4277dc2ff4 100644 --- a/app/controllers/admin/enterprises_controller.rb +++ b/app/controllers/admin/enterprises_controller.rb @@ -146,7 +146,7 @@ module Admin enterprises = OpenFoodNetwork::OrderCyclePermissions.new(spree_current_user, @order_cycle) .visible_enterprises - unless enterprises.empty? + if enterprises.present? enterprises.includes( supplied_products: [:supplier, master: [:images], variants: { option_values: :option_type }] diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 344dd89174..423a4fb104 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -53,7 +53,7 @@ module Api # Use logged in user (spree_current_user) for API authentication (current_api_user) def authenticate_user - return if @current_api_user = try_spree_current_user + return if @current_api_user = spree_current_user if api_key.blank? # An anonymous user diff --git a/app/controllers/api/orders_controller.rb b/app/controllers/api/orders_controller.rb index c011db44ec..7339af3c61 100644 --- a/app/controllers/api/orders_controller.rb +++ b/app/controllers/api/orders_controller.rb @@ -8,7 +8,7 @@ module Api def index authorize! :admin, Spree::Order - search_results = SearchOrders.new(params, spree_current_user) + search_results = SearchOrders.new(params, current_api_user) render json: { orders: serialized_orders(search_results.orders), diff --git a/app/controllers/base_controller.rb b/app/controllers/base_controller.rb index 6cf878a077..8fcdac8fe4 100644 --- a/app/controllers/base_controller.rb +++ b/app/controllers/base_controller.rb @@ -1,4 +1,4 @@ -require 'spree/core/controller_helpers/respond_with_decorator' +require 'spree/core/controller_helpers/respond_with' require 'open_food_network/tag_rule_applicator' class BaseController < ApplicationController diff --git a/app/controllers/cart_controller.rb b/app/controllers/cart_controller.rb index 997573899c..1fbf1b2b94 100644 --- a/app/controllers/cart_controller.rb +++ b/app/controllers/cart_controller.rb @@ -1,4 +1,4 @@ -require 'spree/core/controller_helpers/order_decorator' +require 'spree/core/controller_helpers/order' class CartController < BaseController before_action :check_authorization diff --git a/app/controllers/checkout_controller.rb b/app/controllers/checkout_controller.rb index 8616f4f6ad..c83b5e361d 100644 --- a/app/controllers/checkout_controller.rb +++ b/app/controllers/checkout_controller.rb @@ -241,7 +241,7 @@ class CheckoutController < Spree::StoreController def update_failed(error = RuntimeError.new(order_error)) Bugsnag.notify(error) - flash[:error] = order_error if flash.empty? + flash[:error] = order_error if flash.blank? checkout_failed update_failed_response end diff --git a/app/controllers/enterprises_controller.rb b/app/controllers/enterprises_controller.rb index 6d640b5d7b..7488fa8c4d 100644 --- a/app/controllers/enterprises_controller.rb +++ b/app/controllers/enterprises_controller.rb @@ -68,7 +68,7 @@ class EnterprisesController < BaseController # reset_distributor must be called before any call to current_customer or current_distributor order_cart_reset = OrderCartReset.new(order, params[:id]) order_cart_reset.reset_distributor - order_cart_reset.reset_other!(try_spree_current_user, current_customer) + order_cart_reset.reset_other!(spree_current_user, current_customer) rescue ActiveRecord::RecordNotFound flash[:error] = I18n.t(:enterprise_shop_show_error) redirect_to shops_path diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index c585b63839..c7612d3997 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -16,6 +16,10 @@ class HomeController < BaseController def sell; end + def unauthorized + render 'shared/unauthorized', status: :unauthorized + end + private # Cache the value of the query count diff --git a/app/controllers/spree/admin/base_controller.rb b/app/controllers/spree/admin/base_controller.rb index 7ce02d4a38..4f9cdbb4d0 100644 --- a/app/controllers/spree/admin/base_controller.rb +++ b/app/controllers/spree/admin/base_controller.rb @@ -24,7 +24,7 @@ module Spree # This is in Spree::Core::ControllerHelpers::Auth # But you can't easily reopen modules in Ruby def unauthorized - if try_spree_current_user + if spree_current_user flash[:error] = t(:authorization_failure) redirect_to '/unauthorized' else diff --git a/app/controllers/spree/admin/image_settings_controller.rb b/app/controllers/spree/admin/image_settings_controller.rb index 32aae5c783..476dd401a8 100644 --- a/app/controllers/spree/admin/image_settings_controller.rb +++ b/app/controllers/spree/admin/image_settings_controller.rb @@ -26,7 +26,7 @@ module Spree def update_styles(params) if params[:new_attachment_styles].present? params[:new_attachment_styles].each do |_index, style| - params[:attachment_styles][style[:name]] = style[:value] unless style[:value].empty? + params[:attachment_styles][style[:name]] = style[:value] if style[:value].present? end end @@ -38,7 +38,7 @@ module Spree def update_headers(params) if params[:new_s3_headers].present? params[:new_s3_headers].each do |_index, header| - params[:s3_headers][header[:name]] = header[:value] unless header[:value].empty? + params[:s3_headers][header[:name]] = header[:value] if header[:value].present? end end diff --git a/app/controllers/spree/admin/mail_methods_controller.rb b/app/controllers/spree/admin/mail_methods_controller.rb index aba6ad239f..70d573f319 100644 --- a/app/controllers/spree/admin/mail_methods_controller.rb +++ b/app/controllers/spree/admin/mail_methods_controller.rb @@ -15,7 +15,7 @@ module Spree end def testmail - if TestMailer.test_email(try_spree_current_user).deliver + if TestMailer.test_email(spree_current_user).deliver flash[:success] = Spree.t('admin.mail_methods.testmail.delivery_success') else flash[:error] = Spree.t('admin.mail_methods.testmail.delivery_error') diff --git a/app/controllers/spree/admin/orders_controller.rb b/app/controllers/spree/admin/orders_controller.rb index 22715108e9..8cde99be81 100644 --- a/app/controllers/spree/admin/orders_controller.rb +++ b/app/controllers/spree/admin/orders_controller.rb @@ -27,7 +27,7 @@ module Spree def new @order = Order.create - @order.created_by = try_spree_current_user + @order.created_by = spree_current_user @order.save redirect_to edit_admin_order_url(@order) end diff --git a/app/controllers/spree/admin/payment_methods_controller.rb b/app/controllers/spree/admin/payment_methods_controller.rb index 8825e4ea69..950b97340b 100644 --- a/app/controllers/spree/admin/payment_methods_controller.rb +++ b/app/controllers/spree/admin/payment_methods_controller.rb @@ -97,7 +97,7 @@ module Spree :name, :description, :type, :active, :environment, :display_on, :tag_list, :preferred_enterprise_id, :preferred_server, :preferred_login, :preferred_password, - :calculator_type, + :calculator_type, :preferred_api_key, :preferred_signature, :preferred_solution, :preferred_landing_page, :preferred_logourl, :preferred_test_mode, distributor_ids: [] ) diff --git a/app/controllers/spree/admin/products_controller.rb b/app/controllers/spree/admin/products_controller.rb index 707a65b3f1..76f0066d86 100644 --- a/app/controllers/spree/admin/products_controller.rb +++ b/app/controllers/spree/admin/products_controller.rb @@ -171,7 +171,7 @@ module Spree end def permitted_resource_params - return params[:product] if params[:product].empty? + return params[:product] if params[:product].blank? params.require(:product).permit(::PermittedAttributes::Product.attributes) end diff --git a/app/controllers/spree/admin/reports_controller.rb b/app/controllers/spree/admin/reports_controller.rb index edcf4baf60..a19795ca6d 100644 --- a/app/controllers/spree/admin/reports_controller.rb +++ b/app/controllers/spree/admin/reports_controller.rb @@ -12,7 +12,6 @@ require 'open_food_network/order_cycle_management_report' require 'open_food_network/packing_report' require 'open_food_network/sales_tax_report' require 'open_food_network/xero_invoices_report' -require 'open_food_network/bulk_coop_report' require 'open_food_network/payments_report' require 'open_food_network/orders_and_fulfillments_report' @@ -21,6 +20,11 @@ module Spree class ReportsController < Spree::Admin::BaseController include Spree::ReportsHelper + ORDER_MANAGEMENT_ENGINE_REPORTS = [ + :bulk_coop, + :enterprise_fee_summary + ].freeze + helper_method :render_content? before_action :cache_search_state @@ -91,19 +95,6 @@ module Spree render_report(@report.header, @report.table, params[:csv], "sales_tax.csv") end - def bulk_coop - # -- Prepare form options - @distributors = my_distributors - @report_type = params[:report_type] - - # -- Build Report with Order Grouper - @report = OpenFoodNetwork::BulkCoopReport.new spree_current_user, params, render_content? - @table = order_grouper_table - csv_file_name = "bulk_coop_#{params[:report_type]}_#{timestamp}.csv" - - render_report(@report.header, @table, params[:csv], csv_file_name) - end - def payments # -- Prepare Form Options @distributors = my_distributors @@ -262,7 +253,7 @@ module Spree end def order_grouper_table - order_grouper = OpenFoodNetwork::OrderGrouper.new @report.rules, @report.columns + order_grouper = OpenFoodNetwork::OrderGrouper.new @report.rules, @report.columns, @report order_grouper.table(@report.table_items) end @@ -311,7 +302,7 @@ module Spree # List of reports that have been moved to the Order Management engine def report_in_order_management_engine?(report) - report == :enterprise_fee_summary + ORDER_MANAGEMENT_ENGINE_REPORTS.include?(report) end def timestamp diff --git a/app/controllers/spree/home_controller.rb b/app/controllers/spree/home_controller.rb deleted file mode 100644 index 9fbdabe940..0000000000 --- a/app/controllers/spree/home_controller.rb +++ /dev/null @@ -1,7 +0,0 @@ -module Spree - class HomeController < Spree::StoreController - respond_to :html - - def index; end - end -end diff --git a/app/controllers/spree/orders_controller.rb b/app/controllers/spree/orders_controller.rb index 85939fe1ad..898ec93f99 100644 --- a/app/controllers/spree/orders_controller.rb +++ b/app/controllers/spree/orders_controller.rb @@ -1,5 +1,5 @@ -require 'spree/core/controller_helpers/order_decorator' -require 'spree/core/controller_helpers/auth_decorator' +require 'spree/core/controller_helpers/order' +require 'spree/core/controller_helpers/auth' module Spree class OrdersController < Spree::StoreController @@ -166,7 +166,7 @@ module Spree # recalculates the shipment taxes def update_totals_and_taxes @order.updater.update_totals - @order.shipment&.ensure_correct_adjustment_with_included_tax + @order.shipment&.ensure_correct_adjustment end # Sets the adjustments to open to perform the block's action and restores @@ -194,7 +194,7 @@ module Spree return if session[:access_token] || params[:token] || spree_current_user flash[:error] = I18n.t("spree.orders.edit.login_to_view_order") - require_login_then_redirect_to request.env['PATH_INFO'] + redirect_to main_app.root_path(anchor: "login?after_login=#{request.env['PATH_INFO']}") end def order_to_update diff --git a/app/controllers/spree/store_controller.rb b/app/controllers/spree/store_controller.rb index cc4dd9d537..5486912bd3 100644 --- a/app/controllers/spree/store_controller.rb +++ b/app/controllers/spree/store_controller.rb @@ -6,9 +6,5 @@ module Spree include I18nHelper before_action :set_locale - - def unauthorized - render 'shared/unauthorized', status: :unauthorized - end end end diff --git a/app/controllers/spree/user_registrations_controller.rb b/app/controllers/spree/user_registrations_controller.rb index a55d327ae2..98ad3c8d1c 100644 --- a/app/controllers/spree/user_registrations_controller.rb +++ b/app/controllers/spree/user_registrations_controller.rb @@ -23,7 +23,6 @@ module Spree if resource.save set_flash_message(:notice, :signed_up) sign_in(:spree_user, @user) - session[:spree_user_signup] = true associate_user respond_with resource, location: after_sign_up_path_for(resource) else diff --git a/app/controllers/spree/user_sessions_controller.rb b/app/controllers/spree/user_sessions_controller.rb index 82ca577fc3..df42f622e1 100644 --- a/app/controllers/spree/user_sessions_controller.rb +++ b/app/controllers/spree/user_sessions_controller.rb @@ -11,6 +11,7 @@ module Spree ssl_allowed :login_bar before_action :set_checkout_redirect, only: :create + after_action :ensure_valid_locale_persisted, only: :create def create authenticate_spree_user! @@ -48,5 +49,13 @@ module Spree redirect_to(session["spree_user_return_to"] || default) session["spree_user_return_to"] = nil end + + def ensure_valid_locale_persisted + # When creating a new user session we have to wait until after a successful + # login to be able to persist a selected locale on the current user + + UserLocaleSetter.new(spree_current_user, params[:locale], cookies). + ensure_valid_locale_persisted + end end end diff --git a/app/controllers/spree/users_controller.rb b/app/controllers/spree/users_controller.rb index f524f8d465..c6ce064b57 100644 --- a/app/controllers/spree/users_controller.rb +++ b/app/controllers/spree/users_controller.rb @@ -59,7 +59,7 @@ module Spree if @user authorize! params[:action].to_sym, @user else - redirect_to spree.login_path + redirect_to main_app.login_path end end diff --git a/app/controllers/user_passwords_controller.rb b/app/controllers/user_passwords_controller.rb index e467e915e2..9200b9b6ef 100644 --- a/app/controllers/user_passwords_controller.rb +++ b/app/controllers/user_passwords_controller.rb @@ -10,7 +10,7 @@ class UserPasswordsController < Spree::UserPasswordsController if resource.errors.empty? set_flash_message(:success, :send_instructions) if is_navigational_format? - respond_with resource, location: spree.login_path + respond_with resource, location: main_app.login_path else respond_to do |format| format.html do diff --git a/app/controllers/user_registrations_controller.rb b/app/controllers/user_registrations_controller.rb index 873d7d4036..f02309bf71 100644 --- a/app/controllers/user_registrations_controller.rb +++ b/app/controllers/user_registrations_controller.rb @@ -16,7 +16,6 @@ class UserRegistrationsController < Spree::UserRegistrationsController return render_error(@user.errors) end - session[:spree_user_signup] = true session[:confirmation_return_url] = params[:return_url] associate_user @@ -33,7 +32,7 @@ class UserRegistrationsController < Spree::UserRegistrationsController private def spree_user_params - return params[:spree_user] if params[:spree_user].empty? + return params[:spree_user] if params[:spree_user].blank? PermittedAttributes::User.new(params, :spree_user).call([:remember_me]) end diff --git a/app/helpers/i18n_helper.rb b/app/helpers/i18n_helper.rb index a04405ac97..8c827e1bba 100644 --- a/app/helpers/i18n_helper.rb +++ b/app/helpers/i18n_helper.rb @@ -1,32 +1,9 @@ module I18nHelper def set_locale - # Save a given locale - if params[:locale] && available_locale?(params[:locale]) - spree_current_user&.update!(locale: params[:locale]) - cookies[:locale] = params[:locale] - end - - # After logging in, check if the user chose a locale before - if spree_current_user && spree_current_user.locale.nil? && cookies[:locale] - spree_current_user.update!(locale: params[:locale]) - end - - I18n.locale = spree_current_user.andand.locale || cookies[:locale] || I18n.default_locale + UserLocaleSetter.new(spree_current_user, params[:locale], cookies).set_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) + UserLocaleSetter.new(user).valid_current_locale end end diff --git a/app/mailers/spree/user_mailer.rb b/app/mailers/spree/user_mailer.rb index 68d37c4b99..ba25809b5d 100644 --- a/app/mailers/spree/user_mailer.rb +++ b/app/mailers/spree/user_mailer.rb @@ -20,7 +20,7 @@ module Spree @user = user I18n.with_locale valid_locale(@user) do mail(to: user.email, from: from_address, - subject: t(:welcome_to) + Spree::Config[:site_name]) + subject: t(:welcome_to) + ' ' + Spree::Config[:site_name]) end end diff --git a/app/models/calculator/default_tax.rb b/app/models/calculator/default_tax.rb new file mode 100644 index 0000000000..de888c9283 --- /dev/null +++ b/app/models/calculator/default_tax.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: false + +require_dependency 'spree/calculator' +require 'open_food_network/enterprise_fee_calculator' + +module Calculator + class DefaultTax < Spree::Calculator + def self.description + Spree.t(:default_tax) + end + + def compute(computable) + case computable + when Spree::Order + compute_order(computable) + when Spree::LineItem + compute_line_item(computable) + end + end + + private + + def rate + calculable + end + + # Enable calculation of tax for enterprise fees with tax rates where included_in_price = false + def compute_order(order) + calculator = OpenFoodNetwork::EnterpriseFeeCalculator.new(order.distributor, + order.order_cycle) + + [ + line_items_total(order), + per_item_fees_total(order, calculator), + per_order_fees_total(order, calculator) + ].sum do |total| + round_to_two_places(total * rate.amount) + end + end + + def line_items_total(order) + matched_line_items = order.line_items.select do |line_item| + line_item.product.tax_category == rate.tax_category + end + + matched_line_items.sum(&:total) + end + + # Finds relevant fees for each line_item, + # calculates the tax on them, and returns the total tax + def per_item_fees_total(order, calculator) + order.line_items.sum do |line_item| + calculator.per_item_enterprise_fee_applicators_for(line_item.variant) + .select { |applicator| applicable_rate?(applicator, line_item) } + .sum { |applicator| applicator.enterprise_fee.compute_amount(line_item) } + end + end + + def applicable_rate?(applicator, line_item) + fee = applicator.enterprise_fee + (!fee.inherits_tax_category && fee.tax_category == rate.tax_category) || + (fee.inherits_tax_category && line_item.product.tax_category == rate.tax_category) + end + + # Finds relevant fees for whole order, + # calculates the tax on them, and returns the total tax + def per_order_fees_total(order, calculator) + calculator.per_order_enterprise_fee_applicators_for(order) + .select { |applicator| applicator.enterprise_fee.tax_category == rate.tax_category } + .sum { |applicator| applicator.enterprise_fee.compute_amount(order) } + end + + def compute_line_item(line_item) + if line_item.tax_category == rate.tax_category + if rate.included_in_price + deduced_total_by_rate(line_item.total, rate) + else + round_to_two_places(line_item.total * rate.amount) + end + else + 0 + end + end + + def round_to_two_places(amount) + BigDecimal(amount.to_s).round(2, BigDecimal::ROUND_HALF_UP) + end + + def deduced_total_by_rate(total, rate) + round_to_two_places(total - ( total / (1 + rate.amount) ) ) + end + end +end diff --git a/app/models/spree/calculator/flat_percent_item_total_decorator.rb b/app/models/calculator/flat_percent_item_total.rb similarity index 56% rename from app/models/spree/calculator/flat_percent_item_total_decorator.rb rename to app/models/calculator/flat_percent_item_total.rb index ae95f59ca2..79c2f1f400 100644 --- a/app/models/spree/calculator/flat_percent_item_total_decorator.rb +++ b/app/models/calculator/flat_percent_item_total.rb @@ -1,11 +1,20 @@ +# frozen_string_literal: false + +require_dependency 'spree/calculator' require 'spree/localized_number' -module Spree - Calculator::FlatPercentItemTotal.class_eval do +module Calculator + class FlatPercentItemTotal < Spree::Calculator extend Spree::LocalizedNumber + preference :flat_percent, :decimal, default: 0 + localize_number :preferred_flat_percent + def self.description + Spree.t(:flat_percent) + end + def compute(object) item_total = line_items_for(object).map(&:amount).sum value = item_total * BigDecimal(preferred_flat_percent.to_s) / 100.0 diff --git a/app/models/calculator/flat_rate.rb b/app/models/calculator/flat_rate.rb new file mode 100644 index 0000000000..d8bc14ca8f --- /dev/null +++ b/app/models/calculator/flat_rate.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: false + +require_dependency 'spree/calculator' +require 'spree/localized_number' + +module Calculator + class FlatRate < Spree::Calculator + extend Spree::LocalizedNumber + + preference :amount, :decimal, default: 0 + preference :currency, :string, default: Spree::Config[:currency] + + localize_number :preferred_amount + + def self.description + I18n.t(:flat_rate_per_order) + end + + def compute(_object = nil) + preferred_amount + end + end +end diff --git a/app/models/calculator/flexi_rate.rb b/app/models/calculator/flexi_rate.rb new file mode 100644 index 0000000000..76ae53379a --- /dev/null +++ b/app/models/calculator/flexi_rate.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: false + +require_dependency 'spree/calculator' +require 'spree/localized_number' + +module Calculator + class FlexiRate < Spree::Calculator + extend Spree::LocalizedNumber + + preference :first_item, :decimal, default: 0.0 + preference :additional_item, :decimal, default: 0.0 + preference :max_items, :integer, default: 0 + preference :currency, :string, default: Spree::Config[:currency] + + localize_number :preferred_first_item, + :preferred_additional_item + + def self.description + I18n.t(:flexible_rate) + end + + def self.available?(_object) + true + end + + def compute(object) + max = preferred_max_items.to_i + items_count = line_items_for(object).map(&:quantity).sum + + # check max value to avoid divide by 0 errors + return 0 if max.zero? + + if items_count > max + compute_for(max - 1) + elsif items_count <= max + compute_for(items_count - 1) + end + end + + private + + def compute_for(count) + count * preferred_additional_item.to_f + preferred_first_item.to_f + end + end +end diff --git a/app/models/spree/calculator/per_item_decorator.rb b/app/models/calculator/per_item.rb similarity index 59% rename from app/models/spree/calculator/per_item_decorator.rb rename to app/models/calculator/per_item.rb index e385ad8bf8..49b2df35c4 100644 --- a/app/models/spree/calculator/per_item_decorator.rb +++ b/app/models/calculator/per_item.rb @@ -1,9 +1,15 @@ +# frozen_string_literal: false + +require_dependency 'spree/calculator' require 'spree/localized_number' -module Spree - Calculator::PerItem.class_eval do +module Calculator + class PerItem < Spree::Calculator extend Spree::LocalizedNumber + preference :amount, :decimal, default: 0 + preference :currency, :string, default: Spree::Config[:currency] + localize_number :preferred_amount def self.description @@ -14,11 +20,7 @@ module Spree return 0 if object.nil? number_of_line_items = line_items_for(object).reduce(0) do |sum, line_item| - value_to_add = if matching_products.blank? || matching_products.include?(line_item.product) - line_item.quantity - else - 0 - end + value_to_add = line_item.quantity sum + value_to_add end preferred_amount * number_of_line_items diff --git a/app/models/spree/calculator/price_sack_decorator.rb b/app/models/calculator/price_sack.rb similarity index 59% rename from app/models/spree/calculator/price_sack_decorator.rb rename to app/models/calculator/price_sack.rb index 389649f916..cb6d185bd6 100644 --- a/app/models/spree/calculator/price_sack_decorator.rb +++ b/app/models/calculator/price_sack.rb @@ -1,9 +1,19 @@ +# frozen_string_literal: false + +require_dependency 'spree/calculator' +# For #to_d method on Ruby 1.8 +require 'bigdecimal/util' require 'spree/localized_number' -module Spree - Calculator::PriceSack.class_eval do +module Calculator + class PriceSack < Spree::Calculator extend Spree::LocalizedNumber + preference :minimal_amount, :decimal, default: 0 + preference :normal_amount, :decimal, default: 0 + preference :discount_amount, :decimal, default: 0 + preference :currency, :string, default: Spree::Config[:currency] + localize_number :preferred_minimal_amount, :preferred_normal_amount, :preferred_discount_amount diff --git a/app/models/content_configuration.rb b/app/models/content_configuration.rb index 245140f712..efe91e05b6 100644 --- a/app/models/content_configuration.rb +++ b/app/models/content_configuration.rb @@ -7,15 +7,15 @@ class ContentConfiguration < Spree::Preferences::FileConfiguration preference :logo, :file preference :logo_mobile, :file preference :logo_mobile_svg, :file - has_attached_file :logo, default_url: "/assets/ofn-logo.png" + has_attached_file :logo, default_url: "/default_images/ofn-logo.png" has_attached_file :logo_mobile - has_attached_file :logo_mobile_svg, default_url: "/assets/ofn-logo-mobile.svg" + has_attached_file :logo_mobile_svg, default_url: "/default_images/ofn-logo-mobile.svg" # Home page preference :home_page_alert_html, :text preference :home_hero, :file preference :home_show_stats, :boolean, default: true - has_attached_file :home_hero, default_url: "/assets/home/home.jpg" + has_attached_file :home_hero, default_url: "/default_images/home.jpg" # Map preference :open_street_map_enabled, :boolean, default: false @@ -59,7 +59,7 @@ class ContentConfiguration < Spree::Preferences::FileConfiguration # Footer preference :footer_logo, :file - has_attached_file :footer_logo, default_url: "/assets/ofn-logo-footer.png" + has_attached_file :footer_logo, default_url: "/default_images/ofn-logo-footer.png" # Other preference :footer_facebook_url, :string, default: "https://www.facebook.com/OpenFoodNet" diff --git a/app/models/enterprise_fee.rb b/app/models/enterprise_fee.rb index bbcafa01dc..e973419ece 100644 --- a/app/models/enterprise_fee.rb +++ b/app/models/enterprise_fee.rb @@ -11,7 +11,9 @@ class EnterpriseFee < ActiveRecord::Base has_many :exchanges, through: :exchange_fees FEE_TYPES = %w(packing transport admin sales fundraising).freeze - PER_ORDER_CALCULATORS = ['Spree::Calculator::FlatRate', 'Spree::Calculator::FlexiRate', 'Spree::Calculator::PriceSack'].freeze + PER_ORDER_CALCULATORS = ['Calculator::FlatRate', + 'Calculator::FlexiRate', + 'Calculator::PriceSack'].freeze validates :fee_type, inclusion: { in: FEE_TYPES } validates :name, presence: true diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index f5e4e5f8b4..6eb4727c03 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -185,9 +185,10 @@ class AbilityDecorator can [:admin, :index, :guide, :import, :save, :save_data, :validate_data, :reset_absent_products], ProductImport::ProductImporter # Reports page - can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, + can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing], Spree::Admin::ReportsController + add_bulk_coop_abilities add_enterprise_fee_summary_abilities end @@ -264,9 +265,10 @@ class AbilityDecorator end # Reports page - can [:admin, :index, :customers, :group_buys, :bulk_coop, :sales_tax, :payments, + can [:admin, :index, :customers, :group_buys, :sales_tax, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :xero_invoices], Spree::Admin::ReportsController + add_bulk_coop_abilities add_enterprise_fee_summary_abilities can [:create], Customer @@ -291,6 +293,13 @@ class AbilityDecorator end end + def add_bulk_coop_abilities + # Reveal the report link in spree/admin/reports#index + can [:bulk_coop], Spree::Admin::ReportsController + # Allow direct access to the report resource + can [:admin, :new, :create], :bulk_coop + end + def add_enterprise_fee_summary_abilities # Reveal the report link in spree/admin/reports#index can [:enterprise_fee_summary], Spree::Admin::ReportsController diff --git a/app/models/spree/calculator/default_tax_decorator.rb b/app/models/spree/calculator/default_tax_decorator.rb deleted file mode 100644 index ccc73abb9f..0000000000 --- a/app/models/spree/calculator/default_tax_decorator.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'open_food_network/enterprise_fee_calculator' - -Spree::Calculator::DefaultTax.class_eval do - private - - # Override this method to enable calculation of tax for - # enterprise fees with tax rates where included_in_price = false - def compute_order(order) - matched_line_items = order.line_items.select do |line_item| - line_item.product.tax_category == rate.tax_category - end - - line_items_total = matched_line_items.sum(&:total) - - # Added this line - calculator = OpenFoodNetwork::EnterpriseFeeCalculator.new(order.distributor, order.order_cycle) - - # Added this block, finds relevant fees for each line_item, calculates the tax on them, and returns the total tax - per_item_fees_total = order.line_items.sum do |line_item| - calculator.per_item_enterprise_fee_applicators_for(line_item.variant) - .select { |applicator| - (!applicator.enterprise_fee.inherits_tax_category && applicator.enterprise_fee.tax_category == rate.tax_category) || - (applicator.enterprise_fee.inherits_tax_category && line_item.product.tax_category == rate.tax_category) - } - .sum { |applicator| applicator.enterprise_fee.compute_amount(line_item) } - end - - # Added this block, finds relevant fees for whole order, calculates the tax on them, and returns the total tax - per_order_fees_total = calculator.per_order_enterprise_fee_applicators_for(order) - .select { |applicator| applicator.enterprise_fee.tax_category == rate.tax_category } - .sum { |applicator| applicator.enterprise_fee.compute_amount(order) } - - # round_to_two_places(line_items_total * rate.amount) # Removed this line - - # Added this block - [line_items_total, per_item_fees_total, per_order_fees_total].sum do |total| - round_to_two_places(total * rate.amount) - end - end -end diff --git a/app/models/spree/calculator/flat_rate_decorator.rb b/app/models/spree/calculator/flat_rate_decorator.rb deleted file mode 100644 index d7abe3cf1b..0000000000 --- a/app/models/spree/calculator/flat_rate_decorator.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'spree/localized_number' - -module Spree - Calculator::FlatRate.class_eval do - extend Spree::LocalizedNumber - - localize_number :preferred_amount - - def self.description - I18n.t(:flat_rate_per_order) - end - end -end diff --git a/app/models/spree/calculator/flexi_rate_decorator.rb b/app/models/spree/calculator/flexi_rate_decorator.rb deleted file mode 100644 index de112f4f0b..0000000000 --- a/app/models/spree/calculator/flexi_rate_decorator.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'spree/localized_number' - -module Spree - Calculator::FlexiRate.class_eval do - extend Spree::LocalizedNumber - - localize_number :preferred_first_item, - :preferred_additional_item - - def self.description - I18n.t(:flexible_rate) - end - - def compute(object) - sum = 0 - max = preferred_max_items.to_i - items_count = line_items_for(object).map(&:quantity).sum - # check max value to avoid divide by 0 errors - unless max == 0 - if items_count > max - sum += (max - 1) * preferred_additional_item.to_f + preferred_first_item.to_f - elsif items_count <= max - sum += (items_count - 1) * preferred_additional_item.to_f + preferred_first_item.to_f - end - end - - sum - end - end -end diff --git a/app/models/spree/gateway/stripe_sca.rb b/app/models/spree/gateway/stripe_sca.rb index 21fa698c3b..02681ae210 100644 --- a/app/models/spree/gateway/stripe_sca.rb +++ b/app/models/spree/gateway/stripe_sca.rb @@ -57,8 +57,11 @@ module Spree # NOTE: the name of this method is determined by Spree::Payment::Processing def void(response_code, _creditcard, gateway_options) + payment_intent_id = response_code + payment_intent_response = Stripe::PaymentIntent.retrieve(payment_intent_id, + stripe_account: stripe_account_id) gateway_options[:stripe_account] = stripe_account_id - provider.void(response_code, gateway_options) + provider.refund(payment_intent_response.amount_received, response_code, gateway_options) end # NOTE: the name of this method is determined by Spree::Payment::Processing diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index a36d8e1312..377b72a0f6 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -89,6 +89,18 @@ Spree::Order.class_eval do where("state != ?", state) } + def create_proposed_shipments + adjustments.shipping.delete_all + shipments.destroy_all + + packages = OrderManagement::Stock::Coordinator.new(self).packages + packages.each do |package| + shipments << package.to_shipment + end + + shipments + end + # -- Methods def products_available_from_new_distribution # Check that the line_items in the current order are available from a newly selected distribution @@ -420,8 +432,8 @@ Spree::Order.class_eval do # amount here. def charge_shipping_and_payment_fees! update_totals - return unless payments.any? + return unless pending_payments.any? - payments.first.update_attribute :amount, total + pending_payments.first.update_attribute :amount, total end end diff --git a/app/models/spree/payment_method_decorator.rb b/app/models/spree/payment_method_decorator.rb index 3da3919bf0..4c2770daea 100644 --- a/app/models/spree/payment_method_decorator.rb +++ b/app/models/spree/payment_method_decorator.rb @@ -49,7 +49,7 @@ Spree::PaymentMethod.class_eval do self.class.include Spree::Core::CalculatedAdjustments end - self.calculator ||= Spree::Calculator::FlatRate.new(preferred_amount: 0) + self.calculator ||= Calculator::FlatRate.new(preferred_amount: 0) end def has_distributor?(distributor) diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index 602329b5c1..554309f687 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -63,8 +63,7 @@ Spree::Product.class_eval do scope :visible_for, lambda { |enterprise| joins('LEFT OUTER JOIN spree_variants AS o_spree_variants ON (o_spree_variants.product_id = spree_products.id)'). joins('LEFT OUTER JOIN inventory_items AS o_inventory_items ON (o_spree_variants.id = o_inventory_items.variant_id)'). - where('o_inventory_items.enterprise_id = (?) AND visible = (?)', enterprise, true). - select('DISTINCT spree_products.*') + where('o_inventory_items.enterprise_id = (?) AND visible = (?)', enterprise, true) } # -- Scopes diff --git a/app/models/spree/shipment.rb b/app/models/spree/shipment.rb new file mode 100644 index 0000000000..98ea4ac982 --- /dev/null +++ b/app/models/spree/shipment.rb @@ -0,0 +1,337 @@ +# frozen_string_literal: true + +require 'ostruct' + +module Spree + class Shipment < ActiveRecord::Base + belongs_to :order, class_name: 'Spree::Order' + belongs_to :address, class_name: 'Spree::Address' + belongs_to :stock_location, class_name: 'Spree::StockLocation' + + has_many :shipping_rates, dependent: :delete_all + has_many :shipping_methods, through: :shipping_rates + has_many :state_changes, as: :stateful + has_many :inventory_units, dependent: :delete_all + has_one :adjustment, as: :source, dependent: :destroy + + before_create :generate_shipment_number + after_save :ensure_correct_adjustment, :update_order + + attr_accessor :special_instructions + + accepts_nested_attributes_for :address + accepts_nested_attributes_for :inventory_units + + make_permalink field: :number + + scope :shipped, -> { with_state('shipped') } + scope :ready, -> { with_state('ready') } + scope :pending, -> { with_state('pending') } + scope :with_state, ->(*s) { where(state: s) } + scope :trackable, -> { where("tracking IS NOT NULL AND tracking != ''") } + + # Shipment state machine + # See http://github.com/pluginaweek/state_machine/tree/master for details + state_machine initial: :pending, use_transactions: false do + event :ready do + transition from: :pending, to: :ready, if: lambda { |shipment| + # Fix for #2040 + shipment.determine_state(shipment.order) == 'ready' + } + end + + event :pend do + transition from: :ready, to: :pending + end + + event :ship do + transition from: :ready, to: :shipped + end + after_transition to: :shipped, do: :after_ship + + event :cancel do + transition to: :canceled, from: [:pending, :ready] + end + after_transition to: :canceled, do: :after_cancel + + event :resume do + transition from: :canceled, to: :ready, if: lambda { |shipment| + shipment.determine_state(shipment.order) == :ready + } + transition from: :canceled, to: :pending, if: lambda { |shipment| + shipment.determine_state(shipment.order) == :ready + } + transition from: :canceled, to: :pending + end + after_transition from: :canceled, to: [:pending, :ready], do: :after_resume + end + + def to_param + generate_shipment_number unless number + number.to_s.to_url.upcase + end + + def backordered? + inventory_units.any?(&:backordered?) + end + + def shipped=(value) + return unless value == '1' && shipped_at.nil? + + self.shipped_at = Time.zone.now + end + + def shipping_method + selected_shipping_rate.try(:shipping_method) || shipping_rates.first.try(:shipping_method) + end + + def add_shipping_method(shipping_method, selected = false) + shipping_rates.create(shipping_method: shipping_method, selected: selected) + end + + def selected_shipping_rate + shipping_rates.where(selected: true).first + end + + def selected_shipping_rate_id + selected_shipping_rate.try(:id) + end + + def selected_shipping_rate_id=(id) + shipping_rates.update_all(selected: false) + shipping_rates.update(id, selected: true) + save! + end + + def refresh_rates + return shipping_rates if shipped? + + # The call to Stock::Estimator below will replace the current shipping_method + original_shipping_method_id = shipping_method.try(:id) + self.shipping_rates = OrderManagement::Stock::Estimator.new(order).shipping_rates(to_package) + + keep_original_shipping_method_selection(original_shipping_method_id) + + shipping_rates + end + + def keep_original_shipping_method_selection(original_shipping_method_id) + return if shipping_method&.id == original_shipping_method_id + + rate_for_original_shipping_method = find_shipping_rate_for(original_shipping_method_id) + if rate_for_original_shipping_method.present? + self.selected_shipping_rate_id = rate_for_original_shipping_method.id + else + # If there's no original ship method to keep, or if it cannot be found on the ship rates + # But there's a new ship method selected (first if clause in this method) + # We need to save the shipment so that callbacks are triggered + save! + end + end + + def find_shipping_rate_for(shipping_method_id) + return unless shipping_method_id + + shipping_rates.detect { |rate| + rate.shipping_method_id == shipping_method_id + } + end + + def currency + order ? order.currency : Spree::Config[:currency] + end + + # The adjustment amount associated with this shipment (if any) + # Returns only the first adjustment to match the shipment + # There should never really be more than one. + def cost + adjustment ? adjustment.amount : 0 + end + + alias_method :amount, :cost + + def display_cost + Spree::Money.new(cost, currency: currency) + end + + alias_method :display_amount, :display_cost + + def item_cost + line_items.map(&:amount).sum + end + + def display_item_cost + Spree::Money.new(item_cost, currency: currency) + end + + def total_cost + cost + item_cost + end + + def display_total_cost + Spree::Money.new(total_cost, currency: currency) + end + + def editable_by?(_user) + !shipped? + end + + def manifest + inventory_units.group_by(&:variant).map do |variant, units| + states = {} + units.group_by(&:state).each { |state, iu| states[state] = iu.count } + scoper.scope(variant) + OpenStruct.new(variant: variant, quantity: units.length, states: states) + end + end + + def scoper + @scoper ||= OpenFoodNetwork::ScopeVariantToHub.new(order.distributor) + end + + def line_items + if order.complete? + order.line_items.select { |li| inventory_units.pluck(:variant_id).include?(li.variant_id) } + else + order.line_items + end + end + + def finalize! + InventoryUnit.finalize_units!(inventory_units) + manifest.each { |item| manifest_unstock(item) } + end + + def after_cancel + manifest.each { |item| manifest_restock(item) } + end + + def after_resume + manifest.each { |item| manifest_unstock(item) } + end + + # Updates various aspects of the Shipment while bypassing any callbacks. + # Note that this method takes an explicit reference to the Order object. + # This is necessary because the association actually has a stale (and unsaved) copy of the + # Order and so it will not yield the correct results. + def update!(order) + old_state = state + new_state = determine_state(order) + update_column :state, new_state + after_ship if new_state == 'shipped' && old_state != 'shipped' + end + + # Determines the appropriate +state+ according to the following logic: + # + # pending unless order is complete and +order.payment_state+ is +paid+ + # shipped if already shipped (ie. does not change the state) + # ready all other cases + def determine_state(order) + return 'canceled' if order.canceled? + return 'pending' unless order.can_ship? + return 'pending' if inventory_units.any?(&:backordered?) + return 'shipped' if state == 'shipped' + + order.paid? ? 'ready' : 'pending' + end + + def tracking_url + @tracking_url ||= shipping_method.build_tracking_url(tracking) + end + + def include?(variant) + inventory_units_for(variant).present? + end + + def inventory_units_for(variant) + inventory_units.group_by(&:variant_id)[variant.id] || [] + end + + def to_package + package = OrderManagement::Stock::Package.new(stock_location, order) + inventory_units.includes(:variant).each do |inventory_unit| + package.add inventory_unit.variant, 1, inventory_unit.state_name + end + package + end + + def set_up_inventory(state, variant, order) + inventory_units.create(variant_id: variant.id, state: state, order_id: order.id) + end + + def ensure_correct_adjustment + if adjustment + adjustment.originator = shipping_method + adjustment.label = shipping_method.adjustment_label + adjustment.amount = selected_shipping_rate.cost if adjustment.open? + adjustment.save! + adjustment.reload + elsif selected_shipping_rate_id + shipping_method.create_adjustment(shipping_method.adjustment_label, + order, + self, + true, + "open") + reload # ensure adjustment is present on later saves + end + + update_adjustment_included_tax if adjustment + end + + private + + def manifest_unstock(item) + stock_location.unstock item.variant, item.quantity, self + end + + def manifest_restock(item) + stock_location.restock item.variant, item.quantity, self + end + + def generate_shipment_number + return number if number.present? + + record = true + while record + random = "H#{Array.new(11) { rand(9) }.join}" + record = self.class.where(number: random).first + end + self.number = random + end + + def description_for_shipping_charge + "#{Spree.t(:shipping)} (#{shipping_method.name})" + end + + def validate_shipping_method + return if shipping_method.nil? + + return if shipping_method.include?(address) + + errors.add :shipping_method, Spree.t(:is_not_available_to_shipment_address) + end + + def after_ship + inventory_units.each(&:ship!) + adjustment.finalize! + send_shipped_email + touch :shipped_at + end + + def send_shipped_email + ShipmentMailer.shipped_email(id).deliver + end + + def update_adjustment_included_tax + if Config.shipment_inc_vat && (order.distributor.nil? || order.distributor.charges_sales_tax) + adjustment.set_included_tax! Config.shipping_tax_rate + else + adjustment.set_included_tax! 0 + end + end + + def update_order + order.update! + end + end +end diff --git a/app/models/spree/shipment_decorator.rb b/app/models/spree/shipment_decorator.rb deleted file mode 100644 index b23bd7266f..0000000000 --- a/app/models/spree/shipment_decorator.rb +++ /dev/null @@ -1,41 +0,0 @@ -module Spree - Shipment.class_eval do - def ensure_correct_adjustment_with_included_tax - ensure_correct_adjustment_without_included_tax - - update_adjustment_included_tax if adjustment - end - alias_method_chain :ensure_correct_adjustment, :included_tax - - def update_adjustment_included_tax - if Config.shipment_inc_vat && (order.distributor.nil? || order.distributor.charges_sales_tax) - adjustment.set_included_tax! Config.shipping_tax_rate - else - adjustment.set_included_tax! 0 - end - end - - def manifest - inventory_units.group_by(&:variant).map do |variant, units| - states = {} - units.group_by(&:state).each { |state, iu| states[state] = iu.count } - scoper.scope(variant) - OpenStruct.new(variant: variant, quantity: units.length, states: states) - end - end - - def scoper - @scoper ||= OpenFoodNetwork::ScopeVariantToHub.new(order.distributor) - end - - private - - # NOTE: This is an override of spree's method, needed to allow orders - # without line items (ie. user invoices) to not have inventory units - def require_inventory - return false unless line_items.count > 0 # This line altered - - order.completed? && !order.canceled? - end - end -end diff --git a/app/models/spree/stock/availability_validator.rb b/app/models/spree/stock/availability_validator.rb new file mode 100644 index 0000000000..6c9516cd08 --- /dev/null +++ b/app/models/spree/stock/availability_validator.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module Spree + module Stock + class AvailabilityValidator < ActiveModel::Validator + def validate(line_item) + # OFN specific check for in-memory :skip_stock_check attribute + return if line_item.skip_stock_check + + quantity_to_validate = line_item.quantity - quantity_in_shipment(line_item) + return if quantity_to_validate < 1 + + validate_quantity(line_item, quantity_to_validate) + end + + private + + # This is an adapted version of a fix to the inventory_units not being considered here. + # See #3090 for details. + # This can be removed after upgrading to Spree 2.4. + def quantity_in_shipment(line_item) + shipment = line_item_shipment(line_item) + return 0 unless shipment + + units = shipment.inventory_units_for(line_item.variant) + units.count + end + + def line_item_shipment(line_item) + return line_item.target_shipment if line_item.target_shipment + return line_item.order.shipments.first if line_item.order.andand.shipments.any? + end + + # Overrides Spree v2.0.4 validate method version to: + # - scope variants to hub and thus acivate variant overrides + # - use calculated quantity instead of the line_item.quantity + # - rely on Variant.can_supply? instead of Stock::Quantified.can_supply? + # so that it works correctly for variant overrides + def validate_quantity(line_item, quantity) + line_item.scoper.scope(line_item.variant) + + add_out_of_stock_error(line_item) unless line_item.variant.can_supply? quantity + end + + def add_out_of_stock_error(line_item) + variant = line_item.variant + display_name = variant.name.to_s + display_name += %{(#{variant.options_text})} if variant.options_text.present? + line_item.errors[:quantity] << Spree.t(:out_of_stock, + scope: :order_populator, + item: display_name.inspect) + end + end + end +end diff --git a/app/models/spree/stock/availability_validator_decorator.rb b/app/models/spree/stock/availability_validator_decorator.rb deleted file mode 100644 index c3ee4c4818..0000000000 --- a/app/models/spree/stock/availability_validator_decorator.rb +++ /dev/null @@ -1,49 +0,0 @@ -Spree::Stock::AvailabilityValidator.class_eval do - def validate(line_item) - # OFN specific check for in-memory :skip_stock_check attribute - return if line_item.skip_stock_check - - quantity_to_validate = line_item.quantity - quantity_in_shipment(line_item) - return if quantity_to_validate < 1 - - validate_quantity(line_item, quantity_to_validate) - end - - private - - # This is an adapted version of a fix to the inventory_units not being considered here. - # See #3090 for details. - # This can be removed after upgrading to Spree 2.4. - def quantity_in_shipment(line_item) - shipment = line_item_shipment(line_item) - return 0 unless shipment - - units = shipment.inventory_units_for(line_item.variant) - units.count - end - - def line_item_shipment(line_item) - return line_item.target_shipment if line_item.target_shipment - return line_item.order.shipments.first if line_item.order.andand.shipments.any? - end - - # Overrides Spree v2.0.4 validate method version to: - # - scope variants to hub and thus acivate variant overrides - # - use calculated quantity instead of the line_item.quantity - # - rely on Variant.can_supply? instead of Stock::Quantified.can_supply? - # so that it works correctly for variant overrides - def validate_quantity(line_item, quantity) - line_item.scoper.scope(line_item.variant) - - add_out_of_stock_error(line_item) unless line_item.variant.can_supply? quantity - end - - def add_out_of_stock_error(line_item) - variant = line_item.variant - display_name = variant.name.to_s - display_name += %{(#{variant.options_text})} if variant.options_text.present? - line_item.errors[:quantity] << Spree.t(:out_of_stock, - scope: :order_populator, - item: display_name.inspect) - end -end diff --git a/app/models/spree/stock_item.rb b/app/models/spree/stock_item.rb new file mode 100644 index 0000000000..e6df19b03a --- /dev/null +++ b/app/models/spree/stock_item.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Spree + class StockItem < ActiveRecord::Base + acts_as_paranoid + + belongs_to :stock_location, class_name: 'Spree::StockLocation' + belongs_to :variant, class_name: 'Spree::Variant' + has_many :stock_movements, dependent: :destroy + + validates :stock_location, :variant, presence: true + validates :variant_id, uniqueness: { scope: [:stock_location_id, :deleted_at] } + validates :count_on_hand, numericality: { greater_than_or_equal_to: 0, unless: :backorderable? } + + delegate :weight, to: :variant + delegate :name, to: :variant, prefix: true + + def backordered_inventory_units + Spree::InventoryUnit.backordered_for_stock_item(self) + end + + def adjust_count_on_hand(value) + with_lock do + self.count_on_hand = count_on_hand + value + process_backorders if in_stock? + + save! + end + end + + def in_stock? + count_on_hand.positive? + end + + # Tells whether it's available to be included in a shipment + def available? + in_stock? || backorderable? + end + + def variant + Spree::Variant.unscoped { super } + end + + private + + def count_on_hand=(value) + self[:count_on_hand] = value + end + + def process_backorders + backordered_inventory_units.each do |unit| + break unless in_stock? + + unit.fill_backorder + end + end + end +end diff --git a/app/models/stock/package.rb b/app/models/stock/package.rb deleted file mode 100644 index eada3741c1..0000000000 --- a/app/models/stock/package.rb +++ /dev/null @@ -1,45 +0,0 @@ -# Extends Spree's Package implementation to skip shipping methods that are not -# valid for OFN. -# -# It requires the following configuration in config/initializers/spree.rb: -# -# Spree.config do |config| -# ... -# config.package_factory = Stock::Package -# end -# -module Stock - class Package < Spree::Stock::Package - # Returns all existing shipping categories. - # It does not filter by the shipping categories of the products in the order. - # It allows checkout of products with categories that are not the shipping methods categories - # It disables the matching of product shipping category with shipping method's category - # - # @return [Array] - def shipping_categories - Spree::ShippingCategory.all - end - - # Skips the methods that are not used by the order's distributor - # - # @return [Array] - def shipping_methods - available_shipping_methods = super.to_a - - available_shipping_methods.keep_if do |shipping_method| - ships_with?(order.distributor.shipping_methods.to_a, shipping_method) - end - end - - private - - # Checks whether the given distributor provides the specified shipping method - # - # @param shipping_methods [Array] - # @param shipping_method [Spree::ShippingMethod] - # @return [Boolean] - def ships_with?(shipping_methods, shipping_method) - shipping_methods.include?(shipping_method) - end - end -end diff --git a/app/serializers/api/admin/enterprise_serializer.rb b/app/serializers/api/admin/enterprise_serializer.rb index bcb2fe39d7..e3024fdc46 100644 --- a/app/serializers/api/admin/enterprise_serializer.rb +++ b/app/serializers/api/admin/enterprise_serializer.rb @@ -25,7 +25,7 @@ class Api::Admin::EnterpriseSerializer < ActiveModel::Serializer tag_group = find_match(tag_groups, tag_rule.preferred_customer_tags. split(","). map{ |t| { text: t } }) - if tag_group[:rules].empty? + if tag_group[:rules].blank? tag_groups << tag_group tag_group[:position] = tag_groups.count end diff --git a/app/serializers/api/admin/for_order_cycle/enterprise_serializer.rb b/app/serializers/api/admin/for_order_cycle/enterprise_serializer.rb index 360f135680..7e6fed44f7 100644 --- a/app/serializers/api/admin/for_order_cycle/enterprise_serializer.rb +++ b/app/serializers/api/admin/for_order_cycle/enterprise_serializer.rb @@ -30,9 +30,11 @@ class Api::Admin::ForOrderCycle::EnterpriseSerializer < ActiveModel::Serializer def products_scope products_relation = object.supplied_products if order_cycle.prefers_product_selection_from_coordinator_inventory_only? - products_relation = products_relation.visible_for(order_cycle.coordinator) + products_relation = products_relation. + visible_for(order_cycle.coordinator). + select('DISTINCT spree_products.*') end - products_relation.order(:name) + products_relation end def products diff --git a/app/serializers/api/admin/product_serializer.rb b/app/serializers/api/admin/product_serializer.rb index c4b9f15a34..2be4995305 100644 --- a/app/serializers/api/admin/product_serializer.rb +++ b/app/serializers/api/admin/product_serializer.rb @@ -12,7 +12,7 @@ class Api::Admin::ProductSerializer < ActiveModel::Serializer if object.images.present? object.images.first.attachment.url(:product) else - "/assets/noimage/product.png" + "/noimage/product.png" end end @@ -20,7 +20,7 @@ class Api::Admin::ProductSerializer < ActiveModel::Serializer if object.images.present? object.images.first.attachment.url(:mini) else - "/assets/noimage/mini.png" + "/noimage/mini.png" end end diff --git a/app/serializers/api/admin/units_variant_serializer.rb b/app/serializers/api/admin/units_variant_serializer.rb index dc27e3adc3..f70959b925 100644 --- a/app/serializers/api/admin/units_variant_serializer.rb +++ b/app/serializers/api/admin/units_variant_serializer.rb @@ -3,6 +3,6 @@ class Api::Admin::UnitsVariantSerializer < ActiveModel::Serializer def full_name full_name = object.full_name - object.product.name + (full_name.empty? ? "" : ": #{full_name}") + object.product.name + (full_name.blank? ? "" : ": #{full_name}") end end diff --git a/app/serializers/api/variant_serializer.rb b/app/serializers/api/variant_serializer.rb index 38cc649910..0306c95f95 100644 --- a/app/serializers/api/variant_serializer.rb +++ b/app/serializers/api/variant_serializer.rb @@ -35,7 +35,7 @@ class Api::VariantSerializer < ActiveModel::Serializer if object.product.images.present? object.product.images.first.attachment.url(:mini) else - "/assets/noimage/mini.png" + "/noimage/mini.png" end end end diff --git a/app/services/checkout/form_data_adapter.rb b/app/services/checkout/form_data_adapter.rb index a374444637..1333ed5dbd 100644 --- a/app/services/checkout/form_data_adapter.rb +++ b/app/services/checkout/form_data_adapter.rb @@ -12,6 +12,8 @@ module Checkout move_payment_source_to_payment_attributes! + fill_in_card_type + set_amount_in_payments_attributes construct_saved_card_attributes if @params[:order][:existing_card_id] @@ -31,6 +33,28 @@ module Checkout @params[:order][:payments_attributes].first[:source_attributes] = payment_source_params end + # Ensures cc_type is always passed to the model by inferring the type when + # the frontend didn't provide it. This fixes Pin Payments specifically + # although it might be useful for future payment gateways. + # + # More details: app/assets/javascripts/darkswarm/services/checkout.js.coffee#L70-L98 + def fill_in_card_type + return unless payment_source_attributes + + return if payment_source_attributes.dig(:number).blank? + + payment_source_attributes[:cc_type] ||= card_brand(payment_source_attributes[:number]) + end + + def payment_source_attributes + @payment_source_attributes ||= + params[:order][:payments_attributes]&.first&.dig(:source_attributes) + end + + def card_brand(number) + ActiveMerchant::Billing::CreditCard.brand?(number) + end + def delete_payment_source_params! @params.delete(:payment_source)[ @params[:order][:payments_attributes].first[:payment_method_id].underscore diff --git a/app/services/permitted_attributes/enterprise.rb b/app/services/permitted_attributes/enterprise.rb index f02863b2a5..db7f31f10c 100644 --- a/app/services/permitted_attributes/enterprise.rb +++ b/app/services/permitted_attributes/enterprise.rb @@ -7,7 +7,7 @@ module PermittedAttributes end def call - return @params[:enterprise] if @params[:enterprise].empty? + return @params[:enterprise] if @params[:enterprise].blank? @params.require(:enterprise).permit( basic_permitted_attributes + [ diff --git a/app/services/permitted_attributes/order_cycle.rb b/app/services/permitted_attributes/order_cycle.rb index 6da6175a51..b81dccef0f 100644 --- a/app/services/permitted_attributes/order_cycle.rb +++ b/app/services/permitted_attributes/order_cycle.rb @@ -7,10 +7,11 @@ module PermittedAttributes end def call - return @params[:order_cycle] if @params[:order_cycle].empty? + return @params[:order_cycle] if @params[:order_cycle].blank? @params.require(:order_cycle).permit( :name, :orders_open_at, :orders_close_at, :coordinator_id, + :preferred_product_selection_from_coordinator_inventory_only, incoming_exchanges: permitted_exchange_attributes, outgoing_exchanges: permitted_exchange_attributes, schedule_ids: [], coordinator_fee_ids: [] diff --git a/app/services/permitted_attributes/subscription.rb b/app/services/permitted_attributes/subscription.rb index 2ab3956fca..21e523e4fe 100644 --- a/app/services/permitted_attributes/subscription.rb +++ b/app/services/permitted_attributes/subscription.rb @@ -7,7 +7,7 @@ module PermittedAttributes end def call - return @params[:subscription] if @params[:subscription].empty? + return @params[:subscription] if @params[:subscription].blank? @params.require(:subscription).permit(basic_permitted_attributes + other_permitted_attributes) end diff --git a/app/services/user_default_address_setter.rb b/app/services/user_default_address_setter.rb index 7a83078290..20a80afc3d 100644 --- a/app/services/user_default_address_setter.rb +++ b/app/services/user_default_address_setter.rb @@ -9,7 +9,7 @@ class UserDefaultAddressSetter # Sets the order bill address as the user default bill address def set_default_bill_address - new_bill_address = @order.bill_address.clone.attributes + new_bill_address = @order.bill_address.dup.attributes.except!('created_at', 'updated_at') set_bill_address_attributes(@current_user, new_bill_address) set_bill_address_attributes(@order.customer, new_bill_address) @@ -17,7 +17,7 @@ class UserDefaultAddressSetter # Sets the order ship address as the user default ship address def set_default_ship_address - new_ship_address = @order.ship_address.clone.attributes + new_ship_address = @order.ship_address.dup.attributes.except!('created_at', 'updated_at') set_ship_address_attributes(@current_user, new_ship_address) set_ship_address_attributes(@order.customer, new_ship_address) diff --git a/app/services/user_locale_setter.rb b/app/services/user_locale_setter.rb new file mode 100644 index 0000000000..cd0fa96631 --- /dev/null +++ b/app/services/user_locale_setter.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +class UserLocaleSetter + def initialize(current_user, params_locale = nil, cookies = {}) + @current_user = current_user + @params_locale = params_locale + @cookies = cookies + end + + def set_locale + save_locale_from_params + + I18n.locale = valid_current_locale + end + + def ensure_valid_locale_persisted + return unless current_user && !available_locale?(current_user.locale) + + current_user.update!(locale: valid_current_locale) + end + + def valid_current_locale + if current_user_locale && available_locale?(current_user_locale) + current_user_locale + elsif cookies[:locale] && available_locale?(cookies[:locale]) + cookies[:locale] + else + I18n.default_locale + end + end + + private + + attr_reader :current_user, :params_locale, :cookies + + def save_locale_from_params + return unless params_locale && available_locale?(params_locale) + + current_user&.update!(locale: params_locale) + cookies[:locale] = params_locale + end + + def available_locale?(locale) + Rails.application.config.i18n.available_locales.include?(locale) + end + + def current_user_locale + current_user&.locale + end +end diff --git a/app/views/admin/producer_properties/_producer_property_fields.html.haml b/app/views/admin/producer_properties/_producer_property_fields.html.haml index 6fe0b1d7ad..841a0cfb73 100644 --- a/app/views/admin/producer_properties/_producer_property_fields.html.haml +++ b/app/views/admin/producer_properties/_producer_property_fields.html.haml @@ -11,5 +11,5 @@ %td.value = f.text_field :value, :class => 'autocomplete' %td.actions - - unless @enterprise.producer_properties.empty? + - if @enterprise.producer_properties.present? = link_to_remove_fields t(:remove), f, no_text: true, url: (f.object.persisted? && main_app.admin_enterprise_producer_property_path(@enterprise, f.object)), html: {"onclick" => "if(typeof(enterprise_form) != 'undefined') { angular.element(enterprise_form).scope().setFormDirty() }".html_safe} diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 4701791daa..b5622063a2 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -22,7 +22,7 @@ - if @group.logo.present? %img.group-logo{"src" => @group.logo} - else - %img.group-logo{"src" => image_path('noimage/group.png') } + %img.group-logo{"src" => '/noimage/group.png' } %h2.group-name= @group.name %p= @group.description diff --git a/app/views/home/sell.html.haml b/app/views/home/sell.html.haml index 5238fd4cd8..ad0740979e 100644 --- a/app/views/home/sell.html.haml +++ b/app/views/home/sell.html.haml @@ -18,7 +18,7 @@ %h3= t :sell_producers %p = t :sell_producers_detail - %a{href: "https://openfoodnetwork.org/user-guide/"}= t(:sell_user_guide) + %a{href: ContentConfig.user_guide_link}= t(:sell_user_guide) %a.button.transparent{href: signup_producers_path} = t :register_title @@ -26,7 +26,7 @@ %h3= t :sell_hubs %p = t :sell_hubs_detail - %a{href: "https://openfoodnetwork.org/user-guide/"}= t(:sell_user_guide) + %a{href: ContentConfig.user_guide_link}= t(:sell_user_guide) %a.button.transparent{href: signup_shops_path} = t :register_title @@ -34,7 +34,7 @@ %h3= t :sell_groups %p = t :sell_groups_detail - %a{href: "https://openfoodnetwork.org/user-guide/"}= t(:sell_user_guide) + %a{href: ContentConfig.user_guide_link}= t(:sell_user_guide) %a.button.transparent{href: signup_groups_path} = t :register_title diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index f45b157a51..7fd21a35d3 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -13,7 +13,7 @@ - else = favicon_link_tag "/favicon-staging.ico" %link{href: "https://fonts.googleapis.com/css?family=Roboto:400,300italic,400italic,300,700,700italic|Oswald:300,400,700", rel: "stylesheet", type: "text/css"} - %link{href: "/OFN-v2.woff?eslsji", rel: "preload", as: "font", crossorigin: "anonymous"} + %link{href: font_path("OFN-v2.woff"), rel: "preload", as: "font", crossorigin: "anonymous"} = stylesheet_link_tag "darkswarm/all" = csrf_meta_tags diff --git a/app/views/producers/_fat.html.haml b/app/views/producers/_fat.html.haml index 9b4e3b68b6..7712d43629 100644 --- a/app/views/producers/_fat.html.haml +++ b/app/views/producers/_fat.html.haml @@ -79,8 +79,8 @@ .row.cta-container .columns.small-12 %a.cta-hub{"ng-repeat" => "hub in producer.hubs | orderBy:'-active'", - "ng-href" => "{{::hub.path}}", "ng-attr-target" => "{{ embedded_layout ? '_blank' : undefined }}", - "ng-class" => "::{primary: hub.active, secondary: !hub.active}"} + "ng-href" => "{{::hub.path}}", "ng-attr-target" => "_blank", + "ng-class" => "::{primary: hub.active, secondary: !hub.active}", "target" => "_blank"} %i.ofn-i_068-shop-reversed{"ng-if" => "::hub.active"} %i.ofn-i_068-shop-reversed{"ng-if" => "::!hub.active"} .hub-name{"ng-bind" => "::hub.name"} diff --git a/app/views/producers/_skinny.html.haml b/app/views/producers/_skinny.html.haml index 6e05619d07..8664537362 100644 --- a/app/views/producers/_skinny.html.haml +++ b/app/views/producers/_skinny.html.haml @@ -3,7 +3,7 @@ %span{"ng-if" => "::producer.is_distributor" } .row.vertical-align-middle .columns.small-12 - %a.is_distributor{"ng-href" => "{{::producer.path}}", "ng-attr-target" => "{{ embedded_layout ? '_blank' : undefined}}", "data-is-link" => "true"} + %a.is_distributor{"ng-href" => "{{::producer.path}}", "ng-attr-target" => "_blank", "data-is-link" => "true", "target" => "_blank"} %i{ng: {class: "::producer.producer_icon_font"}} %span.margin-top %strong{"ng-bind" => "::producer.name"} diff --git a/app/views/spree/admin/orders/_filters.html.haml b/app/views/spree/admin/orders/_filters.html.haml index 0479e26a62..3a33806f6f 100644 --- a/app/views/spree/admin/orders/_filters.html.haml +++ b/app/views/spree/admin/orders/_filters.html.haml @@ -4,10 +4,10 @@ .date-range-filter.field = label_tag nil, t(:date_range) .date-range-fields - = text_field_tag "q[completed_at_gt]", nil, class: 'datepicker', datepicker: 'q.completed_at_gt', 'ng-model' => 'q.completed_at_gt', :placeholder => t(:start) + = text_field_tag "q[completed_at_gteq]", nil, class: 'datepicker', datepicker: 'q.completed_at_gteq', 'ng-model' => 'q.completed_at_gteq', :placeholder => t(:start) %span.range-divider %i.icon-arrow-right - = text_field_tag "q[completed_at_lt]", nil, class: 'datepicker', datepicker: 'q.completed_at_lt', 'ng-model' => 'q.completed_at_lt', :placeholder => t(:stop) + = text_field_tag "q[completed_at_lteq]", nil, class: 'datepicker', datepicker: 'q.completed_at_lteq', 'ng-model' => 'q.completed_at_lteq', :placeholder => t(:stop) .field = label_tag nil, t(:status) = select_tag("q[state_eq]", @@ -16,17 +16,17 @@ .four.columns .field = label_tag nil, t(:order_number) - = text_field_tag "q[number_cont]", nil, 'ng-model' => 'q.number_cont' + = text_field_tag "q[number_cont]", nil, "ng-model" => "q.number_cont", "ng-keypress" => "$event.keyCode === 13 && fetchResults()" .field = label_tag nil, t(:email) - = email_field_tag "q[email_cont", nil, 'ng-model' => 'q.email_cont' + = email_field_tag "q[email_cont", nil, "ng-model" => "q.email_cont", "ng-keypress" => "$event.keyCode === 13 && fetchResults()" .four.columns .field = label_tag nil, t(:first_name_begins_with) - = text_field_tag "q[bill_address_firstname_start]", nil, size: 25, 'ng-model' => 'q.bill_address_firstname_start' + = text_field_tag "q[bill_address_firstname_start]", nil, size: 25, "ng-model" => "q.bill_address_firstname_start", "ng-keypress" => "$event.keyCode === 13 && fetchResults()" .field = label_tag nil, t(:last_name_begins_with) - = text_field_tag "q[bill_address_lastname_start]", nil, size: 25, 'ng-model' => 'q.bill_address_lastname_start' + = text_field_tag "q[bill_address_lastname_start]", nil, size: 25, "ng-model" => "q.bill_address_lastname_start", "ng-keypress" => "$event.keyCode === 13 && fetchResults()" .omega.four.columns .field.checkbox %label diff --git a/app/views/spree/admin/orders/_shipment_manifest.html.haml b/app/views/spree/admin/orders/_shipment_manifest.html.haml index fb9e51148f..2706d1bfd6 100644 --- a/app/views/spree/admin/orders/_shipment_manifest.html.haml +++ b/app/views/spree/admin/orders/_shipment_manifest.html.haml @@ -4,7 +4,7 @@ - if line_item.present? %tr.stock-item{ "data-item-quantity" => "#{item.quantity}" } %td.item-image - = mini_image(item.variant) + = render 'spree/shared/variant_thumbnail', variant: item.variant %td.item-name = item.variant.product_and_full_name %td.item-price.align-center diff --git a/app/views/spree/admin/orders/invoice.html.haml b/app/views/spree/admin/orders/invoice.html.haml index bc2b4dc556..ff4a930e5a 100644 --- a/app/views/spree/admin/orders/invoice.html.haml +++ b/app/views/spree/admin/orders/invoice.html.haml @@ -33,16 +33,19 @@ %td{ align: "left" } %strong= "#{t('.to')}:" %br - = @order.bill_address.full_name + - if @order.bill_address + = @order.bill_address.full_name - if @order.andand.customer.andand.code.present? %br = "#{t('.code')}: #{@order.customer.code}" %br - = @order.bill_address.full_address + - if @order.bill_address + = @order.bill_address.full_address %br - if @order.andand.customer.andand.email.present? = "#{@order.customer.email}," - = "#{@order.bill_address.phone}" + - if @order.bill_address + = "#{@order.bill_address.phone}" %td   %td{ align: "left", style: "border-left: .1em solid black; padding-left: 1em" } diff --git a/app/views/spree/admin/orders/invoice2.html.haml b/app/views/spree/admin/orders/invoice2.html.haml index 7964f80e56..b9715c2770 100644 --- a/app/views/spree/admin/orders/invoice2.html.haml +++ b/app/views/spree/admin/orders/invoice2.html.haml @@ -44,14 +44,17 @@ %td{ :align => "right" } = t :invoice_billing_address %br - %strong= @order.bill_address.full_name + - if @order.bill_address + %strong= @order.bill_address.full_name - if @order.andand.customer.andand.code.present? %br = "Code: #{@order.customer.code}" %br - = @order.bill_address.address_part1 + - if @order.bill_address + = @order.bill_address.address_part1 %br - = @order.bill_address.address_part2 + - if @order.bill_address + = @order.bill_address.address_part2 = render 'spree/admin/orders/invoice_table2' diff --git a/app/views/spree/admin/products/index/_filters.html.haml b/app/views/spree/admin/products/index/_filters.html.haml index 5aeaf5e4f9..0e7d47543d 100644 --- a/app/views/spree/admin/products/index/_filters.html.haml +++ b/app/views/spree/admin/products/index/_filters.html.haml @@ -5,7 +5,7 @@ .quick_search.three.columns.alpha %label{ for: 'quick_filter' } %br - %input.quick-search.fullwidth{ ng: {model: 'query'}, name: "quick_filter", type: 'text', placeholder: t('admin.quick_search') } + %input.quick-search.fullwidth{ ng: {model: 'query'}, name: "quick_filter", type: 'text', placeholder: t('admin.quick_search'), "ng-keypress" => "$event.keyCode === 13 && fetchProducts()" } .one.columns   .filter_select.three.columns %label{ for: 'producer_filter' }= t 'producer' diff --git a/app/views/spree/admin/products/new.html.haml b/app/views/spree/admin/products/new.html.haml index 34665fbdcb..908f30b5b9 100644 --- a/app/views/spree/admin/products/new.html.haml +++ b/app/views/spree/admin/products/new.html.haml @@ -83,7 +83,7 @@ %fieldset.no-border-bottom{ id: "image" } %legend{align: "center"}= t(".image") .row - = image_tag "noimage/product.png", class: "four columns alpha" + = image_tag "/noimage/product.png", class: "four columns alpha" .row = f.fields_for 'images_attributes[]', f.object.images.build do |image_fields| = image_fields.file_field :attachment diff --git a/app/views/spree/admin/reports/bulk_coop.html.haml b/app/views/spree/admin/reports/bulk_coop.html.haml deleted file mode 100644 index c644e8dd6e..0000000000 --- a/app/views/spree/admin/reports/bulk_coop.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -= form_for @report.search, :url => spree.bulk_coop_admin_reports_path do |f| - = render 'date_range_form', f: f - - .row - .four.columns.alpha - = label_tag nil, t(:report_distributor) - = f.collection_select(:distributor_id_eq, @distributors, :id, :name, {:include_blank => t(:all)}, {:class => "select2 fullwidth"}) - = label_tag nil, t(:report_type) - %br - = select_tag(:report_type, options_for_select([:bulk_coop_supplier_report, :bulk_coop_allocation, :bulk_coop_packing_sheets, :bulk_coop_customer_payments].map{ |e| [t(".#{e}"), e] }, @report_type)) - %br - %br - = check_box_tag :csv - = label_tag :csv, t(:report_customers_csv) - %br - %br - = button t(:search) - -= render "table", id: "listing_orders", msg_option: t(:search) diff --git a/app/views/spree/admin/variants/_autocomplete.js.erb b/app/views/spree/admin/variants/_autocomplete.js.erb index 9e4c29a50a..c4d1f8d96a 100644 --- a/app/views/spree/admin/variants/_autocomplete.js.erb +++ b/app/views/spree/admin/variants/_autocomplete.js.erb @@ -4,7 +4,7 @@ {{#if variant.image }} {{ else }} - + {{/if}} diff --git a/app/views/spree/layouts/_admin_body.html.haml b/app/views/spree/layouts/_admin_body.html.haml index d5aa05dbee..7b1d643dee 100644 --- a/app/views/spree/layouts/_admin_body.html.haml +++ b/app/views/spree/layouts/_admin_body.html.haml @@ -66,4 +66,4 @@ %div{"data-hook" => "admin_footer_scripts"} %script - = raw "Spree.api_key = \"#{try_spree_current_user.try(:spree_api_key).to_s}\";" + = raw "Spree.api_key = \"#{spree_current_user.try(:spree_api_key).to_s}\";" diff --git a/app/views/spree/orders/_bought.html.haml b/app/views/spree/orders/_bought.html.haml index e739caeff2..f7030b31a8 100644 --- a/app/views/spree/orders/_bought.html.haml +++ b/app/views/spree/orders/_bought.html.haml @@ -16,10 +16,7 @@ %td.cart-item-description %div.item-thumb-image - - if variant.images.length == 0 - = mini_image(variant.product) - - else - = image_tag(variant.images.first.attachment.url(:mini)) + = render 'spree/shared/variant_thumbnail', variant: variant = render 'spree/shared/line_item_name', line_item: line_item %span.already-confirmed= t(:orders_bought_already_confirmed) diff --git a/app/views/spree/orders/_line_item.html.haml b/app/views/spree/orders/_line_item.html.haml index 918a520fdf..19292cb219 100644 --- a/app/views/spree/orders/_line_item.html.haml +++ b/app/views/spree/orders/_line_item.html.haml @@ -2,10 +2,7 @@ %td.cart-item-description{'data-hook' => "cart_item_description"} %div.item-thumb-image{"data-hook" => "cart_item_image"} - - if variant.images.length == 0 - = mini_image(variant.product) - - else - = image_tag(variant.images.first.attachment.url(:mini)) + = render 'spree/shared/variant_thumbnail', variant: variant = render 'spree/shared/line_item_name', line_item: line_item diff --git a/app/views/spree/orders/_summary.html.haml b/app/views/spree/orders/_summary.html.haml index 670a9b6ec7..f1c25fea61 100644 --- a/app/views/spree/orders/_summary.html.haml +++ b/app/views/spree/orders/_summary.html.haml @@ -16,11 +16,7 @@ %td(data-hook = "order_item_description") %div.item-thumb-image{"data-hook" => "order_item_image"} - - if item.variant.images.length == 0 - = mini_image(item.variant.product) - - else - = image_tag(item.variant.images.first.attachment.url(:mini)) - + = render 'spree/shared/variant_thumbnail', variant: item.variant = render 'spree/shared/line_item_name', line_item: item diff --git a/app/views/spree/orders/form/_update_buttons.html.haml b/app/views/spree/orders/form/_update_buttons.html.haml index 5757d1742f..476e7d23d6 100644 --- a/app/views/spree/orders/form/_update_buttons.html.haml +++ b/app/views/spree/orders/form/_update_buttons.html.haml @@ -16,7 +16,7 @@ %i.ofn-i_009-close = t(:cancel_order) .columns.small-12.medium-3 - = button_tag :class => 'button primary radius expand', :id => 'update-button', "ng-disabled" => 'update_order_form.$pristine' do + = button_tag :class => 'button primary expand', :id => 'update-button', "ng-disabled" => 'update_order_form.$pristine' do %i.ofn-i_051-check-big %span{ ng: { show: 'update_order_form.$dirty' } }= t(:save_changes) %span{ ng: { hide: 'update_order_form.$dirty' } }= t(:order_saved) diff --git a/app/views/spree/shared/_variant_thumbnail.html.haml b/app/views/spree/shared/_variant_thumbnail.html.haml new file mode 100644 index 0000000000..719725c927 --- /dev/null +++ b/app/views/spree/shared/_variant_thumbnail.html.haml @@ -0,0 +1,4 @@ +- if variant.product.images.length == 0 + = image_tag("/noimage/mini.png") +- else + = image_tag(variant.product.images.first.attachment.url(:mini)) diff --git a/config/application.rb b/config/application.rb index d48b5d2b8b..3d061b97be 100644 --- a/config/application.rb +++ b/config/application.rb @@ -49,41 +49,36 @@ module Openfoodnetwork # Register Spree calculators initializer 'spree.register.calculators' do |app| app.config.spree.calculators.shipping_methods = [ - Spree::Calculator::FlatPercentItemTotal, - Spree::Calculator::FlatRate, - Spree::Calculator::FlexiRate, - Spree::Calculator::PerItem, - Spree::Calculator::PriceSack, + Calculator::FlatPercentItemTotal, + Calculator::FlatRate, + Calculator::FlexiRate, + Calculator::PerItem, + Calculator::PriceSack, Calculator::Weight ] app.config.spree.calculators.add_class('enterprise_fees') config.spree.calculators.enterprise_fees = [ Calculator::FlatPercentPerItem, - Spree::Calculator::FlatRate, - Spree::Calculator::FlexiRate, - Spree::Calculator::PerItem, - Spree::Calculator::PriceSack, + Calculator::FlatRate, + Calculator::FlexiRate, + Calculator::PerItem, + Calculator::PriceSack, Calculator::Weight ] + app.config.spree.calculators.add_class('payment_methods') config.spree.calculators.payment_methods = [ - Spree::Calculator::FlatPercentItemTotal, - Spree::Calculator::FlatRate, - Spree::Calculator::FlexiRate, - Spree::Calculator::PerItem, - Spree::Calculator::PriceSack + Calculator::FlatPercentItemTotal, + Calculator::FlatRate, + Calculator::FlexiRate, + Calculator::PerItem, + Calculator::PriceSack ] - end - # Every splitter (except Base splitter) will split the order in multiple packages - # Each package will generate a separate shipment in the order - # Base splitter does not split the packages - # So, because in OFN we have locked orders to have only one shipment, - # we must use this splitter and no other - initializer "spree.register.stock_splitters" do |app| - app.config.spree.stock_splitters = [ - Spree::Stock::Splitter::Base + app.config.spree.calculators.add_class('tax_rates') + config.spree.calculators.tax_rates = [ + Calculator::DefaultTax ] end diff --git a/config/initializers/spree.rb b/config/initializers/spree.rb index cc2ff359ba..6ff230c5f5 100644 --- a/config/initializers/spree.rb +++ b/config/initializers/spree.rb @@ -23,14 +23,13 @@ require "#{Rails.root}/app/models/spree/gateway_decorator" Spree.config do |config| config.shipping_instructions = true config.address_requires_state = true - config.admin_interface_logo = 'ofn-logo.png' + config.admin_interface_logo = '/default_images/ofn-logo.png' # -- spree_paypal_express # Auto-capture payments. Without this option, payments must be manually captured in the paypal interface. config.auto_capture = true #config.override_actionmailer_config = false - config.package_factory = Stock::Package config.order_updater_decorator = OrderUpdater # S3 settings diff --git a/config/locales/ar.yml b/config/locales/ar.yml index 9af7be11b0..a2dc262509 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -177,8 +177,7 @@ ar: title: فشل آخر (%{count} أوامر) explainer: فشلت المعالجة التلقائية لهذه الطلبات لسبب غير معروف. لا ينبغي أن يحدث هذا ، يرجى الاتصال بنا إذا كنت ترى هذا. home: "OFN" - title: شبكة الغذاء المفتوح - welcome_to: 'مرحبا بك في' + title: "شبكة الغذاء المفتوح" site_meta_description: "نبدأ من الألف إلى الياء. مع المزارعين والمزارعين على استعداد لرواية قصصهم بفخر وحق. مع الموزعين على استعداد لتوصيل الأشخاص بالمنتجات بطريقة عادلة وبصدق. مع المشترين الذين يعتقدون أن أفضل قرارات التسوق الأسبوعية يمكن ..." search_by_name: البحث بالاسم أو الضاحية ... producers_join: المنتجون الأستراليون مدعوون الآن للانضمام إلى شبكة الغذاء المفتوح. @@ -266,6 +265,7 @@ ar: backordered: "طلبات متأخرة" on hand: "متوفر" ship: "الشحن" + shipping_category: "نوع الشحن" actions: create_and_add_another: "إنشاء وإضافة آخر" create: "انشاء" @@ -1134,13 +1134,13 @@ ar: menu: cart: cart: "سلة" + cart_sidebar: + checkout: "تابع للخروج" + close: "إغلاق" signed_in: profile: "الملف الشخصي" mobile_menu: cart: "سلة" - joyride: - checkout: "الخروج الآن" - already_ordered_products: "تم الطلب بالفعل في دورة الطلب هذه" register_call: selling_on_ofn: "هل أنت مهتم بالحصول على شبكة الغذاء المفتوح؟" register: "سجل هنا" @@ -1691,8 +1691,7 @@ ar: password: كلمه السر remember_me: تذكرنى are_you_sure: "هل أنت واثق؟" - orders_open: الطلب مفتوح - closing: "إغلاق" + orders_open: "الطلب مفتوح" going_back_to_home_page: "الرجوع إلى الصفحة الرئيسية" creating: انشاء updating: تحديث @@ -2709,6 +2708,7 @@ ar: previous: "السابق" last: "الاخير" spree: + more: "أكثر" your_order_is_empty_add_product: "طلبك فارغ ، يرجى البحث عن المنتج أعلاه وإضافته" add_product: "أضف منتج" name_or_sku: "الاسم أو SKU (أدخل أول 4 أحرف على الأقل من اسم المنتج)" diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 55204a9c95..2194636d75 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -43,7 +43,7 @@ ca: after_orders_open_at: s'ha de fer amb el termini obert variant_override: count_on_hand: - using_producer_stock_settings_but_count_on_hand_set: "ha d'estar en blanc perquè s'utilitza la configuració de l'inventari de la productora" + using_producer_stock_settings_but_count_on_hand_set: "ha d'estar en blanc perquè s'utilitza la configuració d'estoc de la productora" on_demand_but_count_on_hand_set: "ha d'estar en blanc si és sota demanda" limited_stock_but_no_count_on_hand: "cal especificar-se perquè força existències limitades" activemodel: @@ -181,8 +181,8 @@ ca: title: Altres errors (%{count} comandes) explainer: El processament automàtic d'aquestes comandes va fallar per un motiu desconegut. Això no hauria de passar, si us plau, contacteu amb nosaltres si esteu veient això. home: "OFN" - title: Open Food Network - welcome_to: 'Benvingut a ' + title: "Open Food Network" + welcome_to: "Benvingut a " site_meta_description: "Comencem des de baix. Amb agricultors i productors disposats a explicar les seves històries amb orgull i sinceritat. Amb distribuïdors connectant persones i productes de manera justa i honesta. Amb compradors que creuen que millors decisions de compra setmanal poden ..." search_by_name: Cercar per nom o barri ... producers_join: Els productors australians son ara benvinguts a unir-se a Open Food Network. @@ -270,6 +270,7 @@ ca: backordered: "Reabastit" on hand: "Disponibles" ship: "Enviament" + shipping_category: "Categoria d'enviament" actions: create_and_add_another: "Crea i afegeix-ne una altra" create: "Crear" @@ -898,7 +899,7 @@ ca: incoming: "2. Productes entrants" outgoing: "3. Productes sortints" exchange_form: - pickup_time_tip: Quan les comandes d'aquest cicle de comandes estiguin preparades per a les consumidores + pickup_time_tip: Quan les comandes d'aquest cicle de comandes estiguin llestes per a les consumidores pickup_instructions_placeholder: "Instruccions de recollida" pickup_instructions_tip: Aquestes instruccions es mostraran a les consumidores després d'haver completat una comanda pickup_time_placeholder: "Preparat per (és a dir, data / hora)" @@ -948,7 +949,7 @@ ca: distributors: distribuïdores variants: variants simple_form: - ready_for: Preparat per + ready_for: Llest per ready_for_placeholder: Data / hora customer_instructions: Instruccions de la consumidora customer_instructions_placeholder: Notes de recollida o de lliurament @@ -1109,7 +1110,7 @@ ca: pause_failure_msg: "Ho sentim, la pausa ha fallat!" confirm_unpause_msg: "Si teniu un cicle de comanda obert a la programació d'aquesta subscripció, es crearà una comanda per a aquesta consumidora. Esteu segur que voleu anul·lar aquesta subscripció?" unpause_failure_msg: "Ho sentim, la represa ha fallat!" - confirm_cancel_open_orders_msg: "Actualment hi ha algunes comandes obertes per a aquesta subscripció. Ja s'ha notificat a les consumidores que les comandes serà atesa. Voleu cancel·lar aquestes comandes o conservar-les?" + confirm_cancel_open_orders_msg: "Actualment hi ha algunes comandes obertes per a aquesta subscripció. Ja s'ha notificat a les consumidores que la comanda seran ateses. Voleu cancel·lar aquestes comandes o conservar-les?" resume_canceled_orders_msg: "Algunes comandes d'aquesta subscripció es poden reprendre ara mateix. Podeu reprendre-les des del menú desplegable de comandes." yes_cancel_them: Cancel·lar-les no_keep_them: Conservar-les @@ -1150,13 +1151,18 @@ ca: menu: cart: cart: "Cistella" + cart_sidebar: + checkout: "Realitza la comanda" + edit_cart: "Edita la cistella" + items_in_cart_singular: "%{num} item a la teva cistella" + items_in_cart_plural: "%{num} item a la teva cistella" + close: "Tanca" + cart_empty: "La teva cistella és buida" + take_me_shopping: "Porta’m de compres!" signed_in: profile: "Perfil" mobile_menu: cart: "Cistella" - joyride: - checkout: "Validar ara" - already_ordered_products: "Ja està demanat en aquest cicle de comanda" register_call: selling_on_ofn: "Estàs interessat en formar part d'Open Food Network?" register: "Registra't aquí" @@ -1387,7 +1393,7 @@ ca: stats_orders: "comandes de menjar" checkout_title: Realitza la comanda checkout_now: Validar ara - checkout_order_ready: Comanda preparada per + checkout_order_ready: Comanda llesta per checkout_hide: Amaga checkout_expand: Expandeix checkout_headline: "Estàs preparada per validar la compra?" @@ -1399,7 +1405,7 @@ ca: checkout_default_ship_address: "Desa per defecte com a adreça d'enviament " checkout_method_free: Gratuït checkout_address_same: L'adreça d'enviament és igual que l'adreça de facturació? - checkout_ready_for: "Preparat per:" + checkout_ready_for: "Llest per:" checkout_instructions: "Alguns comentaris o instruccions especials?" checkout_payment: Pagament checkout_send: Realitza una comanda ara @@ -1522,7 +1528,7 @@ ca: shopping_groups_part_of: "forma part de:" shopping_producers_of_hub: "Productores de%{hub}:" enterprises_next_closing: "Tancament de la comanda següent" - enterprises_ready_for: "Preparat per" + enterprises_ready_for: "Llest per" enterprises_choose: "Escull quan vols la teva comanda:" maps_open: "Obert" maps_closed: "Tancat" @@ -1667,7 +1673,7 @@ ca: orders_fees: Comissions... orders_edit_title: Cistella de la compra orders_edit_headline: El teu cistell de la compra - orders_edit_time: Comanda preparada per + orders_edit_time: Comanda llesta per orders_edit_continue: Continuar comprant orders_edit_checkout: Realitza la compra orders_form_empty_cart: "Cistella buida" @@ -1710,7 +1716,7 @@ ca: password: Contrasenya remember_me: Recorda'm are_you_sure: "Estàs segur?" - orders_open: Comandes obertes + orders_open: "Comandes obertes" closing: "Tancant" going_back_to_home_page: "Tornant a la pàgina d'inici" creating: Creant @@ -2069,6 +2075,7 @@ ca: hub_sidebar_at_least: "Cal seleccionar almenys un grup " hub_sidebar_blue: "blau" hub_sidebar_red: "vermell" + order_cycles_closed_for_hub: "La botiga que has triat està temporalment tancada per a fer comandes. Sisplau, prova una mica més tard" report_customers_distributor: "Distribuïdora" report_customers_supplier: "Proveïdora" report_customers_cycle: "Cicle de Comanda" @@ -2305,6 +2312,7 @@ ca: order_cycles_email_to_producers_notice: 'Els correus electrònics per enviar a les productores estan en cua.' order_cycles_no_permission_to_coordinate_error: "Cap de les vostres organitzacions té permís per coordinar un cicle de comanda" order_cycles_no_permission_to_create_error: "No teniu permís per crear un cicle de comandes coordinat per aquesta organització" + order_cycle_closed: "El cicle de comandes que has triat acaba de tancar. Sisplau prova en un altre moment." back_to_orders_list: "Torna a la llista de comandes" no_orders_found: "No s'han trobat comandes" order_information: "Informació de la comanda" @@ -2560,7 +2568,7 @@ ca: 'yes': "Sota demanda" variant_overrides: on_demand: - use_producer_settings: "Utilitzeu la configuració d'inventari de la productora" + use_producer_settings: "Utilitzeu la configuració d'estoc de la productora" 'yes': "Sí" 'no': "No" inventory_products: "Productes de l'inventari" @@ -2773,6 +2781,7 @@ ca: previous: "Anterior" last: "Últim" spree: + more: "Més" your_order_is_empty_add_product: "La comanda està buida, si us plau cerca i afegeix un producte a dalt" add_product: "Afegeix un producte" name_or_sku: "Nom o codi (introduïu com a mínim els primers 4 caràcters del nom del producte)" diff --git a/config/locales/de_DE.yml b/config/locales/de_DE.yml index 94dd643337..de1588cf38 100644 --- a/config/locales/de_DE.yml +++ b/config/locales/de_DE.yml @@ -181,8 +181,7 @@ de_DE: title: Andere Fehler (%{count} Bestellungen) explainer: Die automatische Verarbeitung dieser Aufträge ist aus einem unbekannten Grund fehlgeschlagen. Dies sollte nicht geschehen, bitte kontaktieren Sie uns, wenn Sie dies sehen. home: "OFN" - title: Open Food Network - welcome_to: 'Willkommen bei' + title: "Open Food Network" site_meta_description: "Wir starten ganz grundsätzlich. Mit LandwirtInnen und GärtnerInnen, die stolz und ehrlich ihre Geschichte erzählen. Mit Lieferanten, die Menschen fair und vertrauenswürdig mit Produkten verbinden. Mit KonsumentInnen, die glauben, daß ihre wöchentlichen Einkaufsentscheidungen..." search_by_name: Suche nach Name oder Ort... producers_join: 'Wir laden Deutsche Produzenten ein, jetzt dem Open Food Network beizutreten. ' @@ -270,6 +269,7 @@ de_DE: backordered: "Nachbestellt" on hand: "Verfügbar" ship: "Liefern" + shipping_category: "Versandkategorie" actions: create_and_add_another: "Erstellen und weitere hinzufügen" create: "Neu" @@ -1142,13 +1142,13 @@ de_DE: menu: cart: cart: "Wagen" + cart_sidebar: + checkout: "Kasse" + close: "Abschließen" signed_in: profile: "Profil" mobile_menu: cart: "Wagen" - joyride: - checkout: "Zur Kasse" - already_ordered_products: "Bereits in diesem Bestellzyklus bestellt" register_call: selling_on_ofn: "Sie möchten selbst im Open Food Network verkaufen?" register: "Hier anmelden" @@ -1687,8 +1687,7 @@ de_DE: password: Passwort remember_me: Erinnere dich an mich are_you_sure: "Bist du sicher?" - orders_open: Bestellungen öffnen - closing: "Schließen" + orders_open: "Bestellungen öffnen" going_back_to_home_page: "Bring dich zurück auf die Homepage" creating: Erstellen updating: Aktualisierung @@ -2741,6 +2740,7 @@ de_DE: previous: "Bisherige" last: "Zuletzt" spree: + more: "Mehr" your_order_is_empty_add_product: "Ihre Bestellung ist leer. Suchen Sie oben ein Produkt und fügen Sie es hinzu" add_product: "Produkt hinzufügen" name_or_sku: "Name oder SKU (geben Sie mindestens die ersten 4 Zeichen des Produktnamens ein)" diff --git a/config/locales/en.yml b/config/locales/en.yml index 4b62644980..3726167337 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -207,8 +207,8 @@ en: explainer: Automatic processing of these orders failed for an unknown reason. This should not occur, please contact us if you are seeing this. home: "OFN" - title: Open Food Network - welcome_to: 'Welcome to ' + title: "Open Food Network" + welcome_to: "Welcome to" site_meta_description: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can…" search_by_name: Search by name or suburb... producers: 'Australian Producers' @@ -1839,8 +1839,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using password: Password remember_me: Remember Me are_you_sure: "Are you sure?" - orders_open: Orders open - closing: "Closing " + orders_open: "Orders open" + closing: "Closing" going_back_to_home_page: "Taking you back to the home page" creating: Creating updating: Updating diff --git a/config/locales/en_AU.yml b/config/locales/en_AU.yml index c940313002..528ee44b6d 100644 --- a/config/locales/en_AU.yml +++ b/config/locales/en_AU.yml @@ -175,8 +175,7 @@ en_AU: title: Other Failure (%{count} orders) explainer: Automatic processing of these orders failed for an unknown reason. This should not occur, please contact us if you are seeing this. home: "OFN" - title: Open Food Network - welcome_to: 'Welcome to ' + title: "Open Food Network" site_meta_description: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can…" search_by_name: Search by name or suburb... producers_join: Australian producers are now welcome to join the Open Food Network. @@ -262,6 +261,7 @@ en_AU: backordered: "Backordered" on hand: "On Hand" ship: "Ship" + shipping_category: "Shipping Category" actions: create_and_add_another: "Create and Add Another" create: "Create" @@ -704,14 +704,11 @@ en_AU: shopfront_message: "\"Home\" message" shopfront_message_placeholder: > Create your home page content to welcome customers and explain how people - can shop with you. - - Include details about your delivery and pick up options, how often you - open the shop for orders, and all the details your customers will need - to understand the process of buying from you. - - You can also include links to your newsletter sign up, so that people - can connect with you to hear when your next order cycle opens. + can shop with you. Include details about your delivery and pick up options, + how often you open the shop for orders, and all the details your customers + will need to understand the process of buying from you. You can also + include links to your newsletter sign up, so that people can connect + with you to hear when your next order cycle opens. shopfront_message_link_tooltip: "Insert / edit link" shopfront_message_link_prompt: "Please enter a URL to insert" shopfront_closed_message: "Shopfront Closed Message" @@ -1141,13 +1138,13 @@ en_AU: menu: cart: cart: "Cart" + cart_sidebar: + checkout: "Checkout" + close: "Close" signed_in: profile: "Profile" mobile_menu: cart: "Cart" - joyride: - checkout: "Checkout now" - already_ordered_products: "Already ordered in this order cycle" register_call: selling_on_ofn: "Interested in getting on the Open Food Network?" register: "Register here" @@ -1688,8 +1685,7 @@ en_AU: password: Password remember_me: Remember Me are_you_sure: "Are you sure?" - orders_open: Orders open - closing: "Closing " + orders_open: "Orders open" going_back_to_home_page: "Taking you back to the home page" creating: Creating updating: Updating @@ -2660,6 +2656,7 @@ en_AU: previous: "Previous" last: "Last" spree: + more: "More" your_order_is_empty_add_product: "Your order is empty, please search for and add a product above" add_product: "Add Product" name_or_sku: "Name or SKU (enter at least first 4 characters of product name)" diff --git a/config/locales/en_BE.yml b/config/locales/en_BE.yml index 4b4d63ac8f..ff1dc8bbc1 100644 --- a/config/locales/en_BE.yml +++ b/config/locales/en_BE.yml @@ -172,8 +172,7 @@ en_BE: title: Other Failure (%{count} orders) explainer: Automatic processing of these orders failed for an unknown reason. This should not occur, please contact us if you are seeing this. home: "OFN" - title: Open Food Network - welcome_to: 'Welcome to ' + title: "Open Food Network" site_meta_description: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can…" search_by_name: Search by name or suburb... producers_join: Australian producers are now welcome to join the Open Food Network. @@ -257,6 +256,7 @@ en_BE: back_to_payments_list: "Back to Payments List" on hand: "On Hand" ship: "Ship" + shipping_category: "Shipping Category" actions: create_and_add_another: "Create and Add Another" create: "Create" @@ -1099,13 +1099,13 @@ en_BE: menu: cart: cart: "Cart" + cart_sidebar: + checkout: "Checkout" + close: "Close" signed_in: profile: "Profile" mobile_menu: cart: "Cart" - joyride: - checkout: "Checkout now" - already_ordered_products: "Already ordered in this order cycle" register_call: selling_on_ofn: "Interested in getting on the Open Food Network?" register: "Register here" @@ -1645,8 +1645,7 @@ en_BE: password: Password remember_me: Remember Me are_you_sure: "Are you sure?" - orders_open: Orders open - closing: "Closing " + orders_open: "Orders open" going_back_to_home_page: "Taking you back to the home page" creating: Creating updating: Updating @@ -2616,6 +2615,7 @@ en_BE: previous: "Previous" last: "Last" spree: + more: "More" your_order_is_empty_add_product: "Your order is empty, please search for and add a product above" add_product: "Add Product" name_or_sku: "Name or SKU (enter at least first 4 characters of product name)" diff --git a/config/locales/en_CA.yml b/config/locales/en_CA.yml index 8af92b5472..3750932c8e 100644 --- a/config/locales/en_CA.yml +++ b/config/locales/en_CA.yml @@ -181,8 +181,8 @@ en_CA: title: Other Failure (%{count} orders) explainer: Automatic processing of these orders failed for an unknown reason. This should not occur, please contact us if you are seeing this. home: "OFN" - title: Open Food Network - welcome_to: 'Welcome to ' + title: "Open Food Network" + welcome_to: "Welcome to" site_meta_description: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can…" search_by_name: Search by name or city producers_join: Producers are welcome to join the Open Food Network. @@ -270,6 +270,7 @@ en_CA: backordered: "Backordered" on hand: "On Hand" ship: "Ship" + shipping_category: "Shipping Category" actions: create_and_add_another: "Create and Add Another" create: "Create" @@ -479,6 +480,7 @@ en_CA: line_number: "Line %{number}:" encoding_error: "Please check the language setting of your source file and ensure it is saved with UTF-8 encoding" unexpected_error: "Product Import encountered an unexpected error whilst opening the file: %{error_message}" + malformed_csv: "Product Import encountered a malformed CSV: %{error_message}" index: notice: "Notice" beta_notice: "This feature is still in beta: you may experience some errors while using it. Please don't hesitate to contact support." @@ -1146,13 +1148,18 @@ en_CA: menu: cart: cart: "Cart" + cart_sidebar: + checkout: "Checkout" + edit_cart: "Edit cart" + items_in_cart_singular: "%{num}item in your cart" + items_in_cart_plural: "%{num}items in your cart" + close: "Close" + cart_empty: "Your cart is empty" + take_me_shopping: "Take me shopping!" signed_in: profile: "Profile" mobile_menu: cart: "Cart" - joyride: - checkout: "Checkout now" - already_ordered_products: "Already ordered in this order cycle" register_call: selling_on_ofn: "Interested in getting on the Open Food Network?" register: "Register here" @@ -1706,8 +1713,8 @@ en_CA: password: Password remember_me: Remember Me are_you_sure: "Are you sure?" - orders_open: Orders open - closing: "Closing " + orders_open: "Orders open" + closing: "Closing" going_back_to_home_page: "Taking you back to the home page" creating: Creating updating: Updating @@ -2065,6 +2072,7 @@ en_CA: hub_sidebar_at_least: "At least one hub must be selected" hub_sidebar_blue: "blue" hub_sidebar_red: "red" + order_cycles_closed_for_hub: "The hub you have selected is temporarily closed for orders. Please try again later." report_customers_distributor: "Distributor" report_customers_supplier: "Supplier" report_customers_cycle: "Order Cycle" @@ -2301,6 +2309,7 @@ en_CA: order_cycles_email_to_producers_notice: 'Emails to be sent to producers have been queued for sending.' 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. Please try again!" back_to_orders_list: "Back to order list" no_orders_found: "No Orders Found" order_information: "Order Information" @@ -2765,6 +2774,7 @@ en_CA: previous: "Previous" last: "Last" spree: + more: "More" your_order_is_empty_add_product: "Your order is empty, please search for and add a product above" add_product: "Add Product" name_or_sku: "Name or SKU (enter at least first 4 characters of product name)" @@ -2801,7 +2811,7 @@ en_CA: city: "City" zip: "Postal Code" country: "Country" - state: "Province" + state: "State" phone: "Phone" update: "Update" use_billing_address: "Use Billing Address" diff --git a/config/locales/en_DE.yml b/config/locales/en_DE.yml index 3f65f77b11..62e613451c 100644 --- a/config/locales/en_DE.yml +++ b/config/locales/en_DE.yml @@ -175,8 +175,7 @@ en_DE: title: Other Failure (%{count} orders) explainer: Automatic processing of these orders failed for an unknown reason. This should not occur, please contact us if you are seeing this. home: "OFN" - title: Open Food Network - welcome_to: 'Welcome to ' + title: "Open Food Network" site_meta_description: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can…" search_by_name: Search by name or suburb... producers_join: Australian producers are now welcome to join the Open Food Network. @@ -262,6 +261,7 @@ en_DE: backordered: "Backordered" on hand: "On Hand" ship: "Ship" + shipping_category: "Shipping Category" actions: create_and_add_another: "Create and Add Another" create: "Create" @@ -1109,13 +1109,13 @@ en_DE: menu: cart: cart: "Cart" + cart_sidebar: + checkout: "Checkout" + close: "Close" signed_in: profile: "Profile" mobile_menu: cart: "Cart" - joyride: - checkout: "Checkout now" - already_ordered_products: "Already ordered in this order cycle" register_call: selling_on_ofn: "Interested in getting on the Open Food Network?" register: "Register here" @@ -1655,8 +1655,7 @@ en_DE: password: Password remember_me: Remember Me are_you_sure: "Are you sure?" - orders_open: Orders open - closing: "Closing " + orders_open: "Orders open" going_back_to_home_page: "Taking you back to the home page" creating: Creating updating: Updating @@ -2627,6 +2626,7 @@ en_DE: previous: "Previous" last: "Last" spree: + more: "More" your_order_is_empty_add_product: "Your order is empty, please search for and add a product above" add_product: "Add Product" name_or_sku: "Name or SKU (enter at least first 4 characters of product name)" diff --git a/config/locales/en_FR.yml b/config/locales/en_FR.yml index 7e58bae598..c514500eff 100644 --- a/config/locales/en_FR.yml +++ b/config/locales/en_FR.yml @@ -181,8 +181,8 @@ en_FR: title: Other Failure (%{count} orders) explainer: Automatic processing of these orders failed for an unknown reason. This should not occur, please contact us if you are seeing this. home: "OFN" - title: Open Food Network - welcome_to: 'Welcome to ' + title: "Open Food Network" + welcome_to: "Welcome to" site_meta_description: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can…" search_by_name: Search by name or suburb... producers_join: Producers are now welcome to join the Open Food Network. @@ -270,6 +270,7 @@ en_FR: backordered: "Backordered" on hand: "On Hand" ship: "Ship" + shipping_category: "Shipping Category" actions: create_and_add_another: "Create and Add Another" create: "Create" @@ -1712,8 +1713,8 @@ en_FR: password: Password remember_me: Remember Me are_you_sure: "Are you sure?" - orders_open: Orders open - closing: "Closing " + orders_open: "Orders open" + closing: "Closing" going_back_to_home_page: "Taking you back to the home page" creating: Creating updating: Updating @@ -2071,6 +2072,7 @@ en_FR: hub_sidebar_at_least: "At least one hub must be selected" hub_sidebar_blue: "blue" hub_sidebar_red: "red" + order_cycles_closed_for_hub: "The hub you have selected is temporarily closed for orders. Please try again later." report_customers_distributor: "Distributor" report_customers_supplier: "Supplier" report_customers_cycle: "Order Cycle" @@ -2307,6 +2309,7 @@ en_FR: order_cycles_email_to_producers_notice: 'Emails to be sent to producers have been queued for sending.' 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. Please try again!" back_to_orders_list: "Back to order list" no_orders_found: "No Orders Found" order_information: "Order Information" @@ -2772,6 +2775,7 @@ en_FR: previous: "Previous" last: "Last" spree: + more: "More" your_order_is_empty_add_product: "Your order is empty, please search for and add a product above" add_product: "Add Product" name_or_sku: "Name or SKU (enter at least first 4 characters of product name)" diff --git a/config/locales/en_GB.yml b/config/locales/en_GB.yml index 5d928e780f..35a9dc1a31 100644 --- a/config/locales/en_GB.yml +++ b/config/locales/en_GB.yml @@ -181,8 +181,8 @@ en_GB: title: Other Failure (%{count} orders) explainer: Automatic processing of these orders failed for an unknown reason. This should not occur, please contact us if you are seeing this. home: "OFN" - title: Open Food Network - welcome_to: 'Welcome to ' + title: "Open Food Network" + welcome_to: "Welcome to" site_meta_description: "The Open Food Network software platform allows farmers to sell produce online, at a price that works for them. It has been built specifically for selling food so it can handle tricky measures or stock levels that only food has - a dozen eggs, a bunch of parsley, a whole chicken that varies in weight…" search_by_name: Search by name, town, county or postcode... producers_join: UK producers are now welcome to join Open Food Network UK. @@ -270,6 +270,7 @@ en_GB: backordered: "Backordered" on hand: "In Stock" ship: "Ship" + shipping_category: "Shipping Category" actions: create_and_add_another: "Create and Add Another" create: "Create" @@ -370,7 +371,10 @@ en_GB: title: "Matomo Settings" matomo_url: "Matomo URL" matomo_site_id: "Matomo Site ID" + matomo_tag_manager_url: "Matomo Tag Manager URL" + info_html: "Matomo is a Web and Mobile Analytics application. You can either host Matomo on-premises or use a cloud-hosted service. See matomo.org for more information." config_instructions_html: "Here you can configure the OFN Matomo integration. The Matomo URL below should point to the Matomo instance where the user tracking information will be sent to; if it is left empty, Matomo user tracking will be disabled. The Site ID field is not mandatory but useful if you are tracking more than one website on a single Matomo instance; it can be found on the Matomo instance console." + config_instructions_tag_manager_html: "Setting the Matomo Tag Manager URL enables Matomo Tag Manager. This tool allows you to set up analytics events. The Matomo Tag Manager URL is copied from the Install Code section of Matomo Tag Manager. Ensure you select the right container and environment as these options change the URL." customers: index: new_customer: "New Customer" @@ -476,6 +480,7 @@ en_GB: line_number: "Line %{number}:" encoding_error: "Please check the language setting of your source file and ensure it is saved with UTF-8 encoding" unexpected_error: "Product Import encountered an unexpected error whilst opening the file: %{error_message}" + malformed_csv: "Product Import found CSV was incorrectly formatted: %{error_message}" index: notice: "Notice" beta_notice: "This feature is still in beta: you may experience some errors while using it. Please don't hesitate to contact support." @@ -1143,13 +1148,18 @@ en_GB: menu: cart: cart: "Basket" + cart_sidebar: + checkout: "Checkout" + edit_cart: "Edit basket" + items_in_cart_singular: "%{num} item in your basket" + items_in_cart_plural: "%{num} items in your basket" + close: "Close" + cart_empty: "Your basket is empty" + take_me_shopping: "Take me shopping!" signed_in: profile: "Profile" mobile_menu: cart: "Basket" - joyride: - checkout: "Checkout now" - already_ordered_products: "Already ordered in this order cycle" register_call: selling_on_ofn: "Interested in selling through the Open Food Network?" register: "Register here" @@ -1703,8 +1713,8 @@ en_GB: password: Password remember_me: Remember Me are_you_sure: "Are you sure?" - orders_open: Orders open - closing: "Closing " + orders_open: "Orders open" + closing: "Closing" going_back_to_home_page: "Taking you back to the home page" creating: Creating updating: Updating @@ -2062,6 +2072,7 @@ en_GB: hub_sidebar_at_least: "At least one hub must be selected" hub_sidebar_blue: "blue" hub_sidebar_red: "red" + order_cycles_closed_for_hub: "The hub you have selected is temporarily closed for orders. Please try again later." report_customers_distributor: "Distributor" report_customers_supplier: "Supplier" report_customers_cycle: "Order Cycle" @@ -2298,6 +2309,7 @@ en_GB: order_cycles_email_to_producers_notice: 'Emails to be sent to producers have been queued for sending.' 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." back_to_orders_list: "Back to order list" no_orders_found: "No Orders Found" order_information: "Order Information" @@ -2769,6 +2781,7 @@ en_GB: previous: "Previous" last: "Last" spree: + more: "More" your_order_is_empty_add_product: "Your order is empty, please search for and add a product above" add_product: "Add Product" name_or_sku: "Name or SKU (enter at least first 4 characters of product name)" @@ -2817,6 +2830,12 @@ en_GB: void: "Void" login: "Login" password: "Password" + signature: "Signature" + solution: "Solution" + landing_page: "Landing Page" + server: "Server" + test_mode: "Test Mode" + logourl: "Logo url" configurations: "Configurations" general_settings: "General Settings" site_name: "Site Name" @@ -2933,6 +2952,12 @@ en_GB: options: "Options" actions: update: "Update" + shared: + error_messages: + errors_prohibited_this_record_from_being_saved: + one: "1 error prohibited this record from being saved:" + other: "%{count} errors prohibited this record from being saved:" + there_were_problems_with_the_following_fields: "There were problems with the following fields" errors: messages: blank: "can't be blank" @@ -3102,9 +3127,11 @@ en_GB: display: "Display" active: "Active" both: "Both" + front_end: "Checkout only" back_end: "Back office only" active_yes: "Yes" active_no: "No" + no_payment_methods_found: "No payment methods found" new: new_payment_method: "New Payment Method" back_to_payment_methods_list: "Back To Payment Methods List" @@ -3134,8 +3161,10 @@ en_GB: active_yes: "Yes" active_no: "No" both: "Both Checkout and Back office" + front_end: "Checkout only" back_end: "Back office only" tags: "Tags" + deactivation_warning: "De-activating a payment method can make the payment method disappear from your list. Alternatively, you can hide a payment method from the checkout page by setting the option 'Display' to 'back office only'." providers: provider: "Provider" payments: @@ -3306,6 +3335,14 @@ en_GB: invalid: invalid order_mailer: cancel_email: + customer_greeting: "Dear %{name}," + instructions_html: "Your order with %{distributor} has been CANCELED. Please retain this cancellation information for your records." + dont_cancel: "If you have changed your mind or don't wish to cancel this order please contact %{email}" + order_summary_canceled_html: "Order Summary #%{number} [CANCELED]" + details: "Here are the details of what you ordered:" + unpaid_order: "Your order was unpaid so no refund has been made" + paid_order: "Your order was paid so %{distributor} has refunded the full amount" + credit_order: "Your order was paid so your account has been credited" subject: "Cancellation of Order" confirm_email: subject: "Order Confirmation" diff --git a/config/locales/en_IE.yml b/config/locales/en_IE.yml index e9b243f1e0..c9112cfb88 100644 --- a/config/locales/en_IE.yml +++ b/config/locales/en_IE.yml @@ -181,8 +181,7 @@ en_IE: title: Other Failure (%{count} orders) explainer: Automatic processing of these orders failed for an unknown reason. This should not occur, please contact us if you are seeing this. home: "OFN" - title: Open Food Network - welcome_to: 'Welcome to ' + title: "Open Food Network" site_meta_description: "The Open Food Network software platform allows farmers to sell produce online, at a price that works for them. It has been built specifically for selling food so it can handle tricky measures or stock levels that only food has - a dozen eggs, a bunch of parsley, a whole chicken that varies in weight…" search_by_name: Search by name, town, county or eircode... producers_join: Producers are now welcome to join Open Food Network Ireland. @@ -270,6 +269,7 @@ en_IE: backordered: "Backordered" on hand: "In Stock" ship: "Ship" + shipping_category: "Shipping Category" actions: create_and_add_another: "Create and Add Another" create: "Create" @@ -370,7 +370,10 @@ en_IE: title: "Matomo Settings" matomo_url: "Matomo URL" matomo_site_id: "Matomo Site ID" + matomo_tag_manager_url: "Matomo Tag Manager URL" + info_html: "Matomo is a Web and Mobile Analytics application. You can either host Matomo on-premises or use a cloud-hosted service. See matomo.org for more information." config_instructions_html: "Here you can configure the OFN Matomo integration. The Matomo URL below should point to the Matomo instance where the user tracking information will be sent to; if it is left empty, Matomo user tracking will be disabled. The Site ID field is not mandatory but useful if you are tracking more than one website on a single Matomo instance; it can be found on the Matomo instance console." + config_instructions_tag_manager_html: "Setting the Matomo Tag Manager URL enables Matomo Tag Manager. This tool allows you to set up analytics events. The Matomo Tag Manager URL is copied from the Install Code section of Matomo Tag Manager. Ensure you select the right container and environment as these options change the URL." customers: index: new_customer: "New Customer" @@ -476,6 +479,7 @@ en_IE: line_number: "Line %{number}:" encoding_error: "Please check the language setting of your source file and ensure it is saved with UTF-8 encoding" unexpected_error: "Product Import encountered an unexpected error whilst opening the file: %{error_message}" + malformed_csv: "Product Import encountered a malformed CSV: %{error_message}" index: notice: "Notice" beta_notice: "This feature is still in beta: you may experience some errors while using it. Please don't hesitate to contact support." @@ -688,6 +692,7 @@ en_IE: ofn_uid_tip: The unique id used to identify the enterprise on Open Food Network. shipping_methods: name: "Name" + applies: "Active?" manage: "Manage Shipping Methods" create_button: "Create New Shipping Method" create_one_button: "Create One Now" @@ -1142,13 +1147,18 @@ en_IE: menu: cart: cart: "Basket" + cart_sidebar: + checkout: "Checkout" + edit_cart: "Edit cart" + items_in_cart_singular: "%{num} item in your cart" + items_in_cart_plural: "%{num} items in your cart" + close: "Close" + cart_empty: "Your cart is empty" + take_me_shopping: "Take me shopping!" signed_in: profile: "Profile" mobile_menu: cart: "Basket" - joyride: - checkout: "Checkout now" - already_ordered_products: "Already ordered in this order cycle" register_call: selling_on_ofn: "Interested in selling through the Open Food Network?" register: "Register here" @@ -1250,7 +1260,7 @@ en_IE: city: City city_placeholder: eg. Dublin postcode: Eircode - postcode_placeholder: eg. D01 F5P2 + postcode_placeholder: eg. D01 XXXX suburb: Town state: County country: Country @@ -1702,8 +1712,7 @@ en_IE: password: Password remember_me: Remember Me are_you_sure: "Are you sure?" - orders_open: Orders open - closing: "Closing " + orders_open: "Orders open" going_back_to_home_page: "Taking you back to the home page" creating: Creating updating: Updating @@ -1775,17 +1784,17 @@ en_IE: producer: "Great! First we need to know a little bit about your farm:" enterprise_name_field: "Enterprise Name:" producer_name_field: "Farm Name:" - producer_name_field_placeholder: "e.g. Charlie's Chilli Farm" + producer_name_field_placeholder: "e.g. Charlie's Farm" producer_name_field_error: "Sorry, this name has already been taken. Please try another." address1_field: "Address line 1:" - address1_field_placeholder: "e.g. 123 Apple Drive" + address1_field_placeholder: "e.g. 123 Apple Road" address1_field_error: "Please enter an address" address2_field: "Address line 2:" suburb_field: "Town:" - suburb_field_placeholder: "eg. Taunton" + suburb_field_placeholder: "eg. Kilbride" suburb_field_error: "Please enter a postal town" postcode_field: "Eircode:" - postcode_field_placeholder: "eg. D01 F5P2" + postcode_field_placeholder: "eg. D01 XXXX" postcode_field_error: "Eircode required" state_field: "County:" state_field_error: "County Required" @@ -1933,7 +1942,7 @@ en_IE: admin_enterprise_groups_contact_city: "Suburb" admin_enterprise_groups_contact_city_placeholder: "eg. Dublin" admin_enterprise_groups_contact_zipcode: "Postcode" - admin_enterprise_groups_contact_zipcode_placeholder: "eg. 3070" + admin_enterprise_groups_contact_zipcode_placeholder: "eg. D01 XXXX" admin_enterprise_groups_contact_state_id: "County" admin_enterprise_groups_contact_country_id: "Country" admin_enterprise_groups_web: "Web Resources" @@ -2017,7 +2026,7 @@ en_IE: change_package: "Change Package" spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" spree_admin_eg_pickup_from_school: "eg. 'Pick-up from Primary School'" - spree_admin_eg_collect_your_order: "eg. 'Please collect your order from 123 Imaginary St, Dublin, D01 F5P2'" + spree_admin_eg_collect_your_order: "eg. 'Please collect your order from 123 Imaginary St, Dublin, D01 XXXX'" spree_classification_primary_taxon_error: "Taxon %{taxon} is the primary taxon of %{product} and cannot be deleted" spree_order_availability_error: "Distributor or order cycle cannot supply the products in your cart" spree_order_populator_error: "That distributor or order cycle can't supply all the products in your cart. Please choose another." @@ -2061,6 +2070,7 @@ en_IE: hub_sidebar_at_least: "At least one hub must be selected" hub_sidebar_blue: "blue" hub_sidebar_red: "red" + order_cycles_closed_for_hub: "The hub you have selected is temporarily closed for orders. Please try again later." report_customers_distributor: "Distributor" report_customers_supplier: "Supplier" report_customers_cycle: "Order Cycle" @@ -2297,6 +2307,7 @@ en_IE: order_cycles_email_to_producers_notice: 'Emails to be sent to producers have been queued for sending.' 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. Please try again!" back_to_orders_list: "Back to order list" no_orders_found: "No Orders Found" order_information: "Order Information" @@ -2768,6 +2779,7 @@ en_IE: previous: "Previous" last: "Last" spree: + more: "More" your_order_is_empty_add_product: "Your order is empty, please search for and add a product above" add_product: "Add Product" name_or_sku: "Name or SKU (enter at least first 4 characters of product name)" @@ -2816,6 +2828,12 @@ en_IE: void: "Void" login: "Login" password: "Password" + signature: "Signature" + solution: "Solution" + landing_page: "Landing Page" + server: "Server" + test_mode: "Test Mode" + logourl: "Logourl" configurations: "Configurations" general_settings: "General Settings" site_name: "Site Name" @@ -2932,6 +2950,12 @@ en_IE: options: "Options" actions: update: "Update" + shared: + error_messages: + errors_prohibited_this_record_from_being_saved: + one: "1 error prohibited this record from being saved:" + other: "%{count} errors prohibited this record from being saved:" + there_were_problems_with_the_following_fields: "There were problems with the following fields" errors: messages: blank: "can't be blank" @@ -3074,6 +3098,8 @@ en_IE: zone: "Zone" calculator: "Calculator" display: "Display" + both: "Both Checkout and Back office" + back_end: "Back office only" no_shipping_methods_found: "No shipping methods found" new: new_shipping_method: "New Shipping Method" @@ -3085,6 +3111,9 @@ en_IE: form: categories: "Categories" zones: "Zones" + both: "Both Checkout and Back office" + back_end: "Back office only" + deactivation_warning: "De-activating a shipping method can make the shipping method disappear from your list. Alternatively, you can hide a shipping method from the checkout page by setting the option 'Display' to 'back office only'." payment_methods: index: payment_methods: "Payment Methods" @@ -3096,8 +3125,11 @@ en_IE: display: "Display" active: "Active" both: "Both" + front_end: "Checkout only" + back_end: "Back office only" active_yes: "Yes" active_no: "No" + no_payment_methods_found: "No payment methods found" new: new_payment_method: "New Payment Method" back_to_payment_methods_list: "Back To Payment Methods List" @@ -3126,7 +3158,11 @@ en_IE: active: "Active" active_yes: "Yes" active_no: "No" + both: "Both Checkout and Back office" + front_end: "Checkout only" + back_end: "Back office only" tags: "Tags" + deactivation_warning: "De-activating a payment method can make the payment method disappear from your list. Alternatively, you can hide a payment method from the checkout page by setting the option 'Display' to 'back office only'." providers: provider: "Provider" payments: @@ -3269,6 +3305,7 @@ en_IE: format: '%Y-%m-%d' js_format: 'yy-mm-dd' orders: + error_flash_for_unavailable_items: "An item in your cart has become unavailable. Please update the selected quantities." edit: login_to_view_order: "Please log in to view your order." bought: @@ -3296,6 +3333,14 @@ en_IE: invalid: invalid order_mailer: cancel_email: + customer_greeting: "Dear %{name}," + instructions_html: "Your order with %{distributor} has been CANCELED. Please retain this cancellation information for your records." + dont_cancel: "If you have changed your mind or don't wish to cancel this order please contact %{email}" + order_summary_canceled_html: "Order Summary #%{number} [CANCELED]" + details: "Here are the details of what you ordered:" + unpaid_order: "Your order was unpaid so no refund has been made" + paid_order: "Your order was paid so %{distributor} has refunded the full amount" + credit_order: "Your order was paid so your account has been credited" subject: "Cancellation of Order" confirm_email: subject: "Order Confirmation" diff --git a/config/locales/en_IN.yml b/config/locales/en_IN.yml new file mode 100644 index 0000000000..e3ea0153f7 --- /dev/null +++ b/config/locales/en_IN.yml @@ -0,0 +1,3445 @@ +en_IN: + language_name: "English" + activerecord: + attributes: + enterprise_fee: + fee_type: Fee Type + spree/order: + payment_state: Payment State + shipment_state: Shipment State + completed_at: Completed At + number: Number + state: State + email: Customer E-Mail + spree/payment: + amount: Amount + spree/product: + primary_taxon: "Product Category" + supplier: "Supplier" + shipping_category_id: "Shipping Category" + variant_unit: "Variant Unit" + variant_unit_name: "Variant Unit Name" + spree/credit_card: + base: "Credit Card" + order_cycle: + orders_close_at: Close date + variant_override: + count_on_hand: "In Stock" + errors: + models: + spree/user: + attributes: + email: + taken: "There's already an account for this email. Please login or reset your password." + spree/order: + no_card: There are no authorised credit cards available to charge + spree/credit_card: + attributes: + base: + card_expired: "has expired" + order_cycle: + 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: + attributes: + order_management/reports/enterprise_fee_summary/parameters: + start_at: "Start" + end_at: "End" + distributor_ids: "Hubs" + producer_ids: "Pricing" + order_cycle_ids: "Order Cycles" + enterprise_fee_ids: "Fees Names" + shipping_method_ids: "Shipping Methods" + payment_method_ids: "Payment Methods" + errors: + messages: + inclusion: "is not included in the list" + models: + order_management/subscriptions/validator: + attributes: + subscription_line_items: + at_least_one_product: "^Please add at least one product" + not_available: "^%{name} is not available from the selected schedule. Your changes have not been saved." + ends_at: + after_begins_at: "must be after begins at" + customer: + does_not_belong_to_shop: "does not belong to %{shop}" + schedule: + not_coordinated_by_shop: "is not coordinated by %{shop}" + payment_method: + not_available_to_shop: "is not available to %{shop}" + invalid_type: "must be a Cash or Stripe method" + charges_not_allowed: "^Credit card charges are not allowed by this customer" + no_default_card: "^No default card available for this customer" + shipping_method: + not_available_to_shop: "is not available to %{shop}" + devise: + confirmations: + send_instructions: "You will receive an email with instructions about how to confirm your account in a few minutes." + failed_to_send: "An error occurred whilst sending your confirmation email." + resend_confirmation_email: "Resend confirmation email." + confirmed: "Thanks for confirming your email! You can now log in." + not_confirmed: "Your email address could not be confirmed. Perhaps you have already completed this step?" + user_confirmations: + spree_user: + send_instructions: "You will receive an email with instructions about how to confirm your account in a few minutes." + confirmation_sent: "Email confirmation sent" + confirmation_not_sent: "Error sending confirmation email" + user_registrations: + spree_user: + signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please open the link to activate your account." + unknown_error: "Something went wrong while creating your account. Check your email address and try again." + failure: + invalid: | + Invalid email or password. + Were you a guest last time? Perhaps you need to create an account or reset your password. + unconfirmed: "You have to confirm your account before continuing." + already_registered: "This email address is already registered. Please log in to continue, or go back and use another email address." + success: + logged_in_succesfully: "Logged in successfully" + user_passwords: + spree_user: + updated_not_active: "Your password has been reset, but your email has not been confirmed yet." + updated: "Your password was changed successfully. You are now signed in." + send_instructions: "You will receive an email with instructions about how to confirm your account in a few minutes." + models: + order_cycle: + cloned_order_cycle_name: "COPY OF %{order_cycle}" + validators: + date_time_string_validator: + not_string_error: "must be a string" + invalid_format_error: "must be valid" + integer_array_validator: + not_array_error: "must be an array" + invalid_element_error: "must contain only valid integers" + enterprise_mailer: + confirmation_instructions: + subject: "Please confirm the email address for %{enterprise}" + welcome: + subject: "%{enterprise} is now on %{sitename}" + email_welcome: "Welcome" + email_registered: "is now part of" + email_userguide_html: "The User Guide with detailed support for setting up your Producer or Hub is here: %{link}" + userguide: "Open Food Network User Guide" + email_admin_html: "You can manage your account by logging into the %{link} or by clicking on 'Profile' in the top right hand side of the homepage, and selecting Administration." + admin_panel: "Admin Panel" + email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next. %{link}" + join_community: "Join the community" + invite_manager: + subject: "%{enterprise} has invited you to be a manager" + producer_mailer: + order_cycle: + subject: "Order cycle report for %{producer}" + shipment_mailer: + shipped_email: + dear_customer: "Dear Customer," + instructions: "Your order has been shipped" + shipment_summary: "Shipment Summary" + subject: "Shipment Notification" + thanks: "Thank you for your order with us." + track_information: "Tracking Information: %{tracking}" + track_link: "Tracking Link: %{url}" + subscription_mailer: + placement_summary_email: + subject: A summary of recently placed subscription orders + greeting: "Hi %{name}," + intro: "Below is a summary of the subscription orders that have just been placed for %{shop}." + confirmation_summary_email: + subject: A summary of recently confirmed subscription orders + greeting: "Hi %{name}," + intro: "Below is a summary of the subscription orders that have just been finalised for %{shop}." + summary_overview: + total: A total of %{count} subscriptions were marked for automatic processing. + success_zero: Of these, none were processed successfully. + success_some: Of these, %{count} were processed successfully. + success_all: All were processed successfully. + issues: Details of the issues encountered are provided below. + summary_detail: + no_message_provided: No error message provided + changes: + title: Insufficient Stock (%{count} orders) + explainer: These orders were processed but insufficient stock was available for some requested items + empty: + title: No Stock (%{count} orders) + explainer: These orders were unable to be processed because no stock was available for any requested items + complete: + title: Already Processed (%{count} orders) + explainer: These orders were already marked as complete, and were therefore left untouched + processing: + title: Error Encountered (%{count} orders) + explainer: Automatic processing of these orders failed due to an error. The error has been listed where possible. + failed_payment: + title: Failed Payment (%{count} orders) + explainer: Automatic processing of payment for these orders failed due to an error. The error has been listed where possible. + other: + title: Other Failure (%{count} orders) + explainer: Automatic processing of these orders failed for an unknown reason. This should not occur, please contact us if you are seeing this. + home: "OFN" + title: "Open Food Network India" + welcome_to: "Welcome to " + site_meta_description: "The Open Food Network India software platform allows farmers to sell produce online, at a price that works for them. It has been built specifically for selling food so it can handle tricky measures or stock levels that only food has - a dozen eggs, a bunch of parsley, a whole chicken that varies in weight…" + search_by_name: Search by name, town, State or postcode... + producers_join: India producers are now welcome to join Open Food Network India. + charges_sales_tax: Charges VAT? + print_invoice: "Print Invoice" + print_ticket: "Print Ticket" + select_ticket_printer: "Select printer for tickets" + send_invoice: "Send Invoice" + resend_confirmation: "Resend Confirmation" + view_order: "View Order" + edit_order: "Edit Order" + ship_order: "Ship Order" + cancel_order: "Cancel Order" + confirm_send_invoice: "An invoice for this order will be sent to the customer. Are you sure you want to continue?" + confirm_resend_order_confirmation: "Are you sure you want to resend the order confirmation email?" + must_have_valid_business_number: "Please enter your company number." + invoice: "Invoice" + more: "More" + say_no: "No" + say_yes: "Yes" + ongoing: Ongoing + bill_address: Billing Address + ship_address: Shipping Address + sort_order_cycles_on_shopfront_by: "Sort Order Cycles On Shopfront By" + required_fields: Required fields are denoted with an asterisk + select_continue: Select and Continue + remove: Remove + or: or + collapse_all: Collapse all + expand_all: Expand all + loading: Loading... + show_more: Show more + show_all: Show all + show_all_with_more: "Show All (%{num} More)" + cancel: Cancel + edit: Edit + clone: Clone + distributors: Distributors + bulk_order_management: Bulk Order Management + enterprises: Enterprises + enterprise_groups: Groups + reports: Reports + variant_overrides: Inventory + import: Import + spree_products: Spree Products + all: All + current: Current + available: Available + dashboard: Dashboard + undefined: undefined + unused: unused + admin_and_handling: Admin & Handling + profile: Profile + supplier_only: Supplier Only + has_shopfront: Has Shopfront + weight: Weight + volume: Volume + items: Items + summary: Summary + detailed: Detailed + updated: Updated + 'yes': "Yes" + 'no': "No" + y: 'Y' + n: 'N' + powered_by: Powered by + blocked_cookies_alert: "Your browser may be blocking cookies needed to use this shopfront. Click below to allow cookies and reload the page." + allow_cookies: "Allow Cookies" + notes: Notes + error: Error + processing_payment: "Processing payment..." + no_pending_payments: "No pending payments" + invalid_payment_state: "Invalid payment state" + filter_results: Filter Results + quantity: Quantity + pick_up: Pick up + copy: Copy + change_my_password: "Change my password" + update_password: "Update password" + password_confirmation: Password Confirmation + reset_password_token: Reset password + expired: has expired, please request a new one + back_to_payments_list: "Back to Payments List" + maestro_or_solo_cards: "Maestro/Solo cards" + backordered: "Backordered" + on hand: "In Stock" + ship: "Ship" + shipping_category: "Shipping Category" + actions: + create_and_add_another: "Create and Add Another" + create: "Create" + cancel: "Cancel" + save: "Save" + edit: "Edit" + update: "Update" + delete: "Delete" + admin: + begins_at: Begins At + begins_on: Begins On + customer: Customer + date: Date + email: Email + ends_at: Ends At + ends_on: Ends On + name: Name + on_hand: In Stock + on_demand: Unlimited + on_demand?: Unlimited? + order_cycle: Order Cycle + payment: Payment + payment_method: Payment Method + phone: Phone + price: Price + producer: Producer + image: Image + product: Product + quantity: Quantity + schedule: Schedule + shipping: Shipping + shipping_method: Shipping Method + shop: Shop + sku: SKU + status_state: State + tags: Tags + variant: Variant + weight: Weight + volume: Volume + items: Items + select_all: Select all + quick_search: Quick Search + clear_all: Clear All + start_date: "Start Date" + end_date: "End Date" + form_invalid: "Form contains missing or invalid fields" + clear_filters: Clear Filters + clear: Clear + save: Save + cancel: Cancel + back: Back + show_more: Show more + show_n_more: Show %{num} more + choose: "Choose..." + please_select: Please select... + column_save_as_default: Save As Default + columns: Columns + actions: Actions + viewing: "Viewing: %{current_view_name}" + description: Description + whats_this: What's this? + tag_has_rules: "Existing rules for this tag: %{num}" + has_one_rule: "has one rule" + has_n_rules: "has %{num} rules" + unsaved_confirm_leave: "There are unsaved changed on this page. Continue without saving?" + unsaved_changes: "You have unsaved changes" + shopfront_settings: + embedded_shopfront_settings: "Embedded Shopfront Settings" + enable_embedded_shopfronts: "Enable Embedded Shopfronts" + embedded_shopfronts_whitelist: "External Domains Whitelist" + number_localization: + number_localization_settings: "Number Localisation Settings" + enable_localized_number: "Use the international thousand/decimal separator logic" + invoice_settings: + edit: + title: "Invoice Settings" + enable_invoices?: "Enable Invoices?" + invoice_style2?: "Use the alternative invoice model that includes total tax breakdown per rate and tax rate info per item (not yet suitable for countries displaying prices excluding tax)" + enable_receipt_printing?: "Show options for printing receipts using thermal printers in order dropdown?" + stripe_connect_settings: + edit: + title: "Stripe Connect" + settings: "Settings" + stripe_connect_enabled: Enable shops to accept payments using Stripe Connect? + no_api_key_msg: No Stripe account exists for this enterprise. + configuration_explanation_html: For detailed instructions on configuring the Stripe Connect integration, please consult this guide. + status: Status + ok: Ok + instance_secret_key: Instance Secret Key + account_id: Account ID + business_name: Business Name + charges_enabled: Charges Enabled + charges_enabled_warning: "Warning: Charges are not enabled for your account" + auth_fail_error: The API key you provided is invalid + empty_api_key_error_html: No Stripe API key has been provided. To set your API key, please follow these instructions + matomo_settings: + edit: + title: "Matomo Settings" + matomo_url: "Matomo URL" + matomo_site_id: "Matomo Site ID" + matomo_tag_manager_url: "Matomo Tag Manager URL" + info_html: "Matomo is a Web and Mobile Analytics application. You can either host Matomo on-premises or use a cloud-hosted service. See matomo.org for more information." + config_instructions_html: "Here you can configure the OFN Matomo integration. The Matomo URL below should point to the Matomo instance where the user tracking information will be sent to; if it is left empty, Matomo user tracking will be disabled. The Site ID field is not mandatory but useful if you are tracking more than one website on a single Matomo instance; it can be found on the Matomo instance console." + config_instructions_tag_manager_html: "Setting the Matomo Tag Manager URL enables Matomo Tag Manager. This tool allows you to set up analytics events. The Matomo Tag Manager URL is copied from the Install Code section of Matomo Tag Manager. Ensure you select the right container and environment as these options change the URL." + customers: + index: + new_customer: "New Customer" + code: Code + duplicate_code: "This code is used already." + bill_address: "Billing Address" + ship_address: "Shipping Address" + update_address_success: 'Address updated successfully.' + update_address_error: 'Sorry! Please input all of the required fields!' + edit_bill_address: 'Edit Billing Address' + edit_ship_address: 'Edit Shipping Address' + required_fileds: 'Required fields are denoted with an asterisk' + select_country: 'Select Country' + select_state: 'Select State' + edit: 'Edit' + update_address: 'Update Address' + confirm_delete: 'Sure to delete?' + search_by_email: "Search by email/code..." + guest_label: 'Guest checkout' + destroy: + has_associated_orders: 'Delete failed: customer has associated orders with this shop' + contents: + edit: + title: Content + header: Header + home_page: Home page + producer_signup_page: Producer signup page + hub_signup_page: Hub signup page + group_signup_page: Group signup page + main_links: Main Menu Links + footer_and_external_links: Footer and External Links + your_content: Your content + user_guide: User Guide + map: Map + enterprise_fees: + index: + title: "Enterprise Fees" + enterprise: "Enterprise" + fee_type: "Fee Type" + name: "Name" + tax_category: "Tax Category" + calculator: "Calculator" + calculator_values: "Calculator Values" + search: "Search" + name_placeholder: "e.g. packing fee" + enterprise_groups: + index: + new_button: New Enterprise Group + enterprise_roles: + form: + manages: manages + enterprise_role: + manages: manages + products: + unit_name_placeholder: 'eg. bunches' + index: + unit: Unit + display_as: Display As + category: Category + tax_category: Tax Category + inherits_properties?: Inherits Properties? + available_on: Available On + av_on: "Av. On" + import_date: Imported + upload_an_image: Upload an image + seo: + product_search_keywords: "Product Search Keywords" + product_search_tip: "Type words to help search your products in the shops. Use space to separate each keyword." + SEO_keywords: "SEO Keywords" + seo_tip: "Type words to help search your products in the web. Use space to separate each keyword." + search: "Search" + properties: + property_name: "Property Name" + inherited_property: "Inherited Property" + variants: + infinity: "Infinity" + to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order." + back_to_products_list: "Back to products list" + editing_product: "Editing Product" + tabs: + product_details: "Product Details" + group_buy_options: "Group Buy Options" + images: "Images" + variants: "Variants" + product_properties: "Product Properties" + product_import: + title: Product Import + file_not_found: File not found or could not be opened + no_data: No data found in spreadsheet + confirm_reset: "This will set stock level to zero on all products for this \n enterprise that are not present in the uploaded file" + model: + no_file: "error: no file uploaded" + could_not_process: "could not process file: invalid filetype" + incorrect_value: incorrect value + conditional_blank: can't be blank if unit_type is blank + no_product: did not match any products in the database + not_found: not found in database + not_updatable: cannot be updated on existing products via product import + blank: can't be blank + products_no_permission: you do not have permission to manage products for this enterprise + inventory_no_permission: you do not have permission to create inventory for this producer + none_saved: did not save any products successfully + line_number: "Line %{number}:" + encoding_error: "Please check the language setting of your source file and ensure it is saved with UTF-8 encoding" + unexpected_error: "Product Import encountered an unexpected error whilst opening the file: %{error_message}" + malformed_csv: "Product Import found CSV was incorrectly formatted: %{error_message}" + index: + notice: "Notice" + beta_notice: "This feature is still in beta: you may experience some errors while using it. Please don't hesitate to contact support." + select_file: Select a spreadsheet to upload + spreadsheet: Spreadsheet + choose_import_type: Select import type + import_into: Import type + product_list: Product list + inventories: Inventories + import: Import + upload: Upload + csv_templates: CSV Templates + product_list_template: Download Product List template + inventory_template: Download Inventory template + category_values: Available Category Values + product_categories: Product Categories + tax_categories: Tax Categories + shipping_categories: Shipping Categories + import: + review: Review + import: Import + save: Save + results: Results + save_imported: Save imported products + no_valid_entries: No valid entries found + none_to_save: There are no entries that can be saved + some_invalid_entries: Imported file contains invalid entries + fix_before_import: Please fix these errors and try importing the file again + save_valid?: Save valid entries for now and discard the others? + no_errors: No errors detected! + save_all_imported?: Save all imported products? + options_and_defaults: Import options and defaults + no_permission: you do not have permission to manage this enterprise + not_found: enterprise could not be found in database + no_name: No name + blank_enterprise: some products do not have an enterprise defined + reset_absent?: Reset absent products + reset_absent_tip: Set stock to zero for all exiting products not present in the file + overwrite_all: Overwrite all + overwrite_empty: Overwrite if empty + default_stock: Set stock level + default_tax_cat: Set tax category + default_shipping_cat: Set shipping category + default_available_date: Set available date + validation_overview: Import validation overview + entries_found: Entries found in imported file + entries_with_errors: Items contain errors and will not be imported + products_to_create: Products will be created + products_to_update: Products will be updated + inventory_to_create: Inventory items will be created + inventory_to_update: Inventory items will be updated + products_to_reset: Existing products will have their stock reset to zero + inventory_to_reset: Existing inventory items will have their stock reset to zero + line: Line + item_line: Item line + import_review: + not_updatable_tip: "The following fields cannot be updated via bulk import for existing products:" + fields_ignored: These fields will be ignored when the imported products are saved. + entries_table: + not_updatable: This field is not updatable via bulk import on existing products + save_results: + final_results: Import final results + products_created: Products created + products_updated: Products updated + inventory_created: Inventory items created + inventory_updated: Inventory items updated + products_reset: Products had stock level reset to zero + inventory_reset: Inventory items had stock level reset to zero + all_saved: "All items saved successfully" + some_saved: "items saved successfully" + save_errors: Save errors + import_again: Upload Another File + view_products: Go To Products Page + view_inventory: Go To Inventory Page + variant_overrides: + loading_flash: + loading_inventory: LOADING INVENTORY + index: + title: Inventory + description: Use this page to manage inventories for your enterprises. Any product details set here will override those set on the 'Products' page + enable_reset?: Enable Stock Reset? + default_stock: "Default stock" + inherit?: Inherit? + add: Add + hide: Hide + import_date: Imported + select_a_shop: Select A Shop + review_now: Review Now + new_products_alert_message: There are %{new_product_count} new products available to add to your inventory. + currently_empty: Your inventory is currently empty + no_matching_products: No matching products found in your inventory + no_hidden_products: No products have been hidden from this inventory + no_matching_hidden_products: No hidden products match your search criteria + no_new_products: No new products are available to add to this inventory + no_matching_new_products: No new products match your search criteria + inventory_powertip: This is your inventory of products. To add products to your inventory, select 'New Products' from the Viewing dropdown. + hidden_powertip: These products have been hidden from your inventory and will not be available to add to your shop. You can click 'Add' to add a product to you inventory. + new_powertip: These products are available to be added to your inventory. Click 'Add' to add a product to your inventory, or 'Hide' to hide it from view. You can always change your mind later! + controls: + back_to_my_inventory: Back to my inventory + orders: + invoice_email_sent: 'Invoice email has been sent' + order_email_resent: 'Order email has been resent' + bulk_management: + 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" + ask: "Ask?" + page_title: "Bulk Order Management" + actions_delete: "Delete Selected" + loading: "Loading orders" + no_results: "No orders found." + group_buy_unit_size: "Group Buy Unit Size" + total_qtt_ordered: "Total Quantity Ordered" + max_qtt_ordered: "Max Quantity Ordered" + current_fulfilled_units: "Current Fulfilled Units" + max_fulfilled_units: "Max Fulfilled Units" + order_error: "Some errors must be resolved before you can update orders.\nAny fields with red borders contain errors." + variants_without_unit_value: "WARNING: Some variants do not have a unit value" + select_variant: "Select a variant" + enterprise: + select_outgoing_oc_products_from: Select outgoing OC products from + enterprises: + index: + title: Enterprises + new_enterprise: New Enterprise + producer?: "Producer?" + package: Package + status: Status + manage: Manage + form: + about_us: + desc_short: Short Description + desc_short_placeholder: Tell us about your enterprise in one or two sentences + desc_long: About Us + desc_long_placeholder: Tell customers about yourself. This information appears on your public profile. + business_details: + abn: Company Number + abn_placeholder: eg. 99 123 456 789 + acn: Charity Number + acn_placeholder: eg. 123 456 789 + display_invoice_logo: Display logo in invoices + invoice_text: Add customized text at the end of invoices + contact: + name: Name + name_placeholder: eg. Amanda Plum + email_address: Public Email Address + email_address_placeholder: eg. hello@food.co.in + email_address_tip: "This email address will be displayed in your public profile" + phone: Phone + phone_placeholder: eg. 98 7654 3210 + website: Website + website_placeholder: eg. www.truffles.co.in + enterprise_fees: + name: Name + fee_type: Fee Type + manage_fees: Manage Enterprise Fees + no_fees_yet: You don't have any enterprise fees yet. + create_button: Create One Now + images: + logo: Logo + promo_image_placeholder: 'This image is displayed in "About Us"' + promo_image_note1: 'PLEASE NOTE:' + promo_image_note2: Any promo image uploaded here will be cropped to 1200 x 260. + promo_image_note3: The promo image is displayed at the top of an enterprise's profile page and pop-ups. + inventory_settings: + text1: You may opt to manage stock levels and prices in via your + inventory: inventory + text2: > + If you are using the inventory tool, you can select whether new products + added by your suppliers need to be added to your inventory before they + can be stocked. If you are not using your inventory to manage your products + you should select the 'recommended' option below: + preferred_product_selection_from_inventory_only_yes: New products can be put into my shopfront (recommended) + preferred_product_selection_from_inventory_only_no: New products must be added to my inventory before they can be put into my shopfront + payment_methods: + name: Name + applies: Applies? + manage: Manage Payment Methods + no_method_yet: You don't have any payment methods yet. + create_button: Create New Payment Method + create_one_button: Create One Now + primary_details: + name: Name + name_placeholder: eg. Professor Plum's Biodynamic Truffles + groups: Groups + groups_tip: Select any groups or regions that you are a member of. This will help customers find your enterprise. + groups_placeholder: Start typing to search available groups... + primary_producer: Primary Producer? + primary_producer_tip: Select 'Producer' if you are a primary producer of food. + producer: Producer + any: Any + none: None + own: Own + sells: Sells + sells_tip: "None - enterprise does not sell to customers directly.
Own - Enterprise sells own products to customers.
Any - Enterprise can sell own or other enterprises products.
" + visible_in_search: Visible in search? + visible_in_search_tip: Determines whether this enterprise will be visible to customers when searching the site. + visible: Visible + not_visible: Not visible + permalink: Permalink (no spaces) + permalink_tip: "This permalink is used to create the url to your shop: %{link}your-shop-name/shop" + link_to_front: Link to shop front + link_to_front_tip: A direct link to your shopfront on the Open Food Network. + ofn_uid: OFN UID + ofn_uid_tip: The unique id used to identify the enterprise on Open Food Network. + shipping_methods: + name: "Name" + applies: "Active?" + manage: "Manage Shipping Methods" + create_button: "Create New Shipping Method" + create_one_button: "Create One Now" + no_method_yet: "You don't have any shipping methods yet." + shop_preferences: + shopfront_requires_login: "Publicly visible shopfront?" + shopfront_requires_login_tip: "Choose whether customers must login to view the shopfront or if it's visible to everybody." + shopfront_requires_login_false: "Public" + shopfront_requires_login_true: "Visible to registered customers only" + recommend_require_login: "We recommend to require users to login when orders can be changed." + allow_guest_orders: "Guest orders" + allow_guest_orders_tip: "Allow checkout as guest or require a registered user." + allow_guest_orders_false: "Require login to order" + allow_guest_orders_true: "Allow guest checkout" + allow_order_changes: "Change orders" + allow_order_changes_tip: "Allow customers to change their order as long the order cycle is open." + allow_order_changes_false: "Placed orders cannot be changed / cancelled" + allow_order_changes_true: "Customers can change / cancel orders while order cycle is open" + enable_subscriptions: "Subscriptions" + enable_subscriptions_tip: "Enable subscriptions functionality?" + enable_subscriptions_false: "Disabled" + enable_subscriptions_true: "Enabled" + shopfront_message: "Shopfront Message" + shopfront_message_placeholder: > + An optional message to welcome customers and explain how to shop with + you. If text is entered here it will be displayed in a home tab when + customers first arrive at your shopfront. + shopfront_message_link_tooltip: "Insert / edit link" + shopfront_message_link_prompt: "Please enter a URL to insert" + shopfront_closed_message: "Shopfront Closed Message" + shopfront_closed_message_placeholder: > + A message which provides a more detailed explanation about why your + shop is closed and/or when customers can expect it to open again. This + is displayed on your shop only when you have no active order cycles + (ie. shop is closed). + shopfront_category_ordering: "Shopfront Category Ordering" + open_date: "Open Date" + 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." + stripe_account_connected: "Stripe account connected." + disconnect: "Disconnect account" + confirm_modal: + title: Connect with Stripe + part1: Stripe is a payment processing service that allows shops on the OFN to accept credit card payments from customers. + part2: To use this feature, you must connect your Stripe account to the OFN. Clicking 'I Agree' below will redirect to you the Stripe website where you can connect an existing Stripe account, or create a new one if you don't already have one. + part3: This will allow the Open Food Network to accept credit card payments from customers on your behalf. Please note that you will need to maintain your own Stripe account, pay the fees Stripe charges and handle any chargebacks and customer service yourself. + i_agree: I Agree + cancel: Cancel + tag_rules: + default_rules: + by_default: By Default + no_rules_yet: No default rules apply yet + add_new_button: '+ Add A New Default Rule' + no_tags_yet: No tags apply to this enterprise yet + no_rules_yet: No rules apply to this tag yet + for_customers_tagged: 'For customers tagged:' + add_new_rule: '+ Add A New Rule' + add_new_tag: '+ Add A New Tag' + users: + email_confirmation_notice_html: "Email confirmation is pending. We've sent a confirmation email to %{email}." + resend: Resend + owner: 'Owner' + contact: "Contact" + contact_tip: "The manager who will receive enterprise emails for orders and notifications. Must have a confirmed email adress." + owner_tip: The primary user responsible for this enterprise. + notifications: Notifications + notifications_tip: Notifications about orders will be send to this email address. + notifications_placeholder: eg. gustav@truffles.com + notifications_note: 'Note: A new email address may need to be confirmed prior to use' + managers: Managers + managers_tip: The other users with permission to manage this enterprise. + invite_manager: "Invite Manager" + invite_manager_tip: "Invite an unregistered user to sign up and become a manager of this enterprise." + add_unregistered_user: "Add an unregistered user" + email_confirmed: "Email confirmed" + email_not_confirmed: "Email not confirmed" + actions: + edit_profile: Settings + properties: Properties + payment_methods: Payment Methods + payment_methods_tip: This enterprise has no payment methods + shipping_methods: Shipping Methods + shipping_methods_tip: This enterprise has shipping methods + enterprise_fees: Enterprise Fees + enterprise_fees_tip: This enterprise has no fees + admin_index: + name: Name + role: Role + sells: Sells + visible: Visible? + owner: Owner + producer: Producer + change_type_form: + producer_profile: Producer Profile + connect_ofn: Connect through OFN + always_free: Free + producer_description_text: Add your products to Open Food Network, allowing hubs to stock your products in their stores. + producer_shop: Producer Shop + sell_your_produce: Sell your own produce + producer_shop_description_text: Sell your products directly to customers through your very own Open Food Network shopfront. + producer_shop_description_text2: A Producer Shop is for your produce only, if you want to sell produce grown/produced off site, select 'Producer Hub'. + producer_hub: Producer Hub + producer_hub_text: Sell produce from self and others + producer_hub_description_text: Your enterprise is the backbone of your local food system. You can sell your own produce as well as produce aggregated from other enterprises through your shopfront on the Open Food Network. + profile: Profile Only + get_listing: Get a listing + profile_description_text: People can find and contact you on the Open Food Network. Your enterprise will be visible on the map, and will be searchable in listings. + hub_shop: Hub Shop + hub_shop_text: Sell produce from others + hub_shop_description_text: Your enterprise is the backbone of your local food system. You aggregate produce from other enterprises and can sell it through your shop on the Open Food Network. + choose_option: Please choose one of the options above. + change_now: Change now + enterprise_user_index: + loading_enterprises: LOADING ENTERPRISES + no_enterprises_found: No enterprises found. + search_placeholder: Search By Name + manage: Manage + manage_link: Settings + producer?: "Producer?" + package: "Package" + status: "Status" + new_form: + owner: Owner + owner_tip: The primary user responsible for this enterprise. + i_am_producer: I am a Producer + contact_name: Contact Name + edit: + editing: 'Settings:' + back_link: Back to enterprises list + new: + title: New Enterprise + back_link: Back to enterprises list + remove_logo: + remove: "Remove Image" + removed_successfully: "Logo removed successfully" + immediate_removal_warning: "The logo will be removed immediately after you confirm." + remove_promo_image: + remove: "Remove Image" + removed_successfully: "Promo image removed successfully" + immediate_removal_warning: "The promo image will be removed immediately after you confirm." + welcome: + welcome_title: Welcome to the Open Food Network! + welcome_text: You have successfully created a + next_step: Next step + choose_starting_point: 'Choose your package:' + profile: 'Profile' + producer_profile: 'Producer Profile' + invite_manager: + user_already_exists: "User already exists" + error: "Something went wrong" + order_cycles: + loading_flash: + loading_order_cycles: LOADING ORDER CYCLES + loading: LOADING... + new: + create: "Create" + cancel: "Cancel" + back_to_list: "Back To List" + edit: + advanced_settings: "Advanced Settings" + save: "Save" + save_and_next: "Save and Next" + next: "Next" + cancel: "Cancel" + back_to_list: "Back To List" + save_and_back_to_list: "Save and Back to List" + choose_products_from: "Choose Products From:" + incoming: + incoming: "Incoming" + supplier: "Supplier" + products: "Products" + receival_details: "Receival Details" + fees: "Fees" + save: "Save" + save_and_next: "Save and Next" + next: "Next" + cancel: "Cancel" + back_to_list: "Back To List" + outgoing: + outgoing: "Outgoing" + distributor: "Distributor" + products: "Products" + tags: "Tags" + delivery_details: "Delivery Details" + fees: "Fees" + previous: "Previous" + save: "Save" + save_and_back_to_list: "Save and Back to List" + cancel: "Cancel" + back_to_list: "Back To List" + wizard_progress: + edit: "1. General Settings" + incoming: "2. Incoming Products" + outgoing: "3. Outgoing Products" + exchange_form: + pickup_time_tip: When orders from this OC will be ready for the customer + pickup_instructions_placeholder: "Pick-up instructions" + pickup_instructions_tip: These instructions are shown to customers after they complete an order + pickup_time_placeholder: "Ready for (ie. Date / Time)" + receival_instructions_placeholder: "Receival instructions" + add_fee: 'Add fee' + remove: 'Remove' + selected: 'selected' + add_exchange_form: + add_supplier: 'Add supplier' + add_distributor: 'Add distributor' + advanced_settings: + title: Advanced Settings + choose_product_tip: You can restrict products incoming and outgoing to only %{inventory}'s inventory. + preferred_product_selection_from_coordinator_inventory_only_here: Coordinator's Inventory Only + preferred_product_selection_from_coordinator_inventory_only_all: All Available Products + save_reload: Save and Reload Page + coordinator_fees: + add: Add coordinator fee + filters: + search_by_order_cycle_name: "Search by Order Cycle name..." + involving: "Involving" + any_enterprise: "Any Enterprise" + any_schedule: "Any Schedule" + form: + general_settings: "General Settings" + incoming: Incoming + supplier: Supplier + receival_details: Receival details + fees: Fees + outgoing: Outgoing + distributor: Distributor + products: Products + tags: Tags + add_a_tag: Add a tag + delivery_details: Pickup / Delivery details + index: + schedule: Schedule + schedules: Schedules + new_schedule: New Schedule + name_and_timing_form: + name: Name + orders_open: Orders open at + coordinator: Coordinator + orders_close: Orders close + row: + suppliers: suppliers + distributors: distributors + variants: variants + simple_form: + ready_for: Ready for + ready_for_placeholder: Date / time + customer_instructions: Customer instructions + customer_instructions_placeholder: Pick-up or delivery notes + products: Products + fees: Fees + destroy_errors: + orders_present: That order cycle has been selected by a customer and cannot be deleted. To prevent customers from accessing it, please close it instead. + schedule_present: That order cycle is linked to a schedule and cannot be deleted. Please unlink or delete the schedule first. + bulk_update: + no_data: Hm, something went wrong. No order cycle data found. + date_warning: + msg: This order cycle is linked to %{n} open subscription orders. Changing this date now will not affect any orders which have already been placed, but should be avoided if possible. Are you sure you want to proceed? + cancel: Cancel + proceed: Proceed + producer_properties: + index: + title: Producer Properties + proxy_orders: + cancel: + could_not_cancel_the_order: Could not cancel the order + resume: + could_not_resume_the_order: Could not resume the order + shared: + user_guide_link: + user_guide: User Guide + enterprises_hubs_tabs: + has_no_payment_methods: "%{enterprise} has no payment methods" + has_no_shipping_methods: "%{enterprise} has no shipping methods" + has_no_enterprise_fees: "%{enterprise} has no enterprise fees" + enterprise_issues: + create_new: Create New + resend_email: Resend Email + has_no_payment_methods: "%{enterprise} currently has no payment methods" + has_no_shipping_methods: "%{enterprise} currently has no shipping methods" + email_confirmation: "Email confirmation is pending. We've sent a confirmation email to %{email}." + not_visible: "%{enterprise} is not visible and so cannot be found on the map or in searches" + reports: + hidden: HIDDEN + unitsize: UNITSIZE + total: TOTAL + total_items: TOTAL ITEMS + supplier_totals: Order Cycle Supplier Totals + supplier_totals_by_distributor: Order Cycle Supplier Totals by Distributor + totals_by_supplier: Order Cycle Distributor Totals by Supplier + customer_totals: Order Cycle Customer Totals + all_products: All products + inventory: Inventory (on hand) + lettuce_share: LettuceShare + mailing_list: Mailing List + addresses: Addresses + payment_methods: Payment Methods Report + delivery: Delivery Report + tax_types: Tax Types + tax_rates: Tax Rates + pack_by_customer: Pack By Customer + pack_by_supplier: Pack By Supplier + orders_and_distributors: + name: Orders And Distributors + description: Orders with distributor details + bulk_coop: + name: Bulk Co-Op + description: Reports for Bulk Co-Op orders + payments: + name: Payment Reports + description: Reports for Payments + orders_and_fulfillment: + name: Orders & Fulfillment Reports + customers: + name: Customers + products_and_inventory: + name: Products & Inventory + users_and_enterprises: + name: Users & Enterprises + description: Enterprise Ownership & Status + order_cycle_management: + name: Order Cycle Management + sales_tax: + name: Sales Tax + xero_invoices: + name: Xero Invoices + description: Invoices for import into Xero + packing: + name: Packing Reports + enterprise_fee_summary: + name: "Enterprise Fee Summary" + description: "Summary of Enterprise Fees collected" + subscriptions: + subscriptions: Subscriptions + new: New Subscription + create: Create Subscription + edit: Edit Subscription + table: + edit_subscription: Edit Subscription + pause_subscription: Pause Subscription + unpause_subscription: Unpause Subscription + cancel_subscription: Cancel Subscription + filters: + query_placeholder: "Search by email..." + setup_explanation: + just_a_few_more_steps: 'Just a few more steps before you can begin:' + enable_subscriptions: "Enable subscriptions for at least one of your shops" + enable_subscriptions_step_1_html: 1. Go to the %{enterprises_link} page, find your shop, and click "Manage" + enable_subscriptions_step_2: 2. Under "Shop Preferences", enable the Subscriptions option + set_up_shipping_and_payment_methods_html: Set up %{shipping_link} and %{payment_link} methods + set_up_shipping_and_payment_methods_note_html: Note that only Cash and Stripe payment methods may
be used with subscriptions + ensure_at_least_one_customer_html: Ensure that at least one %{customer_link} exists + create_at_least_one_schedule: Create at least one Schedule + create_at_least_one_schedule_step_1_html: 1. Go to the on the %{order_cycles_link} page + create_at_least_one_schedule_step_2: 2. Create an order cycle if you have not already done so + create_at_least_one_schedule_step_3: 3. Click '+ New Schedule', and fill out the form + once_you_are_done_you_can_html: Once you are done, you can %{reload_this_page_link} + reload_this_page: reload this page + steps: + details: 1. Basic Details + address: 2. Address + products: 3. Add Products + review: 4. Review & Save + subscription_line_items: + this_is_an_estimate: | + The displayed prices are only an estimate and calculated at the time the subscription is changed. + If you change prices or fees, orders will be updated, but the subscription will still display the old values. + not_in_open_and_upcoming_order_cycles_warning: "There are no open or upcoming order cycles for this product." + autocomplete: + name_or_sku: "NAME OR SKU" + quantity: "Quantity" + add: "Add" + details: + details: Details + invalid_error: Oops! Please fill in all of the required fields... + allowed_payment_method_types_tip: Only Cash and Stripe payment methods may be used at the moment + credit_card: Credit Card + charges_not_allowed: Charges are not allowed by this customer + no_default_card: Customer has no cards available to charge + card_ok: Customer has a card available to charge + begins_at_placeholder: "Select a Date" + ends_at_placeholder: "Optional" + loading_flash: + loading: LOADING SUBSCRIPTIONS + review: + details: Details + address: Address + products: Products + no_open_or_upcoming_order_cycle: "No Upcoming Order Cycle" + products_panel: + save: "SAVE" + saving: "SAVING" + saved: "SAVED" + product_already_in_order: This product has already been added to the order. Please edit the quantity directly. + stock: + insufficient_stock: "Insufficient stock available" + out_of_stock: "Out of Stock" + orders: + number: Number + confirm_edit: Are you sure you want to edit this order? Doing so may make it more difficult to automatically sync changes to the subscription in the future. + confirm_cancel_msg: "Are you sure you want to cancel this subscription? This action cannot be undone." + cancel_failure_msg: "Sorry, cancellation failed!" + confirm_pause_msg: "Are you sure you want to pause this subscription?" + pause_failure_msg: "Sorry, pausing failed!" + confirm_unpause_msg: "If you have an open Order Cycle in this subscription's schedule, an order will be created for this customer. Are you sure you want to unpause this subscription?" + unpause_failure_msg: "Sorry, unpausing failed!" + confirm_cancel_open_orders_msg: "Some orders for this subscription are currently open. The customer has already been notified that the order will be placed. Would you like to cancel these order(s) or keep them?" + resume_canceled_orders_msg: "Some orders for this subscription can be resumed right now. You can resume them from the orders dropdown." + yes_cancel_them: Cancel them + no_keep_them: Keep them + yes_i_am_sure: Yes, I'm sure + order_update_issues_msg: Some orders could not be automatically updated, this is most likely because they have been manually edited. Please review the issues listed below and make any adjustments to individual orders if required. + no_results: + no_subscriptions: No subscriptions yet... + why_dont_you_add_one: Why don't you add one? :) + no_matching_subscriptions: No matching subscriptions found + schedules: + destroy: + associated_subscriptions_error: This schedule cannot be deleted because it has associated subscriptions + controllers: + enterprises: + stripe_connect_cancelled: "Connection to Stripe has been cancelled" + stripe_connect_success: "Stripe account connected successfully" + stripe_connect_fail: Sorry, the connection of your Stripe account failed + stripe_connect_settings: + resource: Stripe Connect configuration + api: + enterprise_logo: + destroy_attachment_does_not_exist: "Logo does not exist" + enterprise_promo_image: + destroy_attachment_does_not_exist: "Promo image does not exist" + orders: + failed_to_update: "Failed to update order" + checkout: + already_ordered: + cart: "cart" + message_html: "You have an order for this order cycle already. Check the %{cart} to see the items you ordered before. You can also cancel items as long as the order cycle is open." + failed: "The checkout failed. Please let us know so that we can process your order." + shops: + hubs: + show_closed_shops: "Show closed shops" + hide_closed_shops: "Hide closed shops" + show_on_map: "Show all on the map" + shared: + menu: + cart: + cart: "Basket" + cart_sidebar: + checkout: "Checkout" + edit_cart: "Edit basket" + items_in_cart_singular: "%{num} item in your basket" + items_in_cart_plural: "%{num} items in your basket" + close: "Close" + cart_empty: "Your basket is empty" + take_me_shopping: "Take me shopping!" + signed_in: + profile: "Profile" + mobile_menu: + cart: "Basket" + register_call: + selling_on_ofn: "Interested in selling through the Open Food Network?" + register: "Register here" + footer: + footer_secure: "Secure and trusted." + footer_secure_text: "Open Food Network uses SSL encryption (2048 bit RSA) everywhere to keep your shopping and payment information private. Our servers do not store your credit card details and payments are processed by PCI-compliant services." + footer_contact_headline: "Keep in touch" + footer_contact_email: "Email us" + footer_nav_headline: "Navigate" + footer_join_headline: "Join us" + footer_join_body: "Create a listing, shop or group directory on the Open Food Network." + footer_join_cta: "Tell me more!" + footer_legal_call: "Read our" + footer_legal_tos: "Terms and conditions" + footer_legal_visit: "Find us on" + footer_legal_text_html: "Open Food Network is a free and open source software platform. Our content is licensed with %{content_license} and our code with %{code_license}." + footer_data_text_with_privacy_policy_html: "We take good care of your data. See our %{privacy_policy} and %{cookies_policy}" + footer_data_text_without_privacy_policy_html: "We take good care of your data. See our %{cookies_policy}" + footer_data_privacy_policy: "privacy policy" + footer_data_cookies_policy: "cookies policy" + shop: + messages: + customer_required: + login: "login" + signup: "signup" + contact: "contact" + require_customer_login: "Only approved customers can access this shop." + require_login_html: "If you're already an approved customer, %{login} or %{signup} to proceed." + require_login_2_html: "Want to start shopping here? Please %{contact} %{enterprise} and ask about joining." + require_customer_html: "If you'd like to start shopping here, please %{contact} %{enterprise} to ask about joining." + select_oc: + select_oc_html: "Please choose when you want your order, to see what products are available." + card_could_not_be_updated: Card could not be updated + card_could_not_be_saved: card could not be saved + spree_gateway_error_flash_for_checkout: "There was a problem with your payment information: %{error}" + invoice_billing_address: "Billing address:" + invoice_column_tax: "VAT" + invoice_column_price: "Price" + invoice_column_item: "Item" + invoice_column_qty: "Qty" + invoice_column_unit_price_with_taxes: "Unit price (Incl. tax)" + invoice_column_unit_price_without_taxes: "Unit price (Excl. tax)" + invoice_column_price_with_taxes: "Total price (Incl. tax)" + invoice_column_price_without_taxes: "Total price (Excl. tax)" + invoice_column_tax_rate: "Tax rate" + invoice_tax_total: "VAT Total:" + tax_invoice: "TAX INVOICE" + tax_total: "Total tax (%{rate}):" + total_excl_tax: "Total (Excl. tax):" + total_incl_tax: "Total (Incl. tax):" + abn: "Company Number:" + acn: "Charity Number:" + invoice_issued_on: "Invoice issued on:" + order_number: "Invoice number:" + date_of_transaction: "Date of transaction:" + ticket_column_qty: "Qty" + ticket_column_item: "Item" + ticket_column_unit_price: "Unit Price" + ticket_column_total_price: "Total Price" + menu_1_title: "Shops" + menu_1_url: "/shops" + menu_2_title: "Map" + menu_2_url: "/map" + menu_3_title: "Pricing" + menu_3_url: "https://about.openfoodindia.org/pricing/" + menu_4_title: "Groups" + menu_4_url: "/groups" + menu_5_title: "About" + menu_5_url: "https://about.openfoodindia.org" + menu_6_title: "Blog" + menu_6_url: "https://about.openfoodindia.org/blog" + menu_7_title: "Support" + menu_7_url: "https://about.openfoodindia.org/support" + logo: "Logo (640x130)" + logo_mobile: "Mobile logo (75x26)" + logo_mobile_svg: "Mobile logo (SVG)" + home_hero: "Hero image" + home_show_stats: "Show statistics" + footer_logo: "Logo (220x76)" + footer_facebook_url: "Facebook URL" + footer_twitter_url: "Twitter URL" + footer_instagram_url: "Instagram URL" + footer_linkedin_url: "LinkedIn URL" + footer_googleplus_url: "Google Plus URL" + footer_pinterest_url: "Pinterest URL" + footer_email: "Email" + footer_links_md: "Links" + footer_about_url: "About URL" + user_guide_link: "User Guide Link" + name: Name + first_name: First Name + last_name: Last Name + email: Email + phone: Phone + next: Next + address: Address + address_placeholder: eg. 123 High Street + address2: Address (contd.) + city: City + city_placeholder: eg. Newcastle + postcode: Postcode + postcode_placeholder: eg. 3070 + suburb: Suburb + state: State + country: Country + unauthorized: Unauthorized + terms_of_service: "Terms of service" + on_demand: Unlimited + none: None + not_allowed: Not allowed + no_shipping: no shipping methods + no_payment: no payment methods + no_shipping_or_payment: no shipping or payment methods + unconfirmed: unconfirmed + days: days + authorization_failure: "Authorisation Failure" + label_shop: "Shop" + label_shops: "Shops" + label_map: "Map" + label_producer: "Producer" + label_producers: "Producers" + label_groups: "Groups" + label_about: "About" + label_connect: "Connect" + label_learn: "Learn" + label_blog: "Blog" + label_support: "Support" + label_shopping: "Shopping" + label_login: "Login" + label_logout: "Logout" + label_signup: "Sign up" + label_administration: "Administration" + label_admin: "Admin" + label_account: "Account" + label_more: "Show more" + label_less: "Show less" + label_notices: "Community Forum" + cart_items: "items" + cart_headline: "Your shopping cart" + total: "Total" + cart_updating: "Updating cart..." + cart_empty: "Cart empty" + cart_edit: "Edit your cart" + card_number: Card Number + card_securitycode: "Security Code" + card_expiry_date: Expiry Date + card_masked_digit: "X" + card_expiry_abbreviation: "Exp" + new_credit_card: "New credit card" + my_credit_cards: My credit cards + add_new_credit_card: Add new credit card + saved_cards: Saved cards + add_a_card: Add a Card + add_card: Add Card + you_have_no_saved_cards: You haven't saved any cards yet + saving_credit_card: Saving credit card... + card_has_been_removed: "Your card has been removed (number: %{number})" + card_could_not_be_removed: Sorry, the card could not be removed + invalid_credit_card: "Invalid credit card" + ie_warning_headline: "Your browser is out of date :-(" + ie_warning_text: "For the best Open Food Network experience, we strongly recommend upgrading your browser:" + ie_warning_chrome: Download Chrome + ie_warning_firefox: Download Firefox + ie_warning_ie: Upgrade Internet Explorer + ie_warning_other: "Can't upgrade your browser? Try Open Food Network on your smartphone :-)" + legal: + cookies_policy: + header: "How We Use Cookies" + desc_part_1: "Cookies are very small text files that are stored on your computer when you visit some websites." + desc_part_2: "In OFN we are fully respectful of your privacy. We use only the cookies that are necessary for delivering you the service of selling/buying food online. We don’t sell any of your data. We might in the future propose you to share some of your data to build new commons services that could be useful for the ecosystem (like logistics services for short food systems) but we are not yet there, and we won’t do it without your authorization :-)" + desc_part_3: "We use cookies mainly to remember who you are if you 'log in' to the service, or to be able to remember the items you put in your cart even if you are not logged in. If you keep navigating on the website without clicking on “Accept cookies”, we assume you are giving us consent to store the cookies that are essential for the functioning of the website. Here is the list of cookies we use!" + essential_cookies: "Essential Cookies" + essential_cookies_desc: "The following cookies are strictly necessary for the operation of our website." + essential_cookies_note: "Most cookies only contain a unique identifier, but no other data, so your email address and password for instance are never contained in them and never exposed." + cookie_domain: "Set By:" + cookie_session_desc: "Used to allow the website to remember users between page visits, for example, remember items in your cart." + cookie_consent_desc: "Used to maintain status of user consent to store cookies" + cookie_remember_me_desc: "Used if the user has requested the website to remember him. This cookie is automatically deleted after 12 days. If as a user you want that cookie to be deleted, you only need to logout. If you don’t want that cookie to be installed on your computer you shouldn’t check the “remember me” checkbox when logging in." + cookie_openstreemap_desc: "Used by our friendly open source mapping provider (OpenStreetMap) to ensure that it does not receive too many requests during a given time period, to prevent abuse of their services." + cookie_stripe_desc: "Data collected by our payment processor Stripe for fraud detection https://stripe.com/cookies-policy/legal. Not all shops use Stripe as a payment method but it is a good practice to prevent fraud to apply it to all pages. Stripe probably build a picture of which of our pages usually interact with their API and then flag anything unusual. So setting the Stripe cookie has a broader function than simply the provision of a payment method to a user. Removing it could affect the security of the service itself. You can learn more about Stripe and read its privacy policy at https://stripe.com/privacy." + statistics_cookies: "Statistics Cookies" + statistics_cookies_desc: "The following are not strictly necessary, but help to provide you with the best user experience by allowing us to analyse user behaviour, identify which features you use most, or don’t use, understand user experience issues, etc." + statistics_cookies_matomo_desc_html: "To collect and analyse platform usage data, we use Matomo (ex Piwik), an open source analytics tool that is GDPR compliant and protects your privacy." + statistics_cookies_matomo_optout: "Would you like to opt-out of Matomo analytics? We use Matomo to help us improve our service, but we don't collect any personal data." + cookie_matomo_basics_desc: "Matomo first party cookies to collect statistics." + cookie_matomo_heatmap_desc: "Matomo Heatmap & Session Recording cookie." + cookie_matomo_ignore_desc: "Cookie used to exclude user from being tracked." + disabling_cookies_header: "Warning on disabling cookies" + disabling_cookies_desc: "As a user you can always allow, block or delete Open Food Network’s or any other website cookies whenever you want to through your browser’s setting control. Each browser has a different operative. Here are the links:" + disabling_cookies_firefox_link: "https://support.mozilla.org/en-US/kb/enable-and-disable-cookies-website-preferences" + disabling_cookies_chrome_link: "https://support.google.com/chrome/answer/95647" + disabling_cookies_ie_link: "https://support.microsoft.com/en-us/help/17442/windows-internet-explorer-delete-manage-cookies" + disabling_cookies_safari_link: "https://www.apple.com/legal/privacy/en-ww/cookies/" + disabling_cookies_note: "But be aware that if you delete or modify the essential cookies used by Open Food Network, the website won’t work, you will not be able to add anything to your cart neither to checkout for instance." + cookies_banner: + cookies_usage: "This site uses cookies in order to make your navigation frictionless and secure, and to help us understand how you use it in order to improve the features we offer." + cookies_definition: "Cookies are very small text files that are stored on your computer when you visit some websites." + cookies_desc: "We use only the cookies that are necessary for delivering you the service of selling/buying food online. We don’t sell any of your data. We use cookies mainly to remember who you are if you ‘log in’ to the service, or to be able to remember the items you put in your cart even if you are not logged in. If you keep navigating on the website without clicking on “Accept cookies”, we assume you are giving us consent to store the cookies that are essential for the functioning of the website." + cookies_policy_link_desc: "If you want to learn more, check our" + cookies_policy_link: "cookies policy" + cookies_accept_button: "Accept Cookies" + home_shop: Shop Now + brandstory_headline: "Growing Local Food Online" + brandstory_intro: "THE online community helping you to build a successful food enterprise" + brandstory_part1: "The Open Food Network software platform allows farmers to sell produce online, at a price that works for them. It has been built specifically for selling food so it can handle tricky measures or stock levels that only food has - a dozen eggs, a bunch of parsley, a whole chicken that varies in weight…" + brandstory_part2: "Food Producers can create an online shop, collect payments, sell through other shops on the platform and access reduced-rate courier services." + brandstory_part3: "Wholesalers can integrate OFN with their existing systems and manage buying groups to  supply customers with their produce through our national network of food hubs and shops." + brandstory_part4: "Communities can bring together producers in their local area to create virtual farmers’ markets, building a resilient local food economy." + brandstory_part5_strong: "And what’s just as important as the software itself are the values which underpin it. " + brandstory_part6: "If you sell good food - as a farmer, farmer’s market, food co-op, or food hub- then choose software that aligns with your values to build food systems for people and planet, not profit. By working collectively rather than competitively, we share the costs of developing new software, and we ensure that our project is resilient!" + learn_body: "Explore models, stories and resources to support you to develop your fair food business or organisation. Find training, events and other opportunities to learn from peers." + learn_cta: "Get Inspired" + connect_body: "Search our full directories of producers, hubs and groups to find fair food traders near you. List your business or organisation on the OFN so buyers can find you. Join the community to get advice and solve problems together." + connect_cta: "Go Exploring" + system_headline: "Selling on OFN - 3 easy steps" + system_step1: "1. Create Your Enterprise" + system_step1_text: "Set up your enterprise with a name, description, photos, contact details and social media links." + system_step2: "2. Add Your Products" + system_step2_text: "Add products to your shop - your own and/or from other producers around you. Set images, descriptions, prices, stock levels and flexible weights and measures." + system_step3: "3. Plan your Deliveries" + system_step3_text: "Set up payment methods. Create multiple pick-up points and delivery details. Create recurring orders and regular distributions." + cta_headline: "The sustainable Food Network of the future." + cta_label: "I'm Ready" + stats_headline: "We're creating a new food system." + stats_producers: "food producers" + stats_shops: "food shops" + stats_shoppers: "food shoppers" + stats_orders: "food orders" + checkout_title: Checkout + checkout_now: Checkout now + checkout_order_ready: Order ready for + checkout_hide: Hide + checkout_expand: Expand + checkout_headline: "Ok, ready to checkout?" + checkout_as_guest: "Checkout as guest" + checkout_details: "Your details" + checkout_billing: "Billing info" + checkout_default_bill_address: "Save as default billing address" + checkout_shipping: Shipping info + checkout_default_ship_address: "Save as default shipping address" + checkout_method_free: Free + checkout_address_same: Shipping address same as billing address? + checkout_ready_for: "Ready for:" + checkout_instructions: "Any comments or special instructions?" + checkout_payment: Payment + checkout_send: Place order now + checkout_your_order: Your order + checkout_cart_total: Cart total + checkout_shipping_price: Shipping + checkout_total_price: Total + checkout_back_to_cart: "Back to cart" + cost_currency: "Cost Currency" + order_paid: PAID + order_not_paid: NOT PAID + order_total: Total order + order_payment: "Paying via:" + order_billing_address: Billing address + order_delivery_on: Delivery on + order_delivery_address: Delivery address + order_delivery_time: Delivery time + order_special_instructions: "Your notes:" + order_pickup_time: Ready for collection + order_pickup_instructions: Collection instructions + order_produce: Produce + order_total_price: Total + 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 Store + order_back_to_cart: Back To Cart + bom_tip: "Use this page to alter product quantities across multiple orders. Products may also be removed from orders entirely, if required." + unsaved_changes_warning: "Unsaved changes exist and will be lost if you continue." + unsaved_changes_error: "Fields with red borders contain errors." + products: "Products" + products_in: "in %{oc}" + products_at: "at %{distributor}" + products_elsewhere: "Products found elsewhere" + email_confirmed: "Thank you for confirming your email address." + email_confirmation_activate_account: "Before we can activate your new account, we need to confirm your email address." + email_confirmation_greeting: "Hi, %{contact}!" + email_confirmation_profile_created: "A profile for %{name} has been successfully created! To activate your Profile we need to confirm this email address." + email_confirmation_click_link: "Please click the link below to confirm your email and to continue setting up your profile." + email_confirmation_link_label: "Confirm this email address »" + email_confirmation_help_html: "After confirming your email you can access your administration account for this enterprise. See the %{link} to find out more about %{sitename}'s features and to start using your profile or online store." + email_confirmation_notice_unexpected: "You received this message because you signed up on %{sitename}, or were invited to sign up by someone you probably know. If you don't understand why you are receiving this email, please write to %{contact}." + email_social: "Connect with Us:" + email_contact: "Email us:" + email_signoff: "Cheers," + email_signature: "%{sitename} Team" + email_confirm_customer_greeting: "Hi %{name}," + email_confirm_customer_intro_html: "Thanks for shopping at %{distributor}!" + email_confirm_customer_number_html: "Order confirmation #%{number}" + email_confirm_customer_details_html: "Here are your order details from %{distributor}:" + email_confirm_customer_signoff: "Kind regards," + email_confirm_shop_greeting: "Hi %{name}," + email_confirm_shop_order_html: "Well done! You have a new order for %{distributor}!" + email_confirm_shop_number_html: "Order confirmation #%{number}" + email_order_summary_item: "Item" + email_order_summary_quantity: "Qty" + email_order_summary_sku: "Product Code" + email_order_summary_price: "Price" + email_order_summary_subtotal: "Subtotal:" + email_order_summary_total: "Total:" + email_order_summary_includes_tax: "(includes tax):" + email_payment_paid: PAID + email_payment_not_paid: NOT PAID + email_payment_summary: Payment summary + email_payment_method: "Paying via:" + email_so_placement_intro_html: "You have a new order with %{distributor}" + email_so_placement_details_html: "Here are the details of your order for %{distributor}:" + email_so_placement_changes: "Unfortunately, not all products that you requested were available. The original quantities that you requested appear crossed-out below." + email_so_payment_success_intro_html: "An automatic payment has been processed for your order from %{distributor}." + email_so_placement_explainer_html: "This order was automatically created for you." + email_so_edit_true_html: "You can make changes until orders close on %{orders_close_at}." + email_so_edit_false_html: "You can view details of this order at any time." + email_so_contact_distributor_html: "If you have any questions you can contact %{distributor} via %{email}." + email_so_contact_distributor_to_change_order_html: "This order was automatically created for you. You can make changes until orders close on %{orders_close_at} by contacting %{distributor} via %{email}." + email_so_confirmation_intro_html: "Your order with %{distributor} is now confirmed" + email_so_confirmation_explainer_html: "This order was automatically placed for you, and it has now been finalised." + email_so_confirmation_details_html: "Here's everything you need to know about your order from %{distributor}:" + email_so_empty_intro_html: "We tried to place a new order with %{distributor}, but had some problems..." + email_so_empty_explainer_html: "Unfortunately, none of products that you ordered were available, so no order has been placed. The original quantities that you requested appear crossed-out below." + email_so_empty_details_html: "Here are the details of the unplaced order for %{distributor}:" + email_so_failed_payment_intro_html: "We tried to process a payment, but had some problems..." + email_so_failed_payment_explainer_html: "The payment for your subscription with %{distributor} failed because of a problem with your credit card. %{distributor} has been notified of this failed payment." + email_so_failed_payment_details_html: "Here are the details of the failure provided by the payment gateway:" + email_shipping_delivery_details: Delivery details + email_shipping_delivery_time: "Delivery on:" + email_shipping_delivery_address: "Delivery address:" + email_shipping_collection_details: Collection details + email_shipping_collection_time: "Ready for collection:" + email_shipping_collection_instructions: "Collection instructions:" + email_special_instructions: "Your notes:" + email_signup_greeting: Hello! + email_signup_welcome: "Welcome to %{sitename}!" + email_signup_confirmed_email: "Thanks for confirming your email." + email_signup_shop_html: "You can now log in at %{link}." + email_signup_text: "Thanks for joining the network. If you are a customer, we look forward to introducing you to many fantastic farmers, wonderful food hubs and delicious food! If you are a producer or food enterprise, we are excited to have you as a part of the network." + email_signup_help_html: "We welcome all your questions and feedback; you can use the Send Feedback button on the site or email us at %{email}" + invite_email: + greeting: "Hello!" + invited_to_manage: "You have been invited to manage %{enterprise} on %{instance}." + confirm_your_email: "You should have received or will soon receive an email with a confirmation link. You won’t be able to access %{enterprise}'s profile until you have confirmed your email." + set_a_password: "You will then be prompted to set a password before you are able to administer the enterprise." + mistakenly_sent: "Not sure why you have received this email? Please contact %{owner_email} for more information." + producer_mail_greeting: "Dear" + producer_mail_text_before: "We now have all the consumer orders for the next food delivery." + producer_mail_order_text: "Here is a summary of the orders for your products:" + producer_mail_delivery_instructions: "Stock pickup/delivery instructions:" + producer_mail_signoff: "Thanks and best wishes" + shopping_oc_closed: Orders are closed + shopping_oc_closed_description: "Please wait until the next cycle opens (or contact us directly to see if we can accept any late orders)" + shopping_oc_last_closed: "The last cycle closed %{distance_of_time} ago" + shopping_oc_next_open: "The next cycle opens in %{distance_of_time}" + shopping_oc_select: "Select..." + shopping_tabs_home: "Notices" + shopping_tabs_shop: "Shop" + shopping_tabs_about: "About" + shopping_tabs_contact: "Contact" + shopping_contact_address: "Address" + shopping_contact_web: "Contact" + shopping_contact_social: "Follow" + shopping_groups_part_of: "is part of:" + shopping_producers_of_hub: "%{hub}'s producers:" + enterprises_next_closing: "Next order closing" + enterprises_ready_for: "Ready for" + enterprises_choose: "Choose when you want your order:" + maps_open: "Open" + maps_closed: "Closed" + hubs_buy: "Shop for:" + hubs_shopping_here: "Shopping here" + hubs_orders_closed: "Orders closed" + hubs_profile_only: "Profile only" + hubs_delivery_options: "Delivery options" + hubs_pickup: "Pickup" + hubs_delivery: "Delivery" + hubs_producers: "Our producers" + hubs_filter_by: "Filter by" + hubs_filter_type: "Type" + hubs_filter_delivery: "Delivery" + hubs_filter_property: "Property" + hubs_matches: "Showing results for" + hubs_intro: Shop in your local area + hubs_distance: Closest to + hubs_distance_filter: "Show me shops near %{location}" + shop_changeable_orders_alert_html: + one: Your order with %{shop} / %{order} is open for review. You can make changes until %{oc_close}. + other: You have %{count} orders with %{shop} currently open for review. You can make changes until %{oc_close}. + orders_changeable_orders_alert_html: This order has been confirmed, but you can make changes until %{oc_close}. + products_clear: Clear + products_showing: "Showing:" + products_results_for: "Results for" + products_or: "or" + products_and: "and" + products_filters_in: "in" + products_with: with + products_search: "Search..." + products_filter_by: "Filter by" + products_filter_selected: "selected" + products_filter_heading: "Filters" + products_filter_clear: "Clear" + products_filter_done: "Done" + products_loading: "Loading products..." + products_updating_cart: "Updating cart..." + products_cart_empty: "Cart empty" + products_edit_cart: "Edit your cart" + products_from: from + products_change: "No changes to save." + products_update_error: "Saving failed with the following error(s):" + products_update_error_msg: "Saving failed." + products_update_error_data: "Save failed due to invalid data:" + products_changes_saved: "Changes saved." + products_no_results_html: "Sorry, no results found for %{query}" + products_clear_search: "Clear search" + search_no_results_html: "Sorry, no results found for %{query}. Try another search?" + components_profiles_popover: "Profiles do not have a shopfront on the Open Food Network, but may have their own physical or online shop elsewhere" + components_profiles_show: "Show profiles" + components_filters_nofilters: "No filters" + components_filters_clearfilters: "Clear all filters" + groups_title: Groups + groups_headline: Groups / regions + groups_text: "Every producer is unique. Every business has something different to offer. Our groups are collectives of producers, hubs and distributors who share something in common like location, farmers market or philosophy. This makes your shopping experience easier. So explore our groups and have the curating done for you." + groups_search: "Search name or keyword" + groups_no_groups: "No groups found" + groups_about: "About Us" + groups_producers: "Our producers" + groups_hubs: "Our hubs" + groups_contact_web: Contact + groups_contact_social: Follow + groups_contact_address: Address + groups_contact_email: Email us + groups_contact_website: Visit our website + groups_contact_facebook: Follow us on Facebook + groups_signup_title: Sign up as a group + groups_signup_headline: Groups sign up + groups_signup_intro: "We're an amazing platform for collaborative marketing, the easiest way for your members and stakeholders to reach new markets. We're non-profit, affordable, and simple." + groups_signup_email: Email us + groups_signup_motivation1: We transform food systems fairly. + groups_signup_motivation2: It's why we get out of bed every day. We're a global non-profit, based on open source code. We play fair. You can always trust us. + groups_signup_motivation3: We know you have big ideas, and we want to help. We'll share our knowledge, networks and resources. We know that isolation doesn't create change, so we'll partner with you. + groups_signup_motivation4: We meet you where you are. + groups_signup_motivation5: You might be an alliance of food hubs, producers, or distributors, and an industry body, or a local government. + groups_signup_motivation6: Whatever your role in your local food movement, we're ready to help. However you come to wonder what Open Food Network would look like or is doing in your part of the world, let's start the conversation. + groups_signup_motivation7: We make food movements make more sense. + groups_signup_motivation8: You need to activate and enable your networks, we offer a platform for conversation and action. You need real engagement. We’ll help reach all the players, all the stakeholders, all the sectors. + groups_signup_motivation9: You need resourcing. We’ll bring all our experience to bear. You need cooperation. We’ll better connect you to a global network of peers. + groups_signup_pricing: Group Account + groups_signup_studies: Case Studies + groups_signup_contact: Ready to discuss? + groups_signup_contact_text: "Get in touch to discover what OFN can do for you:" + groups_signup_detail: "Here's the detail." + login_invalid: "Invalid email or password" + producers_about: About us + producers_buy: Shop for + producers_contact: Contact + producers_contact_phone: Call + producers_contact_social: Follow + producers_buy_at_html: "Shop for %{enterprise} products at:" + producers_filter: Filter by + producers_filter_type: Type + producers_filter_property: Property + producers_title: Producers + producers_headline: Find local producers + producers_signup_title: Sign up as a producer + producers_signup_headline: Food producers. Meet OFN. + producers_signup_motivation: Sell your food and tell your stories. Save time and money on every overhead. We support innovation without the risk. We're levelling the playing field. + producers_signup_send: Register + producers_signup_enterprise: Why the Open Food Network? + producers_signup_studies: Stories from our producers. + producers_signup_cta_headline: Join now! + producers_signup_cta_action: Register + producers_signup_detail: Got a question? + products_item: Item + products_description: Description + products_variant: Variant + products_quantity: Quantity + products_available: Available? + products_producer: "Producer" + products_price: "Price" + name_or_sku: "NAME OR SKU" + register_title: Find out more + sell_title: "Register" + sell_headline: "Get on the Open Food Network!" + sell_motivation: "Showcase your beautiful food." + sell_producers: "Producers" + sell_hubs: "Hubs" + sell_groups: "Groups" + sell_producers_detail: "Set up a profile for your business on the OFN in just minutes. At any time you can upgrade your profile to an online store and sell your products direct to customers." + sell_hubs_detail: "Set up a profile for your food enterprise or organisation on the OFN. At any time you can upgrade your profile to a multi-producer shop." + sell_groups_detail: "Set up a tailored directory of enterprises (producers and other food enterprises) for your region or for your organisation." + sell_user_guide: "Find out more in our user guide." + sell_listing_price: "Listing a profile on OFN India is free. Plans for shops and hubs start from as little as ₹100 per month. For more detail on pricing visit https://about.openfoodindia.org/pricing/ ." + sell_embed: "We collectively budget for new feature development from the international OFN community. This way the huge cost of good software development can be shared. If you want a new feature, chances are someone in France, South Africa, Australia, India or Brazil might want it too! Use the Community Forum to suggest features you'd like to see." + sell_ask_services: "Ask us about OFN services." + shops_title: Shops + shops_headline: Shopping, transformed. + shops_text: Food grows in cycles, farmers harvest in cycles, and we order food in cycles. If you find an order cycle closed, check back soon. + shops_signup_title: Sign up as a hub + shops_signup_headline: Food hubs, unlimited. + shops_signup_motivation: Whatever your model, we support you. However you change, we're with you. We're non-profit, independent, and open-sourced. We're the software partners you've dreamed of. + shops_signup_action: Join now + shops_signup_pricing: Enterprise Accounts + shops_signup_stories: Stories from our hubs. + shops_signup_help: We're ready to help. + shops_signup_help_text: You need a better return. You need new buyers and logistics partners. You need your story told across wholesale, retail, and the kitchen table. + shops_signup_detail: Here's the detail. + orders: Orders + orders_fees: Fees... + orders_edit_title: Shopping cart + orders_edit_headline: Your shopping cart + orders_edit_time: Order ready for + orders_edit_continue: Continue shopping + orders_edit_checkout: Checkout + orders_form_empty_cart: "Empty cart" + orders_form_subtotal: Produce subtotal + orders_form_admin: Admin & Handling + orders_form_total: Total + orders_oc_expired_headline: Orders have closed for this order cycle + orders_oc_expired_text: "Sorry, orders for this order cycle closed %{time} ago! Please contact your hub directly to see if they can accept late orders." + orders_oc_expired_text_others_html: "Sorry, orders for this order cycle closed %{time} ago! Please contact your hub directly to see if they can accept late orders %{link}." + orders_oc_expired_text_link: "or see the other order cycles available at this hub" + orders_oc_expired_email: "Email:" + orders_oc_expired_phone: "Phone:" + orders_show_title: Order Confirmation + orders_show_time: Order ready on + orders_show_order_number: "Order #%{number}" + orders_show_cancelled: Cancelled + orders_show_confirmed: Confirmed + orders_your_order_has_been_cancelled: "Your order has been cancelled" + orders_could_not_cancel: "Sorry, the order could not be cancelled" + orders_cannot_remove_the_final_item: "Cannot remove the final item from an order, please cancel the order instead." + orders_bought_items_notice: + one: "An additional item is already confirmed for this order cycle" + other: "%{count} additional items already confirmed for this order cycle" + orders_bought_edit_button: Edit confirmed items + orders_bought_already_confirmed: "* already confirmed" + orders_confirm_cancel: Are you sure you want to cancel this order? + order_processed_successfully: "Your order has been processed successfully" + products_cart_distributor_choice: "Distributor for your order:" + products_cart_distributor_change: "Your distributor for this order will be changed to %{name} if you add this product to your cart." + products_cart_distributor_is: "Your distributor for this order is %{name}." + products_distributor_error: "Please complete your order at %{link} before shopping with another distributor." + products_oc: "Order cycle for your order:" + products_oc_change: "Your order cycle for this order will be changed to %{name} if you add this product to your cart." + products_oc_is: "Your order cycle for this order is %{name}." + products_oc_error: "Please complete your order from %{link} before shopping in a different order cycle." + products_oc_current: "your current order cycle" + products_max_quantity: Max quantity + products_distributor: Distributor + products_distributor_info: When you select a distributor for your order, their address and pickup times will be displayed here. + password: Password + remember_me: Remember Me + are_you_sure: "Are you sure?" + orders_open: "Orders open" + closing: "Closing " + going_back_to_home_page: "Taking you back to the home page" + creating: Creating + updating: Updating + failed_to_create_enterprise: "Failed to create your enterprise." + failed_to_create_enterprise_unknown: "Failed to create your enterprise.\nPlease ensure all fields are completely filled out." + failed_to_update_enterprise_unknown: "Failed to update your enterprise.\nPlease ensure all fields are completely filled out." + enterprise_confirm_delete_message: "This will also delete the %{product} that this enterprise supplies. Are you sure you want to continue?" + order_not_saved_yet: "Your order hasn't been saved yet. Give us a few seconds to finish!" + filter_by: "Filter by" + hide_filters: "Hide filters" + one_filter_applied: "1 filter applied" + x_filters_applied: " filters applied" + submitting_order: "Submitting your order: please wait" + confirm_hub_change: "Are you sure? This will change your selected hub and remove any items in your shopping cart." + confirm_oc_change: "Are you sure? This will change your selected order cycle and remove any items in your shopping cart." + location_placeholder: "Type in a location..." + error_required: "can't be blank" + error_number: "must be number" + error_email: "must be email address" + error_not_found_in_database: "%{name} not found in database" + error_not_primary_producer: "%{name} is not enabled as a producer" + error_no_permission_for_enterprise: "\"%{name}\": you do not have permission to manage products for this enterprise" + item_handling_fees: "Item Handling Fees (included in item totals)" + january: "January" + february: "February" + march: "March" + april: "April" + may: "May" + june: "June" + july: "July" + august: "August" + september: "September" + october: "October" + november: "November" + december: "December" + email_not_found: "Email address not found" + email_unconfirmed: "You must confirm your email address before you can reset your password." + email_required: "You must provide an email address" + logging_in: "Hold on a moment, we're logging you in" + signup_email: "Your email" + choose_password: "Choose a password" + confirm_password: "Confirm password" + action_signup: "Sign up now" + forgot_password: "Forgot password?" + password_reset_sent: "An email with instructions on resetting your password has been sent!" + reset_password: "Reset password" + update_and_recalculate_fees: "Update And Recalculate Fees" + registration: + steps: + introduction: + registration_greeting: "Hi there!" + registration_intro: "You can now create a profile for your Producer or Hub" + registration_checklist: "What do I need?" + registration_time: "5-10 minutes" + registration_enterprise_address: "Enterprise address" + registration_contact_details: "Primary contact details" + registration_logo: "Your logo image" + registration_promo_image: "Landscape image for your profile" + registration_about_us: "'About Us' text" + registration_outcome_headline: "What do I get?" + registration_outcome1_html: "Your profile helps people find and contact you on the Open Food Network." + registration_outcome2: "Use this space to tell the story of your enterprise, to help drive connections to your social and online presence." + registration_outcome3: "It's also the first step towards trading on the Open Food Network, or opening an online store." + registration_action: "Let's get started!" + details: + title: "Details" + headline: "Let's Get Started" + enterprise: "Yippee! First need to know a little bit about your enterprise:" + producer: "Great! First we need to know a little bit about your farm:" + enterprise_name_field: "Enterprise Name:" + producer_name_field: "Farm Name:" + producer_name_field_placeholder: "e.g. Charlie's Chilli Farm" + producer_name_field_error: "Sorry, this name has already been taken. Please try another." + address1_field: "Address line 1:" + address1_field_placeholder: "e.g. 123 Apple Drive" + address1_field_error: "Please enter an address" + address2_field: "Address line 2:" + suburb_field: "Town:" + suburb_field_placeholder: "eg. Taunton" + suburb_field_error: "Please enter a postal town" + postcode_field: "Postcode:" + postcode_field_placeholder: "eg. TA1 TAA" + postcode_field_error: "Postcode required" + state_field: "State:" + state_field_error: "State Required" + country_field: "Country:" + country_field_error: "Please select a country" + contact: + title: "Contact" + who_is_managing_enterprise: "Who is responsible for managing %{enterprise}?" + contact_field: "Primary Contact" + contact_field_placeholder: "Contact Name" + contact_field_required: "Please enter a primary contact." + phone_field: "Phone number" + phone_field_placeholder: "eg. 07123123123" + type: + title: "Type" + headline: "Last step to add %{enterprise}!" + question: "Are you a producer?" + yes_producer: "Yes, I'm a producer" + no_producer: "No, I'm not a producer" + producer_field_error: "Please choose one. Are you are producer?" + yes_producer_help: "Producers make tasty things to eat and/or drink. You're a producer if you grow, rear, brew, bake, ferment ... etc." + 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." + create_profile: "Create profile" + about: + title: "About" + headline: "Nice one!" + message: "Now let's flesh out the details about" + success: "Success! %{enterprise} added to the Open Food Network" + registration_exit_message: "If you exit this wizard at any stage, you can continue to create your profile by going to the admin interface." + enterprise_description: "Short Description" + enterprise_description_placeholder: "A short sentence describing your enterprise" + enterprise_long_desc: "Long Description" + enterprise_long_desc_placeholder: "This is your opportunity to tell the story of your enterprise - what makes you different and wonderful? We'd suggest keeping your description to under 600 characters or 150 words." + enterprise_long_desc_length: "%{num} characters / up to 600 recommended" + enterprise_abn: "Company Number" + enterprise_abn_placeholder: "eg. 99 123 456 789" + enterprise_acn: "Charity Number" + enterprise_acn_placeholder: "eg. 123 456 789" + enterprise_tax_required: "You need to make a selection." + images: + title: "Images" + headline: "Thanks!" + description: "Let's upload some pictures so your profile looks great! :)" + uploading: "Uploading..." + continue: "Continue" + back: "Back" + logo: + select_logo: "Step 1. Select logo image" + logo_tip: "Tip: Square images will work best, preferably at least 300×300px" + logo_label: "Choose a logo image" + logo_drag: "Drag and drop your logo here" + review_logo: "Step 2. Review your logo" + review_logo_tip: "Tip: for best results, your logo should fill the available space" + logo_placeholder: "Your logo will appear here for review once uploaded" + promo: + select_promo_image: "Step 3. Select promo image" + promo_image_tip: "Tip: Shown as a banner, preferred size is 1200×260px" + promo_image_label: "Choose a promo image" + promo_image_drag: "Drag and drop your promo here" + review_promo_image: "Step 4. Review your promo banner" + review_promo_image_tip: "Tip: for best results, your promo image should fill the available space" + promo_image_placeholder: "Your logo will appear here for review once uploaded" + social: + title: "Social" + enterprise_final_step: "Final step!" + enterprise_social_text: "How can people find %{enterprise} online?" + website: "Website" + website_placeholder: "eg. openfoodnetwork.org.au" + facebook: "Facebook" + facebook_placeholder: "eg. www.facebook.com/PageNameHere" + linkedin: "LinkedIn" + linkedin_placeholder: "eg. www.linkedin.com/YourNameHere" + twitter: "Twitter" + twitter_placeholder: "eg. @twitter_handle" + instagram: "Instagram" + instagram_placeholder: "eg. @instagram_handle" + limit_reached: + headline: "Oh no!" + message: "You have reached the limit!" + text: "You have reached the limit for the number of enterprises you are allowed to own on the" + action: "Return to the homepage" + finished: + headline: "Finished!" + thanks: "Thanks for filling out the details for %{enterprise}." + login: "You can change or update your enterprise at any stage by logging into Open Food Network and going to Admin." + action: "Go to Enterprise Dashboard" + back: "Back" + continue: "Continue" + action_or: "OR" + enterprise_limit: Enterprise Limit + shipping_method_destroy_error: "That shipping method cannot be deleted as it is referenced by an order: %{number}." + fees: "Fees" + item_cost: "Item cost" + bulk: "Bulk" + shop_variant_quantity_min: "min" + shop_variant_quantity_max: "max" + follow: "Follow" + shop_for_products_html: "Shop for %{enterprise} products at:" + change_shop: "Change shop to:" + shop_at: "Shop now at:" + price_breakdown: "Full price breakdown" + admin_fee: "Admin fee" + sales_fee: "Sales fee" + packing_fee: "Packing fee" + transport_fee: "Transport fee" + fundraising_fee: "Fundraising fee" + price_graph: "Price graph" + included_tax: "Included tax" + balance: "Balance" + transaction: "Transaction" + transaction_date: "Date" + payment_state: "Payment status" + shipping_state: "Shipping status" + value: "Value" + balance_due: "Balance due" + credit: "Credit" + Paid: "Paid" + Ready: "Ready" + ok: OK + not_visible: not visible + you_have_no_orders_yet: "You have no orders yet" + show_only_complete_orders: "Only show complete orders" + successfully_created: '%{resource} has been successfully created!' + successfully_removed: '%{resource} has been successfully removed!' + successfully_updated: '%{resource} has been successfully updated!' + running_balance: "Running balance" + outstanding_balance: "Outstanding balance" + admin_enterprise_relationships: "Enterprise Permissions" + admin_enterprise_relationships_everything: "Everything" + admin_enterprise_relationships_permits: "permits" + admin_enterprise_relationships_seach_placeholder: "Search" + admin_enterprise_relationships_button_create: "Create" + admin_enterprise_relationships_to: "to" + admin_enterprise_groups: "Enterprise Groups" + admin_enterprise_groups_name: "Name" + admin_enterprise_groups_owner: "Owner" + admin_enterprise_groups_on_front_page: "On front page?" + admin_enterprise_groups_enterprise: "Enterprises" + admin_enterprise_groups_data_powertip: "The primary user responsible for this group." + admin_enterprise_groups_data_powertip_logo: "This is the logo for the group" + admin_enterprise_groups_data_powertip_promo_image: "This image is displayed at the top of the Group profile" + admin_enterprise_groups_contact: "Contact" + admin_enterprise_groups_contact_phone_placeholder: "eg. 98 7654 3210" + admin_enterprise_groups_contact_address1_placeholder: "eg. 123 High Street" + admin_enterprise_groups_contact_city: "Suburb" + admin_enterprise_groups_contact_city_placeholder: "eg. Newcastle" + admin_enterprise_groups_contact_zipcode: "Postcode" + admin_enterprise_groups_contact_zipcode_placeholder: "eg. 3070" + admin_enterprise_groups_contact_state_id: "State" + admin_enterprise_groups_contact_country_id: "Country" + admin_enterprise_groups_web: "Web Resources" + admin_enterprise_groups_web_twitter: "eg. @the_prof" + admin_enterprise_groups_web_website_placeholder: "eg. www.truffles.co.in" + admin_order_cycles: "Admin Order Cycles" + open: "Open" + close: "Close" + create: "Create" + search: "Search" + supplier: "Supplier" + product_name: "Product Name" + product_description: "Product Description" + units: "Unit Size" + coordinator: "Coordinator" + distributor: "Distributor" + enterprise_fees: "Enterprise Fees" + process_my_order: "Process My Order" + delivery_instructions: Delivery Instructions + delivery_method: Delivery Method + fee_type: "Fee Type" + tax_category: "Tax Category" + calculator: "Calculator" + calculator_values: "Calculator values" + calculator_settings_warning: "If you are changing the calculator type, you must save first before you can edit the calculator settings" + flat_percent_per_item: "Flat Percent (per item)" + flat_rate_per_item: "Flat Rate (per item)" + flat_rate_per_order: "Flat Rate (per order)" + flexible_rate: "Flexible Rate" + price_sack: "Price Sack" + new_order_cycles: "New Order Cycles" + new_order_cycle: "New Order Cycle" + select_a_coordinator_for_your_order_cycle: "Select a coordinator for your order cycle" + notify_producers: 'Notify producers' + edit_order_cycle: "Edit Order Cycle" + roles: "Roles" + update: "Update" + delete: Delete + add_producer_property: "Add producer property" + in_progress: "In Progress" + started_at: "Started at" + queued: "Queued" + scheduled_for: "Scheduled for" + customers: "Customers" + please_select_hub: "Please select a Hub" + loading_customers: "Loading Customers" + no_customers_found: "No customers found" + go: "Go" + hub: "Hub" + producer: "Producer" + product: "Product" + price: "Price" + on_hand: "In stock" + review: "Review" + save_changes: "Save Changes" + order_saved: "Order Saved" + no_products: No Products + spree_admin_overview_enterprises_header: "My Enterprises" + spree_admin_overview_enterprises_footer: "MANAGE MY ENTERPRISES" + spree_admin_enterprises_hubs_name: "Name" + spree_admin_enterprises_create_new: "CREATE NEW" + spree_admin_enterprises_shipping_methods: "Shipping Methods" + spree_admin_enterprises_fees: "Enterprise Fees" + spree_admin_enterprises_none_create_a_new_enterprise: "CREATE A NEW ENTERPRISE" + spree_admin_enterprises_none_text: "You don't have any enterprises yet" + spree_admin_enterprises_tabs_hubs: "HUBS" + spree_admin_enterprises_producers_manage_products: "MANAGE PRODUCTS" + spree_admin_enterprises_create_new_product: "CREATE A NEW PRODUCT" + spree_admin_single_enterprise_alert_mail_confirmation: "Please confirm the email address for" + spree_admin_single_enterprise_alert_mail_sent: "We've sent an email to" + spree_admin_overview_action_required: "Action Required" + spree_admin_overview_check_your_inbox: "Please check your inbox for further instructions. Thanks!" + spree_admin_unit_value: Unit Value + spree_admin_unit_description: Unit Description + spree_admin_variant_unit: Variant unit + spree_admin_variant_unit_scale: Variant unit scale + spree_admin_supplier: Supplier + spree_admin_product_category: Product Category + spree_admin_variant_unit_name: Variant unit name + unit_name: "Unit name" + change_package: "Change Package" + spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" + spree_admin_eg_pickup_from_school: "eg. 'Pick-up from Primary School'" + spree_admin_eg_collect_your_order: "eg. 'Please collect your order from 123 Imaginary St, Newcastle, NE1 1AA'" + spree_classification_primary_taxon_error: "Taxon %{taxon} is the primary taxon of %{product} and cannot be deleted" + spree_order_availability_error: "Distributor or order cycle cannot supply the products in your cart" + spree_order_populator_error: "That distributor or order cycle can't supply all the products in your cart. Please choose another." + spree_order_populator_availability_error: "That product is not available from the chosen distributor or order cycle." + spree_distributors_error: "At least one hub must be selected" + spree_user_enterprise_limit_error: "^%{email} is not permitted to own any more enterprises (limit is %{enterprise_limit})." + spree_variant_product_error: must have at least one variant + your_profil_live: "Your profile live" + on_ofn_map: "on the Open Food Network map" + see: "See" + live: "live" + manage: "Manage" + resend: "Resend" + add_and_manage_products: "Add & manage products" + add_and_manage_order_cycles: "Add & manage order cycles" + manage_order_cycles: "Manage order cycles" + manage_products: "Manage products" + edit_profile_details: "Edit profile details" + edit_profile_details_etc: "Change your profile description, images, etc." + order_cycle: "Order Cycle" + order_cycles: "Order Cycles" + enterprise_relationships: "Enterprise permissions" + remove_tax: "Remove tax" + first_name_begins_with: "First name begins with" + last_name_begins_with: "Surname begins with" + enterprise_tos_link: "Enterprise Terms of Service link" + enterprise_tos_message: "We want to work with people that share our aims and values. As such we ask new enterprises to agree to our " + enterprise_tos_link_text: "Terms of Service." + enterprise_tos_agree: "I agree to the above Terms of Service" + tax_settings: "Tax Settings" + products_require_tax_category: "products require tax category" + admin_shared_address_1: "Address" + admin_shared_address_2: "Address (cont.)" + admin_share_city: "City" + admin_share_zipcode: "Postcode" + admin_share_country: "Country" + admin_share_state: "State" + hub_sidebar_hubs: "Hubs" + hub_sidebar_none_available: "None Available" + hub_sidebar_manage: "Manage" + hub_sidebar_at_least: "At least one hub must be selected" + hub_sidebar_blue: "blue" + hub_sidebar_red: "red" + order_cycles_closed_for_hub: "The hub you have selected is temporarily closed for orders. Please try again later." + report_customers_distributor: "Distributor" + report_customers_supplier: "Supplier" + report_customers_cycle: "Order Cycle" + report_customers_type: "Report Type" + report_customers_csv: "Download as csv" + report_producers: "Producers:" + report_type: "Report Type:" + report_hubs: "Hubs:" + report_payment: "Payment Methods:" + report_distributor: "Distributor:" + report_payment_by: 'Payments By Type' + report_itemised_payment: 'Itemised Payment Totals' + report_payment_totals: 'Payment Totals' + report_all: 'all' + report_order_cycle: "Order Cycle:" + report_enterprises: "Enterprises:" + report_users: "Users:" + report_tax_rates: Tax rates + report_tax_types: Tax types + report_header_order_cycle: Order Cycle + report_header_user: User + report_header_email: Email + report_header_status: Status + report_header_comments: Comments + report_header_first_name: First Name + report_header_last_name: Last Name + report_header_phone: Phone + report_header_suburb: Suburb + report_header_address: Address + report_header_billing_address: Billing Address + report_header_relationship: Relationship + report_header_hub: Hub + report_header_hub_address: Hub Address + report_header_to_hub: To Hub + report_header_hub_code: Hub Code + report_header_code: Code + report_header_paid: Paid? + report_header_delivery: Delivery? + report_header_shipping: Shipping + report_header_shipping_method: Shipping Method + report_header_shipping_instructions: Shipping instructions + report_header_ship_street: Ship Street + report_header_ship_street_2: Ship Street 2 + report_header_ship_city: Ship City + report_header_ship_postcode: Ship Postcode + report_header_ship_state: Ship State + report_header_billing_street: Billing Street + report_header_billing_street_2: Billing Street 2 + report_header_billing_street_3: Billing Street 3 + report_header_billing_street_4: Billing Street 4 + report_header_billing_city: Billing City + report_header_billing_postcode: Billing Postcode + report_header_billing_state: Billing State + report_header_incoming_transport: Incoming Transport + report_header_special_instructions: Special Instructions + report_header_order_number: Order number + report_header_date: Date + report_header_confirmation_date: Confirmation Date + report_header_tags: Tags + report_header_items: Items + report_header_items_total: "Items total %{currency_symbol}" + report_header_taxable_items_total: "Taxable Items Total (%{currency_symbol})" + report_header_sales_tax: "Sales Tax (%{currency_symbol})" + report_header_delivery_charge: "Delivery Charge (%{currency_symbol})" + report_header_tax_on_delivery: "Tax on Delivery (%{currency_symbol})" + report_header_tax_on_fees: "Tax on Fees (%{currency_symbol})" + report_header_total_tax: "Total Tax (%{currency_symbol})" + report_header_enterprise: Enterprise + report_header_customer: Customer + report_header_customer_code: Customer Code + report_header_product: Product + report_header_product_properties: Product Properties + report_header_quantity: Quantity + report_header_max_quantity: Max Quantity + report_header_variant: Variant + report_header_variant_value: Variant Value + report_header_variant_unit: Variant Unit + report_header_total_available: Total available + report_header_unallocated: Unallocated + report_header_max_quantity_excess: Max Quantity Excess + report_header_taxons: Taxons + report_header_supplier: Supplier + report_header_producer: Producer + report_header_producer_suburb: Producer Suburb + report_header_unit: Unit + report_header_group_buy_unit_quantity: Group Buy Unit Quantity + report_header_cost: Cost + report_header_shipping_cost: Shipping Cost + report_header_curr_cost_per_unit: Curr. Cost per Unit + report_header_total_shipping_cost: Total Shipping Cost + report_header_payment_method: Payment Method + report_header_sells: Sells + report_header_visible: Visible + report_header_price: Price + report_header_unit_size: Unit Size + report_header_distributor: Distributor + report_header_distributor_address: Distributor address + report_header_distributor_city: Distributor city + report_header_distributor_postcode: Distributor postcode + report_header_delivery_address: Delivery Address + report_header_delivery_postcode: Delivery Postcode + report_header_bulk_unit_size: Bulk Unit Size + report_header_weight: Weight + report_header_sum_total: Sum Total + report_header_date_of_order: Date of Order + report_header_amount_owing: Amount Owing + report_header_amount_paid: Amount Paid + report_header_units_required: Units Required + report_header_remainder: Remainder + report_header_order_date: Order date + report_header_order_id: Order Id + report_header_item_name: Item name + report_header_temp_controlled_items: Temp Controlled Items? + report_header_customer_name: Customer Name + report_header_customer_email: Customer Email + report_header_customer_phone: Customer Phone + report_header_customer_city: Customer City + report_header_payment_state: Payment State + report_header_payment_type: Payment Type + report_header_item_price: "Item (%{currency})" + report_header_item_fees_price: "Item + Fees (%{currency})" + report_header_admin_handling_fees: "Admin & Handling (%{currency})" + report_header_ship_price: "Ship (%{currency})" + report_header_pay_fee_price: "Pay fee (%{currency})" + report_header_total_price: "Total (%{currency})" + report_header_product_total_price: "Product Total (%{currency})" + report_header_shipping_total_price: "Shipping Total (%{currency})" + report_header_outstanding_balance_price: "Outstanding Balance (%{currency})" + report_header_eft_price: "EFT (%{currency})" + report_header_paypal_price: "PayPal (%{currency})" + report_header_sku: Product Code + report_header_amount: Amount + report_header_balance: Balance + report_header_total_cost: "Total Cost" + report_header_total_ordered: Total Ordered + report_header_total_max: Total Max + report_header_total_units: Total Units + report_header_sum_max_total: "Sum Max Total" + report_header_total_excl_vat: "Total excl. tax (%{currency_symbol})" + report_header_total_incl_vat: "Total incl. tax (%{currency_symbol})" + report_header_temp_controlled: TempControlled? + report_header_is_producer: Producer? + report_header_not_confirmed: Not Confirmed + report_header_gst_on_income: 20%(VAT on Income) + report_header_gst_free_income: Zero Rated Income + report_header_total_untaxable_produce: Total untaxable produce (no tax) + report_header_total_taxable_produce: Total taxable produce (tax inclusive) + report_header_total_untaxable_fees: Total untaxable fees (no tax) + report_header_total_taxable_fees: Total taxable fees (tax inclusive) + report_header_delivery_shipping_cost: Delivery Shipping Cost (tax inclusive) + report_header_transaction_fee: Transaction Fee (no tax) + report_header_total_untaxable_admin: Total untaxable admin adjustments (no tax) + report_header_total_taxable_admin: Total taxable admin adjustments (tax inclusive) + initial_invoice_number: "Initial invoice number:" + invoice_date: "Invoice date:" + due_date: "Invoice date:" + account_code: "Account code:" + equals: "Equals" + contains: "contains" + discount: "Discount" + filter_products: " Filter Products" + delete_product_variant: "The last variant cannot be deleted!" + progress: "progress" + saving: "Saving.." + success: "success" + failure: "failure" + unsaved_changes_confirmation: "Unsaved changes will be lost. Continue anyway?" + one_product_unsaved: "Changes to one product remain unsaved." + products_unsaved: "Changes to %{n} products remain unsaved." + is_already_manager: "is already a manager!" + no_change_to_save: "No change to save" + user_invited: "%{email} has been invited to manage this enterprise" + add_manager: "Add an existing user" + users: "Users" + about: "About" + images: "Images" + web: "Web" + primary_details: "Primary Details" + adrdress: "Address" + contact: "Contact" + social: "Social" + business_details: "Business Details" + properties: "Properties" + shipping: "Shipping" + shipping_methods: "Shipping Methods" + payment_methods: "Payment Methods" + payment_method_fee: "Transaction fee" + 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" + inventory_settings: "Inventory Settings" + tag_rules: "Tag Rules" + shop_preferences: "Shop Preferences" + enterprise_fee_whole_order: Whole order + enterprise_fee_by: "%{type} 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" + validation_msg_is_associated_with_an_exising_customer: "is associated with an existing customer" + content_configuration_pricing_table: "(TODO: Pricing table)" + content_configuration_case_studies: "(TODO: Case studies)" + content_configuration_detail: "(TODO: Detail)" + enterprise_name_error: "has already been taken. If this is your enterprise and you would like to claim ownership, or if you would like to trade with this enterprise please contact the current manager of this profile at %{email}." + enterprise_owner_error: "^%{email} is not permitted to own any more enterprises (limit is %{enterprise_limit})." + enterprise_role_uniqueness_error: "^That role is already present." + inventory_item_visibility_error: must be true or false + product_importer_file_error: "error: no file uploaded" + product_importer_spreadsheet_error: "could not process file: invalid filetype" + product_importer_products_save_error: did not save any products successfully + product_import_file_not_found_notice: 'File not found or could not be opened' + product_import_no_data_in_spreadsheet_notice: 'No data found in spreadsheet' + order_choosing_hub_notice: Your hub has been selected. + order_cycle_selecting_notice: Your order cycle has been selected. + adjustments_tax_rate_error: "^Please check that the tax rate for this adjustment is correct." + active_distributors_not_ready_for_checkout_message_singular: >- + The hub %{distributor_names} is listed in an active order cycle, but does not + have valid shipping and payment methods. Until you set these up, customers will + not be able to shop at this hub. + active_distributors_not_ready_for_checkout_message_plural: >- + The hubs %{distributor_names} are listed in an active order cycle, but do not + have valid shipping and payment methods. Until you set these up, customers will + not be able to shop at these hubs. + enterprise_fees_update_notice: Your enterprise fees have been updated. + enterprise_register_package_error: "Please select a package" + enterprise_register_error: "Could not complete registration for %{enterprise}" + enterprise_register_success_notice: "Congratulations! Registration for %{enterprise} is complete!" + enterprise_bulk_update_success_notice: "Enterprises updated successfully" + enterprise_bulk_update_error: 'Update failed' + enterprise_shop_show_error: "The shop you are looking for doesn't exist or is inactive on OFN. Please check other shops." + order_cycles_create_notice: 'Your order cycle has been created.' + order_cycles_update_notice: 'Your order cycle has been updated.' + order_cycles_bulk_update_notice: 'Order cycles have been updated.' + order_cycles_clone_notice: "Your order cycle %{name} has been cloned." + order_cycles_email_to_producers_notice: 'Emails to be sent to producers have been queued for sending.' + 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." + back_to_orders_list: "Back to order list" + no_orders_found: "No Orders Found" + order_information: "Order Information" + date_completed: "Date Completed" + amount: "Amount" + state_names: + ready: Ready + pending: Pending + shipped: Shipped + js: + saving: 'Saving...' + changes_saved: 'Changes saved.' + save_changes_first: Save changes first. + all_changes_saved: All changes saved + unsaved_changes: You have unsaved changes + all_changes_saved_successfully: All changes saved successfully + oh_no: "Oh no! I was unable to save your changes." + unauthorized: "You are unauthorised to access this page." + error: Error + unavailable: Unavailable + profile: Profile + hub: Hub + shop: Shop + choose: Choose + resolve_errors: Please resolve the following errors + more_items: "+ %{count} More" + default_card_updated: Default Card Updated + cart: + add_to_cart_failed: > + There was a problem adding this product to your basket. Perhaps it has become + unavailable or the shop is closing. + admin: + enterprise_limit_reached: "You have reached the standard limit of enterprises per account. Write to %{contact_email} if you need to increase it." + modals: + got_it: Got it + close: "Close" + invite: "Invite" + invite_title: "Invite an unregistered user" + tag_rule_help: + title: Tag Rules + overview: Overview + overview_text: > + Tag rules provide a way to describe which items are visible or otherwise + to which customers. Items can be Shipping Methods, Payment Methods, + Products and Order Cycles. + by_default_rules: "'By Default...' Rules" + by_default_rules_text: > + Default rules allow you to hide items so that they are not visible by + default. This behaviour can then be overriden by non-default rules for + customers with particular tags. + customer_tagged_rules: "'Customers Tagged...' Rules" + customer_tagged_rules_text: > + By creating rules related to a specific customer tag, you can override + the default behaviour (whether it be to show or to hide items) for customers + with the specified tag. + panels: + save: SAVE + saved: SAVED + saving: SAVING + enterprise_package: + hub_profile: Hub Profile + hub_profile_cost: "COST: Visit our Pricing page for more detail" + hub_profile_text1: > + People can find and contact you on the Open Food Network. Your enterprise + will be visible on the map, and will be searchable in listings. + hub_profile_text2: > + As a producer, making connections within your local food system through + the Open Food Network will always be free. + hub_shop: Hub Shop + hub_shop_text1: > + Hubs can take many forms - CSA, veg box scheme, food co-op, community + buying group, farmers’ market, an online shop front for an independent + shop and many others… + hub_shop_text2: > + Products aggregated from lots of local food enterprises can be stocked + and sold via your shop front. Hubs can form the backbone of your local + food economy. + hub_shop_text3: > + Open Food Network aims to provide the tools and support for you to run + your organisation or business in a manner which suits you. + + If you also want to sell your own products, you will need to switch + this enterprise to be a producer. + choose_package: Please Choose a Package + choose_package_text1: > + Your enterprise will not be fully activated until a package is selected + from the options on the left. + choose_package_text2: > + Click on an option to see more detailed information about each package, + and hit the red SAVE button when you are done! + profile_only: Profile Only + profile_only_cost: "There is no cost for a Producer Profile without a shop front." + profile_only_text1: > + A profile makes you visible and contactable to others and is a way to + share your story. + profile_only_text2: > + You can set up your products and invite other people to sell them for + you. + profile_only_text3: > + You can choose which Open Food Network ‘hubs’ you would like to stock + and sell your products.  Leave the hassle of selling your food to someone + else and focus on growing, making, brewing, baking... + producer_shop: Producer Shop + producer_shop_text1: > + Sell only your own products directly to customers and buyers with your + own Open Food Network shopfront. + producer_shop_text2: > + If you would also like to stock items grown/baked/made by others, please + select ‘Hub’. + producer_hub: Producer Hub + producer_hub_text1: > + Hubs can take many forms - CSA, veg box scheme, food co-op, community + buying group, farmers’ market, an online shop front for an independent + shop and many others… + producer_hub_text2: > + Products aggregated from lots of local food enterprises (including your + own, if you are also a food producer) can be stocked and sold via your + shop front. Hubs can form the backbone of your local food economy. + producer_hub_text3: > + Open Food Network aims to provide the tools and support for you to run + your organisation or business in a manner which suits you. + get_listing: Get a listing + always_free: Visit our Pricing page for more detail + sell_produce_others: Sell produce from others + sell_own_produce: Sell your own produce + sell_both: Sell products from multiple producers. + enterprise_producer: + producer: Producer + producer_text1: > + Farmers, growers, small holders, crofters, cottage enterprises, market + gardens, CSAs, community gardens, city farms that make yummy things + to eat or drink. You're a food producer if you grow it, raise it, brew + it, bake it, ferment it, milk it or mould it. + producer_text2: > + Food producers can also sell their products (and those of others) through + Shops or Hubs on the Open Food Network. + non_producer: Non-producer + non_producer_text1: > + A ‘Non-producer’, such as a Farmers’ Market or veg box co-ordinator, + specialises in aggregating food from local producers (eg. farmers, growers, + cottage enterprises, community/city gardens) to sell to customers. They + do not offer food that they produced themselves for sale. + non_producer_text2: > + This option is best suited for those wishing to operate Open Food Network + ‘Hubs’ but not produce their own food. + producer_desc: Producer + producer_example: eg. Farmers, growers, small holders, crofters, cottage enterprises, market gardens, CSAs, community gardens, city farms... + non_producer_desc: Non-producer + non_producer_example: eg. Farmers’ Market/Veg Box Scheme/Community group co-ordinators, Food Hubs, Retail Shops, Wholesale Distributors, Buying Groups, Food Co-ops… + enterprise_status: + status_title: "%{name} is set up and ready to go!" + severity: Severity + description: Description + resolve: Resolve + exchange_products: + load_more_variants: "Load More Variants" + load_all_variants: "Load All Variants" + select_all_variants: "Select All %{total_number_of_variants} Variants" + variants_loaded: "%{num_of_variants_loaded} of %{total_number_of_variants} Variants Loaded" + loading_variants: "Loading Variants" + tag_rules: + shipping_method_tagged_top: "Shipping methods tagged" + shipping_method_tagged_bottom: "are:" + payment_method_tagged_top: "Payment methods tagged" + payment_method_tagged_bottom: "are:" + order_cycle_tagged_top: "Order Cycles tagged" + order_cycle_tagged_bottom: "are:" + inventory_tagged_top: "Inventory variants tagged" + inventory_tagged_bottom: "are:" + new_tag_rule_dialog: + select_rule_type: "Select a rule type:" + add_rule: "Add Rule" + enterprise_fees: + inherit_from_product: "Inherit From Product" + orders: + index: + per_page: "%{results} per page" + view_file: "View File" + compiling_invoices: "Compiling Invoices" + bulk_invoice_created: "Bulk Invoice created" + bulk_invoice_failed: "Failed to create Bulk Invoice" + please_wait: "Please wait until the PDF is ready before closing this window." + order_state: + address: "address" + adjustments: "adjustments" + awaiting_return: "awaiting return" + canceled: "cancelled" + cart: "cart" + complete: "complete" + confirm: "confirm" + delivery: "delivery" + paused: "paused" + payment: "payment" + pending: "pending" + resumed: "resumed" + returned: "returned" + skrill: "skrill" + shipment_states: + backorder: "backorder" + partial: "partial" + pending: "pending" + ready: "ready" + shipped: "shipped" + canceled: "cancelled" + payment_states: + balance_due: "balance due" + completed: "completed" + checkout: "checkout" + credit_owed: "credit owed" + failed: "failed" + paid: "paid" + pending: "pending" + processing: "processing" + void: "void" + invalid: "invalid" + resend_user_email_confirmation: + resend: "Resend" + sending: "Resend..." + done: "Resend done ✓" + failed: "Resend failed ✗" + order_cycles: + schedules: + adding_a_new_schedule: "Adding A New Schedule" + updating_a_schedule: "Updating A Schedule" + create_schedule: "Create Schedule" + update_schedule: "Update Schedule" + delete_schedule: "Delete Schedule" + schedule_name_placeholder: "Schedule Name" + created_schedule: "Created schedule" + updated_schedule: "Updated schedule" + deleted_schedule: "Deleted schedule" + name_required_error: "Please enter a name for this schedule" + no_order_cycles_error: "Please select at least one order cycle (drag and drop)" + available: "Available" + selected: "Selected" + customers: + index: + add_customer: "Add Customer" + add_a_new_customer_for: "Add a new customer for %{shop_name}" + customer_placeholder: "customer@example.org" + valid_email_error: "Please enter a valid email address" + subscriptions: + error_saving: "Error saving subscription" + new: + please_select_a_shop: "Please select a shop" + insufficient_stock: "Insufficient stock available, only %{on_hand} remaining" + out_of_stock: + reduced_stock_available: Reduced stock available + out_of_stock_text: > + While you've been shopping, the stock levels for one or more of the products + 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': "Unlimited" + 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" + reset_stock_levels: Reset Stock Levels To Defaults + changes_to: Changes to + one_override: one override + overrides: overrides + remain_unsaved: remain unsaved. + no_changes_to_save: No changes to save.' + no_authorisation: "I couldn't get authorisation to save those changes, so they remain unsaved." + some_trouble: "I had some trouble saving: %{errors}" + changing_on_hand_stock: Changing 'in stock' stock levels... + stock_reset: Stocks reset to defaults. + tag_rules: + show_hide_variants: 'Show or Hide variants in my shopfront' + show_hide_shipping: 'Show or Hide shipping methods at checkout' + show_hide_payment: 'Show or Hide payment methods at checkout' + show_hide_order_cycles: 'Show or Hide order cycles in my shopfront' + visible: VISIBLE + not_visible: NOT VISIBLE + services: + unsaved_changes_message: Unsaved changes currently exist, save now or ignore? + save: SAVE + ignore: IGNORE + add_to_order_cycle: "add to order cycle" + manage_products: "manage products" + edit_profile: "edit profile" + add_products_to_inventory: "add products to inventory" + resources: + could_not_delete_customer: 'Could not delete customer' + product_import: + confirmation: | + This will set stock level to zero on all products for this + enterprise that are not present in the uploaded file. + order_cycles: + create_failure: "Failed to create order cycle" + update_success: 'Your order cycle has been updated.' + update_failure: "Failed to update order cycle" + no_distributors: There are no distributors in this order cycle. This order cycle will not be visible to customers until you add one. Would you like to continue saving this order cycle?' + enterprises: + producer: "Producer" + non_producer: "Non-Producer" + customers: + select_shop: 'Please select a shop first' + could_not_create: Sorry! Could not create + subscriptions: + closes: closes + closed: closed + close_date_not_set: Close date not set + spree: + users: + order: "Order" + registration: + welcome_to_ofn: "Welcome to the Open Food Network!" + signup_or_login: "Start By signing up (or logging in)" + have_an_account: "Already have an account?" + action_login: "Log in now." + inflections: + each: + one: "each" + other: "each" + bunch: + one: "bunch" + other: "bunches" + pack: + one: "pack" + other: "packs" + box: + one: "box" + other: "boxes" + bottle: + one: "bottle" + other: "bottles" + jar: + one: "jar" + other: "jars" + head: + one: "head" + other: "heads" + bag: + one: "bag" + other: "bags" + loaf: + one: "loaf" + other: "loaves" + single: + one: "single" + other: "singles" + tub: + one: "tub" + other: "tubs" + punnet: + one: "punnet" + other: "punnets" + packet: + one: "packet" + other: "packets" + item: + one: "item" + other: "items" + dozen: + one: "dozen" + other: "dozens" + unit: + one: "unit" + other: "units" + serve: + one: "serve" + other: "serves" + tray: + one: "tray" + other: "trays" + piece: + one: "piece" + other: "pieces" + pot: + one: "pot" + other: "pots" + bundle: + one: "bundle" + other: "bundles" + flask: + one: "flask" + other: "flasks" + basket: + one: "basket" + other: "baskets" + sack: + one: "sack" + other: "sacks" + producers: + signup: + start_free_profile: "Start with a free profile, and expand when you're ready!" + order_management: + reports: + enterprise_fee_summaries: + filters: + date_range: "Date Range" + report_format_csv: "Download as CSV" + generate_report: "Generate Report" + report: + none: "None" + select_and_search: "Select filters and click on GENERATE REPORT to access your data." + enterprise_fee_summary: + date_end_before_start_error: "must be after start" + parameter_not_allowed_error: "You are not authorised to use one or more selected filters for this report." + fee_calculated_on_transfer_through_all: "All" + fee_calculated_on_transfer_through_entire_orders: "Entire Orders through %{distributor}" + tax_category_various: "Various" + fee_type: + payment_method: "Payment Transaction" + shipping_method: "Shipment" + fee_placements: + supplier: "Incoming" + distributor: "Outgoing" + coordinator: "Coordinator" + tax_category_name: + shipping_instance_rate: "Platform Rate" + formats: + csv: + header: + fee_type: "Fee Type" + enterprise_name: "Enterprise Owner" + fee_name: "Fee Name" + customer_name: "Customer" + fee_placement: "Fee Placement" + fee_calculated_on_transfer_through_name: "Fee Calc on Transfer Through" + tax_category_name: "Tax Category" + total_amount: "$$ SUM" + html: + header: + fee_type: "Fee Type" + enterprise_name: "Enterprise Owner" + fee_name: "Fee Name" + customer_name: "Customer" + fee_placement: "Fee Placement" + fee_calculated_on_transfer_through_name: "Fee Calc on Transfer Through" + tax_category_name: "Tax Category" + total_amount: "$$ SUM" + invalid_filter_parameters: "The filters you selected for this report are invalid." + order: "Order" + distribution: "Distribution" + order_details: "Order Details" + customer_details: "Customer Details" + adjustments: "Adjustments" + payments: "Payments" + return_authorizations: "Return Authorisations" + payment: "Payment" + payment_method: "Payment Method" + shipment: "Shipment" + shipment_inc_vat: "Shipment including VAT" + shipping_tax_rate: "Shipping Tax Rate" + category: "Category" + delivery: "Delivery" + temperature_controlled: "Temperature Controlled" + new_product: "New Product" + administration: "Administration" + logged_in_as: "Logged in as" + account: "Account" + logout: "Logout" + date_range: "Date Range" + status: "status" + new: "New" + start: "Start" + end: "End" + stop: "Stop" + first: "First" + previous: "Previous" + last: "Last" + spree: + more: "More" + your_order_is_empty_add_product: "Your order is empty, please search for and add a product above" + add_product: "Add Product" + name_or_sku: "Name or SKU (enter at least first 4 characters of product name)" + resend: Resend + back_to_orders_list: Back To Orders List + return_authorizations: Return Authorisations + cannot_create_returns: Cannot create returns as this order has no shipped units. + select_stock: "Select stock" + location: "Location" + count_on_hand: "Count In Stock" + quantity: "Quantity" + on_demand: "Unlimited" + on_hand: "In Stock" + package_from: "package from" + item_description: "Item Description" + price: "Price" + total: "Total" + edit: "Edit" + split: "Split" + delete: "Delete" + cannot_set_shipping_method_without_address: "Cannot set shipping method until customer details are provided." + no_tracking_present: "No tracking details provided." + order_total: "Order Total" + customer_details: "Customer Details" + customer_search: "Customer Search" + choose_a_customer: "Choose a customer" + account: "Account" + billing_address: "Billing Address" + shipping_address: "Shipping Address" + first_name: "First name" + last_name: "Last name" + street_address: "Street Address" + street_address_2: "Street Address (cont'd)" + city: "City" + zip: "Postcode" + country: "Country" + state: "State" + phone: "Phone" + update: "Update" + use_billing_address: "Use Billing Address" + adjustments: "Adjustments" + continue: "Continue" + fill_in_customer_info: "Please fill in customer info" + new_payment: "New Payment" + capture: "Capture" + void: "Void" + login: "Login" + password: "Password" + signature: "Signature" + solution: "Solution" + landing_page: "Landing Page" + server: "Server" + test_mode: "Test Mode" + logourl: "Logo url" + configurations: "Configurations" + general_settings: "General Settings" + site_name: "Site Name" + site_url: "Site URL" + default_seo_title: "Default SEO Title" + default_meta_description: "Default Meta Description" + default_meta_keywords: "Default Meta Keywords" + security_settings: "Security Settings" + allow_ssl_in_development_and_test: "Allow SSL to be used when in development and test modes" + allow_ssl_in_production: "Allow SSL to be used in production mode" + allow_ssl_in_staging: "Allow SSL to be used in staging mode" + currency_decimal_mark: "Currency decimal mark" + currency_settings: "Currency Settings" + currency_symbol_position: Put "currency symbol before or after pound amount?" + currency_thousands_separator: "Currency thousands separator" + hide_cents: "Hide pence" + display_currency: "Display currency" + choose_currency: "Choose Currency" + mail_method_settings: "Mail Method Settings" + general: "General" + enable_mail_delivery: "Enable Mail Delivery" + send_mails_as: "Send Mails As" + smtp_send_all_emails_as_from_following_address: "Send all mails as from the following address." + send_copy_of_all_mails_to: "Send Copy of All Mails To" + smtp_send_copy_to_this_addresses: "Sends a copy of all outgoing mails to this address. For multiple addresses, separate with commas." + intercept_email_address: "Intercept Email Address" + intercept_email_instructions: "Override email recipient and replace with this address." + image_settings: "Image Settings" + image_settings_warning: "You will need to regenerate thumbnails if you update the paperclip styles. Use rake paperclip:refresh:thumbnails CLASS=Spree::Image to do this." + attachment_default_style: Attachments Style + attachment_default_url: "Attachments Default URL" + attachment_path: "Attachments Path" + attachment_styles: "Paperclip Styles" + attachment_url: "Attachments URL" + add_new_style: "Add New Style" + image_settings_updated: "Image Settings successfully updated." + tax_categories: "Tax Categories" + listing_tax_categories: "Listing Tax Categories" + back_to_tax_categories_list: "Back To Tax Categories List" + tax rate: "Tax Rates" + new_tax_rate: "New Tax Rate" + tax_category: "Tax Category" + rate: "Rate" + tax_rate_amount_explanation: "Tax rates are a decimal amount to aid in calculations, (i.e. if the tax rate is 5% then enter 0.05)" + included_in_price: "Included in Price" + show_rate_in_label: "Show rate in label" + back_to_tax_rates_list: "Back to Tax Rates List" + tax_settings: "Tax Settings" + zones: "Zones" + new_zone: "New Zone" + default_tax: "Default Tax" + default_tax_zone: "Default Tax Zone" + country_based: "Country Based" + state_based: "State Based" + countries: "Countries" + listing_countries: "Listing Countries" + iso_name: "ISO Name" + states_required: "Counties Required" + editing_country: "Editing Country" + back_to_countries_list: "Back to Countries List" + states: "Counties" + abbreviation: "Abbreviation" + new_state: "New State" + payment_methods: "Payment Methods" + taxonomies: "Taxonomies" + new_taxonomy: "New Taxonomy" + back_to_taxonomies_list: "Back to Taxonomies List" + shipping_methods: "Shipping Methods" + shipping_categories: "Shipping Categories" + new_shipping_category: "New Shipping Category" + back_to_shipping_categories: "Back To Shipping Categories" + name: "Name" + description: "Description" + type: "Type" + default: "default" + calculator: "Calculator" + zone: "Zone" + display: "Display" + environment: "Environment" + active: "Active" + nore: "More" + no_results: "No results" + create: "Create" + loading: "Loading" + flat_percent: "Flat Percent" + per_kg: "Per Kg" + amount: "Amount" + currency: "Currency" + first_item: "First Item Cost" + additional_item: "Additional Item Cost" + max_items: "Max Items" + minimal_amount: "Minimal Amount" + normal_amount: "Normal Amount" + discount_amount: "Discount Amount" + no_images_found: "No Images Found" + new_image: "New Image" + filename: "Filename" + alt_text: "Alternative Text" + thumbnail: "Thumbnail" + back_to_images_list: "Back To Images List" + email: Email + account_updated: "Account updated!" + email_updated: "The account will be updated once the new email is confirmed." + my_account: "My account" + date: "Date" + time: "Time" + inventory_error_flash_for_insufficient_quantity: "An item in your cart has become unavailable." + inventory: Inventory + zipcode: Postcode + weight: Weight (per kg) + error_user_destroy_with_orders: "Users with completed orders may not be deleted" + cannot_create_payment_without_payment_methods: "You cannot create a payment for an order without any payment methods defined." + please_define_payment_methods: "Please define some payment methods first." + options: "Options" + actions: + update: "Update" + shared: + error_messages: + errors_prohibited_this_record_from_being_saved: + one: "1 error prohibited this record from being saved:" + other: "%{count} errors prohibited this record from being saved:" + there_were_problems_with_the_following_fields: "There were problems with the following fields" + errors: + messages: + blank: "can't be blank" + layouts: + admin: + login_nav: + header: + store: Store + admin: + tab: + dashboard: "Dashboard" + orders: "Orders" + bulk_order_management: "Bulk Order Management" + subscriptions: "Subscriptions" + products: "Products" + option_types: "Option Types" + properties: "Properties" + variant_overrides: "Inventory" + reports: "Reports" + configuration: "Configuration" + users: "Users" + roles: "Roles" + order_cycles: "Order Cycles" + enterprises: "Enterprises" + enterprise_relationships: "Permissions" + customers: "Customers" + groups: "Groups" + product_properties: + index: + inherits_properties_checkbox_hint: "Inherit properties from %{supplier}? (unless overridden above)" + add_product_properties: "Add Product Properties" + select_from_prototype: "Select From Prototype" + properties: + index: + properties: "Properties" + new_property: "New Property" + name: "Name" + presentation: "Presentation" + new: + new_property: "New Property" + edit: + editing_property: "Editing Property" + back_to_properties_list: "Back To Properties List" + form: + name: "Name" + presentation: "Presentation" + return_authorizations: + index: + new_return_authorization: "New Return Authorisation" + return_authorizations: "Return Authorisations" + back_to_orders_list: "Back To Orders List" + rma_number: "RMA Number" + status: "Status" + amount: "Amount" + cannot_create_returns: "Cannot create returns as this order has no shipped units." + continue: "Continue" + new: + new_return_authorization: "New Return Authorisation" + back_to_return_authorizations_list: "Back To Return Authorisation List" + continue: "Continue" + edit: + receive: "receive" + are_you_sure: "Are you sure?" + return_authorization: "Return Authorisation" + form: + product: "Product" + quantity_shipped: "Quantity Shipped" + quantity_returned: "Quantity Returned" + return_quantity: "Return Quantity" + amount: "Amount" + rma_value: "RMA Value" + reason: "Reason" + stock_location: "Stock Location" + states: + authorized: "Authorised" + received: "Received" + canceled: "Canceled" + orders: + index: + listing_orders: "Listing Orders" + new_order: "New Order" + capture: "Capture" + ship: "Ship" + edit: "Edit" + order_not_updated: "The order could not be updated" + note: "Note" + first: "First" + last: "Last" + previous: "Previous" + next: "Next" + loading: "Loading" + no_orders_found: "No Orders Found" + results_found: "%{number} Results found." + viewing: "Viewing %{start} to %{end}." + print_invoices: "Print Invoices" + sortable_header: + payment_state: "Payment State" + shipment_state: "Shipment State" + completed_at: "Completed At" + number: "Number" + state: "State" + email: "Customer E-Mail" + invoice: + issued_on: "Issued on" + tax_invoice: "TAX INVOICE" + code: "Code" + from: "From" + to: "Bill to" + shipping: "Shipping" + form: + distribution_fields: + title: "Distribution" + distributor: "Distributor:" + order_cycle: "Order cycle:" + line_item_adjustments: "Line Item Adjustments" + order_adjustments: "Order Adjustments" + order_total: "Order Total" + overview: + enterprises_header: + ofn_with_tip: Enterprises are Producers and/or Hubs and are the basic unit of organisation within the Open Food Network. + products: + active_products: + zero: "You don't have any active products." + one: "You have one active product" + other: "You have %{count} active products" + order_cycles: + order_cycles: "Order Cycles" + order_cycles_tip: "Order cycles determine when and where your products are available to customers." + you_have_active: + zero: "You don't have any active order cycles." + one: "You have one active order cycle." + other: "You have %{count} active order cycles." + manage_order_cycles: "MANAGE ORDER CYCLES" + shipping_methods: + index: + shipping_methods: "Shipping Methods" + new_shipping_method: "New Shipping Method" + name: "Name" + products_distributor: "Distributor" + zone: "Zone" + calculator: "Calculator" + display: "Display" + both: "Both Checkout and Back office" + back_end: "Back office only" + no_shipping_methods_found: "No shipping methods found" + new: + new_shipping_method: "New Shipping Method" + back_to_shipping_methods_list: "Back To Shipping Methods List" + edit: + editing_shipping_method: "Editing Shipping Method" + new: "New" + back_to_shipping_methods_list: "Back To Shipping Methods List" + form: + categories: "Categories" + zones: "Zones" + both: "Both Checkout and Back office" + back_end: "Back office only" + deactivation_warning: "De-activating a shipping method can make the shipping method disappear from your list. Alternatively, you can hide a shipping method from the checkout page by setting the option 'Display' to 'back office only'." + payment_methods: + index: + payment_methods: "Payment Methods" + new_payment_method: "New Payment Method" + name: "Name" + products_distributor: "Distributor" + provider: "Provider" + environment: "Environment" + display: "Display" + active: "Active" + both: "Both" + front_end: "Checkout only" + back_end: "Back office only" + active_yes: "Yes" + active_no: "No" + no_payment_methods_found: "No payment methods found" + new: + new_payment_method: "New Payment Method" + back_to_payment_methods_list: "Back To Payment Methods List" + edit: + new: "New" + 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... + stripe_disabled_msg: Stripe payments have been disabled by the system administrator. + request_failed_msg: Sorry. Something went wrong when trying to verify account details with Stripe... + account_missing_msg: No Stripe account exists for this enterprise. + connect_one: Connect One + access_revoked_msg: Access to this Stripe account has been revoked, please reconnect your account. + status: Status + connected: Connected + account_id: Account ID + business_name: Business Name + charges_enabled: Charges Enabled + form: + name: "Name" + description: "Description" + environment: "Environment" + display: "Display" + active: "Active" + active_yes: "Yes" + active_no: "No" + both: "Both Checkout and Back office" + front_end: "Checkout only" + back_end: "Back office only" + tags: "Tags" + deactivation_warning: "De-activating a payment method can make the payment method disappear from your list. Alternatively, you can hide a payment method from the checkout page by setting the option 'Display' to 'back office only'." + providers: + provider: "Provider" + payments: + source_forms: + stripe: + error_saving_payment: Error saving payment + submitting_payment: Submitting payment... + products: + image_upload_error: "The product image was not recognised. Please upload an image in PNG or JPG format." + new: + title: "New Product" + new_product: "New Product" + supplier: "Supplier" + product_name: "Product Name" + units: "Unit Size" + value: "Value" + unit_name: "Unit name" + price: "Price" + on_hand: "In Stock" + on_demand: "Unlimited" + product_description: "Product Description" + image: "Image" + or: "or" + unit_name_placeholder: 'eg. bunches' + index: + header: + title: Bulk Edit Products + indicators: + title: LOADING PRODUCTS + no_products: "No products yet. Why don't you add some?" + no_results: "Sorry, no results match" + products_head: + name: Name + unit: Unit + display_as: Display As + category: Category + tax_category: Tax Category + inherits_properties?: Inherits Properties? + available_on: Available On + av_on: "Av. On" + import_date: "Import Date" + products_variant: + variant_has_n_overrides: "This variant has %{n} override(s)" + new_variant: "New variant" + product_name: Product Name + primary_taxon_form: + product_category: Product Category + group_buy_form: + group_buy: "Group Buy?" + bulk_unit_size: Bulk unit size + display_as: + display_as: Display As + reports: + table: + select_and_search: "Select filters and click on %{option} to access your data." + bulk_coop: + bulk_coop_supplier_report: 'Bulk Co-op - Totals by Supplier' + bulk_coop_allocation: 'Bulk Co-op - Allocation' + bulk_coop_packing_sheets: 'Bulk Co-op - Packing Sheets' + bulk_coop_customer_payments: 'Bulk Co-op - Customer Payments' + 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}." + variants: + index: + sku: "Product Code" + price: "Price" + options: "Options" + no_results: "No results" + to_add_variants_you_must_first_define: "To add variants, you must first define" + option_types: "Option Types" + option_values: "Option Values" + and: "and" + new_variant: "New Variant" + show_active: "Show Active" + show_deleted: "Show Deleted" + new: + new_variant: "New Variant" + form: + cost_price: "Cost Price" + sku: "Product Code" + price: "Price" + display_as: "Display As" + display_name: "Display Name" + display_as_placeholder: 'eg. 2 kg' + display_name_placeholder: 'eg. Tomatoes' + autocomplete: + out_of_stock: "Out of Stock" + producer_name: "Producer" + unit: "Unit" + shared: + sortable_header: + name: "Name" + number: "Number" + state: "State" + payment_state: "Payment State" + shipment_state: "Shipment State" + email: "Email" + total: "Total" + general_settings: + edit: + legal_settings: "Legal Settings" + cookies_consent_banner_toggle: "Display cookies consent banner" + privacy_policy_url: "Privacy Policy URL" + enterprises_require_tos: "Enterprises must accept Terms of Service" + cookies_policy_matomo_section: "Display Matomo section on cookies policy page" + footer_tos_url: "Terms of Service URL" + checkout: + payment: + stripe: + choose_one: Choose one + enter_new_card: Enter details for a new card + used_saved_card: "Use a saved card:" + or_enter_new_card: "Or, enter details for a new card:" + remember_this_card: Remember this card? + stripe_sca: + choose_one: Choose one + enter_new_card: Enter details for a new card + used_saved_card: "Use a saved card:" + or_enter_new_card: "Or, enter details for a new card:" + remember_this_card: Remember this card? + date_picker: + format: '%Y-%m-%d' + js_format: 'yy-mm-dd' + orders: + error_flash_for_unavailable_items: "An item in your basket has become unavailable. Please update the selected quantities." + edit: + login_to_view_order: "Please log in to view your order." + bought: + item: "Already ordered in this order cycle" + line_item: + insufficient_stock: "Insufficient stock available, only %{on_hand} remaining" + out_of_stock: "Out of Stock" + unavailable_item: "Currently unavailable" + shipment_states: + backorder: backorder + partial: partial + pending: pending + ready: ready + shipped: shipped + payment_states: + balance_due: balance due + completed: completed + checkout: checkout + credit_owed: credit owed + failed: failed + paid: paid + pending: pending + processing: processing + void: void + invalid: invalid + order_mailer: + cancel_email: + customer_greeting: "Dear %{name}," + instructions_html: "Your order with %{distributor} has been CANCELED. Please retain this cancellation information for your records." + dont_cancel: "If you have changed your mind or don't wish to cancel this order please contact %{email}" + order_summary_canceled_html: "Order Summary #%{number} [CANCELED]" + details: "Here are the details of what you ordered:" + unpaid_order: "Your order was unpaid so no refund has been made" + paid_order: "Your order was paid so %{distributor} has refunded the full amount" + credit_order: "Your order was paid so your account has been credited" + subject: "Cancellation of Order" + confirm_email: + subject: "Order Confirmation" + invoice_email: + hi: "Hi %{name}" + invoice_attached_text: Please find attached an invoice for your recent order from + order_state: + address: address + adjustments: adjustments + awaiting_return: awaiting return + canceled: cancelled + cart: cart + complete: complete + confirm: confirm + delivery: delivery + paused: paused + payment: payment + pending: pending + resumed: resumed + returned: returned + skrill: skrill + subscription_state: + active: active + pending: pending + ended: ended + paused: paused + canceled: cancelled + user_mailer: + reset_password_instructions: + request_sent_text: | + A request to reset your password has been made. + If you did not make this request, simply ignore this email. + link_text: > + If you did make this request just click the link below: + issue_text: | + If the above URL does not work try copying and pasting it into your browser. + If you continue to have problems please feel free to contact us. + confirmation_instructions: + subject: Please confirm your OFN account + users: + form: + account_settings: Account Settings + show: + tabs: + orders: Orders + cards: Credit Cards + transactions: Transactions + settings: Account Settings + unconfirmed_email: "Pending email confirmation for: %{unconfirmed_email}. Your email address will be updated once the new email is confirmed." + orders: + open_orders: Open Orders + past_orders: Past Orders + transactions: + transaction_history: Transaction History + open_orders: + order: Order + shop: Shop + changes_allowed_until: Changes Allowed Until + items: Items + total: Total + edit: Edit + cancel: Cancel + closed: Closed + until: Until + past_orders: + order: Order + shop: Shop + completed_at: Completed At + items: Items + total: Total + paid?: Paid? + view: View + saved_cards: + default?: Default? + delete?: Delete? + cards: + authorised_shops: Authorised Shops + authorised_shops_popover: This is the list of shops which are permitted to charge your default credit card for any subscriptions (ie. repeating orders) you may have. Your card details will be kept secure and will not be shared with shop owners. You will always be notified when you are charged. + saved_cards_popover: This is the list of cards you have opted to save for later use. Your 'default' will be selected automatically when you checkout an order, and can be charged by any shops you have allowed to do so (see right). + authorised_shops: + shop_name: "Shop Name" + allow_charges?: "Allow Charges?" + localized_number: + invalid_format: has an invalid format. Please enter a number. + api: + invalid_api_key: "Invalid API key (%{key}) specified." + unauthorized: "You are not authorised to perform that action." + invalid_resource: "Invalid resource. Please fix errors and try again." + resource_not_found: "The resource you were looking for could not be found." + access: "API Access" + key: "Key" + clear_key: "Clear key" + regenerate_key: "Regenerate Key" + no_key: "No key" + generate_key: "Generate API key" + key_generated: "Key generated" + key_cleared: "Key cleared" + shipment: + cannot_ready: "Cannot ready shipment." + invalid_taxonomy_id: "Invalid taxonomy id." diff --git a/config/locales/en_NZ.yml b/config/locales/en_NZ.yml index c3ed978054..97f7ccf95b 100644 --- a/config/locales/en_NZ.yml +++ b/config/locales/en_NZ.yml @@ -181,8 +181,8 @@ en_NZ: title: Other Failure (%{count} orders) explainer: Automatic processing of these orders failed for an unknown reason. This should not occur, please contact us if you are seeing this. home: "OFN" - title: Open Food Network - welcome_to: 'Welcome to ' + title: "Open Food Network" + welcome_to: "Welcome to" site_meta_description: "We begin from the ground up. With farmers, growers, and producers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can…" search_by_name: Search by name or suburb... producers_join: New Zealand producers are now welcome to join the Open Food Network. @@ -270,6 +270,7 @@ en_NZ: backordered: "Backordered" on hand: "On Hand" ship: "Ship" + shipping_category: "Shipping Category" actions: create_and_add_another: "Create and Add Another" create: "Create" @@ -479,6 +480,7 @@ en_NZ: line_number: "Line %{number}:" encoding_error: "Please check the language setting of your source file and ensure it is saved with UTF-8 encoding" unexpected_error: "Product Import encountered an unexpected error whilst opening the file: %{error_message}" + malformed_csv: "Product Import encountered a malformed CSV: %{error_message}" index: notice: "Notice" beta_notice: "This feature is still in beta: you may experience some errors while using it. Please don't hesitate to contact support." @@ -1146,15 +1148,20 @@ en_NZ: menu: cart: cart: "Cart" + cart_sidebar: + checkout: "Checkout" + edit_cart: "Edit cart" + items_in_cart_singular: "%{num} item in your cart" + items_in_cart_plural: "%{num} items in your cart" + close: "Close" + cart_empty: "Your cart is empty" + take_me_shopping: "Take me shopping!" signed_in: profile: "Profile" mobile_menu: cart: "Cart" - joyride: - checkout: "Checkout now" - already_ordered_products: "Already ordered in this order cycle" register_call: - selling_on_ofn: "Interested in getting on the Open Food Network?" + selling_on_ofn: "Want to set up a shop on the Open Food Network?" register: "Register here" footer: footer_secure: "Secure and trusted." @@ -1163,7 +1170,7 @@ en_NZ: footer_contact_email: "Email us" footer_nav_headline: "Navigate" footer_join_headline: "Join us" - footer_join_body: "Create a listing, shop or group directory on the Open Food Network." + footer_join_body: "Create a listing, build a shop or group directory on the Open Food Network." footer_join_cta: "Tell me more!" footer_legal_call: "Read our" footer_legal_tos: "Terms and conditions" @@ -1371,7 +1378,7 @@ en_NZ: system_step1: "1. Search" system_step1_text: "Search our diverse, independent shops for seasonal local food. Search by neighbourhood and food category, or whether you prefer delivery or pickup." system_step2: "2. Shop" - system_step2_text: "Transform your transactions with affordable local food from diverse producers and hubs. Know the stories behind your food and the people who make it!" + system_step2_text: "Choose what you want, checkout as guest or make an account, you are good to go. Its that simple. If you'd like a box every week then ask for a subscription." system_step3: "3. Pick-up / Delivery" system_step3_text: "Hang on for your delivery, or visit your producer or hub for a more personal connection with your food. Food shopping as diverse as nature intended it." cta_headline: "Shopping that makes the world a better place." @@ -1706,8 +1713,8 @@ en_NZ: password: Password remember_me: Remember Me are_you_sure: "Are you sure?" - orders_open: Orders open - closing: "Closing " + orders_open: "Orders open" + closing: "Closing" going_back_to_home_page: "Taking you back to the home page" creating: Creating updating: Updating @@ -2065,6 +2072,7 @@ en_NZ: hub_sidebar_at_least: "At least one hub must be selected" hub_sidebar_blue: "blue" hub_sidebar_red: "red" + order_cycles_closed_for_hub: "The hub you have selected is temporarily closed for orders. Please try again later." report_customers_distributor: "Distributor" report_customers_supplier: "Supplier" report_customers_cycle: "Order Cycle" @@ -2301,6 +2309,7 @@ en_NZ: order_cycles_email_to_producers_notice: 'Emails to be sent to producers have been queued for sending.' 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. Please try again!" back_to_orders_list: "Back to order list" no_orders_found: "No Orders Found" order_information: "Order Information" @@ -2766,6 +2775,7 @@ en_NZ: previous: "Previous" last: "Last" spree: + more: "More" your_order_is_empty_add_product: "Your order is empty, please search for and add a product above" add_product: "Add Product" name_or_sku: "Name or SKU (enter at least first 4 characters of product name)" diff --git a/config/locales/en_PH.yml b/config/locales/en_PH.yml index 2ca3436aa6..7ac90cfc1b 100644 --- a/config/locales/en_PH.yml +++ b/config/locales/en_PH.yml @@ -181,8 +181,7 @@ en_PH: title: Other Failure (%{count} orders) explainer: Automatic processing of these orders failed for an unknown reason. This should not occur, please contact us if you are seeing this. home: "OFN" - title: Open Food Network - welcome_to: 'Welcome to ' + title: "Open Food Network" site_meta_description: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can…" search_by_name: Search by name or city... producers_join: Philippine producers are now welcome to join the Open Food Network. @@ -270,6 +269,7 @@ en_PH: backordered: "Backordered" on hand: "On Hand" ship: "Ship" + shipping_category: "Shipping Category" actions: create_and_add_another: "Create and Add Another" create: "Create" @@ -1140,13 +1140,13 @@ en_PH: menu: cart: cart: "Cart" + cart_sidebar: + checkout: "Checkout" + close: "Close" signed_in: profile: "Profile" mobile_menu: cart: "Cart" - joyride: - checkout: "Checkout now" - already_ordered_products: "Already ordered in this order cycle" register_call: selling_on_ofn: "Interested in getting on the Open Food Network?" register: "Register here" @@ -1689,8 +1689,7 @@ en_PH: password: Password remember_me: Remember Me are_you_sure: "Are you sure?" - orders_open: Orders open - closing: "Closing " + orders_open: "Orders open" going_back_to_home_page: "Taking you back to the home page" creating: Creating updating: Updating @@ -2744,6 +2743,7 @@ en_PH: previous: "Previous" last: "Last" spree: + more: "More" your_order_is_empty_add_product: "Your order is empty, please search for and add a product above" add_product: "Add Product" name_or_sku: "Name or SKU (enter at least first 4 characters of product name)" diff --git a/config/locales/en_US.yml b/config/locales/en_US.yml index 7917bbc6e6..f8c377e354 100644 --- a/config/locales/en_US.yml +++ b/config/locales/en_US.yml @@ -181,8 +181,7 @@ en_US: title: Other Failure (%{count} orders) explainer: Automatic processing of these orders failed for an unknown reason. This should not occur, please contact us if you are seeing this. home: "OFN" - title: Open Food Network - welcome_to: 'Welcome to ' + title: "Open Food Network" site_meta_description: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can seriously change the world." search_by_name: Search by name or city... producers_join: US producers are now welcome to join the Open Food Network. @@ -270,6 +269,7 @@ en_US: backordered: "Backordered" on hand: "On Hand" ship: "Ship" + shipping_category: "Shipping Category" actions: create_and_add_another: "Create and Add Another" create: "Create" @@ -1712,8 +1712,7 @@ en_US: password: Password remember_me: Remember Me are_you_sure: "Are you sure?" - orders_open: Orders open - closing: "Closing " + orders_open: "Orders open" going_back_to_home_page: "Taking you back to the home page" creating: Creating updating: Updating @@ -2071,6 +2070,7 @@ en_US: hub_sidebar_at_least: "At least one hub must be selected" hub_sidebar_blue: "blue" hub_sidebar_red: "red" + order_cycles_closed_for_hub: "The hub you have selected is temporarily closed for orders. Please try again later." report_customers_distributor: "Distributor" report_customers_supplier: "Supplier" report_customers_cycle: "Order Cycle" @@ -2307,6 +2307,7 @@ en_US: order_cycles_email_to_producers_notice: 'Emails to be sent to producers have been queued for sending.' 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. Please try again with a different order cycle!" back_to_orders_list: "Back to order list" no_orders_found: "No Orders Found" order_information: "Order Information" @@ -2772,6 +2773,7 @@ en_US: previous: "Previous" last: "Last" spree: + more: "More" your_order_is_empty_add_product: "Your cart is empty, please search for and add a product above" add_product: "Add Product" name_or_sku: "Name or SKU (enter at least first 4 characters of product name)" diff --git a/config/locales/en_ZA.yml b/config/locales/en_ZA.yml index b105c0fc11..aa488086cc 100644 --- a/config/locales/en_ZA.yml +++ b/config/locales/en_ZA.yml @@ -181,8 +181,7 @@ en_ZA: title: Other Failure (%{count} orders) explainer: Automatic processing of these orders failed for an unknown reason. This should not occur, please contact us if you are seeing this. home: "OFN" - title: Open Food Network - welcome_to: 'Welcome to ' + title: "Open Food Network" site_meta_description: "We begin with healthy soil. With organic farmers and growers proudly rising to tell their stories. With distributors ready to connect people with green, nutritious, fair and affordable products. With buyers who believe that better, more local shopping decisions can seriously change the world." search_by_name: Search by name, town, province or postcode... producers_join: South African producers are now welcome to join Open Food Network SA. @@ -270,6 +269,7 @@ en_ZA: backordered: "Backordered" on hand: "In Stock" ship: "Ship" + shipping_category: "Shipping Category" actions: create_and_add_another: "Create and Add Another" create: "Create" @@ -1142,13 +1142,13 @@ en_ZA: menu: cart: cart: "Basket" + cart_sidebar: + checkout: "Checkout" + close: "Close" signed_in: profile: "Profile" mobile_menu: cart: "Basket" - joyride: - checkout: "Checkout now" - already_ordered_products: "Already ordered in this order cycle" register_call: selling_on_ofn: "Interested in selling through the Open Food Network?" register: "Register here" @@ -1217,11 +1217,11 @@ en_ZA: menu_4_title: "Groups" menu_4_url: "/groups" menu_5_title: "About" - menu_5_url: "https://www.openfoodnetwork.co.za" + menu_5_url: "https://www.openfoodnetwork.co.za/" menu_6_title: "Blog" - menu_6_url: "https://www.openfoodnetwork.co.za/blog" + menu_6_url: "https://www.openfoodnetwork.co.za/blog/" menu_7_title: "Support" - menu_7_url: "https://www.openfoodnetwork.co.za/support" + menu_7_url: "https://www.openfoodnetwork.co.za/support/" logo: "Logo (640x130)" logo_mobile: "Mobile logo (75x26)" logo_mobile_svg: "Mobile logo (SVG)" @@ -1702,8 +1702,7 @@ en_ZA: password: Password remember_me: Remember Me are_you_sure: "Are you sure?" - orders_open: Orders open - closing: "Closing " + orders_open: "Orders open" going_back_to_home_page: "Taking you back to the home page" creating: Creating updating: Updating @@ -2690,6 +2689,7 @@ en_ZA: previous: "Previous" last: "Last" spree: + more: "More" your_order_is_empty_add_product: "Your order is empty, please search for and add a product above" add_product: "Add Product" name_or_sku: "Name or SKU (enter at least first 4 characters of product name)" diff --git a/config/locales/es.yml b/config/locales/es.yml index aef7d7c62b..75eed08b9d 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -181,8 +181,7 @@ es: title: Otros fallos (%{count} pedidos) explainer: El procesamiento automático de estas órdenes falló por un motivo desconocido. Esto no debería ocurrir, contáctanos si estás viendo esto. home: "OFN" - title: Open Food Network - welcome_to: 'Bienvenido a ' + title: "Open Food Network" site_meta_description: "Nosotros empezamos desde abajo. Con granjeros y productoras listas para contar sus historias con orgullo y autenticidad. Con distribuidoras listas para conectar gente con productos de forma justa y honesta. Con compradores que creen que mejores decisiones de compras semanales pueden..." search_by_name: Buscar por nombre o municipio... producers_join: Las productoras australianas ahora son bienvenidas a unirse a Open Food Network. @@ -270,6 +269,7 @@ es: backordered: "Reabastecido" on hand: "Disponibles" ship: "Envío" + shipping_category: "Categoría de envío" actions: create_and_add_another: "Crear y agregar otro" create: "Crear" @@ -1149,13 +1149,13 @@ es: menu: cart: cart: "Carrito" + cart_sidebar: + checkout: "Validar" + close: "Cerrar" signed_in: profile: "Perfil" mobile_menu: cart: "Carrito" - joyride: - checkout: "Validar el carrito ahora" - already_ordered_products: "Pedido en este ciclo de pedido" register_call: selling_on_ofn: "¿Estás interesada en entrar en Open Food Network?" register: "Regístrate aquí" @@ -1709,8 +1709,7 @@ es: password: Contraseña remember_me: Recordarme are_you_sure: "¿Está seguro?" - orders_open: Pedidos abiertos - closing: "Cerrando " + orders_open: "Pedidos abiertos" going_back_to_home_page: "Le estamos llevando de vuelta a la página de inicio" creating: Creando updating: Actualizando @@ -2773,6 +2772,7 @@ es: previous: "Anterior" last: "Último" spree: + more: "Más" your_order_is_empty_add_product: "Su pedido está vacío, busque y añada un producto arriba" add_product: "Añadir Producto" name_or_sku: "Nombre o código SKU (ingrese al menos los primeros 4 caracteres del nombre del producto)" diff --git a/config/locales/es_CR.yml b/config/locales/es_CR.yml index 891c34c21b..d22b5cdff1 100644 --- a/config/locales/es_CR.yml +++ b/config/locales/es_CR.yml @@ -181,8 +181,7 @@ es_CR: title: Otros fallos (%{count} pedidos) explainer: El procesamiento automático de estas órdenes falló por un motivo desconocido. Esto no debería ocurrir, contáctanos si estás viendo esto. home: "OFN" - title: Open Food Network - welcome_to: 'Bienvenido a ' + title: "Open Food Network" site_meta_description: "Nosotros empezamos desde abajo. Con granjeros y productores listos para contar sus historias con orgullo y autenticidad. Con distribuidores listos para conectar gente con productos de forma justa y honesta. Con compradores que creen que mejores decisiones de compras semanales pueden..." search_by_name: Buscar por nombre o cantón... producers_join: Los productores costarricenses son bienvenidos a unirse ahora a Open Food Network (LaFeriaCR). @@ -270,6 +269,7 @@ es_CR: backordered: "Pedido pendiente" on hand: "Disponibles" ship: "Envío" + shipping_category: "Categoría de envío" actions: create_and_add_another: "Crear y agregar otro" create: "Crear" @@ -1143,15 +1143,15 @@ es_CR: menu: cart: cart: "Carrito" + cart_sidebar: + checkout: "Validar el carrito" + close: "Cerrar" signed_in: profile: "Perfil" mobile_menu: cart: "Carrito" - joyride: - checkout: "Validar el carrito ahora" - already_ordered_products: "Pedido en este ciclo de pedido" register_call: - selling_on_ofn: "¿Quiere ser parte de Open Food Network (LaFeriaCR)?" + selling_on_ofn: "¿Tiene interés en vender a través de Open Food Network (LaFeriaCR)?" register: "Regístrarse aquí" footer: footer_secure: "Seguro y de confianza." @@ -1692,8 +1692,7 @@ es_CR: password: Contraseña remember_me: Recordarme are_you_sure: "¿Está seguro?" - orders_open: Pedidos abiertos - closing: "Cerrando " + orders_open: "Pedidos abiertos" going_back_to_home_page: "Le estamos llevando de vuelta a la página de inicio" creating: Creando updating: Actualizando @@ -2752,6 +2751,7 @@ es_CR: previous: "Anterior" last: "Último" spree: + more: "Más" your_order_is_empty_add_product: "Su pedido está vacío, busque y añada un producto arriba" add_product: "Añadir Producto" name_or_sku: "Nombre o código SKU (ingrese al menos los primeros 4 caracteres del nombre del producto)" diff --git a/config/locales/fil_PH.yml b/config/locales/fil_PH.yml index 6bf53bd65f..94f2cac702 100644 --- a/config/locales/fil_PH.yml +++ b/config/locales/fil_PH.yml @@ -181,8 +181,7 @@ fil_PH: title: Iba Pang Dahilan ng Error (%{count}na order) explainer: Hindi naging matagumpay ang awtomatikong pagproseso ng mga order na ito dahil sa hindi maipaliwanag na kadahilanan. Makipag-uganayan sa amin kung nakikita ang mensaheng ito. home: "OFN" - title: Open Food Network - welcome_to: 'Maligayang Pagdating sa' + title: "Open Food Network" site_meta_description: "Kami ay nagsimula sa pinakaibaba pataas. Kasama ang mga magsasaka at tagatanim na handang ibahagi at ipagmalaki ang kanilang mga kuwento. May mga tapat at patas na Distributors na handang makipag-usap sa mga gumagawa ng produkto. At mga mamimili na naniniwala na ang mabusising lingguhang pamimili ay..." search_by_name: Hanapin gamit ang pangalan o lungsod... producers_join: Ang mga Pilipinong producers ay inaanyayahang naming sumali sa Open Food Network. @@ -270,6 +269,7 @@ fil_PH: backordered: "na-backorder" on hand: "on hand" ship: "ship" + shipping_category: "Kategorya ng Pagpapadala" actions: create_and_add_another: "Gumawa at magdagdag ng isa pa" create: "gumawa" @@ -718,7 +718,7 @@ fil_PH: ito ay makikita sa home tab kapag ang customer ay nasa loob na ng inyong shopfront. shopfront_message_link_tooltip: "ilagay/ i-edit ang link" - shopfront_message_link_prompt: "ilagay ang URL na ipapasok" + shopfront_message_link_prompt: "ipasok ang URL na ilalagay" shopfront_closed_message: "pagsasarang mensahe ng Shopfront" shopfront_closed_message_placeholder: > isang mensahe na nagbibigay ng mas detalyadong paliwanag kung bakit @@ -1035,7 +1035,7 @@ fil_PH: edit: i-edit ang subscription table: edit_subscription: i-edit ang subscription - pause_subscription: ihinto ang subscription + pause_subscription: panandaliang ihinto ang subscription unpause_subscription: ituloy ang subscription cancel_subscription: kanselahin ang nauulit na order filters: @@ -1074,8 +1074,8 @@ fil_PH: allowed_payment_method_types_tip: Cash at pamamaraang Stripe lamang ang maaaring gamitin sa kasalukuyan. credit_card: Credit Card charges_not_allowed: ang paniningil ay hindi pinapayagan ng customer na ito - no_default_card: ang customer ay walang mga card na maaaring singilin - card_ok: ang customer ay may card na maaaring singilin + no_default_card: ang customer ay walang mga card na maaaring i-charge + card_ok: ang customer ay may card na maaaring i-charge begins_at_placeholder: "pumili ng petsa" ends_at_placeholder: "opsyonal" loading_flash: @@ -1095,7 +1095,7 @@ fil_PH: out_of_stock: "Walang stock" orders: number: bilang - confirm_edit: sigurado ka bang nais mo ayusin ang order na ito? maaaring magdulot ito ng mas mahirap na awtomatikong pagsasaayos ng mga subscription sa hinaharap. + confirm_edit: sigurado ka bang nais mo i-edit ang order na ito? maaaring magdulot ito ng mas mahirap na awtomatikong pagsasaayos ng mga subscription sa hinaharap. confirm_cancel_msg: "sigurado ka bang nais mong kanselahin ang nauulit na order na ito? hindi na ito maaaring ibalik kapag nagawa na." cancel_failure_msg: "paumanhin ngunit hindi naging matagumpay ang pagkansela!" confirm_pause_msg: "sigurado ka bang nais mong pansamantalang itigil ang subscription na ito?" @@ -1143,13 +1143,13 @@ fil_PH: menu: cart: cart: "Cart" + cart_sidebar: + checkout: "checkout" + close: "isara" signed_in: profile: "Profile" mobile_menu: cart: "Cart" - joyride: - checkout: "Mag-checkout na" - already_ordered_products: "Naka-order na sa order cycle na ito" register_call: selling_on_ofn: "Interesado ka ba maging parte ng Open Food Network?" register: "Magrehistro dito" @@ -1175,7 +1175,7 @@ fil_PH: customer_required: login: "mag-log-in" signup: "mag-sign-up" - contact: "makipag-ugnay" + contact: "makipag-ugnayan" require_customer_login: "ang mga inaprubahang customer lamang ang maaaring gumamit ng shop na ito." require_customer_html: "kung nais mong magsimulang mamili rito, maaaring%{contact}%{enterprise} at magtanong kung paano sumali." card_could_not_be_updated: hindi ma-update ang card @@ -1252,7 +1252,7 @@ fil_PH: state: province country: Bansa unauthorized: hindi awtorisado - terms_of_service: "mga palatuntunan ng serbisyo" + terms_of_service: "mga tuntunin ng serbisyo" on_demand: on demand none: wala not_allowed: hindi pinapayagan @@ -1266,7 +1266,7 @@ fil_PH: label_shops: "mga shop" label_map: "mapa" label_producer: "Producer" - label_producers: "Producers" + label_producers: "Mga Producer" label_groups: "mga grupo" label_about: "tungkol sa" label_connect: "kumonekta" @@ -1288,7 +1288,7 @@ fil_PH: total: "kabuuan" cart_updating: "ina-update ang cart" cart_empty: "walang laman ang cart" - cart_edit: "ayusin ang cart" + cart_edit: "i-edit ang cart" card_number: numero ng card card_securitycode: "Security code" card_expiry_date: petsa ng pag-expire @@ -1297,15 +1297,15 @@ fil_PH: new_credit_card: "bagong credit card" my_credit_cards: ang aking mga credit card add_new_credit_card: magdagdag ng bagong credit card - saved_cards: i-save ang mga card + saved_cards: naka-save na mga card add_a_card: magdagdag ng card add_card: idagdag ang card you_have_no_saved_cards: wala ka pang nai-save na card. saving_credit_card: sine-save ang credit card... - card_has_been_removed: "ang iyong card ay tinanggal na (numero:%{number})" - card_could_not_be_removed: paumanhin, hindi maalis ang card + card_has_been_removed: "ang iyong card ay natanggal na (numero:%{number})" + card_could_not_be_removed: paumanhin, hindi matanggal ang card invalid_credit_card: "hindi valid ang credit card" - ie_warning_headline: "ang iyong browser ay luma na :-(" + ie_warning_headline: "ang iyong browser ay out of date :-(" ie_warning_text: "para sa pinakamagandang karanasan sa Open Food Network, mariin naming nirerekomenda ang pag-upgrade ng inyong browser:" ie_warning_chrome: i-download ang Chrome ie_warning_firefox: i-download ang Firefox @@ -1692,8 +1692,7 @@ fil_PH: password: Password remember_me: tandaan ako are_you_sure: "Sigurado ka ba?" - orders_open: bukas ang mga order - closing: "sinasara" + orders_open: "bukas ang mga order" going_back_to_home_page: "binabalik ka sa homepage" creating: ginagawa updating: ina-update @@ -2381,7 +2380,7 @@ fil_PH: din ito upang maisalaysay ang inyong kwento. profile_only_text2: > kung mas nais mong magpokus sa paggawa ng pagkain at ipaubaya sa iba - ang pagbebenta nito, hindi kailangan ang shop sa Open Food Network. + ang pagbebenta nito, hindi na kailangan ang shop sa Open Food Network. profile_only_text3: > idagdag ang inyong produkto sa Open Food Network, upang mabigyan ng permiso ang mga hub na ilagay ang inyong produkto sa kanilang mga tindahan. @@ -2390,9 +2389,9 @@ fil_PH: Direktang ibenta ang inyong produkto sa mga customer sa pamamagitan ng iyong sariling Open Food Network shopfront. producer_shop_text2: > - ang shop ng Producer ay para lamang sa iyong mga produkto, kung nais - mong magtinda ng mga produkto na itinanim/ginawa sa labas ng site, piliin - ang 'Hub ng producer'. + ang Producer Shop ay para lamang sa iyong mga produkto, kung nais mong + magtinda ng mga produkto na itinanim/ginawa sa labas ng site, piliin + ang 'Producer Hub'. producer_hub: Producer Hub producer_hub_text1: > ang iyong enterprise ang saligan ng iyong lokal na sistema ng pagkain. @@ -2400,9 +2399,8 @@ fil_PH: pinagsama sama mula sa ibang enterprise sa pamamagitan ng iyong shopfront sa Open Food Network. producer_hub_text2: > - ang mga hub ng producer ay maaring may iba't ibang anyo tulad ng CSA, - isang programang veggie-box o isang co-op ng pagkain na may hardin sa - rooftop. + ang mga producer hub ay maaring may iba't ibang anyo tulad ng CSA, isang + programang veggie-box o isang co-op ng pagkain na may hardin sa rooftop. producer_hub_text3: > ang layunin ng Open Food Network ay sumuporta sa mga modelo ng hub gaano man ito karami, kaya ano man ang inyong sitwasyon, nais naming ibigay @@ -2549,14 +2547,14 @@ fil_PH: 'yes': "oo" 'no': "hindi" inventory_products: "mga produktong naka-imbentaryo" - hidden_products: "mga nakatagong pprodukto" - new_products: "bagong produkto" + hidden_products: "nakatagong mga produkto" + new_products: "bagong mga produkto" reset_stock_levels: i-reset ang lebel ng stock sa default changes_to: mga pagbabago sa one_override: isang override overrides: mga override remain_unsaved: nananatiling hindi naka-save. - no_changes_to_save: walang pagbabago na ise-save + no_changes_to_save: walang mga pagbabago na ise-save no_authorisation: "hindi ko makuha ang awtorisasyon para i-save ang mga pagbabago kaya mananatili ang mga ito na hindi naka-save." some_trouble: "nagkaroon ako ng problema sa pagse-save: %{errors}" changing_on_hand_stock: pagbabago sa lebel ng hand stock @@ -2758,6 +2756,7 @@ fil_PH: previous: "nauna" last: "huli" spree: + more: "Karagdagang Impormasyon" your_order_is_empty_add_product: "ang iyong order ay walang laman, humanap at magdagdag ng produkto sa itaas." add_product: "magdagdag ng produkto" name_or_sku: "Pangalan o SKU (ipasok ang 4 na letra sa pangalan ng produkto)" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 8935f5a339..50b821e721 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -181,8 +181,8 @@ fr: title: Autre échec (%{count} commandes) explainer: Le traitement automatique de ces commandes a échoué pour une raison inconnue. Cela n'aurait pas dû arriver, veuillez nous contacter si vous constatez quelque chose d'anormal. home: "OFF" - title: Open Food France - welcome_to: 'Bienvenue sur ' + title: "Open Food France" + welcome_to: "Bienvenue sur" site_meta_description: "Tout commence dans le sol. Avec ces paysans, agriculteurs, producteurs, engagés pour une agriculture durable et régénératrice, et désireux de partager leur histoire et leur passion avec fierté. Avec ces distributeurs souhaitant reconnecter les individus à leurs aliments et aux gens qui les produisent, soutenir les prises de conscience, dans une démarche de transparence, d'honnêteté, en assurant une juste rémunération des producteurs. Avec ces acheteurs qui croient que de meilleures décisions d'achats peuvent ..." search_by_name: Recherche par nom ou département... producers_join: Les producteurs et autres hubs basés en France sont invités à rejoindre Open Food France. @@ -270,6 +270,7 @@ fr: backordered: "(à volonté)" on hand: "En stock" ship: "Expédier" + shipping_category: "Condition de transport" actions: create_and_add_another: "Créer et ajouter nouveau" create: "Créer" @@ -1188,8 +1189,8 @@ fr: signup: "inscrivez-vous" contact: "contacter" require_customer_login: "Seul les acheteurs autorisés peuvent accéder à cette boutique." - require_login_html: "Si vous êtes déjà autorisé à accéder à la boutique, %{login}ou %{signup}pour continuer." - require_login_2_html: "Vous souhaitez réaliser vos courses ici? Merci de %{contact}%{enterprise} afin d'avoir l'autorisation d'accès à la boutique." + require_login_html: "Si vous êtes déjà autorisé à accéder à la boutique, %{login} ou %{signup} pour continuer." + require_login_2_html: "Vous souhaitez réaliser vos courses ici ? Merci de %{contact} %{enterprise} afin d'avoir l'autorisation d'accès à la boutique." require_customer_html: "Si vous voulez demander à y accéder, veuillez %{contact} %{enterprise}." select_oc: select_oc_html: "Merci de sélectionner une option dans la liste déroulante, afin de voir quel produits sont disponibles." @@ -1652,7 +1653,7 @@ fr: sell_hubs_detail: "Créer un profil pour votre entreprise de distribution ou organisation sur OFFrance. A tout moment vous pourrez créer une boutique multi-fournisseurs." sell_groups_detail: "Créer un répertoire sur mesure (regroupant différents producteurs et hubs de distribution) pour votre région ou votre organisation." sell_user_guide: "En savoir plus en explorant le guide utilisateur." - sell_listing_price: "L'inscription sur OFFrance est gratuite. Ouvrir et gérer une boutique sur OFFrance ou créer un groupe sur OFFrance pour votre organisation ou réseau régional, n'est pas gratuit, mais le prix est libre: soit 2% du chiffre d'affaire (ou autre forme de contribution libre designée par votre hub), soit une contribution \"en compétences\" à Open Food France (développement de fonctionnalités, recherche de financement, support utilisateur, etc.), soit un mix des deux." + sell_listing_price: "L'inscription sur OFFrance est gratuite. Ouvrir et gérer une boutique sur OFFrance ou créer un groupe sur OFFrance pour votre organisation ou réseau régional, n'est pas gratuit. Il existe deux formules : soit la formule \"petit débrouillard\" où vous pouvez opter pour un montant libre à nous reverser (mais vous ne pouvez pas disposer de support personnalisé), soit vous nous reversez 1% du volume de vente réalisé sur la plateforme pour bénéfier de notre accompagnement. Ce pourcentage peut être dégressif à partir d'un certain montant. Veuillez nous contacter pour en savoir plus !" sell_embed: "Nous pouvons aussi intégrer votre boutique OFFrance dans votre propre site web ou construire un site web d'alimentation locale sur mesure pour votre région." sell_ask_services: "Nous consulter sur les services des partenaires OFFrance." shops_title: Boutiques @@ -1714,8 +1715,8 @@ fr: password: Mot de passe remember_me: Se souvenir de moi are_you_sure: "Confirmer?" - orders_open: Boutique ouverte - closing: "Fermeture " + orders_open: "Boutique ouverte" + closing: "Fermeture" going_back_to_home_page: "Retour à la page d'accueil" creating: Création updating: Mettre à jour @@ -2073,6 +2074,7 @@ fr: hub_sidebar_at_least: "Sélectionnez un/des hubs" hub_sidebar_blue: "bleu" hub_sidebar_red: "rouge" + order_cycles_closed_for_hub: "La boutique sélectionnée a fermé temporairement ses commandes. Veuillez essayez à nouveau ultérieurement." report_customers_distributor: "Distributeur" report_customers_supplier: "Fournisseur" report_customers_cycle: "Cycle de vente" @@ -2311,6 +2313,7 @@ fr: order_cycles_email_to_producers_notice: 'Les emails à destination des producteurs ont été mis en file d''attente.' order_cycles_no_permission_to_coordinate_error: "Aucune de vos entreprises n'a les droits requis pour coordonner un cycle de vente" order_cycles_no_permission_to_create_error: "Vous n'avez pas les droits requis pour créer un cycle de vente coordonné par cette entreprise" + order_cycle_closed: "Le cycle de vente que vous avez sélectionné a fermé. " back_to_orders_list: "Retour à la liste des commandes" no_orders_found: "Aucune commande trouvée pour ces critères" order_information: "Info commande" @@ -2802,6 +2805,7 @@ fr: previous: "Précédent" last: "Fin" spree: + more: "Plus" your_order_is_empty_add_product: "Votre commande est vide, veuillez ajouter des produits" add_product: "Ajouter un produit" name_or_sku: "Nom ou Ref Produit (entrer au moins les 4 premiers caractères du nom du produit)" @@ -3135,7 +3139,7 @@ fr: zones: "Zones" both: "Vu par l'acheteur sur la boutique" back_end: "Visible pour l'administration uniquement" - deactivation_warning: "Désactiver une méthode de livraison peut engendre sa disparition de la liste ici. Si vous souhaitez uniquement ne plus l'afficher pour l'acheteur, modifiez-là et utilisez l'option d'affichage \"visible pour l'administrateur uniquement\"." + deactivation_warning: "Désactiver une méthode de livraison peut engendrer sa disparition de la liste ici. Si vous souhaitez uniquement ne plus l'afficher pour l'acheteur, modifiez-là et utilisez l'option d'affichage \"visible pour l'administrateur uniquement\"." payment_methods: index: payment_methods: "Méthodes de paiement" diff --git a/config/locales/fr_BE.yml b/config/locales/fr_BE.yml index 5b2e89defd..a8822f7337 100644 --- a/config/locales/fr_BE.yml +++ b/config/locales/fr_BE.yml @@ -181,8 +181,7 @@ fr_BE: title: Autre échec (%{count} commandes) explainer: Le traitement automatique de ces commandes a échoué pour une raison inconnue. Cela n'aurait pas dû arriver, veuillez nous contacter si vous constatez quelque chose d'anormal. home: "OFN" - title: Open Food Network - welcome_to: 'Bienvenue sur ' + title: "Open Food Network" site_meta_description: "C’est ce que nous faisons en créant une plateforme qui veut mettre en contact :\n● des paysans passionnés, engagés pour une agriculture durable et régénératrice,\n● des distributeurs de produits locaux, adeptes de circuits courts, qui agissent en toute transparence et assurent une juste rémunération des producteurs,\n● des acheteurs qui veulent changer le monde en mangeant mieux," search_by_name: Recherche par nom ou province... producers_join: Les producteurs et autres magasins basés en Belgique sont invités à rejoindre Open Food Network. @@ -270,6 +269,7 @@ fr_BE: backordered: "En rupture de stock" on hand: "En stock" ship: "Expédier" + shipping_category: "Catégorie de livraison" actions: create_and_add_another: "Créer et ajouter un nouveau" create: "Créer" @@ -1142,13 +1142,13 @@ fr_BE: menu: cart: cart: "Panier" + cart_sidebar: + checkout: "Etape suivante (coordonnées)" + close: "Ferme" signed_in: profile: "Profil" mobile_menu: cart: "Panier" - joyride: - checkout: "Passer la commande" - already_ordered_products: "Déjà commandé dans ce cycle de vente" register_call: selling_on_ofn: "Intéressé·e à vendre sur Open Food Network?" register: "Démarrez ici" @@ -1696,8 +1696,7 @@ fr_BE: password: Mot de passe remember_me: Se souvenir de moi are_you_sure: "Confirmer?" - orders_open: Comptoir ouvert - closing: "Fermeture " + orders_open: "Comptoir ouvert" going_back_to_home_page: "Retour à la page d'accueil" creating: Création updating: Mettre à jour @@ -2695,6 +2694,7 @@ fr_BE: previous: "Précédent" last: "Fin" spree: + more: "Plus" your_order_is_empty_add_product: "Votre commande est vide, merci de chercher et d'ajouter un des produits ci-dessus" add_product: "Ajoutez un produit" name_or_sku: "Nom ou N° d'article (entrer au moins 4 lettres du nom du produit) " diff --git a/config/locales/fr_CA.yml b/config/locales/fr_CA.yml index 78d26c0da0..7b58f542a8 100644 --- a/config/locales/fr_CA.yml +++ b/config/locales/fr_CA.yml @@ -182,8 +182,8 @@ fr_CA: title: Autre échec (%{count} commandes) explainer: Le traitement automatique de ces commandes a échoué pour une raison inconnue. Cela n'aurait pas dû arriver, veuillez nous contacter si vous lisez ce message. home: "OFN" - title: 'Open Food Network ' - welcome_to: 'Bienvenue sur ' + title: "Open Food Network " + welcome_to: "Bienvenue sur" site_meta_description: "Tout commence dans le sol. Avec ces paysans, agriculteurs, producteurs, engagés pour une agriculture durable et régénératrice, et désireux de partager leur histoire et leur passion avec fierté. Avec ces distributeurs souhaitant reconnecter les individus à leurs aliments et aux gens qui les produisent, soutenir les prises de conscience, dans une démarche de transparence, d'honnêteté, en assurant une juste rémunération des producteurs. Avec ces acheteurs qui croient que de meilleures décisions d'achats peuvent ..." search_by_name: Recherche par nom ou ville... producers_join: Les producteurs et autres hubs basés au Québec sont invités à rejoindre Open Food Network Canada. @@ -271,6 +271,7 @@ fr_CA: backordered: "à volonté" on hand: "En stock" ship: "Expédier" + shipping_category: "Condition de transport" actions: create_and_add_another: "Créer et ajouter nouveau" create: "Créer" @@ -480,6 +481,7 @@ fr_CA: line_number: "Ligne :%{number}" encoding_error: "Veuillez vérifier le paramètre de langue de votre code source et vous assurer qu'il est encodé en UTF-8" unexpected_error: "L'import de fichier produits à rencontré une erreur inconnue à l'ouverture du fichier : %{error_message}" + malformed_csv: "L'outil d'import a rencontré un fichier CSV avec le ou les problèmes suivants :%{error_message}" index: notice: "Notice" beta_notice: "Cette fonctionnalité est en mode bêta : il est possible que vous rencontriez des erreurs en l'utilisant. N'hésitez pas à contacter le support utilisateurs." @@ -1148,13 +1150,18 @@ fr_CA: menu: cart: cart: "Panier" + cart_sidebar: + checkout: "Finalisation commande" + edit_cart: "Modifier le panier" + items_in_cart_singular: "%{num}élément dans le panier" + items_in_cart_plural: "%{num}éléments dans le panier" + close: "Ferme" + cart_empty: "Le panier est vide" + take_me_shopping: "Continuer mes achats" signed_in: profile: "Profil" mobile_menu: cart: "Panier" - joyride: - checkout: "Passer la commande" - already_ordered_products: "Déjà commandé dans ce cycle de vente" register_call: selling_on_ofn: "Vous souhaitez proposer vos produits sur Open Food Network?" register: "Démarrez ici" @@ -1708,8 +1715,8 @@ fr_CA: password: Mot de passe remember_me: Se souvenir de moi are_you_sure: "Confirmer?" - orders_open: Boutique ouverte - closing: "Fermeture " + orders_open: "Boutique ouverte" + closing: "Fermeture" going_back_to_home_page: "Retour à la page d'accueil" creating: Création updating: Mettre à jour @@ -2067,6 +2074,7 @@ fr_CA: hub_sidebar_at_least: "Sélectionnez un/des hubs" hub_sidebar_blue: "bleu" hub_sidebar_red: "rouge" + order_cycles_closed_for_hub: "La boutique sélectionnée a fermé temporairement ses commandes. Veuillez essayez à nouveau ultérieurement." report_customers_distributor: "Distributeur" report_customers_supplier: "Fournisseurs" report_customers_cycle: "Cycle de vente" @@ -2305,6 +2313,7 @@ fr_CA: order_cycles_email_to_producers_notice: 'Les emails à destination des producteurs ont été mis en file d''attente pour envoi.' order_cycles_no_permission_to_coordinate_error: "Aucune de vos entreprises n'a les droits requis pour coordonner un cycle de vente" order_cycles_no_permission_to_create_error: "Vous n'avez pas les droits requis pour créer un cycle de vente coordonné par cette entreprise" + order_cycle_closed: "Le cycle de vente que vous avez sélectionné a fermé. " back_to_orders_list: "Retour à la liste des commandes" no_orders_found: "Aucune commande trouvée" order_information: "Info commande" @@ -2779,6 +2788,7 @@ fr_CA: previous: "Précédent" last: "Fin" spree: + more: "Plus" your_order_is_empty_add_product: "Votre commande est vide, veuillez ajouter des produits" add_product: "Ajouter un produit" name_or_sku: "Nom ou Ref Produit (entrer au moins les 4 premiers caractères du nom du produit)" diff --git a/config/locales/it.yml b/config/locales/it.yml index 7d0ef72e69..ebbf8932d0 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -181,8 +181,7 @@ it: title: Altro errore (%{count} gentili richieste) explainer: L'elaborazione automatica di queste gentili richieste non è riuscita per una ragione sconosciuta. Questo non dovrebbe accadere, ti preghiamo di contattarci se visualizzi questo messaggio. home: "OFN" - title: Open Food Network - welcome_to: 'Benvenuto su ' + title: "Open Food Network" site_meta_description: "Cominciamo da zero. Con i produttori e gli allevatori pronti a raccontare le loro storie, sinceramente e orgogliosamente. Con i distributori pronti a connettere le persone con i prodotti in modo giusto ed equo. Con i compratori che credono che migliori decisioni per l'acquisto settimanale possano..." search_by_name: Cerca per nome o zona... producers_join: I Produttori sono ora invitati ad unirsi ad Open Food Network @@ -270,6 +269,7 @@ it: backordered: "Ordini arretrati" on hand: "Disponibile" ship: "Spedizione" + shipping_category: "Categoria Spedizioni" actions: create_and_add_another: "Crea e Aggiungi un Altro" create: "Crea" @@ -1148,13 +1148,13 @@ it: menu: cart: cart: "Carrello" + cart_sidebar: + checkout: "Paga" + close: "Chiuso" signed_in: profile: "Profilo" mobile_menu: cart: "Carrello" - joyride: - checkout: "Checkout adesso" - already_ordered_products: "Già richiesto in questo ciclo di richieste" register_call: selling_on_ofn: "Interessato ad entrare in Open Food Network?" register: "Registrati qui" @@ -1708,8 +1708,7 @@ it: password: Password remember_me: Ricordami are_you_sure: "Sei sicuro?" - orders_open: Richieste aperte - closing: "In chiusura " + orders_open: "Richieste aperte" going_back_to_home_page: "Reindirizzamento alla homepage" creating: In creazione updating: In aggiornamento @@ -2771,6 +2770,7 @@ it: previous: "Precedente" last: "Ultimo" spree: + more: "Di più" your_order_is_empty_add_product: "Il tuo ordine è vuoto, cerca e aggiungi un prodotto qui sopra" add_product: "Aggiungi prodotto" name_or_sku: "Nome o SKU (inserisci almeno i primi 4 caratteri del nome del prodotto)" diff --git a/config/locales/nb.yml b/config/locales/nb.yml index bf8f4cba79..c47ad93636 100644 --- a/config/locales/nb.yml +++ b/config/locales/nb.yml @@ -181,8 +181,7 @@ nb: title: Annen Feil (%{count} bestillinger) explainer: Automatisk behandling av disse bestillingene mislyktes av en ukjent grunn. Dette bør ikke skje, vennligst kontakt oss hvis du ser dette. home: "OFN" - title: Open Food Network - welcome_to: 'Velkommen til ' + title: "Open Food Network" site_meta_description: "Vi begynner fra grunnen. Med bønder og dyrkere klare til å fortelle sine historier, stolt og virkelig. Med distributører klare til å koble mennesker med produkter på en rettferdig og ærlig måte. Med kunder som tror på at ukentlige innkjøpsrutiner kan..." search_by_name: Søk på navn eller sted... producers_join: Norske produsenter er nå velkommen til å bli med i Open Food Network. @@ -270,6 +269,7 @@ nb: backordered: "Restbestilte" on hand: "Tilgjengelig" ship: "Levere" + shipping_category: "Leveringskategori" actions: create_and_add_another: "Opprett og legg til en annen" create: "Opprett" @@ -1147,13 +1147,18 @@ nb: menu: cart: cart: "Handlekurv" + cart_sidebar: + checkout: "Kassen" + edit_cart: "Rediger handlevogn" + items_in_cart_singular: "%{num} vare i handlekurven" + items_in_cart_plural: "%{num} varer i handlekurven" + close: "Lukk" + cart_empty: "Handlekurven din er tom" + take_me_shopping: "Ta meg shopping!" signed_in: profile: "Profil" mobile_menu: cart: "Handlekurv" - joyride: - checkout: "Sjekk ut nå" - already_ordered_products: "Allerede bestilt i denne bestillingssyklusen" register_call: selling_on_ofn: "Interessert i å bli med i Open Food Network?" register: "Registrer her" @@ -1707,8 +1712,7 @@ nb: password: Passord remember_me: Husk meg are_you_sure: "Er du sikker?" - orders_open: Åpen for bestilling - closing: "Stenger " + orders_open: "Åpen for bestilling" going_back_to_home_page: "Tar deg tilbake til hjemmesiden" creating: Oppretter updating: Oppdaterer @@ -2766,6 +2770,7 @@ nb: previous: "Tidligere" last: "Siste" spree: + more: "Mer" your_order_is_empty_add_product: "Bestillingen din er tom, vennligst søk etter og legg til et produkt over" add_product: "Legg til produkt" name_or_sku: "Navn eller SKU (skriv inn minst 4 tegn av produktnavn)" diff --git a/config/locales/nl_BE.yml b/config/locales/nl_BE.yml index 2a80116410..61e9b4a7d9 100644 --- a/config/locales/nl_BE.yml +++ b/config/locales/nl_BE.yml @@ -172,8 +172,7 @@ nl_BE: title: Andere mislukking (%{count}orders) explainer: De automatische verwerking van deze bestellingen is om een onbekende reden mislukt. Dit mag niet gebeuren, neem contact met ons op als u dit ziet. home: "OFN" - title: Open Food Network - welcome_to: 'Welkom op ' + title: "Open Food Network" site_meta_description: "Dit is wat we doen door een platform te creëren dat verbinding wil maken:\n● gepassioneerde boeren, die zich inzetten voor duurzame en regeneratieve landbouw,\n● handelaren van lokale producten, aanhangers van kortsluitingen, die in volledige transparantie handelen en een eerlijke vergoeding van de producenten garanderen,\n● kopers die de wereld willen veranderen door beter te eten," search_by_name: Zoeken op naam of voorstad.... producers_join: Belgische producenten zijn nu welkom om zich aan te sluiten bij het Open Food Network. @@ -257,6 +256,7 @@ nl_BE: back_to_payments_list: "Terug naar Betalingslijst" on hand: "Bij de Hand" ship: "Verzenden" + shipping_category: "Verzendcategorie" actions: create_and_add_another: "Een ander maken en toevoegen" create: "Maak" @@ -1102,13 +1102,13 @@ nl_BE: menu: cart: cart: "kar" + cart_sidebar: + checkout: "Kassa" + close: "Sluit" signed_in: profile: "Profiel" mobile_menu: cart: "kar" - joyride: - checkout: "Naar de kassa" - already_ordered_products: "Reeds besteld in de huidige bestelcyclus" register_call: selling_on_ofn: "Geïnteresseerd om deel uit te maken van het Open Food Network?" register: "Registreer hier" @@ -1648,8 +1648,7 @@ nl_BE: password: Wachtwoord remember_me: Onthoud Me are_you_sure: "Ben je zeker?" - orders_open: Openstaande Bestellingen - closing: "Sluitend" + orders_open: "Openstaande Bestellingen" going_back_to_home_page: "Terugkerend naar de startpagina" creating: Aanmakend updating: Updatend @@ -2625,6 +2624,7 @@ nl_BE: previous: "Voorgaande" last: "Laatst" spree: + more: "Meer" your_order_is_empty_add_product: "Je bestelling is leeg, gelieve hierboven een product te zoeken en toe te voegen " add_product: "Voeg een product toe" name_or_sku: "Beschrijving of artikelnummer ( tenminste 4 letters van de productnaam invoeren )" diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 2e4d9c06de..0e9da2fb53 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -166,8 +166,7 @@ pt: title: Outra Falha (%{count} encomendas) explainer: 'O processamento automático destas encomendas falhou devido a uma razão desconhecida. Isto não deveria estar a acontecer, por favor contacte-nos se estiver a ver esta mensagem. ' home: "OFN" - title: Open Food Network - welcome_to: 'Bem-vindo à' + title: "Open Food Network" site_meta_description: "Começamos a partir da terra. Com agricultores e produtores prontos a contarem as suas histórias com um brilho nos olhos. Com distribuidores prontos a estabelecerem ligações entre pessoas e produtos de forma justa e honesta. Com consumidores que acreditam que melhores decisões no momento da compra..." search_by_name: Procurar por nome ou localidade... producers_join: Produtores e produtoras de proximidade, estão convidados a juntarem-se à Open Food Network! @@ -1082,11 +1081,11 @@ pt: show_on_map: "Mostrar todas as lojas no mapa" shared: menu: + cart_sidebar: + checkout: "Finalizar compra" + close: "Fechar" signed_in: profile: "Perfil" - joyride: - checkout: "Finalizar compra agora" - already_ordered_products: "Já encomendado neste ciclo de encomendas" register_call: selling_on_ofn: "Tem interesse em participar na Open Food Network?" register: "Registe-se aqui" @@ -1623,8 +1622,7 @@ pt: password: Palavra-passe remember_me: Lembrar meu login are_you_sure: "Tem a certeza?" - orders_open: Encomendas abertas - closing: "Fechando" + orders_open: "Encomendas abertas" going_back_to_home_page: "Voltando à pagina inicial" creating: Criando updating: Atualizando @@ -2582,6 +2580,7 @@ pt: previous: "Anterior" last: "Último" spree: + more: "Mais" your_order_is_empty_add_product: "A sua encomenda está vazia, por favor procure e adicione um produto em cima" add_product: "Adicionar Produto" name_or_sku: "Nome ou Código (insira pelo menos 4 caracteres)" diff --git a/config/locales/pt_BR.yml b/config/locales/pt_BR.yml index e2cae6de9f..acdf438a4d 100644 --- a/config/locales/pt_BR.yml +++ b/config/locales/pt_BR.yml @@ -181,8 +181,8 @@ pt_BR: title: Outras Falhas(%{count} pedidos) explainer: O processamento automático desses pedidos falhou por um motivo desconhecido. Isso não deve ocorrer, entre em contato conosco se você está vendo isso. home: "OFB" - title: Open Food Brasil - welcome_to: 'Bem-vindo a' + title: "Open Food Brasil" + welcome_to: "Bem vindo a" site_meta_description: "A nossa proposta é conectar agricultoras/es, produtoras/es e consumidoras/es que estão próximos entre si. Buscamos, assim, potencializar a circulação e a comercialização de produtos agroecológicos, orgânicos e artesanais em circuitos curtos, bem como criar novas formas de relação, orientadas pela troca, diálogo e confiança." search_by_name: Procurar por nome ou localidade producers_join: Produtores nacionais estão convidados a se unirem à Open Food Brasil. @@ -270,6 +270,7 @@ pt_BR: backordered: "Pedidos em atraso" on hand: "Disponível" ship: "Envio" + shipping_category: "Tipos de Frete" actions: create_and_add_another: "Criar e adicionar outro" create: "Criar" @@ -370,7 +371,10 @@ pt_BR: title: "Configurações do Matomo" matomo_url: "Matomo URL" matomo_site_id: "Matomo Site ID" + matomo_tag_manager_url: "URL do Gestor de Tag Matomo" + info_html: "Matomo é uma aplicação de Mobile e Web Analytics . Você pode hospedar o Matomo localmente ou usar um serviço de hospedagem na nuvem. Veja matomo.org para mais informações." config_instructions_html: "Aqui você pode configurar a integração do OFN Matomo. O URL do Matomo abaixo deve apontar para a instância do Matomo para onde as informações de rastreamento do usuário serão enviadas; se for deixado em branco, o rastreamento de usuários do Matomo será desativado. O campo ID do site não é obrigatório, mas é útil se você estiver controlando mais de um site em uma única instância do Matomo. ele pode ser encontrado no console da instância Matomo." + config_instructions_tag_manager_html: "Ao configurar a URL para o Matomo Tag Manager, você habilita o Matomo Tag Manager. Esta ferramenta permite que você configure eventos de 'analytics'. A URL do Matomo Tag Manager é copiada do Matomo Tag Manager. Certifique-se de que você seleciou o 'container' e o 'environment' corretos, já que estas opções mudam o URL. " customers: index: new_customer: "Novo cliente" @@ -476,6 +480,7 @@ pt_BR: line_number: "Linha %{number}:" encoding_error: "Verifique a configuração de idioma do seu arquivo de origem e verifique se ele foi salvo com a codificação UTF-8" unexpected_error: "A Importação do produto encontrou um erro inesperado ao abrir o arquivo: %{error_message}" + malformed_csv: "A importação de produto encontrou um CSV com erro: %{error_message}" index: notice: "Aviso " beta_notice: "Esse recurso ainda está na versão beta: você pode encontrar alguns erros ao usá-lo. Por favor, não hesite em entrar em contato com o suporte." @@ -1141,13 +1146,18 @@ pt_BR: menu: cart: cart: "Carrinho" + cart_sidebar: + checkout: "Fechar pedido" + edit_cart: "Editar carrinho" + items_in_cart_singular: "%{num}intem no seu carrinho" + items_in_cart_plural: "%{num}itens no seu carrinho" + close: "Fechado" + cart_empty: "Seu carrinho está vazio" + take_me_shopping: "Quero comprar" signed_in: profile: "Perfil" mobile_menu: cart: "Carrinho" - joyride: - checkout: "Fechar pedido agora" - already_ordered_products: "Já pediu neste ciclo de pedidos" register_call: selling_on_ofn: "Interessado em registrar a sua iniciativa na Open Food Brasil?" register: "Registre-se aqui" @@ -1372,10 +1382,10 @@ pt_BR: cta_headline: "Compras que fazem do mundo um lugar melhor." cta_label: "Estou pronto" stats_headline: "Estamos trabalhando para relocalizar os sistemas agroalimentares" - stats_producers: "produtores de alimentos" - stats_shops: "lojas de alimentos" - stats_shoppers: "consumidores de alimentos" - stats_orders: "pedidos de alimentos" + stats_producers: "produtores" + stats_shops: "lojas" + stats_shoppers: "consumidores" + stats_orders: "pedidos" checkout_title: Fechar pedido checkout_now: Fechar pedido agora checkout_order_ready: Pedido pronto para @@ -1612,7 +1622,7 @@ pt_BR: producers_title: Produtores producers_headline: Encontre produtores locais producers_signup_title: Inscreva-se como produtor - producers_signup_headline: Produtores de alimentos, empoderados. + producers_signup_headline: Visibilidade para o pequeno produtor. producers_signup_motivation: Comercialize seus produtos e conte sua história para um mercado novo e diferenciado. Economize tempo e dinheiro em todas as despesas gerais. Nós apoiamos inovação sem risco. Nós nivelamos o campo de jogo. producers_signup_send: Junte-se agora producers_signup_enterprise: Contas de iniciativas @@ -1701,7 +1711,7 @@ pt_BR: password: Senha remember_me: Lembre-me are_you_sure: "Tem certeza?" - orders_open: Pedidos abertos + orders_open: "Pedidos abertos" closing: "Fechando" going_back_to_home_page: "Voltando à pagina inicial" creating: Criando @@ -2060,6 +2070,7 @@ pt_BR: hub_sidebar_at_least: "Pelo menos uma central deve ser selecionado" hub_sidebar_blue: "azul" hub_sidebar_red: "vermelho" + order_cycles_closed_for_hub: "A Central selecionada está temporariamente fechada para pedidos. Por favor tente mais tarde. " report_customers_distributor: "Distribuidor" report_customers_supplier: "Fornecedor" report_customers_cycle: "Ciclo de pedidos" @@ -2296,6 +2307,7 @@ pt_BR: order_cycles_email_to_producers_notice: 'E-mails a serem enviados aos produtores foram encaminhados para envio.' order_cycles_no_permission_to_coordinate_error: "Nenhuma das suas iniciativas tem permissão para coordenar um ciclo de pedidos" order_cycles_no_permission_to_create_error: "Você não tem permissão para criar um ciclo de pedidos coordenado por esta iniciativa" + order_cycle_closed: "O ciclo de pedidos selecionado acabou de fechar, por favor tente novamente depois!" back_to_orders_list: "Voltar à lista de pedidos" no_orders_found: "Nenhum pedido encontrado" order_information: "Informações sobre pedidos" @@ -2766,6 +2778,7 @@ pt_BR: previous: "Anterior" last: "Último" spree: + more: "Mais" your_order_is_empty_add_product: "Seu pedido está vazio, pesquise e adicione um produto acima" add_product: "Adicionar produto" name_or_sku: "Nome ou SKU (digite pelo menos os 4 primeiros caracteres do nome do produto)" @@ -2811,9 +2824,15 @@ pt_BR: fill_in_customer_info: "Por favor, preencha as informações do cliente" new_payment: "Novo Pagamento" capture: "Capturar" - void: "Vazio" + void: "Apagar" login: "Login" password: "Senha" + signature: "Assinatura" + solution: "Solução" + landing_page: "Página Inicial" + server: "Servidor" + test_mode: "Modo Teste" + logourl: "url da logo" configurations: "Configurações" general_settings: "Configurações Gerais" site_name: "Nome do site" @@ -2930,6 +2949,12 @@ pt_BR: options: "Opções" actions: update: "Atualizar" + shared: + error_messages: + errors_prohibited_this_record_from_being_saved: + one: "1 erro não permitiu que este registro fosse salvo:" + other: "%{count}erros não permitiram que esse registro fosse salvo:" + there_were_problems_with_the_following_fields: "Houve problemas com os seguintes campos" errors: messages: blank: "Não pode ser vazio" @@ -3087,19 +3112,23 @@ pt_BR: zones: "Zonas" both: "Tanto Checkout quanto Área Administrativa" back_end: "Somente Área Administrativa" - deactivation_warning: "Desativar um método de envio pode fazer com que ele desapareça da sua lista. Como alternativa, você pode esconder um método de envio da página de checkout s" + deactivation_warning: "Desativar um método de envio pode fazer com que ele desapareça da sua lista. Como alternativa, você pode esconder um método de envio da página de checkout trocando a opção 'Exibição' para 'Somente área administrativa'." payment_methods: index: payment_methods: "Métodos de pagamento" new_payment_method: "Novo método de pagamento" name: "Nome" products_distributor: "Distribuidor" + provider: "Provedor" environment: "Ambiente" display: "Exibição" + active: "Ativo" both: "Ambos" + front_end: "Apenas checkout" back_end: "Somente Área Administrativa" active_yes: "Sim" active_no: "Não" + no_payment_methods_found: "Não foi encontrado nenhum método de pagamento" new: new_payment_method: "Novo método de pagamento" back_to_payment_methods_list: "Voltar à lista de formas de pagamento" @@ -3125,11 +3154,16 @@ pt_BR: description: "Descrição" environment: "Ambiente" display: "Exibição" + active: "Ativo" active_yes: "Sim" active_no: "Não" both: "Tanto Checkout quanto Área Administrativa" + front_end: "Apenas Checkout" back_end: "Somente Área Administrativa" tags: "Tags" + deactivation_warning: "Desativar o método de pagamento pode fazer com que ele desapareça da sua lista. Como alternativa, você pode escondê-lo da página de checkout, trocando " + providers: + provider: "Provedor" payments: source_forms: stripe: @@ -3298,6 +3332,14 @@ pt_BR: invalid: inváliod order_mailer: cancel_email: + customer_greeting: "Querid@ %{name}," + instructions_html: "Seu pedido com %{distributor} foi CANCELADO. Por favor guarde este aviso de cancelamento para seu registro." + dont_cancel: "Se você mudou de ideia ou não quer mais cancelar este pedido, por favor entre em contato com %{email}" + order_summary_canceled_html: "Resumo do Pedido #%{number} [CANCELADO]" + details: "Aqui estão os detalhes do seu pedido:" + unpaid_order: "O pagamento do pedido foi cancelado, portanto não houve devolução " + paid_order: "Seu pedido foi pago, então %{distributor}devolveu o valor total" + credit_order: "Seu pedido foi pago, então você ganhou créditos na sua conta" subject: "Cancelamento do Pedido" confirm_email: subject: "Confimação de Pedido" diff --git a/config/locales/sv.yml b/config/locales/sv.yml index addd879f88..92d5d360e6 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -47,8 +47,7 @@ sv: order_cycle: subject: "Sammanställning över beställningsperiod för %{producer}" home: "OFN" - title: Open Food Network - welcome_to: 'Välkommen till' + title: "Open Food Network" site_meta_description: "Vi börjar från grunden. Med bönder och odlare redo att berätta sina berättelser stolt och verkligt. Med distributörer redo att ansluta människor med produkter rättvist och ärligt. Med köpare som tror att bättre veckovisa köpbeslut kan ..." search_by_name: Sök på ortnamn eller förort... producers_join: Svenska producenter kan nu gå med i Open Food Network. Välkomna! @@ -633,11 +632,11 @@ sv: show_on_map: "Visa allt på kartan" shared: menu: + cart_sidebar: + checkout: "Utcheckning" + close: "Stängd" signed_in: profile: "Profil" - joyride: - checkout: "Slutför beställning" - already_ordered_products: "Redan beställd i denna beställningsomgång" register_call: selling_on_ofn: "Intresserad av att gå med i Open Food Network?" register: "Registrera dig här" @@ -1069,8 +1068,7 @@ sv: password: Lösenord remember_me: Kom ihåg mig are_you_sure: "Är du säker?" - orders_open: Beställningar kan göras - closing: "Stänger" + orders_open: "Beställningar kan göras" going_back_to_home_page: "Tillbaka till startsidan" creating: Skapar updating: Uppdaterar diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 87598248e1..1bcf7af309 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -3,10 +3,10 @@ tr: activerecord: attributes: enterprise_fee: - fee_type: Ücret TÜRÜ + fee_type: Ücret Türü spree/order: payment_state: Ödeme Durumu - shipment_state: ' Teslimat Durumu' + shipment_state: ' TESLİMAT Durumu' completed_at: Oluşturulma TARİHİ number: Numara state: Durum @@ -16,11 +16,11 @@ tr: spree/product: primary_taxon: "ÜRÜN KATEGORİSİ" supplier: "TEDARİKÇİ" - shipping_category_id: "Teslimat KategOrİSİ" + shipping_category_id: "TESLİMAT KATEGORİSİ" variant_unit: "Varyant BİRİMİ" variant_unit_name: "Varyant Birim Adı" spree/credit_card: - base: "Kredi kartı" + base: "Kredİ kartı" order_cycle: orders_close_at: BİTİŞ TARİHİ variant_override: @@ -53,7 +53,7 @@ tr: end_at: "BİTİŞ" distributor_ids: "Pazarlar" producer_ids: "ÜRETİCİLER" - order_cycle_ids: "SİPARİŞ DÖNEMLERİ" + order_cycle_ids: "Sipariş Dönemleri" enterprise_fee_ids: "ÜCRET İSİMLERİ" shipping_method_ids: "TESLİMAT YÖNTEMLERİ" payment_method_ids: "ÖDEME YÖNTEMLERİ" @@ -78,7 +78,7 @@ tr: charges_not_allowed: "^ Bu müşteri tarafından kredi kartı ücretlendirme kabul edilmemektedir." no_default_card: "^ Bu müşteri için kayıtlı kart bulunmuyor" shipping_method: - not_available_to_shop: "%{shop} için kullanıma uygun değil" + not_available_to_shop: "%{shop} için müsait değil" devise: confirmations: send_instructions: "Birkaç dakika içinde, hesabınızı nasıl doğrulayacağınız ile ilgili talimatları içeren bir e-posta alacaksınız." @@ -128,7 +128,7 @@ tr: email_userguide_html: "Tezgah veya Pazar hesapları ayarları ile ilgili ayrıntılı desteğe sahip Kullanıcı Kılavuzu burada: %{link}" userguide: "Açık Gıda Ağı Kullanıcı Kılavuzu" email_admin_html: "%{link}'dan oturum açarak veya ana sayfanın sağ üst kısmındaki ayarlar simgesini tıkladıktan sonra Yönetim' butonu üzerinden hesabınızı yönetebilirsiniz." - admin_panel: "Yönetici Paneli" + admin_panel: "YönetiİCİ Panelİ" email_community_html: "Ayrıca, AGA yazılımı ve bir işletmeyi yönetmek ile ilgili fikir alışverişlerinde bulunduğumuz bir forumumuz var. Sizleri de paylaşmaya bekliyoruz. Sürekli olarak kendimizi yenillemeye çalışıyoruz ve düşünceleriniz bundan sonra olacakları şekillendirecek. %{link}" join_community: "Topluluğa katılın !" invite_manager: @@ -139,7 +139,7 @@ tr: shipment_mailer: shipped_email: dear_customer: "Değerli müşterimiz," - instructions: "Siparişiniz kargoya teslim edildi" + instructions: "Siparişiniz gönderildi" shipment_summary: "Teslimat Özeti" subject: "TESLİMAT BİLDİRİMİ" thanks: "İş birliğiniz için teşekkür ederim." @@ -181,9 +181,9 @@ tr: title: Diğer Hatalar (%{count} sipariş) explainer: Bu siparişlerin otomatik olarak işlenmesi bilinmeyen bir nedenden dolayı başarısız oldu. Bu gerçekleşmemesi gereken bir durum, bu hatayı alıyorsanız lütfen bizimle iletişime geçin. home: "AGA" - title: Açık Gıda Ağı - welcome_to: 'Hoşgeldiniz' - site_meta_description: "Açık Gıda Ağı, yerel, bağımsız, adil ve temiz bir gıda sistemi oluşturmak için tasarlanan bir sosyal girişim projesidir. Üretici ve türeticilerin bir araya gelerek aracısız bir gıda düzeni ile her açıdan daha sağlıklı bir toplum yaratmaları için çözümler sunar. Toplum yararına çalışır, iletişim, dürüstlük ve dayanışmayı destekler." + title: "Açık Gıda Ağı" + welcome_to: "Hoşgeldiniz" + site_meta_description: "Açık Gıda Ağı, bağımsız, adil ve temiz bir gıda sistemi oluşturmak için tasarlanan bir sosyal girişim projesidir. Üretici ve türeticilerin bir araya gelerek aracısız bir gıda düzeni ile her açıdan daha sağlıklı bir toplum yaratmaları için çözümler sunar. Toplum yararına çalışır, iletişim, dayanışma ve şeffaflığı destekler." search_by_name: Üretici adına veya konuma göre arama yapın... producers_join: Bağımsız gıda üreticileri! Açık Gıda Ağı sizler için kullanıma açıldı. charges_sales_tax: KDV Uyguluyor mu? @@ -206,7 +206,7 @@ tr: ongoing: Devam eden bill_address: Fatura Adresi ship_address: Teslimat Adresi - sort_order_cycles_on_shopfront_by: "SİPARİŞ DÖNEMLERİNİ MAĞAZAMDA ŞUNA GÖRE SIRALA" + sort_order_cycles_on_shopfront_by: "SİPARİŞ DÖNEMLERİNİ DÜKKANIMDA ŞUNA GÖRE SIRALA" required_fields: Zorunlu alanlar yıldız ile belirtilmiştir select_continue: Seç ve Devam Et remove: Kaldır @@ -221,9 +221,9 @@ tr: edit: Düzenle clone: Kopyala distributors: Dağıtımcılar - bulk_order_management: Toplu Sipariş Yönetimi + bulk_order_management: TOPLU SİPARİŞ YÖNETİMİ enterprises: İşletmeler - enterprise_groups: Gruplar + enterprise_groups: AĞLAR reports: Raporlar variant_overrides: Stok import: Aktar @@ -237,7 +237,7 @@ tr: admin_and_handling: Yönetici & İşlemler profile: Profil supplier_only: Sadece Tedarikçi - has_shopfront: MAĞAZASı Var + has_shopfront: DÜKKANI VAR weight: Ağırlık volume: Hacim items: Kalemler @@ -256,7 +256,7 @@ tr: processing_payment: "Ödeme İşlemi Gerçekleştiriliyor.." no_pending_payments: "Bekleyen ödeme yok" invalid_payment_state: "Geçersiz ödeme durumu" - filter_results: Sonuçları Filtrele + filter_results: SONUÇLARI FİLTRELE quantity: Miktar pick_up: Teslim Alma copy: Kopyala @@ -270,6 +270,7 @@ tr: backordered: "Bakiye Sipariş" on hand: "Mevcut" ship: "Teslimat" + shipping_category: "Teslimat KategOrİSİ" actions: create_and_add_another: "Oluştur ve Yeni Ekle" create: "Oluştur" @@ -286,13 +287,13 @@ tr: email: E-posta ends_at: Bitiş ends_on: Bitiş vakti - name: İsİM + name: Ad on_hand: Mevcut on_demand: Talep Üzerine on_demand?: Talep Üzerine? order_cycle: Sipariş Dönemi payment: Ödeme - payment_method: Ödeme Şekli + payment_method: ÖDEME YÖNTEMİ phone: Telefon price: Fiyat producer: ÜRETİCİ @@ -301,18 +302,18 @@ tr: quantity: Miktar schedule: Takvim shipping: Teslimat - shipping_method: Teslimat Yöntemi - shop: Tezgah + shipping_method: TESLİMAT YÖNTEMİ + shop: Dükkan sku: Stok Kodu status_state: Durum - tags: Etiketler + tags: ETİKETLER variant: Varyant weight: Ağırlık volume: Hacim items: Ölçüler select_all: Hepsini seç quick_search: Hızlı Arama - clear_all: Hepsini Temizle + clear_all: HEPSİNİ TEMİZLE start_date: "Başlangıç Tarihi" end_date: "Bitiş Tarihi" form_invalid: "Formda eksik veya geçersiz alanlar var" @@ -337,8 +338,8 @@ tr: unsaved_confirm_leave: "Kaydedilmemiş değişiklikler var. Kaydetmeden devam etmek istiyor musunuz?" unsaved_changes: "Kaydedilmemiş değişiklikleriniz var" shopfront_settings: - embedded_shopfront_settings: "YERLEŞTİRİLMİŞ MAĞAZA AYARLARI" - enable_embedded_shopfronts: "YERLEŞTİRİLMİŞ MAĞAZALARI ETKİNLEŞTİR" + embedded_shopfront_settings: "YERLEŞTİRİLMİŞ DÜKKAN AYARLARI" + enable_embedded_shopfronts: "YERLEŞTİRİLMİŞ DÜKKANLARI ETKİNLEŞTİR" embedded_shopfronts_whitelist: "Beyaz Listedeki harici Domain'ler" number_localization: number_localization_settings: "Numara Yerelleştirme Ayarları" @@ -441,7 +442,7 @@ tr: upload_an_image: Görsel yükleyin seo: product_search_keywords: "Ürün araması için anahtar kelimeler" - product_search_tip: "Pazarlarda ürünlerinizin aranmasına yardımcı olacak kelimeler yazın. Her bir anahtar kelimeyi ayırmak için boşluk kullanın." + product_search_tip: "Pazaryerinde ürünlerinizin aranmasına yardımcı olacak kelimeler yazın. Her bir anahtar kelimeyi ayırmak için boşluk kullanın." SEO_keywords: "Arama Motoru Anahtar Kelimeleri" seo_tip: "Ürünlerinizi internet üzerinde aramanıza yardımcı olacak kelimeler yazın. Her bir anahtar kelimeyi ayırmak için boşluk kullanın." search: "Ara" @@ -514,7 +515,7 @@ tr: options_and_defaults: İçe aktarma seçenekleri ve varsayılanlar no_permission: bu işletmeyi yönetme izniniz yok not_found: işletme veritabanında bulunamadı - no_name: İsİMSİZ + no_name: İSİMSİZ blank_enterprise: bazı ürünler için tanımlanmış bir işletme yok reset_absent?: Mevcut olmayan ürünleri sıfırla reset_absent_tip: Dosyada bulunmayan tüm mevcut ürünler için stoğu sıfıra ayarlayın @@ -566,7 +567,7 @@ tr: add: Ekle hide: Sakla import_date: Aktarıldı - select_a_shop: Bir Mağaza Seçin + select_a_shop: Bir Dükkan Seçin review_now: Şimdi İncele new_products_alert_message: Stoklarınıza eklemek için %{new_product_count} yeni ürün var. currently_empty: Stoklarınız şu anda boş @@ -576,7 +577,7 @@ tr: no_new_products: Stoklara eklenecek yeni ürün yok no_matching_new_products: Arama kriterlerinize uyan yeni ürün yok inventory_powertip: Burası sizin ürün envanteriniz, stok takip merkeziniz. Ürün eklemek için menüdeki 'Yeni Ürün' butonun tıklayın. - hidden_powertip: Bu ürünler stoklarınızda gizlendi ve mağazanızda görünür olmayacak. Stoklarınıza ürün eklemek için 'Ekle' butonuna tıklayabilirsiniz. + hidden_powertip: Bu ürünler stoklarınızda gizlendi ve dükkanınızda görünür olmayacak. Stoklarınıza ürün eklemek için 'Ekle' butonuna tıklayabilirsiniz. new_powertip: Bu ürünler stoklarınıza eklenmeye hazır. Stoğunuza ürün eklemek için 'Ekle'yi veya tezgahınızda gizlemek için 'Gizle'yi tıklayın. İstediğiniz zaman fikrinizi değiştirebilirsiniz! controls: back_to_my_inventory: Stoklarıma geri dön @@ -592,7 +593,7 @@ tr: product_unit: "Ürün: Birim" weight_volume: "Ağırlık / Hacim" ask: "Sor?" - page_title: "Toplu Sipariş Yönetimi" + page_title: "TOPLU SİPARİŞ YÖNETİMİ" actions_delete: "Seçilenleri Sil" loading: "Siparişler yükleniyor" no_results: "Sipariş bulunamadı." @@ -638,9 +639,9 @@ tr: website: İnternet sitesi website_placeholder: 'Örn: www.yesilciftlik.com' enterprise_fees: - name: ad + name: Ad fee_type: Ücret Türü - manage_fees: İşletme Ücretlerini Yönet + manage_fees: İŞLETME ÜCRETLERİNİ YÖNET no_fees_yet: Henüz herhangi bir işletme ücretiniz yok. create_button: Şimdi Oluştur images: @@ -657,21 +658,21 @@ tr: ürünün satış listenize eklenmeden önce Stok listenize eklenip eklenmemesi gerektiğini seçebilirsiniz. Eğer ürünlerinizi yönetmek için Stok sistemini kullanmıyorsanız 'tavsiye edilen' seçeneği ile devam etmeniz gerekir. - preferred_product_selection_from_inventory_only_yes: MAĞAZANIZA YENİ ÜRÜNLER EKLENEBİLİR (ÖNERİLİR) - preferred_product_selection_from_inventory_only_no: Yeni ürünler mağazanıza eklemeden önce Stok listesine girilmelidir + preferred_product_selection_from_inventory_only_yes: DÜKKANINIZA YENİ ÜRÜNLER EKLENEBİLİR (ÖNERİLİR) + preferred_product_selection_from_inventory_only_no: Yeni ürünler dükkanınıza eklemeden önce Stok listesine girilmelidir payment_methods: name: Ad applies: Uygulanma? - manage: Ödeme Yöntemlerini Yönet + manage: ÖDEME YÖNTEMLERİNİ YÖNET no_method_yet: Henüz herhangi bir ödeme yönteminiz yok. - create_button: Yeni Ödeme Yöntemi Oluştur + create_button: YENİ ÖDEME YÖNTEMİ OLUŞTUR create_one_button: Şimdi Oluştur primary_details: name: Ad name_placeholder: Örn. Ahmet Amca'nın Ekolojik Meyveleri - groups: Gruplar - groups_tip: Üyesi olduğunuz grupları veya bölgeleri seçin. Bu, müşterilerin işletmenizi bulmasına yardımcı olur. - groups_placeholder: Mevcut grupları aramak için yazmaya başlayın ... + groups: AĞLAR + groups_tip: Dahil olduğunuz ağları veya bölgeleri seçin. Bu, müşterilerin işletmenizi bulmasına yardımcı olur. + groups_placeholder: Mevcut ağları aramak için yazmaya başlayın ... primary_producer: Birincil üretici misiniz? primary_producer_tip: Siz de birincil üreticisiyseniz 'Üretici' yi seçin. producer: ÜRETİCİ @@ -680,26 +681,26 @@ tr: own: KENDİ ÜRÜNLERİ sells: NE SATIYOR sells_tip: "Hiç - İşletme müşterilere doğrudan satış yapmaz.
Kendi - İşletme müşterilerine kendi ürünlerini satar.
Hepsi - İşletme kendi ürünlerini veya diğer işletmelerin ürünlerini satabilir.
" - visible_in_search: Haritada görünsün mü? + visible_in_search: ARAMADA GÖRÜNÜR MÜ? visible_in_search_tip: Sitede arama yapılırken bu işletmenin müşteriler tarafından görünür olup olmayacağını belirler. - visible: Görünür - not_visible: Gizli + visible: GÖRÜNÜR + not_visible: GİZLİ permalink: Bağlantı Adı (boşluk yok) - permalink_tip: "Bağlantı adı, mağazanızın bağlantı URL'sini oluşturmak için kullanılır: %{link}sectiginiz-ad/tezgah" - link_to_front: Mağaza linkiniz - link_to_front_tip: Açık Gıda Ağı’ndaki mağazanıza doğrudan bağlantı linki + permalink_tip: "Bağlantı adı, mağazanızın bağlantı URL'sini oluşturmak için kullanılır: %{link}sectiginiz-ad/dükkan" + link_to_front: Dükkan linkiniz + link_to_front_tip: Açık Gıda Ağı’ndaki dükkanınıza doğrudan bağlantı linki ofn_uid: AGA KN ofn_uid_tip: Açık Gıda Ağı'na kayıtlı işletmenize özel tanımlanan kimlik numarası shipping_methods: name: "Ad" - applies: "Aktif?" + applies: "AKTİF?" manage: "Teslimat Yöntemlerini Yönet" - create_button: "Yeni Teslimat Yöntemi Oluştur" + create_button: "Yenİ Teslİmat Yöntemİ Oluştur" create_one_button: "Şimdi Oluştur" no_method_yet: "Henüz herhangi bir teslimat yönteminiz yok." shop_preferences: - shopfront_requires_login: "MAĞAZANIZ HERKESE AÇIK MI?" - shopfront_requires_login_tip: "Mağazanızın yalnızca üyelerinize mi yoksa herkese mi açık olduğunu seçin." + shopfront_requires_login: "DÜKKANINIZ HERKESE AÇIK MI?" + shopfront_requires_login_tip: "Dükkanınızın yalnızca üyelerinize mi yoksa herkese mi açık olduğunu seçin." shopfront_requires_login_false: "Herkese açık" shopfront_requires_login_true: "Yalnızca kayıtlı müşteriler tarafından görülebilir" recommend_require_login: "Siparişlerin değiştirilebileceği durumlarda kullanıcıların oturum açmasını öneriyoruz." @@ -715,21 +716,22 @@ tr: enable_subscriptions_tip: "Üyelik işlevselliği etkinleştirilsin mi?" enable_subscriptions_false: "Kapalı" enable_subscriptions_true: "Etkin" - shopfront_message: "MAĞAZA MESAJINIZ" + shopfront_message: "DÜKKAN MESAJINIZ" shopfront_message_placeholder: > - Müşterilerinize merhaba diyebilir, tezgahınız ve alışveriş şartlarınız + Müşterilerinize merhaba diyebilir, dükkanınız ve alışveriş şartlarınız ile ilgili bilgi verebilirsiniz. Yazdıklarınız, müşteriler mağazanızı ziyaret ettiğinde görünür olacak. shopfront_message_link_tooltip: "Bağlantı ekle/düzenle" shopfront_message_link_prompt: "Lütfen eklemek için bir URL girin" - shopfront_closed_message: "KAPALI MAĞAZA MESAJI" + shopfront_closed_message: "KAPALI DÜKKAN MESAJI" shopfront_closed_message_placeholder: > - Mağazanızın neden satışa kapalı olduğunu ve ne zaman açılacağını müşterilerinize - açıklayan bir mesaj yazın. Bu mesaj, açık sipariş döneminiz olmadığında - sizi ziyaret edenler tarafından görülecek. - shopfront_category_ordering: "MAĞAZA KATEGORİ SIRALAMASI" - open_date: "Açılış Tarihi" - close_date: "Kapanış Tarih" + Dükkanınızın neden satışa kapalı olduğunu ve ne zaman açılacağını müşterilerinize + açıklayan bir mesaj yazın. Bu mesaj, açık bir sipariş döneminiz olmadığında + sizi ziyaret edenler tarafından görülecek. (örn. neden kapalı olduğunu + ve ne zaman açılacağını belirtebilirsiniz) + shopfront_category_ordering: "DÜKKAN KATEGORİ SIRALAMASI" + open_date: "AÇILIŞ TARİHİ" + close_date: "KAPANIŞ TARİHİ" social: twitter_placeholder: "Örneğin. @the_usta" instagram_placeholder: "Örneğin. the_usta" @@ -777,10 +779,10 @@ tr: email_not_confirmed: "E-posta onaylanmadı" actions: edit_profile: Ayarlar - properties: Özellikler - payment_methods: Ödeme Yöntemleri + properties: ÖZELLİKLER + payment_methods: ÖDEME YÖNTEMLERİ payment_methods_tip: Bu işletmenin henüz ödeme yöntemi yok - shipping_methods: Teslimat Yöntemleri + shipping_methods: TESLİMAT YÖNTEMLERİ shipping_methods_tip: Bu işletme teslimat yöntemlerine sahip enterprise_fees: İşletme Ücretleri enterprise_fees_tip: Bu işletmeye kayıtlı bir ücret yok @@ -788,27 +790,27 @@ tr: name: Ad role: Görev sells: NE SATIYOR - visible: Haritada görünür mü? + visible: GÖRÜNÜR MÜ? owner: Sahip producer: ÜRETİCİ change_type_form: producer_profile: ÜRETİCİ PROFİLİ - connect_ofn: AGA üzerinden bağlan + connect_ofn: AGA PLATFORMUNA KATIL always_free: HER ZAMAN ÜCRETSİZ producer_description_text: Ürünlerinizi Açık Gıda Ağı'na yükleyin. Böylece ürünleriniz pazar hesapları üzerinden satışa sunulabilir. - producer_shop: ÜRETİCİ TEZGAHI - sell_your_produce: Kendi ürününü sat - producer_shop_description_text: Açık Gıda Ağı üzerinden açtığınız bireysel mağazanız ile ürünlerinizi doğrudan müşterilere ulaştırabilirsiniz. - producer_shop_description_text2: Üretici Tezgahı sadece sizin ürünleriniz içindir. Üretiminiz haricindeki ürünleri satabilmek için lütfen 'Üretici Pazarı' seçeneğini seçin. + producer_shop: ÜRETİCİ DÜKKANI + sell_your_produce: KENDİ ÜRÜNÜNÜ SAT + producer_shop_description_text: Açık Gıda Ağı üzerinden açtığınız bireysel dükkanınız ile ürünlerinizi doğrudan müşterilere ulaştırabilirsiniz. + producer_shop_description_text2: Üretici Dükkanı sadece sizin ürünleriniz içindir. Üretiminiz haricindeki ürünleri satabilmek için lütfen 'Üretici Pazarı' seçeneğini seçin. producer_hub: ÜRETİCİ PAZARI - producer_hub_text: Kendi ürünleriniz ile beraber başkalarının ürünlerini de satın + producer_hub_text: KENDİ ÜRÜNLERİNİZ İLE BERABER BAŞKALARININ ÜRÜNLERİNİ DE SATIN producer_hub_description_text: İşletmeniz, yerel gıda sisteminizin bel kemiğidir. Açık Gıda Ağı'ndaki mağazanız aracılığıyla kendi ürünlerinizi ve çevrenizdeki diğer üreticilerin ürünlerini beraber satabilirsiniz. profile: Yalnızca Profil get_listing: Görünür ol profile_description_text: İnsanlar Açık Gıda Ağı üzerinden sizi bulabilir ve sizinle iletişim kurabilir. İşletmeniz haritada ve listelerde görünür olacak. hub_shop: Türetici Pazarı - hub_shop_text: Başkalarının ürünlerini satın - hub_shop_description_text: İşletmeniz yerel gıda sisteminizin belkemiğidir. Diğer işletmelerin ürünlerini bir araya getirebilir, Açık Gıda Ağı'na kayıtlı mağazanız üzerinden alıcılara ulaştırabilirsiniz. + hub_shop_text: BAŞKALARININ ÜRÜNLERİNİ SATIN + hub_shop_description_text: İşletmeniz yerel gıda sisteminizin belkemiğidir. Diğer işletmelerin ürünlerini bir araya getirebilir, Açık Gıda Ağı'na kayıtlı dükkanınız üzerinden alıcılara ulaştırabilirsiniz. choose_option: Lütfen yukarıdaki seçeneklerden birini seçin. change_now: Değiştir enterprise_user_index: @@ -881,7 +883,7 @@ tr: outgoing: "Giden" distributor: "Dağıtımcı" products: "Ürünler" - tags: "Etiketler" + tags: "ETİKETLER" delivery_details: "Teslimat Detayları" fees: "Ücretler" previous: "Önceki" @@ -892,18 +894,18 @@ tr: wizard_progress: edit: "1. Genel Ayarlar" incoming: "2. Gelen Ürünler" - outgoing: "3. Giden Ürünler" + outgoing: "3. GİDEN ÜRÜNLER" exchange_form: pickup_time_tip: Bu sipariş dönemine ait siparişlerin müşteriler için hazır olma tarihi - pickup_instructions_placeholder: "Teslimat Talimatları" + pickup_instructions_placeholder: "TESLİMAT DETAYLARI" pickup_instructions_tip: Bu talimatlar, siparişi tamamladıktan sonra müşterilere iletilir pickup_time_placeholder: "Teslimat (örn. Tarih / Saat)" - receival_instructions_placeholder: "Teslim Alma talimatları" + receival_instructions_placeholder: "TESLİM ALMA TALİMATLARI" add_fee: 'Ücret ekle' remove: 'Kaldır' selected: 'seçildi' add_exchange_form: - add_supplier: 'Tedarikçi ekle' + add_supplier: 'TEDARİKÇİ EKLE' add_distributor: 'Dağıtımcı ekle' advanced_settings: title: Gelişmiş Ayarlar @@ -927,17 +929,17 @@ tr: outgoing: Giden distributor: Dağıtımcı products: Ürünler - tags: Etiketler + tags: ETİKETLER add_a_tag: Etiket ekle delivery_details: Teslimat/Gönderim Bilgileri index: schedule: Takvim - schedules: Takvimler + schedules: TAKVİM new_schedule: Yeni Takvim name_and_timing_form: name: Ad - orders_open: 'SİPARİŞ AÇILIŞ:' - coordinator: Koordinatör + orders_open: Siparişler Açık + coordinator: KOORDİNATÖR orders_close: SİPARİŞ KAPANIŞ row: suppliers: tedarikçileri @@ -1013,7 +1015,7 @@ tr: orders_and_fulfillment: name: Siparişler ve Gerçekleşme Raporları customers: - name: Müşteriler + name: MÜŞTERİLER products_and_inventory: name: Ürünler ve Stok users_and_enterprises: @@ -1045,9 +1047,9 @@ tr: query_placeholder: "E-posta ile ara ..." setup_explanation: just_a_few_more_steps: 'Başlamadan önce birkaç adım kaldı:' - enable_subscriptions: "Mağazalarınızdan en az biri için üyelikleri etkinleştirin" - enable_subscriptions_step_1_html: 1. %{enterprises_link} sayfasına gidin, tezgahınızı bulun ve ‘Yönet’i tıklayın - enable_subscriptions_step_2: 2. 'Mağaza Tercihleri' altındaki Üyelikler seçeneğini etkinleştirin + enable_subscriptions: "Dükkanınızdan en az biri için üyelikleri etkinleştirin" + enable_subscriptions_step_1_html: 1. %{enterprises_link} sayfasına gidin, dükkanınızı bulun ve ‘Yönet’i tıklayın + enable_subscriptions_step_2: 2. 'Dükkan Tercihleri' altındaki Üyelikler seçeneğini etkinleştirin set_up_shipping_and_payment_methods_html: '%{shipping_link} ve %{payment_link} yöntemlerini ayarlayın' set_up_shipping_and_payment_methods_note_html: Yalnızca Nakit ve Online ödeme yöntemlerinin
'ü üyeliklerle kullanılabilir ensure_at_least_one_customer_html: En az bir %{customer_link} bulunduğundan emin olun @@ -1139,20 +1141,25 @@ tr: failed: "Ödeme başarısız oldu. Siparişinizi işleme koyabilmemiz için lütfen bizimle iletişime geçin." shops: hubs: - show_closed_shops: "Kapalı mağazaları göster" - hide_closed_shops: "Kapalı mağazaları gizle" + show_closed_shops: "Kapalı dükkanları göster" + hide_closed_shops: "Kapalı dükkanları gizle" show_on_map: "Tümünü haritada göster" shared: menu: cart: cart: "Sepet" + cart_sidebar: + checkout: "Ödeme Yap" + edit_cart: "Sepeti düzenle" + items_in_cart_singular: "Sepetnizide %{num} ürün var" + items_in_cart_plural: "Sepetinizde %{num} ürün var" + close: "KAPANIŞ" + cart_empty: "Sepetiniz boş" + take_me_shopping: "Alışveriş yap!" signed_in: profile: "Profil" mobile_menu: cart: "Sepet" - joyride: - checkout: "Alışverişi Tamamla" - already_ordered_products: "Bu sipariş dönemi içinde zaten sipariş verildi" register_call: selling_on_ofn: "Açık Gıda Ağı üzerinden satış yapmak ister misiniz?" register: "Buradan kaydolun" @@ -1163,7 +1170,7 @@ tr: footer_contact_email: "Bize e-posta gönderin" footer_nav_headline: "Gezin" footer_join_headline: "Bize katılın" - footer_join_body: "Açık Gıda Ağı üzerinden ürünlerinizi sergileyin, çevirimiçi mağazanızı oluşturun veya grubunuzu listeleyin." + footer_join_body: "Açık Gıda Ağı üzerinden ürünlerinizi sergileyin, dükkanınızı oluşturun veya topluluğunuzu, grubunuzu listeleyin." footer_join_cta: "Daha fazlasını anlat!" footer_legal_call: "Okuyun" footer_legal_tos: "Şartlar ve koşullar" @@ -1179,7 +1186,7 @@ tr: login: "Oturum Aç / Kaydol" signup: "Kaydol" contact: "İLETİŞİM" - require_customer_login: "Yalnızca onaylı müşteriler buradan alışveriş yapabilir." + require_customer_login: "Yalnızca onaylı müşteriler (üyeler) buradan alışveriş yapabilir." require_login_html: "Zaten onaylanmış bir müşteri iseniz, devam etmek için %{login} veya %{signup}" require_login_2_html: "Buradan alışveriş mi yapmak istiyorsunuz? Lütfen katılmak için sorun: %{contact}%{enterprise} " require_customer_html: "Buradan alışveriş yapmaya başlamak istiyorsanız, katılmak için lütfen sorun: %{contact} %{enterprise} " @@ -1218,7 +1225,7 @@ tr: menu_2_url: "/map" menu_3_title: "ÜRETİCİLER" menu_3_url: "/producers" - menu_4_title: "GRUPLAR" + menu_4_title: "AĞLAR" menu_4_url: "/groups" menu_5_title: "HAKKIMIZDA" menu_5_url: "https://hakkimizda.acikgida.com" @@ -1242,7 +1249,7 @@ tr: footer_links_md: "Bağlantılar" footer_about_url: "HAKKIMIZDA URL" user_guide_link: "Kullanıcı Kılavuzu Linki" - name: İSİM + name: Ad first_name: Ad last_name: Soyadı email: E-posta @@ -1269,12 +1276,12 @@ tr: unconfirmed: doğrulanmamış days: günler authorization_failure: "Yetki Hatası" - label_shop: "Pazar" + label_shop: "Dükkan" label_shops: "PAZAR YERİ" label_map: "HARİTA" label_producer: "ÜRETİCİ" label_producers: "ÜRETİCİLER" - label_groups: "Gruplar" + label_groups: "AĞLAR" label_about: "HAKKIMIZDA" label_connect: "Bağlan" label_learn: "Öğren" @@ -1354,12 +1361,12 @@ tr: cookies_policy_link_desc: "Daha fazla bilgi edinmek istiyorsanız," cookies_policy_link: "çerezler politikası" cookies_accept_button: "Çerezleri kabul et" - home_shop: ŞİMDİ ALIŞVERİŞ YAPIN + home_shop: 'ALIŞVERİŞE BAŞLAYIN ' brandstory_headline: "Bağımsız, adil ve temiz gıda ..." brandstory_intro: "Bazen sistemi düzeltmenin en iyi yolu yeni bir sistem yaratmaktır…" - brandstory_part1: "Açık Gıda Ağı, yerel, bağımsız, adil ve temiz bir gıda sistemi oluşturmak için tasarlanan bir sosyal girişim projesidir. Üretici ve türeticilerin bir araya gelerek aracısız bir gıda düzeni ile her açıdan daha sağlıklı bir toplum yaratmaları için çözümler sunar. Toplum yararına çalışır, iletişim, dürüstlük ve dayanışmayı destekler." + brandstory_part1: "Açık Gıda Ağı, adil, temiz bir gıda sistemi oluşturmak için tasarlanan bir sosyal girişim projesidir. Üretici ve türeticilerin bir araya gelerek aracısız bir gıda düzeni ile her açıdan daha sağlıklı bir toplum yaratmaları için çözümler sunar. Toplum yararına çalışır, iletişim, dürüstlük ve dayanışmayı destekler." brandstory_part2: "AGA, üreticilere ve alıcılara aracısız ticaret faydaları sağlar ve toplumsal iletişimi ve güveni cesaretlendirir. Gıda yetiştiriciliği ve satışının kendine özgü ihtiyaçlarını karşılamak için geliştirilmiştir. Temiz gıdaya ulaşım sürecini kolaylaştırır." - brandstory_part3: "Platform üzerinden yalnızca yerel ve bağımsız gıda üreticileri ve dağıtımcıları satış yapabilir. Siz de ürünlerinizi AGA üzerinden oluşturduğunuz tezgah ile doğrudan alıcılara ulaştırabilir, dilerseniz bölgenizdeki diğer üreticiler ile bir araya gelerek kendi ortak 'Üretici Pazarı' veya 'Türetici Pazarı' nızı oluşturabilirsiniz. Bu şekilde çeşitliliği ve bereketi artırır, dayanışmanın getirdiği faydalardan da yararlanabilirsiniz." + brandstory_part3: "Platform üzerinden yalnızca yerel ve bağımsız gıda üreticileri ve dağıtımcıları satış yapabilir. Siz de ürünlerinizi AGA üzerinden oluşturduğunuz dükan ile doğrudan alıcılara ulaştırabilir, dilerseniz bölgenizdeki diğer üreticiler ile bir araya gelerek kendi ortak 'Üretici Pazarı' veya 'Türetici Pazarı' nızı oluşturabilirsiniz. Bu şekilde çeşitliliği ve bereketi artırır, dayanışmanın getirdiği faydalardan da yararlanabilirsiniz." brandstory_part4: "Her yerde çalışıyor. Her şeyi değiştiriyor." brandstory_part5_strong: "Biz buna Açık Gıda Ağı diyoruz." brandstory_part6: "Hepimiz gıdamızı seviyoruz. Artık gıda sistemimizi de sevmeye başlayabiliriz." @@ -1369,7 +1376,7 @@ tr: connect_cta: "Keşfedin" system_headline: "Nasıl çalışıyor ?" system_step1: "1. Ara" - system_step1_text: "Yerel, adil, temiz ve mevsimsel gıda için, bağımsız ve cesur üreticilerimizin mağazalarından alışveriş yapın. Konuma, ürün kategorisine, üretici özelliklerine veya teslimat tercihlerine göre arama yapabilirsiniz. " + system_step1_text: "Yerel, adil, temiz ve mevsimsel gıda için, bağımsız ve cesur üreticilerimizin ve dağıtımcılarımızın dükkanlarından alışveriş yapın. Konuma, ürün kategorisine, üretici özelliklerine veya teslimat tercihlerine göre arama yapabilirsiniz. " system_step2: "2. Alışveriş Yap" system_step2_text: "Gıdanızı yerel üretici tezgahlarından, üretici ve türetici pazarlarından temin edin. Yaşanabilir bir dünya için alışkanlıklarınızı şimdi değiştirin. Gıdanızın ve onu size getiren insanların hikayelerini öğrenin!" system_step3: "3. Teslimat " @@ -1378,7 +1385,7 @@ tr: cta_label: "Hazırım" stats_headline: "Yeni bir gıda sistemi yaratıyoruz." stats_producers: "ÜRETİCİ" - stats_shops: "MAĞAZA" + stats_shops: "DÜKKAN" stats_shoppers: "TÜRETİCİ" stats_orders: "ALIŞVERİŞ" checkout_title: Ödeme Yap @@ -1416,9 +1423,9 @@ tr: order_special_instructions: "Notların:" order_pickup_time: Teslimat için hazır order_pickup_instructions: Teslim Alma Talimatları - order_produce: Üretim + order_produce: Ürün order_total_price: Toplam - order_includes_tax: (vergi dahil) + order_includes_tax: (vergi) order_payment_paypal_successful: PayPal ile ödemeniz başarıyla işlendi. order_hub_info: Pazar Bilgisi order_back_to_store: Siteye Geri Dön @@ -1456,7 +1463,7 @@ tr: email_order_summary_price: "Fiyat" email_order_summary_subtotal: "Ara toplam:" email_order_summary_total: "Toplam:" - email_order_summary_includes_tax: "(vergi dahil):" + email_order_summary_includes_tax: "(vergi):" email_payment_paid: ÖDENDİ email_payment_not_paid: ÖDENMEDİ email_payment_summary: Ödeme özeti @@ -1537,7 +1544,7 @@ tr: hubs_matches: "Bunu mu arıyordunuz?" hubs_intro: Bulunduğunuz bölgeden alışveriş yapın hubs_distance: En yakın - hubs_distance_filter: "Bana %{location} yakınındaki tezgahları göster" + hubs_distance_filter: "Bana %{location} yakınındaki dükkanları göster" shop_changeable_orders_alert_html: one: %{shop} / %{order} ile siparişiniz incelemeye açık. %{oc_close} tarihine kadar değişiklik yapabilirsiniz. other: Şu anda düzenlemeye açık %{shop} ile %{count} siparişiniz var. %{oc_close} tarihine kadar değişiklik yapabilirsiniz. @@ -1568,15 +1575,15 @@ tr: products_no_results_html: "Üzgünüz, sonuç bulunamadı %{query}" products_clear_search: "Aramayı temizle" search_no_results_html: "Üzgünüz, %{query} sonuç bulunamadı. Başka bir arama yapmak ister misiniz?" - components_profiles_popover: "Açık Gıda Ağında bir mağazası olmayan ancak başka bir sitede kendi satış siteleri olan hesaplar" + components_profiles_popover: "Açık Gıda Ağında bir dükkanı olmayan ancak başka bir sitede kendi satış siteleri olan hesaplar" components_profiles_show: "Profilleri göster" components_filters_nofilters: "Filtresiz" - components_filters_clearfilters: "Tüm filtreleri temizle" + components_filters_clearfilters: "TÜM FİLTRELERİ TEMİZLE" groups_title: Gruplar groups_headline: Gruplar - groups_text: "Her üretici özeldir ve her işletmenin ortaya koyabileceği farklı bir değer vardır. Üyelerimiz, ürünlerini, emeklerini ve gıdanın ortak değerleri paylaşan üretici, türetici ve dağıtımcı kolektifleridir. Bu bileşenler, adil ve temiz gıdaya ulaşım yollarını kolaylaştırır ve bozulmuş gıda sistemini hep beraber düzeltmemize yardımcı olur." + groups_text: "Her üretici özeldir ve her işletmenin ortaya koyabileceği farklı bir değer vardır. Üyelerimiz, ürünlerini, emeklerini ve gıdanın ortak değerlerini paylaşan üretici, türetici ve dağıtımcı kolektifleridir. Bu bileşenler, adil ve temiz gıdaya ulaşım yollarını kolaylaştırır ve bozulmuş gıda sistemini düzeltmemize öncülük ederler." groups_search: "İsim veya anahtar kelime ile ara" - groups_no_groups: "Grup bulunamadı" + groups_no_groups: "Ağ bulunamadı" groups_about: "Hakkımızda" groups_producers: "ÜRETİCİLERİMİZ" groups_hubs: "Pazarlarımız" @@ -1587,7 +1594,7 @@ tr: groups_contact_website: İnternet sitemizi ziyaret edin groups_contact_facebook: Bizi Facebook'tan takip edin groups_signup_title: Grup olarak kaydolun - groups_signup_headline: Gruplara kaydolma + groups_signup_headline: Ağ kayıt groups_signup_intro: "İşbirliği ve dayanışma temelli satış-pazarlama yöntemleri için harika bir platformuz. Yeni müşterilere ulaşmak ve doğru bir gıda sistemi oluşturmak için.. Basit, şeffaf, dürüst bir sosyal girişim." groups_signup_email: Bize e-posta gönderin groups_signup_motivation1: Gıda sistemlerini adil bir şekilde dönüştürüyoruz. @@ -1608,7 +1615,7 @@ tr: producers_about: Hakkımızda producers_buy: Alışveriş ürünleri producers_contact: İLETİŞİM - producers_contact_phone: Ara + producers_contact_phone: 'Tel:' producers_contact_social: Takip et producers_buy_at_html: "%{enterprise} ürünleri için buradan alışveriş yapın:" producers_filter: Filtrele @@ -1639,15 +1646,15 @@ tr: sell_motivation: "Temiz gıdanızı sergileyin." sell_producers: "Üreticiler" sell_hubs: "Pazarlar" - sell_groups: "Gruplar" + sell_groups: "AĞLAR" sell_producers_detail: "AGA üzerinden işletmeniz adına bir profil oluşturun. Dilediğiniz zaman profilinizi bir tezgaha yükseltebilir ve ürünlerinizi müşterilerinize doğrudan satabilirsiniz." sell_hubs_detail: "AGA üzerinden gıda işletmeniz veya topluluğunuz için bir profil oluşturun. İstediğiniz zaman profilinizi çok üreticili bir pazara yükseltebilirsiniz." sell_groups_detail: "Bölgenizdeki veya ağınızdaki işletmelerin (üreticilerin, pazarların veya diğer grupların) detaylı rehber listesini oluşturun." sell_user_guide: "Kullanım kılavuzumuzdan daha fazla bilgi edinin." sell_listing_price: "AGA üzerinde görünür olmak ücretsizdir. Platform üzerinden satış yapan işletmeler için işlem başına uygulanan ücretlendirme KDV dahil %5'tir. Kar amacı gütmeyen topluluklar, kooperatifler ve ekoloji/gıda temelli dernekler ile çalışan üreticiler karşılıklı dayanışma ve özel fiyatlandırma için iletişime geçebilirler. Fiyatlandırma hakkında bilgi almak için, üst menüdeki Hakkımızda bağlantısını kullanarak Fiyatlandırma bölümünü ziyaret edin." - sell_embed: "Kendi e-ticaret siteniz olsa bile Açık Gıda Ağı size hikayenizi anlatmanız ve ürünlerinizi satmanız için yeni ve farklı bir seçenek sunuyor. Siz de bu ailenin bir parçası olun, hep beraber büyüyelim!" + sell_embed: "Kendi e-ticaret siteniz veya farklı satış kanallarınız olsa bile Açık Gıda Ağı size hikayenizi anlatmanız ve ürünlerinizi satmanız için yeni ve farklı bir seçenek sunuyor. Siz de bu ailenin bir parçası olun, hep beraber büyüyelim!" sell_ask_services: "Detaylar için bizimle iletişime geçin." - shops_title: Mağazalar + shops_title: Dükkanlar shops_headline: Alışveriş biçim değiştiriyor shops_text: Gıda dönemsel yetiştirilir, dönemsel hasat edilir ve dönemsel sipariş edilir. Aradığınız mağazanın sipariş dönemi kapalı ise kısa süre sonra tekrar kontrol edin. shops_signup_title: Pazar olarak kaydolun @@ -1694,11 +1701,11 @@ tr: products_cart_distributor_choice: "Siparişinizin dağıtımcısı:" products_cart_distributor_change: "Bu ürünü sepetinize eklerseniz, bu sipariş için dağıtımcınız %{name} olarak değiştirilecektir." products_cart_distributor_is: "Bu sipariş için dağıtımcınız %{name}." - products_distributor_error: "Başka bir dağıtımcıdan alışveriş yapmadan önce lütfen siparişinizi %{link} adresinden tamamlayın." + products_distributor_error: "Başka bir dağıtımcıdan alışveriş yapmadan önce lütfen %{link} adresinden siparişinizi tamamlayın." products_oc: "Siparişiniz için sipariş dönemi:" products_oc_change: "Bu ürünü sepetinize eklerseniz, bu sipariş için sipariş döneminiz %{name} olarak değiştirilecektir." products_oc_is: "Bu sipariş için sipariş döneminiz %{name}." - products_oc_error: "Farklı bir sipariş döneminden alışveriş yapmadan önce lütfen siparişinizi %{link} ' tamamlayın." + products_oc_error: "Farklı bir sipariş döneminden alışveriş yapmadan önce lütfen %{link} siparişinizi tamamlayın." products_oc_current: "mevcut sipariş döneminiz" products_max_quantity: Maks miktar products_distributor: Dağıtımcı @@ -1706,7 +1713,7 @@ tr: password: Parola remember_me: Beni Hatırla are_you_sure: "Emin misiniz?" - orders_open: Siparişler açık + orders_open: "Siparişler Açık" closing: "Kapanış" going_back_to_home_page: "Ana sayfaya yönlendiriliyorsunuz.." creating: oluşturuluyor @@ -1812,7 +1819,7 @@ tr: producer_field_error: "Lütfen birini seçin. Üretici misiniz?" yes_producer_help: "Siz de birşeyler yetiştiriyor, büyütüyor, sağıyor, hasat ediyor, pişiriyor kurutuyor veya hazırlıyorsanız, üreticisiniz demektir." no_producer_help: "Üretici değilseniz muhtemelen gıdaya ulaşım sağlayan, dağıtım veya satış yapan bir grup ya da işletmesiniz. Bir dükkan, kooperatif, gıda topluluğu, restaurant veya toptancı bile olabilirsiniz. " - create_profile: "Profil oluştur" + create_profile: "Profil Oluştur" about: title: "HAKKIMIZDA" headline: "Güzel!" @@ -1879,7 +1886,7 @@ tr: back: "Geri" continue: "Devam et" action_or: "VEYA" - enterprise_limit: İşletme Limiti + enterprise_limit: İŞLETME LİMİTİ shipping_method_destroy_error: "Bu teslimat yöntemi silinemez çünkü tamamlanmış siparişler mevcut\n: %{number}." fees: "Ücretler" item_cost: "Ürün maliyeti" @@ -1888,7 +1895,7 @@ tr: shop_variant_quantity_max: "maks" follow: "Takip et" shop_for_products_html: "%{enterprise} ürünleri için şuradan alışveriş yapın:" - change_shop: "Mağazayı şuna değiştir:" + change_shop: "Dükkanı şuna değiştir:" shop_at: "Şimdi alışveriş yapın:" price_breakdown: "Fiyat dökümü" admin_fee: "Yönetim Ücreti" @@ -1911,7 +1918,7 @@ tr: ok: tamam not_visible: görünmez you_have_no_orders_yet: "Henüz siparişiniz yok" - show_only_complete_orders: "Yalnızca tamamlanan siparişleri göster" + show_only_complete_orders: "YALNIZCA TAMAMLANAN SİPARİŞLERİ GÖSTER" successfully_created: '%{resource} BAŞARIYLA OLUŞTURULDU!' successfully_removed: '%{resource} BAŞARIYLA KALDIRILDI!' successfully_updated: '%{resource} BAŞARIYLA GÜNCELLENDİ!' @@ -1952,7 +1959,7 @@ tr: product_name: "Ürün Adı" product_description: "Ürün Açıklaması" units: "Ölçü Birimi" - coordinator: "Koordinatör" + coordinator: "KOORDİNATÖR" distributor: "Dağıtımcı" enterprise_fees: "İşletme Ücretleri" process_my_order: "Siparişimi İşle" @@ -1968,20 +1975,20 @@ tr: flat_rate_per_order: "Sabit Ücret (sipariş başına) " flexible_rate: "Esnek Ücret" price_sack: "Değişken Ücret" - new_order_cycles: "Yeni Sipariş Dönemleri" - new_order_cycle: "Yeni Sipariş Dönemi" + new_order_cycles: "YENİ SİPARİŞ DÖNEMİ" + new_order_cycle: "YENİ SİPARİŞ DÖNEMİ" select_a_coordinator_for_your_order_cycle: "Sipariş döneminiz için bir koordinatör seçin" notify_producers: 'Üreticilere bildir' edit_order_cycle: "Sipariş Dönemini Düzenle" roles: "Roller" update: "Güncelle" delete: Sil - add_producer_property: "Üretici özelliği ekle" + add_producer_property: "ÜRETİCİ ÖZELLİĞİ EKLE" in_progress: "Devam ediyor" started_at: "Başlama tarihi" queued: "sıraya eklendi" scheduled_for: "Planlanan tarih" - customers: "Müşteriler" + customers: "MÜŞTERİLER" please_select_hub: "Lütfen bir pazar seçin" loading_customers: "Müşteriler Yükleniyor" no_customers_found: "Müşteri bulunamadı" @@ -1999,7 +2006,7 @@ tr: spree_admin_overview_enterprises_footer: "İŞLETMELERİMİ YÖNET" spree_admin_enterprises_hubs_name: "ad" spree_admin_enterprises_create_new: "YENİ OLUŞTUR" - spree_admin_enterprises_shipping_methods: "Teslimat Yöntemleri" + spree_admin_enterprises_shipping_methods: "TESLİMAT YÖNTEMLERİ" spree_admin_enterprises_fees: "İşletme Ücretleri" spree_admin_enterprises_none_create_a_new_enterprise: "YENİ BİR İŞLETME OLUŞTUR" spree_admin_enterprises_none_text: "Henüz hiç işletmeniz yok" @@ -2018,8 +2025,8 @@ tr: spree_admin_product_category: Ürün Kategorisi spree_admin_variant_unit_name: Varyant Birimi Adı unit_name: "Birim adı" - change_package: "Hesap Türünü Değiştir" - spree_admin_single_enterprise_hint: "İpucu: İnsanların sizi bulmasına izin vermek için, alttaki görünürlük kısmını açın." + change_package: "HESAP TÜRÜNÜ DEĞİŞTİR" + spree_admin_single_enterprise_hint: "İpucu: İnsanların sizi bulmasına izin vermek için 'Görünür' olmayı ayarlamayı unutmayın:" spree_admin_eg_pickup_from_school: "Örn. 'Teslimat noktası: Moda İlkokulu Bahçesi'" spree_admin_eg_collect_your_order: "Örn. Lütfen siparişinizi Moda Cad. No:17 Temiz Dükkan'dan teslim alınız." spree_classification_primary_taxon_error: "%{taxon} cinsi, %{product}ürününün birincil cinsidir ve silinemez" @@ -2065,6 +2072,7 @@ tr: hub_sidebar_at_least: "En az bir pazar seçilmelidir" hub_sidebar_blue: "mavi" hub_sidebar_red: "kırmızı" + order_cycles_closed_for_hub: "Seçtiğiniz pazar geçici olarak siparişlere kapalı durumdadır. Lütfen daha sonra tekrar deneyiniz." report_customers_distributor: "Dağıtımcı" report_customers_supplier: "Tedarikçi" report_customers_cycle: "Sipariş Dönemi" @@ -2073,7 +2081,7 @@ tr: report_producers: "ÜRECİTİLER:" report_type: "Rapor türü:" report_hubs: "Pazarlar:" - report_payment: "Ödeme yöntemleri:" + report_payment: "ÖDEME YÖNTEMLERİ:" report_distributor: "Dağıtımcı:" report_payment_by: 'Türüne Göre Ödemeler' report_itemised_payment: 'Ayrıntılı Ödeme Toplamları' @@ -2104,7 +2112,7 @@ tr: report_header_paid: Ödeme? report_header_delivery: Teslimat? report_header_shipping: Teslimat - report_header_shipping_method: Teslimat Yöntemi + report_header_shipping_method: TESLİMAT YÖNTEMİ report_header_shipping_instructions: Teslimat bilgileri report_header_ship_street: Teslimat Adresi report_header_ship_street_2: Teslimat Adresi 2 @@ -2121,9 +2129,9 @@ tr: report_header_incoming_transport: Gelen Nakliye report_header_special_instructions: Özel Talimatlar report_header_order_number: Sipariş numarası - report_header_date: tarih + report_header_date: Tarih report_header_confirmation_date: Onay tarihi - report_header_tags: Etiketler + report_header_tags: ETİKETLER report_header_items: Kalemler report_header_items_total: "Toplam ürün %{currency_symbol}" report_header_taxable_items_total: "Vergiye tabi kalemler toplamı (%{currency_symbol})" @@ -2145,7 +2153,7 @@ tr: report_header_total_available: Toplam mevcut report_header_unallocated: Paylaştırılmamış report_header_max_quantity_excess: Maksimum Miktar Fazlası - report_header_taxons: sınıflar + report_header_taxons: KATEGORİ report_header_supplier: Tedarikçi report_header_producer: Üretici report_header_producer_suburb: Yapımcı İlçesi @@ -2155,7 +2163,7 @@ tr: report_header_shipping_cost: Teslimat Maliyeti report_header_curr_cost_per_unit: Birim Maliyet report_header_total_shipping_cost: Toplam Teslimat Maliyeti - report_header_payment_method: Ödeme Yöntemi + report_header_payment_method: ÖDEME YÖNTEMİ report_header_sells: NE SATIYOR report_header_visible: Görünür report_header_price: Fiyat @@ -2223,7 +2231,7 @@ tr: due_date: "Vade tarihi:" account_code: "Hesap kodu:" equals: "Eşittir" - contains: "içerik" + contains: "İÇERİYOR" discount: "İndirim" filter_products: "Ürünleri Filtrele" delete_product_variant: "Son varyant silinemez!" @@ -2242,22 +2250,22 @@ tr: about: "HAKKIMIZDA" images: "Görseller" web: "Web" - primary_details: "Temel Bilgiler" + primary_details: "TEMEL BİLGİLER" adrdress: "Adres" contact: "İLETİŞİM" social: "Sosyal" business_details: "İşletme Detayları" - properties: "Özellikler" + properties: "ÖZELLİKLER" shipping: "Teslimat" - shipping_methods: "Teslimat Yöntemleri" - payment_methods: "Ödeme yöntemleri" + shipping_methods: "TESLİMAT YÖNTEMLERİ" + payment_methods: "ÖDEME YÖNTEMLERİ" payment_method_fee: "İşlem ücreti" payment_processing_failed: "Ödeme işlenemedi, lütfen girdiğiniz bilgileri kontrol edin" payment_method_not_supported: "Bu ödeme yöntemi desteklenmiyor. Lütfen başka birini seçiniz." payment_updated: "Ödeme Güncellendi" inventory_settings: "Stok Ayarları" - tag_rules: "Etiket Kuralları" - shop_preferences: "Mağaza Tercihleri" + tag_rules: "ETİKET KURALLARI" + shop_preferences: "Dükkan Tercihleri" enterprise_fee_whole_order: Tüm sipariş enterprise_fee_by: "%{role}%{enterprise_name} tarafından%{type} ücreti" validation_msg_relationship_already_established: "^ Bu ilişki zaten kurulmuş." @@ -2301,6 +2309,7 @@ tr: order_cycles_email_to_producers_notice: 'Üreticilere gönderilecek e-postalar gönderilmek üzere sıraya alınmıştır.' order_cycles_no_permission_to_coordinate_error: "Hiçbir işletmenizin sipariş dönemini koordine etme izni yok" order_cycles_no_permission_to_create_error: "Bu işletme tarafından koordine edilen bir sipariş dönemi oluşturma izniniz yok" + order_cycle_closed: "Seçmiş olduğunuz sipariş dönemi az önce kapandı. Lütfen daha sonra tekrar deneyin :(" back_to_orders_list: "Sipariş listesine geri dön" no_orders_found: "SİPARİŞ BULUNAMADI" order_information: "Sipariş Bilgisi" @@ -2323,24 +2332,24 @@ tr: unavailable: Kullanım dışı profile: Profil hub: pazar - shop: MAĞAZA + shop: Dükkan choose: Seç resolve_errors: Lütfen aşağıdaki hataları düzeltin more_items: "+ %{count} Daha " default_card_updated: Varsayılan Kart Güncellendi cart: add_to_cart_failed: > - Bu ürünü sepete eklerken bir hata oluştu. Ürün stokları bitmiş veya mağaza + Bu ürünü sepete eklerken bir hata oluştu. Ürün stokları bitmiş veya dükkan kapanıyor olabilir. admin: enterprise_limit_reached: "Hesap başına standart işletme sınırına ulaştınız. Artırmanız gerekiyorsa %{contact_email}'a yazın." modals: got_it: Anladım - close: "KAPANIŞ" + close: "Kapat" invite: "Davet et" invite_title: "Kayıtlı olmayan bir kullanıcıyı davet et" tag_rule_help: - title: Etiket Kuralları + title: ETİKET KURALLARI overview: genel bakış overview_text: > Etiket kuralları müşterileri özelinde hangi seçeneklerin görünür veya @@ -2351,7 +2360,7 @@ tr: Varsayılan kurallar istediğiniz ürünleri gizlemenizi ve varsayılanda görünür olmamalarını sağlar. Bu tercihlerinizi, müşteri bazlı etiketler için kurallar belirleyerek değiştirebilirsiniz. - customer_tagged_rules: "'Müşteriler Etiketleri' Kuralları" + customer_tagged_rules: "'Müşteri Etiketleri' Kuralları" customer_tagged_rules_text: > Spesifik bir müşteri etiketi için kural oluşturarak, varsayılan görünürlük kuralını da güncellemiş olursunuz. (yalnızca ilgili müşteriler için) @@ -2368,7 +2377,7 @@ tr: hub_profile_text2: > Bir profil sahibi olmak, Açık Gıda Ağı üzerinden bağlantı kurmak her zaman ücretsiz olacaktır. - hub_shop: Pazar Mağazası + hub_shop: Pazar Dükkanı hub_shop_text1: > İşletmeniz yerel gıda sisteminizin bel kemiğidir. Ulaşabilir olduğunuz bölgedeki üreticilerin ürünlerini bir araya getirip Açık Gıda Ağı üzerinden @@ -2392,17 +2401,17 @@ tr: ve yeni işbirlikleri aramak için güzel bir yerdesiniz. profile_only_text2: > Eğer yalnızca çiftçiliğe odaklanmak ve ürünlerin satış kısmını başkalarına - bırakmak istiyorsanız, Açık Gıda Ağı üzerinde bir tezgah sahibi olmanıza + bırakmak istiyorsanız, Açık Gıda Ağı üzerinde bir dükkan sahibi olmanıza gerek yoktur. profile_only_text3: > Ürünlerinizi Açık Gıda Ağı'na ekleyin. Böylece üretici & türetici pazarları gibi diğer hesaplar ürünlerinizi kendi listelerine ekleyip satışa sunabilsinler. - producer_shop: ÜRETİCİ TEZGAHI + producer_shop: ÜRETİCİ DÜKKANI producer_shop_text1: > - Açık Gıda Ağı üzerindeki mağazanız aracılığıyla ürünlerinizi alıcılara + Açık Gıda Ağı üzerindeki dükkanınız aracılığıyla ürünlerinizi alıcılara direk olarak satın. producer_shop_text2: > - Üretici Tezgahı yalnızca kendi ürünlerinizi satmanız içindir. Kendi + Üretici Dükkanı yalnızca kendi ürünlerinizi satmanız içindir. Kendi üretiminiz haricindeki ürünlerden de satmak isterseniz lütfen 'Üretici Pazarı' nı seçin. producer_hub: Üretici Pazarı @@ -2420,8 +2429,8 @@ tr: en iyi şekilde yönetmeniz için tüm imkanları ve araçları sağlamaya hazırız. get_listing: Görünür Olun always_free: HER ZAMAN ÜCRETSİZ - sell_produce_others: Başkalarının ürünlerini sat - sell_own_produce: Kendi ürününü sat + sell_produce_others: BAŞKALARININ ÜRÜNLERİNİ SAT + sell_own_produce: KENDİ ÜRÜNÜNÜ SAT sell_both: Kendi ürünlerini ve başkalarının ürünlerini sat enterprise_producer: producer: Üretici @@ -2460,7 +2469,7 @@ tr: tag_rules: shipping_method_tagged_top: "Teslimat Yöntemi etiketi" shipping_method_tagged_bottom: "ise durumu:" - payment_method_tagged_top: "Ödeme Yönteml etiketi" + payment_method_tagged_top: "Ödeme Yöntemi etiketi" payment_method_tagged_bottom: "ise durumu:" order_cycle_tagged_top: "Sipariş Dönemi etiketi" order_cycle_tagged_bottom: "ise durumu:" @@ -2541,7 +2550,7 @@ tr: subscriptions: error_saving: "Üyelik kaydedilirken hata oluştu" new: - please_select_a_shop: "Lütfen bir mağaza seçin" + please_select_a_shop: "Lütfen bir dükkan seçin" insufficient_stock: "Yetersiz stok, sadece %{on_hand} kaldı" out_of_stock: reduced_stock_available: Azaltılmış stok mevcut @@ -2572,20 +2581,20 @@ tr: changing_on_hand_stock: Eldeki stok seviyeleri değiştiriliyor ... stock_reset: Stoklar varsayılana ayarlanır tag_rules: - show_hide_variants: 'MAĞAZAMDA VARYANTLARI GÖSTER VEYA GİZLE' + show_hide_variants: 'Dükkanımda varyantları göster veya gizle' show_hide_shipping: 'Ödeme sırasında teslimat yöntemlerini göster veya gizle' show_hide_payment: 'Ödeme sırasında ödeme yöntemlerini göster veya gizle' - show_hide_order_cycles: 'MAĞAZAMDA SİPARİŞ DÖNEMLERİNİ GÖSTER VEYA GİZLE' + show_hide_order_cycles: 'Dükkanımda sipariş dönemlerini göster veya gizle' visible: GÖRÜNÜR - not_visible: GÖRÜNÜR DEĞİL + not_visible: GİZLİ services: unsaved_changes_message: Kaydedilmemiş değişiklikler var, şimdi kaydet veya yoksay? save: KAYDET ignore: YOKSAY - add_to_order_cycle: "sipariş dönemine ekle" - manage_products: "ürünleri yönet" - edit_profile: "profili Düzenle" - add_products_to_inventory: "Stoka ürün ekle" + add_to_order_cycle: "SİPARİŞ DÖNEMİNE EKLE" + manage_products: "ÜRÜNLERİ YÖNET" + edit_profile: "PROFİLİ DÜZENLE" + add_products_to_inventory: "STOKA ÜRÜN EKLE" resources: could_not_delete_customer: 'Müşteri silinemedi' product_import: @@ -2601,7 +2610,7 @@ tr: producer: "Üretici" non_producer: "Üretici Değil" customers: - select_shop: 'Lütfen önce bir mağaza seçin' + select_shop: 'Lütfen önce bir dükkan seçin' could_not_create: Afedersiniz! Oluşturulamadı subscriptions: closes: kapanır @@ -2713,7 +2722,7 @@ tr: fee_placements: supplier: "Gelen" distributor: "Giden" - coordinator: "Koordinatör" + coordinator: "KOORDİNATÖR" tax_category_name: shipping_instance_rate: "Platform Hızı" formats: @@ -2731,7 +2740,7 @@ tr: header: fee_type: "Ücret Türü" enterprise_name: "İşletme Sahibi" - fee_name: "ÜCRET ADI" + fee_name: "Ücret Adı" customer_name: "Müşteri" fee_placement: "Ücret Yerleşimi" fee_calculated_on_transfer_through_name: "Aktarma Ücreti Hesaplama" @@ -2746,7 +2755,7 @@ tr: payments: "Ödemeler" return_authorizations: "İade Yetkileri" payment: "Ödeme" - payment_method: "Ödeme yöntemi" + payment_method: "ÖDEME YÖNTEMİ" shipment: "Teslimat" shipment_inc_vat: "KDV dahil teslimat" shipping_tax_rate: "Kargo Vergi Oranı" @@ -2760,7 +2769,7 @@ tr: logout: "Çıkış Yap" date_range: "Tarih aralığı" status: "durum" - new: "Yeni" + new: "YENİ" start: "Başlangıç" end: "Son" stop: "Bitiş" @@ -2768,6 +2777,7 @@ tr: previous: "Önceki" last: "Son" spree: + more: "Daha Fazla" your_order_is_empty_add_product: "Sepetiniz boş, lütfen bir ürün arayın ve ekleyin" add_product: "ürün ekle" name_or_sku: "Ad veya STOK KODU (ürün adının en az ilk 4 karakterini girin)" @@ -2797,7 +2807,7 @@ tr: account: "Hesap" billing_address: "Fatura Adresi" shipping_address: "Teslimat Adresi" - first_name: "İsim" + first_name: "Ad" last_name: "Soyadı" street_address: "Açık adres" street_address_2: "Açık Adres (devam)" @@ -2885,11 +2895,11 @@ tr: states: "Şehİrler" abbreviation: "Kısaltma" new_state: "Yeni Şehir" - payment_methods: "Ödeme yöntemleri" - taxonomies: "cinsler" - new_taxonomy: "Yeni Cins" - back_to_taxonomies_list: "Cinsler Listesine Geri Dön" - shipping_methods: "Teslimat Yöntemleri" + payment_methods: "ÖDEME YÖNTEMLERİ" + taxonomies: "KATEGORİ" + new_taxonomy: "YENİ KATEGORİ" + back_to_taxonomies_list: "Kategori Listesine Geri Dön" + shipping_methods: "TESLİMAT YÖNTEMLERİ" shipping_categories: "Nakliye Kategorileri" new_shipping_category: "Yeni Nakliye Kategorisi" back_to_shipping_categories: "Teslimat Kategorilerine Geri Dön" @@ -2901,7 +2911,7 @@ tr: zone: "bölge" display: "GÖSTER" environment: "çevre" - active: "Aktif" + active: "AKTİF" nore: "Daha fazla" no_results: "Sonuç yok" create: "Oluştur" @@ -2956,11 +2966,11 @@ tr: tab: dashboard: "KONTROL PANELİ" orders: "SİPARİŞLER" - bulk_order_management: "Toplu Sipariş Yönetimi" + bulk_order_management: "TOPLU SİPARİŞ YÖNETİMİ" subscriptions: "Üyelikler" products: "Ürünler" option_types: "Seçenek Türleri" - properties: "Özellikler" + properties: "ÖZELLİKLER" variant_overrides: "Stok" reports: "Raporlar" configuration: "KURULUM" @@ -2969,8 +2979,8 @@ tr: order_cycles: "SİPARİŞ DÖNEMLERİ" enterprises: "İşletmeler" enterprise_relationships: "İZİNLER" - customers: "Müşteriler" - groups: "Gruplar" + customers: "MÜŞTERİLER" + groups: "AĞLAR" product_properties: index: inherits_properties_checkbox_hint: "Özellikler %{supplier}'den devralınsın mı? (yukarıda geçersiz kılınmadıkça)" @@ -2978,9 +2988,9 @@ tr: select_from_prototype: "Prototipten Seç" properties: index: - properties: "Özellikler" + properties: "ÖZELLİKLER" new_property: "Yeni Özellik" - name: "ad" + name: "Ad" presentation: "Sunum" new: new_property: "Yeni Özellik" @@ -2988,7 +2998,7 @@ tr: editing_property: "Özellikleri Düzenle" back_to_properties_list: "Özellikler Listesine Geri Dön" form: - name: "ad" + name: "Ad" presentation: "Sunum" return_authorizations: index: @@ -3079,9 +3089,9 @@ tr: manage_order_cycles: "SİPARİŞ DÖNEMLERİNİ YÖNET" shipping_methods: index: - shipping_methods: "Teslimat Yöntemleri" - new_shipping_method: "Yeni Teslimat Yöntemi" - name: "ad" + shipping_methods: "TESLİMAT YÖNTEMLERİ" + new_shipping_method: "YENİ TESLİMAT YÖNTEMİ" + name: "Ad" products_distributor: "Dağıtımcı" zone: "bölge" calculator: "Hesaplama" @@ -3090,12 +3100,12 @@ tr: back_end: "Sadece panel" no_shipping_methods_found: "Hiçbir teslimat yöntemi bulunamadı" new: - new_shipping_method: "Yeni Teslimat Yöntemi" - back_to_shipping_methods_list: "Teslimat Yöntemlerine Geri Dön" + new_shipping_method: "YENİ TESLİMAT YÖNTEMİ" + back_to_shipping_methods_list: "Teslİmat Yöntemlerİne Gerİ Dön" edit: - editing_shipping_method: "Teslimat Yöntemini Düzenle" - new: "Yeni" - back_to_shipping_methods_list: "Teslimat Yöntemlerine Geri Dön" + editing_shipping_method: "Teslİmat YöntemİNİ Düzenle" + new: "YENİ" + back_to_shipping_methods_list: "Teslİmat Yöntemlerİne Gerİ Dön" form: categories: "Kategoriler" zones: "bölgeler" @@ -3105,13 +3115,13 @@ tr: payment_methods: index: payment_methods: "ÖDEME YÖNTEMLERİ" - new_payment_method: "Yeni Ödeme Yöntemi" - name: "İSİM" + new_payment_method: "YENİ ÖDEME YÖNTEMİ" + name: "Ad" products_distributor: "Dağıtımcı" provider: "Sağlayıcı" environment: "Çevre" display: "GÖSTER" - active: "Aktif" + active: "AKTİF" both: "Her ikisi de" front_end: "Sadece Ödeme" back_end: "Sadece panel" @@ -3119,10 +3129,10 @@ tr: active_no: "Hayır" no_payment_methods_found: "Ödeme yöntemi bulunamadı" new: - new_payment_method: "Yeni Ödeme Yöntemi" + new_payment_method: "YENİ ÖDEME YÖNTEMİ" back_to_payment_methods_list: "Ödeme Yöntemleri Listesine Geri Dön" edit: - new: "Yeni" + new: "YENİ" editing_payment_method: "Ödeme Yöntemi Düzenleniyor" back_to_payment_methods_list: "Ödeme Yöntemleri Listesine Geri Dön" stripe_connect: @@ -3136,20 +3146,20 @@ tr: status: durum connected: bağlı account_id: Hesap Kimliği - business_name: işletme adı + business_name: İşletme Adı charges_enabled: Masraflar Etkin form: - name: "İSİM" + name: "Ad" description: "Açıklama" environment: "Çevre" display: "GÖSTER" - active: "Aktif" + active: "AKTİF" active_yes: "Evet" active_no: "Hayır" both: "Ödeme Sayfası ve Panel" front_end: "Sadece Ödeme" back_end: "Sadece panel" - tags: "Etiketler" + tags: "ETİKETLER" deactivation_warning: "Bir ödeme yöntemini kaldırmak listenizden silinmesine sebep olabilir. Alternatif olarak, ödeme yöntemi ayarını 'Göster' yerine 'Sadece Panel' olarak değiştirebilirsiniz. " providers: provider: "Sağlayıcı" @@ -3183,7 +3193,7 @@ tr: no_products: "Henüz ürün yok. Neden biraz eklemiyorsun?" no_results: "Üzgünüz, sonuç bulunamadı" products_head: - name: ad + name: Ad unit: birim display_as: Gösterme Şekli category: Kategori @@ -3216,7 +3226,7 @@ tr: listing_users: "Kullanıcılar Listeleniyor" new_user: "Yeni Kullanıcı" user: "Kullanıcı" - enterprise_limit: "İşletme Limiti" + enterprise_limit: "İŞLETME LİMİTİ" search: "Ara" email: "E-posta" edit: @@ -3226,7 +3236,7 @@ tr: form: email: "E-posta" roles: "Roller" - enterprise_limit: "İşletme Limiti" + enterprise_limit: "İŞLETME LİMİTİ" confirm_password: "Parolayı Onayla" password: "Parola" email_confirmation: @@ -3255,7 +3265,7 @@ tr: display_as_placeholder: 'örn. 2 kg' display_name_placeholder: 'örn. Domates' autocomplete: - out_of_stock: "Tükendi" + out_of_stock: "Stokta Yok" producer_name: "Üretici" unit: "Birim" shared: @@ -3351,7 +3361,7 @@ tr: returned: iade edildi skrill: skrill subscription_state: - active: aktif + active: AKTİF pending: bekliyor ended: bitti paused: durduruldu @@ -3385,7 +3395,7 @@ tr: transaction_history: İşlem Geçmişi open_orders: order: Sipariş - shop: MAĞAZA + shop: DÜKKAN changes_allowed_until: Değişiklikler Şu Zamana Kadar İzin Verildi items: Ürünler total: Toplam @@ -3395,7 +3405,7 @@ tr: until: Şu zamana kadar past_orders: order: Sipariş - shop: MAĞAZA + shop: DÜKKAN completed_at: Tamamlanma Tarihi items: Ürünler total: Toplam @@ -3409,7 +3419,7 @@ tr: authorised_shops_popover: Sahip olabileceğiniz tüm üyelikler (mesela tekrarlayan siparişleriniz) için varsayılan kredi kartınızdan işlem yapmasına izin verilen işletmelerin listesidir. Kart bilgileriniz güvende tutulacak ve işletme sahipleriyle paylaşılmayacak. Ödeme alındığında her zaman bilgilendirileceksiniz. saved_cards_popover: Bu, daha sonra kullanmak üzere kaydetmeyi seçtiğiniz kartların listesidir. Bir siparişin ödemesi tamamlandığında 'varsayılan'ınız otomatik olarak seçilir ve izin verdiğiniz satıcılar tarafından ücretlendirilebilir (sağa bakın). authorised_shops: - shop_name: "MAĞAZA ADI" + shop_name: "DÜKKAN ADI" allow_charges?: "Ücretlere İzin Verilsin mi?" localized_number: invalid_format: geçersiz bir biçime sahip. Lütfen bir numara giriniz. @@ -3428,4 +3438,4 @@ tr: key_cleared: "Anahtar temizlendi" shipment: cannot_ready: "Gönderim hazırlanamıyor." - invalid_taxonomy_id: "Geçersiz sınıflandırma kimliği." + invalid_taxonomy_id: "Geçersiz kategori kimliği." diff --git a/config/routes.rb b/config/routes.rb index 877ca407d3..a657f7f234 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ Openfoodnetwork::Application.routes.draw do + root :to => 'home#index' # Redirects from old URLs avoid server errors and helps search engines @@ -8,6 +9,7 @@ Openfoodnetwork::Application.routes.draw do get "/about_us", to: redirect(ContentConfig.footer_about_url) get "/login", to: redirect("/#/login") + get '/unauthorized', :to => 'home#unauthorized', :as => :unauthorized get "/discourse/login", to: "discourse_sso#login" get "/discourse/sso", to: "discourse_sso#sso" diff --git a/config/routes/spree.rb b/config/routes/spree.rb index ca5993aca5..5aecadbf49 100644 --- a/config/routes/spree.rb +++ b/config/routes/spree.rb @@ -1,7 +1,5 @@ # Overriding Devise routes to use our own controller Spree::Core::Engine.routes.draw do - root to: 'home#index' - devise_for :spree_user, :class_name => 'Spree::User', :controllers => { :sessions => 'spree/user_sessions', @@ -15,7 +13,6 @@ Spree::Core::Engine.routes.draw do resources :users, :only => [:edit, :update] devise_scope :spree_user do - get '/login' => 'user_sessions#new', :as => :login post '/login' => 'user_sessions#create', :as => :create_new_session get '/logout' => 'user_sessions#destroy', :as => :logout get '/signup' => 'user_registrations#new', :as => :signup @@ -174,8 +171,6 @@ Spree::Core::Engine.routes.draw do # Used by spree_paypal_express get '/checkout/:state', :to => 'checkout#edit', :as => :checkout_state - - get '/unauthorized', :to => 'home#unauthorized', :as => :unauthorized get '/content/cvv', :to => 'content#cvv', :as => :cvv get '/content/*path', :to => 'content#show', :as => :content end diff --git a/db/migrate/20200512070717_add_lock_version_to_stock_items.rb b/db/migrate/20200512070717_add_lock_version_to_stock_items.rb new file mode 100644 index 0000000000..416a43f62e --- /dev/null +++ b/db/migrate/20200512070717_add_lock_version_to_stock_items.rb @@ -0,0 +1,5 @@ +class AddLockVersionToStockItems < ActiveRecord::Migration + def change + add_column :spree_stock_items, :lock_version, :integer, default: 0 + end +end diff --git a/db/migrate/20200514174526_reset_negative_nonbackorderable_count_on_hand_in_stock_items.rb b/db/migrate/20200514174526_reset_negative_nonbackorderable_count_on_hand_in_stock_items.rb new file mode 100644 index 0000000000..c57730c99a --- /dev/null +++ b/db/migrate/20200514174526_reset_negative_nonbackorderable_count_on_hand_in_stock_items.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class ResetNegativeNonbackorderableCountOnHandInStockItems < ActiveRecord::Migration + module Spree + class StockItem < ActiveRecord::Base + self.table_name = "spree_stock_items" + end + end + + def up + Spree::StockItem.where(backorderable: false) + .where("count_on_hand < 0") + .update_all(count_on_hand: 0) + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/migrate/20200616162646_move_all_calculators_outside_the_spree_namespace.rb b/db/migrate/20200616162646_move_all_calculators_outside_the_spree_namespace.rb new file mode 100644 index 0000000000..b357242db2 --- /dev/null +++ b/db/migrate/20200616162646_move_all_calculators_outside_the_spree_namespace.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +class MoveAllCalculatorsOutsideTheSpreeNamespace < ActiveRecord::Migration + def up + convert_calculator("DefaultTax") + convert_calculator("FlatPercentItemTotal") + convert_calculator("FlatRate") + convert_calculator("FlexiRate") + convert_calculator("PerItem") + convert_calculator("PriceSack") + end + + def down + revert_calculator("DefaultTax") + revert_calculator("FlatPercentItemTotal") + revert_calculator("FlatRate") + revert_calculator("FlexiRate") + revert_calculator("PerItem") + revert_calculator("PriceSack") + end + + private + + def convert_calculator(calculator_base_name) + update_calculator("Spree::Calculator::" + calculator_base_name, + "Calculator::" + calculator_base_name) + end + + def revert_calculator(calculator_base_name) + update_calculator("Calculator::" + calculator_base_name, + "Spree::Calculator::" + calculator_base_name) + end + + def update_calculator(from, to) + Spree::Calculator.connection.execute( + "UPDATE spree_calculators SET type = '" + to + "' WHERE type = '" + from + "'" + ) + end +end diff --git a/db/migrate/20200630070422_require_timestamps.rb b/db/migrate/20200630070422_require_timestamps.rb new file mode 100644 index 0000000000..2075876bca --- /dev/null +++ b/db/migrate/20200630070422_require_timestamps.rb @@ -0,0 +1,253 @@ +class RequireTimestamps < ActiveRecord::Migration + def up + current_time = Time.zone.now + + change_column_null :customers, :created_at, false, current_time + change_column_null :customers, :updated_at, false, current_time + change_column_null :delayed_jobs, :created_at, false, current_time + change_column_null :delayed_jobs, :updated_at, false, current_time + change_column_null :distributors_shipping_methods, :created_at, false, current_time + change_column_null :distributors_shipping_methods, :updated_at, false, current_time + change_column_null :enterprise_fees, :created_at, false, current_time + change_column_null :enterprise_fees, :updated_at, false, current_time + change_column_null :enterprises, :created_at, false, current_time + change_column_null :enterprises, :updated_at, false, current_time + change_column_null :exchange_fees, :created_at, false, current_time + change_column_null :exchange_fees, :updated_at, false, current_time + change_column_null :exchange_variants, :created_at, false, current_time + change_column_null :exchange_variants, :updated_at, false, current_time + change_column_null :exchanges, :created_at, false, current_time + change_column_null :exchanges, :updated_at, false, current_time + change_column_null :inventory_items, :created_at, false, current_time + change_column_null :inventory_items, :updated_at, false, current_time + change_column_null :order_cycle_schedules, :created_at, false, current_time + change_column_null :order_cycle_schedules, :updated_at, false, current_time + change_column_null :order_cycles, :created_at, false, current_time + change_column_null :order_cycles, :updated_at, false, current_time + change_column_null :producer_properties, :created_at, false, current_time + change_column_null :producer_properties, :updated_at, false, current_time + change_column_null :proxy_orders, :created_at, false, current_time + change_column_null :proxy_orders, :updated_at, false, current_time + change_column_null :schedules, :created_at, false, current_time + change_column_null :schedules, :updated_at, false, current_time + change_column_null :sessions, :created_at, false, current_time + change_column_null :sessions, :updated_at, false, current_time + change_column_null :spree_activators, :created_at, false, current_time + change_column_null :spree_activators, :updated_at, false, current_time + change_column_null :spree_addresses, :created_at, false, current_time + change_column_null :spree_addresses, :updated_at, false, current_time + change_column_null :spree_adjustments, :created_at, false, current_time + change_column_null :spree_adjustments, :updated_at, false, current_time + change_column_null :spree_calculators, :created_at, false, current_time + change_column_null :spree_calculators, :updated_at, false, current_time + change_column_null :spree_configurations, :created_at, false, current_time + change_column_null :spree_configurations, :updated_at, false, current_time + change_column_null :spree_credit_cards, :created_at, false, current_time + change_column_null :spree_credit_cards, :updated_at, false, current_time + change_column_null :spree_gateways, :created_at, false, current_time + change_column_null :spree_gateways, :updated_at, false, current_time + change_column_null :spree_inventory_units, :created_at, false, current_time + change_column_null :spree_inventory_units, :updated_at, false, current_time + change_column_null :spree_line_items, :created_at, false, current_time + change_column_null :spree_line_items, :updated_at, false, current_time + change_column_null :spree_log_entries, :created_at, false, current_time + change_column_null :spree_log_entries, :updated_at, false, current_time + change_column_null :spree_option_types, :created_at, false, current_time + change_column_null :spree_option_types, :updated_at, false, current_time + change_column_null :spree_option_values, :created_at, false, current_time + change_column_null :spree_option_values, :updated_at, false, current_time + change_column_null :spree_orders, :created_at, false, current_time + change_column_null :spree_orders, :updated_at, false, current_time + change_column_null :spree_payment_methods, :created_at, false, current_time + change_column_null :spree_payment_methods, :updated_at, false, current_time + change_column_null :spree_payments, :created_at, false, current_time + change_column_null :spree_payments, :updated_at, false, current_time + change_column_null :spree_preferences, :created_at, false, current_time + change_column_null :spree_preferences, :updated_at, false, current_time + change_column_null :spree_product_option_types, :created_at, false, current_time + change_column_null :spree_product_option_types, :updated_at, false, current_time + change_column_null :spree_product_properties, :created_at, false, current_time + change_column_null :spree_product_properties, :updated_at, false, current_time + change_column_null :spree_products, :created_at, false, current_time + change_column_null :spree_products, :updated_at, false, current_time + change_column_null :spree_promotion_rules, :created_at, false, current_time + change_column_null :spree_promotion_rules, :updated_at, false, current_time + change_column_null :spree_properties, :created_at, false, current_time + change_column_null :spree_properties, :updated_at, false, current_time + change_column_null :spree_return_authorizations, :created_at, false, current_time + change_column_null :spree_return_authorizations, :updated_at, false, current_time + change_column_null :spree_shipments, :created_at, false, current_time + change_column_null :spree_shipments, :updated_at, false, current_time + change_column_null :spree_shipping_categories, :created_at, false, current_time + change_column_null :spree_shipping_categories, :updated_at, false, current_time + change_column_null :spree_shipping_method_categories, :created_at, false, current_time + change_column_null :spree_shipping_method_categories, :updated_at, false, current_time + change_column_null :spree_shipping_methods, :created_at, false, current_time + change_column_null :spree_shipping_methods, :updated_at, false, current_time + change_column_null :spree_shipping_rates, :created_at, false, current_time + change_column_null :spree_shipping_rates, :updated_at, false, current_time + change_column_null :spree_skrill_transactions, :created_at, false, current_time + change_column_null :spree_skrill_transactions, :updated_at, false, current_time + change_column_null :spree_state_changes, :created_at, false, current_time + change_column_null :spree_state_changes, :updated_at, false, current_time + change_column_null :spree_stock_items, :created_at, false, current_time + change_column_null :spree_stock_items, :updated_at, false, current_time + change_column_null :spree_stock_locations, :created_at, false, current_time + change_column_null :spree_stock_locations, :updated_at, false, current_time + change_column_null :spree_stock_movements, :created_at, false, current_time + change_column_null :spree_stock_movements, :updated_at, false, current_time + change_column_null :spree_stock_transfers, :created_at, false, current_time + change_column_null :spree_stock_transfers, :updated_at, false, current_time + change_column_null :spree_tax_categories, :created_at, false, current_time + change_column_null :spree_tax_categories, :updated_at, false, current_time + change_column_null :spree_tax_rates, :created_at, false, current_time + change_column_null :spree_tax_rates, :updated_at, false, current_time + change_column_null :spree_taxonomies, :created_at, false, current_time + change_column_null :spree_taxonomies, :updated_at, false, current_time + change_column_null :spree_taxons, :created_at, false, current_time + change_column_null :spree_taxons, :updated_at, false, current_time + change_column_null :spree_tokenized_permissions, :created_at, false, current_time + change_column_null :spree_tokenized_permissions, :updated_at, false, current_time + change_column_null :spree_users, :created_at, false, current_time + change_column_null :spree_users, :updated_at, false, current_time + change_column_null :spree_zone_members, :created_at, false, current_time + change_column_null :spree_zone_members, :updated_at, false, current_time + change_column_null :spree_zones, :created_at, false, current_time + change_column_null :spree_zones, :updated_at, false, current_time + change_column_null :stripe_accounts, :created_at, false, current_time + change_column_null :stripe_accounts, :updated_at, false, current_time + change_column_null :subscription_line_items, :created_at, false, current_time + change_column_null :subscription_line_items, :updated_at, false, current_time + change_column_null :subscriptions, :created_at, false, current_time + change_column_null :subscriptions, :updated_at, false, current_time + change_column_null :tag_rules, :created_at, false, current_time + change_column_null :tag_rules, :updated_at, false, current_time + change_column_null :column_preferences, :created_at, false, current_time + change_column_null :column_preferences, :updated_at, false, current_time + end + + def down + change_column_null :customers, :created_at, true + change_column_null :customers, :updated_at, true + change_column_null :delayed_jobs, :created_at, true + change_column_null :delayed_jobs, :updated_at, true + change_column_null :distributors_shipping_methods, :created_at, true + change_column_null :distributors_shipping_methods, :updated_at, true + change_column_null :enterprise_fees, :created_at, true + change_column_null :enterprise_fees, :updated_at, true + change_column_null :enterprises, :created_at, true + change_column_null :enterprises, :updated_at, true + change_column_null :exchange_fees, :created_at, true + change_column_null :exchange_fees, :updated_at, true + change_column_null :exchange_variants, :created_at, true + change_column_null :exchange_variants, :updated_at, true + change_column_null :exchanges, :created_at, true + change_column_null :exchanges, :updated_at, true + change_column_null :inventory_items, :created_at, true + change_column_null :inventory_items, :updated_at, true + change_column_null :order_cycle_schedules, :created_at, true + change_column_null :order_cycle_schedules, :updated_at, true + change_column_null :order_cycles, :created_at, true + change_column_null :order_cycles, :updated_at, true + change_column_null :producer_properties, :created_at, true + change_column_null :producer_properties, :updated_at, true + change_column_null :proxy_orders, :created_at, true + change_column_null :proxy_orders, :updated_at, true + change_column_null :schedules, :created_at, true + change_column_null :schedules, :updated_at, true + change_column_null :sessions, :created_at, true + change_column_null :sessions, :updated_at, true + change_column_null :spree_activators, :created_at, true + change_column_null :spree_activators, :updated_at, true + change_column_null :spree_addresses, :created_at, true + change_column_null :spree_addresses, :updated_at, true + change_column_null :spree_adjustments, :created_at, true + change_column_null :spree_adjustments, :updated_at, true + change_column_null :spree_calculators, :created_at, true + change_column_null :spree_calculators, :updated_at, true + change_column_null :spree_configurations, :created_at, true + change_column_null :spree_configurations, :updated_at, true + change_column_null :spree_credit_cards, :created_at, true + change_column_null :spree_credit_cards, :updated_at, true + change_column_null :spree_gateways, :created_at, true + change_column_null :spree_gateways, :updated_at, true + change_column_null :spree_inventory_units, :created_at, true + change_column_null :spree_inventory_units, :updated_at, true + change_column_null :spree_line_items, :created_at, true + change_column_null :spree_line_items, :updated_at, true + change_column_null :spree_log_entries, :created_at, true + change_column_null :spree_log_entries, :updated_at, true + change_column_null :spree_option_types, :created_at, true + change_column_null :spree_option_types, :updated_at, true + change_column_null :spree_option_values, :created_at, true + change_column_null :spree_option_values, :updated_at, true + change_column_null :spree_orders, :created_at, true + change_column_null :spree_orders, :updated_at, true + change_column_null :spree_payment_methods, :created_at, true + change_column_null :spree_payment_methods, :updated_at, true + change_column_null :spree_payments, :created_at, true + change_column_null :spree_payments, :updated_at, true + change_column_null :spree_preferences, :created_at, true + change_column_null :spree_preferences, :updated_at, true + change_column_null :spree_product_option_types, :created_at, true + change_column_null :spree_product_option_types, :updated_at, true + change_column_null :spree_product_properties, :created_at, true + change_column_null :spree_product_properties, :updated_at, true + change_column_null :spree_products, :created_at, true + change_column_null :spree_products, :updated_at, true + change_column_null :spree_promotion_rules, :created_at, true + change_column_null :spree_promotion_rules, :updated_at, true + change_column_null :spree_properties, :created_at, true + change_column_null :spree_properties, :updated_at, true + change_column_null :spree_return_authorizations, :created_at, true + change_column_null :spree_return_authorizations, :updated_at, true + change_column_null :spree_shipments, :created_at, true + change_column_null :spree_shipments, :updated_at, true + change_column_null :spree_shipping_categories, :created_at, true + change_column_null :spree_shipping_categories, :updated_at, true + change_column_null :spree_shipping_method_categories, :created_at, true + change_column_null :spree_shipping_method_categories, :updated_at, true + change_column_null :spree_shipping_methods, :created_at, true + change_column_null :spree_shipping_methods, :updated_at, true + change_column_null :spree_shipping_rates, :created_at, true + change_column_null :spree_shipping_rates, :updated_at, true + change_column_null :spree_skrill_transactions, :created_at, true + change_column_null :spree_skrill_transactions, :updated_at, true + change_column_null :spree_state_changes, :created_at, true + change_column_null :spree_state_changes, :updated_at, true + change_column_null :spree_stock_items, :created_at, true + change_column_null :spree_stock_items, :updated_at, true + change_column_null :spree_stock_locations, :created_at, true + change_column_null :spree_stock_locations, :updated_at, true + change_column_null :spree_stock_movements, :created_at, true + change_column_null :spree_stock_movements, :updated_at, true + change_column_null :spree_stock_transfers, :created_at, true + change_column_null :spree_stock_transfers, :updated_at, true + change_column_null :spree_tax_categories, :created_at, true + change_column_null :spree_tax_categories, :updated_at, true + change_column_null :spree_tax_rates, :created_at, true + change_column_null :spree_tax_rates, :updated_at, true + change_column_null :spree_taxonomies, :created_at, true + change_column_null :spree_taxonomies, :updated_at, true + change_column_null :spree_taxons, :created_at, true + change_column_null :spree_taxons, :updated_at, true + change_column_null :spree_tokenized_permissions, :created_at, true + change_column_null :spree_tokenized_permissions, :updated_at, true + change_column_null :spree_users, :created_at, true + change_column_null :spree_users, :updated_at, true + change_column_null :spree_zone_members, :created_at, true + change_column_null :spree_zone_members, :updated_at, true + change_column_null :spree_zones, :created_at, true + change_column_null :spree_zones, :updated_at, true + change_column_null :stripe_accounts, :created_at, true + change_column_null :stripe_accounts, :updated_at, true + change_column_null :subscription_line_items, :created_at, true + change_column_null :subscription_line_items, :updated_at, true + change_column_null :subscriptions, :created_at, true + change_column_null :subscriptions, :updated_at, true + change_column_null :tag_rules, :created_at, true + change_column_null :tag_rules, :updated_at, true + change_column_null :column_preferences, :created_at, true + change_column_null :column_preferences, :updated_at, true + end +end diff --git a/db/migrate/20200702112157_add_stateful_id_index_to_state_changes.rb b/db/migrate/20200702112157_add_stateful_id_index_to_state_changes.rb new file mode 100644 index 0000000000..8a52643cff --- /dev/null +++ b/db/migrate/20200702112157_add_stateful_id_index_to_state_changes.rb @@ -0,0 +1,5 @@ +class AddStatefulIdIndexToStateChanges < ActiveRecord::Migration + def change + add_index :spree_state_changes, :stateful_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 930e3e955d..8df4304ca2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20200624091611) do +ActiveRecord::Schema.define(version: 20200702112157) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -32,8 +32,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.string "action_name", null: false t.string "column_name", null: false t.boolean "visible", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "column_preferences", ["user_id", "action_name", "column_name"], name: "index_column_prefs_on_user_id_and_action_name_and_column_name", unique: true, using: :btree @@ -51,8 +51,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.integer "enterprise_id", null: false t.string "code" t.integer "user_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "bill_address_id" t.integer "ship_address_id" t.string "name" @@ -75,8 +75,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.datetime "failed_at" t.string "locked_by" t.string "queue" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree @@ -92,8 +92,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do create_table "distributors_shipping_methods", force: true do |t| t.integer "distributor_id" t.integer "shipping_method_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "distributors_shipping_methods", ["distributor_id"], name: "index_distributors_shipping_methods_on_distributor_id", using: :btree @@ -103,8 +103,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.integer "enterprise_id" t.string "fee_type" t.string "name" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "tax_category_id" t.boolean "inherits_tax_category", default: false, null: false end @@ -190,8 +190,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.integer "address_id" t.text "pickup_times" t.string "next_collection_at" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.text "distributor_info" t.string "logo_file_name" t.string "logo_content_type" @@ -229,8 +229,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do create_table "exchange_fees", force: true do |t| t.integer "exchange_id" t.integer "enterprise_fee_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "exchange_fees", ["enterprise_fee_id"], name: "index_exchange_fees_on_enterprise_fee_id", using: :btree @@ -239,8 +239,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do create_table "exchange_variants", force: true do |t| t.integer "exchange_id" t.integer "variant_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "exchange_variants", ["exchange_id"], name: "index_exchange_variants_on_exchange_id", using: :btree @@ -252,8 +252,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.integer "receiver_id" t.text "pickup_time" t.text "pickup_instructions" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.boolean "incoming", default: false, null: false t.text "receival_instructions" end @@ -266,8 +266,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.integer "enterprise_id", null: false t.integer "variant_id", null: false t.boolean "visible", default: true, null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "inventory_items", ["enterprise_id", "variant_id"], name: "index_inventory_items_on_enterprise_id_and_variant_id", unique: true, using: :btree @@ -275,8 +275,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do create_table "order_cycle_schedules", force: true do |t| t.integer "order_cycle_id", null: false t.integer "schedule_id", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "order_cycle_schedules", ["order_cycle_id"], name: "index_order_cycle_schedules_on_order_cycle_id", using: :btree @@ -287,8 +287,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.datetime "orders_open_at" t.datetime "orders_close_at" t.integer "coordinator_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end create_table "producer_properties", force: true do |t| @@ -296,8 +296,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.integer "producer_id" t.integer "property_id" t.integer "position", default: 0, null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "producer_properties", ["position"], name: "index_producer_properties_on_position", using: :btree @@ -308,8 +308,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.integer "subscription_id", null: false t.integer "order_id" t.datetime "canceled_at" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "order_cycle_id", null: false t.datetime "placed_at" t.datetime "confirmed_at" @@ -321,15 +321,15 @@ ActiveRecord::Schema.define(version: 20200624091611) do create_table "schedules", force: true do |t| t.string "name", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end create_table "sessions", force: true do |t| t.string "session_id", null: false t.text "data" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "sessions", ["session_id"], name: "index_sessions_on_session_id", using: :btree @@ -338,8 +338,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do create_table "spree_activators", force: true do |t| t.string "description" t.datetime "expires_at" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.datetime "starts_at" t.string "name" t.string "event_name" @@ -363,8 +363,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.string "alternative_phone" t.integer "state_id" t.integer "country_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "company" t.float "latitude" t.float "longitude" @@ -379,8 +379,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.string "label" t.string "source_type" t.integer "adjustable_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.boolean "mandatory" t.integer "originator_id" t.string "originator_type" @@ -413,15 +413,15 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.string "type" t.integer "calculable_id", null: false t.string "calculable_type", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end create_table "spree_configurations", force: true do |t| t.string "name" t.string "type", limit: 50 - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "spree_configurations", ["name", "type"], name: "index_configurations_on_name_and_type", using: :btree @@ -446,8 +446,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.string "start_year" t.string "issue_number" t.integer "address_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "gateway_customer_profile_id" t.string "gateway_payment_profile_id" t.integer "user_id" @@ -466,16 +466,16 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.string "environment", default: "development" t.string "server", default: "test" t.boolean "test_mode", default: true - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end create_table "spree_inventory_units", force: true do |t| t.string "state" t.integer "variant_id" t.integer "order_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "shipment_id" t.integer "return_authorization_id" t.boolean "pending", default: true @@ -490,8 +490,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.integer "variant_id" t.integer "quantity", null: false t.decimal "price", precision: 8, scale: 2, null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "max_quantity" t.string "currency" t.decimal "distribution_fee", precision: 10, scale: 2 @@ -507,15 +507,15 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.integer "source_id" t.string "source_type" t.text "details" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end create_table "spree_option_types", force: true do |t| t.string "name", limit: 100 t.string "presentation", limit: 100 - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "position", default: 0, null: false end @@ -524,8 +524,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.string "name" t.string "presentation" t.integer "option_type_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end create_table "spree_option_values_line_items", id: false, force: true do |t| @@ -550,8 +550,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.string "state" t.decimal "adjustment_total", precision: 10, scale: 2, default: 0.0, null: false t.integer "user_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.datetime "completed_at" t.integer "bill_address_id" t.integer "ship_address_id" @@ -581,8 +581,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.text "description" t.boolean "active", default: true t.string "environment", default: "development" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.datetime "deleted_at" t.string "display_on" end @@ -590,8 +590,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do create_table "spree_payments", force: true do |t| t.decimal "amount", precision: 10, scale: 2, default: 0.0, null: false t.integer "order_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "source_id" t.string "source_type" t.integer "payment_method_id" @@ -635,8 +635,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do create_table "spree_preferences", force: true do |t| t.text "value" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "key" t.string "value_type" end @@ -670,16 +670,16 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.integer "position" t.integer "product_id" t.integer "option_type_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end create_table "spree_product_properties", force: true do |t| t.string "value" t.integer "product_id" t.integer "property_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "position", default: 0 end @@ -704,8 +704,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.string "meta_keywords" t.integer "tax_category_id" t.integer "shipping_category_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "supplier_id" t.boolean "group_buy" t.float "group_buy_unit_size" @@ -758,8 +758,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.integer "user_id" t.integer "product_group_id" t.string "type" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "spree_promotion_rules", ["product_group_id"], name: "index_promotion_rules_on_product_group_id", using: :btree @@ -776,8 +776,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do create_table "spree_properties", force: true do |t| t.string "name" t.string "presentation", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end create_table "spree_return_authorizations", force: true do |t| @@ -786,8 +786,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.decimal "amount", precision: 10, scale: 2, default: 0.0, null: false t.integer "order_id" t.text "reason" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "stock_location_id" end @@ -810,8 +810,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.datetime "shipped_at" t.integer "order_id" t.integer "address_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "state" t.integer "stock_location_id" end @@ -821,16 +821,16 @@ ActiveRecord::Schema.define(version: 20200624091611) do create_table "spree_shipping_categories", force: true do |t| t.string "name" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.boolean "temperature_controlled", default: false, null: false end create_table "spree_shipping_method_categories", force: true do |t| t.integer "shipping_method_id", null: false t.integer "shipping_category_id", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "spree_shipping_method_categories", ["shipping_category_id"], name: "index_spree_shipping_method_categories_on_shipping_category_id", using: :btree @@ -838,8 +838,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do create_table "spree_shipping_methods", force: true do |t| t.string "name" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "display_on" t.datetime "deleted_at" t.boolean "require_ship_address", default: true @@ -857,8 +857,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.integer "shipping_method_id" t.boolean "selected", default: false t.decimal "cost", precision: 8, scale: 2, default: 0.0 - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "spree_shipping_rates", ["shipment_id", "shipping_method_id"], name: "spree_shipping_rates_join_index", unique: true, using: :btree @@ -870,8 +870,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.integer "transaction_id" t.integer "customer_id" t.string "payment_type" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end create_table "spree_state_changes", force: true do |t| @@ -879,12 +879,14 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.string "previous_state" t.integer "stateful_id" t.integer "user_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "stateful_type" t.string "next_state" end + add_index "spree_state_changes", ["stateful_id"], name: "index_spree_state_changes_on_stateful_id", using: :btree + create_table "spree_states", force: true do |t| t.string "name" t.string "abbr" @@ -895,10 +897,11 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.integer "stock_location_id" t.integer "variant_id" t.integer "count_on_hand", default: 0, null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.boolean "backorderable", default: false t.datetime "deleted_at" + t.integer "lock_version", default: 0 end add_index "spree_stock_items", ["stock_location_id", "variant_id"], name: "stock_item_by_loc_and_var_id", using: :btree @@ -907,8 +910,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do create_table "spree_stock_locations", force: true do |t| t.string "name" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "address1" t.string "address2" t.string "city" @@ -926,8 +929,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.integer "stock_item_id" t.integer "quantity", default: 0 t.string "action" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "originator_id" t.string "originator_type" end @@ -939,8 +942,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.string "reference" t.integer "source_location_id" t.integer "destination_location_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "number" end @@ -951,8 +954,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do create_table "spree_tax_categories", force: true do |t| t.string "name" t.string "description" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.boolean "is_default", default: false t.datetime "deleted_at" end @@ -961,8 +964,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.decimal "amount", precision: 8, scale: 5 t.integer "zone_id" t.integer "tax_category_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.boolean "included_in_price", default: false t.string "name" t.boolean "show_rate_in_label", default: true @@ -971,8 +974,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do create_table "spree_taxonomies", force: true do |t| t.string "name", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "position", default: 0 end @@ -982,8 +985,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.string "name", null: false t.string "permalink" t.integer "taxonomy_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "lft" t.integer "rgt" t.string "icon_file_name" @@ -1004,8 +1007,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.integer "permissable_id" t.string "permissable_type" t.string "token" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "spree_tokenized_permissions", ["permissable_id", "permissable_type"], name: "index_tokenized_name_and_type", using: :btree @@ -1028,8 +1031,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.string "login" t.integer "ship_address_id" t.integer "bill_address_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "authentication_token" t.string "unlock_token" t.datetime "locked_at" @@ -1075,15 +1078,15 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.integer "zoneable_id" t.string "zoneable_type" t.integer "zone_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end create_table "spree_zones", force: true do |t| t.string "name" t.string "description" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.boolean "default_tax", default: false t.integer "zone_members_count", default: 0 end @@ -1091,8 +1094,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do create_table "stripe_accounts", force: true do |t| t.string "stripe_user_id" t.string "stripe_publishable_key" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "enterprise_id" end @@ -1102,8 +1105,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.integer "subscription_id", null: false t.integer "variant_id", null: false t.integer "quantity", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.decimal "price_estimate", precision: 8, scale: 2 end @@ -1118,8 +1121,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do t.integer "shipping_method_id", null: false t.datetime "begins_at" t.datetime "ends_at" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "bill_address_id", null: false t.integer "ship_address_id", null: false t.datetime "canceled_at" @@ -1147,8 +1150,8 @@ ActiveRecord::Schema.define(version: 20200624091611) do create_table "tag_rules", force: true do |t| t.integer "enterprise_id", null: false t.string "type", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.boolean "is_default", default: false, null: false t.integer "priority", default: 99, null: false end diff --git a/engines/order_management/app/controllers/order_management/reports/bulk_coop_controller.rb b/engines/order_management/app/controllers/order_management/reports/bulk_coop_controller.rb new file mode 100644 index 0000000000..5a308fbdab --- /dev/null +++ b/engines/order_management/app/controllers/order_management/reports/bulk_coop_controller.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module OrderManagement + module Reports + class BulkCoopController < Spree::Admin::BaseController + before_filter :load_report_parameters + before_filter :load_permissions + + def new; end + + def create + return respond_to_invalid_parameters unless @report_parameters.valid? + + @report_parameters.authorize!(@permissions) + + @report = report_klass::ReportService.new(@permissions, params[:report], spree_current_user) + renderer.render(self) + rescue ::Reports::Authorizer::ParameterNotAllowedError => e + flash[:error] = e.message + render_report_form + end + + private + + def respond_to_invalid_parameters + flash[:error] = I18n.t("invalid_filter_parameters", scope: i18n_scope) + render_report_form + end + + def i18n_scope + "order_management.reports.enterprise_fee_summary" + end + + def render_report_form + render action: :new + end + + def report_klass + OrderManagement::Reports::BulkCoop + end + + def load_report_parameters + @report_parameters = report_klass::Parameters.new(params[:report] || {}) + end + + def load_permissions + @permissions = report_klass::Permissions.new(spree_current_user) + end + + def report_renderer_klass + case params[:report_format] + when "csv" + report_klass::Renderers::CsvRenderer + when nil, "", "html" + report_klass::Renderers::HtmlRenderer + else + raise Reports::UnsupportedReportFormatException + end + end + + def renderer + @renderer ||= report_renderer_klass.new(@report) + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/bulk_coop/authorizer.rb b/engines/order_management/app/services/order_management/reports/bulk_coop/authorizer.rb new file mode 100644 index 0000000000..b0e398e0a0 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/bulk_coop/authorizer.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module OrderManagement + module Reports + module BulkCoop + class Authorizer < ::Reports::Authorizer + def authorize! + require_ids_allowed(parameters.distributor_ids, permissions.allowed_distributors) + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_allocation_report.rb b/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_allocation_report.rb new file mode 100644 index 0000000000..9513f51e9b --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_allocation_report.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module OrderManagement + module Reports + module BulkCoop + class BulkCoopAllocationReport + def header + [ + I18n.t(:report_header_customer), + I18n.t(:report_header_product), + I18n.t(:report_header_bulk_unit_size), + I18n.t(:report_header_variant), + I18n.t(:report_header_variant_value), + I18n.t(:report_header_variant_unit), + I18n.t(:report_header_weight), + I18n.t(:report_header_sum_total), + I18n.t(:report_header_total_available), + I18n.t(:report_header_unallocated), + I18n.t(:report_header_max_quantity_excess), + ] + end + + def rules + [ + { + group_by: proc { |line_item| line_item.product }, + sort_by: proc { |product| product.name }, + summary_columns: [ + :total_label, + :variant_product_name, + :variant_product_group_buy_unit_size_f, + :empty_cell, + :empty_cell, + :empty_cell, + :empty_cell, + :total_amount, + :total_available, + :remainder, + :max_quantity_excess + ] + }, + { + group_by: proc { |line_item| line_item.order }, + sort_by: proc { |order| order.to_s } + } + ] + end + + def columns + [ + :order_billing_address_name, + :product_name, + :product_group_buy_unit_size, + :full_name, + :option_value_value, + :option_value_unit, + :weight_from_unit_value, + :total_amount, + :empty_cell, + :empty_cell, + :empty_cell + ] + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_report.rb b/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_report.rb new file mode 100644 index 0000000000..8adaef7f33 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_report.rb @@ -0,0 +1,279 @@ +# frozen_string_literal: true + +require "open_food_network/reports/line_items" + +module OrderManagement + module Reports + module BulkCoop + class BulkCoopReport + REPORT_TYPES = [ + :bulk_coop_supplier_report, + :bulk_coop_allocation, + :bulk_coop_packing_sheets, + :bulk_coop_customer_payments + ].freeze + + attr_reader :params + def initialize(user, params = {}, render_table = false) + @params = params + @user = user + @render_table = render_table + + @supplier_report = BulkCoopSupplierReport.new + @allocation_report = BulkCoopAllocationReport.new + end + + def header + case params[:report_type] + when "bulk_coop_supplier_report" + @supplier_report.header + when "bulk_coop_allocation" + @allocation_report.header + when "bulk_coop_packing_sheets" + [I18n.t(:report_header_customer), + I18n.t(:report_header_product), + I18n.t(:report_header_variant), + I18n.t(:report_header_sum_total)] + when "bulk_coop_customer_payments" + [I18n.t(:report_header_customer), + I18n.t(:report_header_date_of_order), + I18n.t(:report_header_total_cost), + I18n.t(:report_header_amount_owing), + I18n.t(:report_header_amount_paid)] + else + [I18n.t(:report_header_supplier), + I18n.t(:report_header_product), + I18n.t(:report_header_product), + I18n.t(:report_header_bulk_unit_size), + I18n.t(:report_header_variant), + I18n.t(:report_header_weight), + I18n.t(:report_header_sum_total), + I18n.t(:report_header_sum_max_total), + I18n.t(:report_header_units_required), + I18n.t(:report_header_remainder)] + end + end + + def search + report_line_items.orders + end + + def table_items + return [] unless @render_table + + report_line_items.list(line_item_includes) + end + + def rules + case params[:report_type] + when "bulk_coop_supplier_report" + @supplier_report.rules + when "bulk_coop_allocation" + @allocation_report.rules + when "bulk_coop_packing_sheets" + [{ group_by: proc { |li| li.product }, + sort_by: proc { |product| product.name } }, + { group_by: proc { |li| li.full_name }, + sort_by: proc { |full_name| full_name } }, + { group_by: proc { |li| li.order }, + sort_by: proc { |order| order.to_s } }] + when "bulk_coop_customer_payments" + [{ group_by: proc { |li| li.order }, + sort_by: proc { |order| order.completed_at } }] + else + [{ group_by: proc { |li| li.product.supplier }, + sort_by: proc { |supplier| supplier.name } }, + { group_by: proc { |li| li.product }, + sort_by: proc { |product| product.name }, + summary_columns: [proc { |lis| lis.first.product.supplier.name }, + proc { |lis| lis.first.product.name }, + proc { |lis| lis.first.product.group_buy_unit_size || 0.0 }, + proc { |_lis| "" }, + proc { |_lis| "" }, + proc { |lis| lis.sum { |li| li.quantity * (li.weight_from_unit_value || 0) } }, + proc { |lis| lis.sum { |li| (li.max_quantity || 0) * (li.weight_from_unit_value || 0) } }, + proc { |lis| ( (lis.first.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| [li.max_quantity || 0, li.quantity || 0].max * (li.weight_from_unit_value || 0) } / lis.first.product.group_buy_unit_size ) ).floor }, + proc { |lis| lis.sum { |li| [li.max_quantity || 0, li.quantity || 0].max * (li.weight_from_unit_value || 0) } - ( ( (lis.first.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| [li.max_quantity || 0, li.quantity || 0].max * (li.weight_from_unit_value || 0) } / lis.first.product.group_buy_unit_size ) ).floor * (lis.first.product.group_buy_unit_size || 0) ) }] }, + { group_by: proc { |li| li.full_name }, + sort_by: proc { |full_name| full_name } }] + end + end + + def columns + case params[:report_type] + when "bulk_coop_supplier_report" + @supplier_report.columns + when "bulk_coop_allocation" + @allocation_report.columns + when "bulk_coop_packing_sheets" + [ + :order_billing_address_name, + :product_name, + :full_name, + :total_quantity + ] + when "bulk_coop_customer_payments" + [ + :order_billing_address_name, + :order_completed_at, + :customer_payments_total_cost, + :customer_payments_amount_owed, + :customer_payments_amount_paid + ] + else + [ + :product_supplier_name, + :product_name, + :product_group_buy_unit_size, + :full_name, + :weight_from_unit_value, + :total_quantity, + :total_max_quantity, + :empty_cell, + :empty_cell + ] + end + end + + private + + def line_item_includes + [{ order: [:bill_address], + variant: [{ option_values: :option_type }, { product: :supplier }] }] + end + + def order_permissions + return @order_permissions unless @order_permissions.nil? + + @order_permissions = ::Permissions::Order.new(@user, @params[:q]) + end + + def report_line_items + @report_line_items ||= OpenFoodNetwork::Reports::LineItems.new(order_permissions, @params) + end + + def customer_payments_total_cost(line_items) + line_items.map(&:order).uniq.sum(&:total) + end + + def customer_payments_amount_owed(line_items) + line_items.map(&:order).uniq.sum(&:outstanding_balance) + end + + def customer_payments_amount_paid(line_items) + line_items.map(&:order).uniq.sum(&:payment_total) + end + + def empty_cell(_line_items) + "" + end + + def full_name(line_items) + line_items.first.full_name + end + + def group_buy_unit_size(line_items) + (line_items.first.variant.product.group_buy_unit_size || 0.0) / + (line_items.first.product.variant_unit_scale || 1) + end + + def max_quantity_excess(line_items) + max_quantity_amount(line_items) - total_amount(line_items) + end + + def max_quantity_amount(line_items) + line_items.sum do |line_item| + max_quantity = [line_item.max_quantity || 0, line_item.quantity || 0].max + max_quantity * scaled_unit_value(line_item.variant) + end + end + + def option_value_value(line_items) + OpenFoodNetwork::OptionValueNamer.new(line_items.first).value + end + + def option_value_unit(line_items) + OpenFoodNetwork::OptionValueNamer.new(line_items.first).unit + end + + def order_billing_address_name(line_items) + billing_address = line_items.first.order.bill_address + billing_address.firstname + " " + billing_address.lastname + end + + def order_completed_at(line_items) + line_items.first.order.completed_at.to_s + end + + def product_group_buy_unit_size(line_items) + line_items.first.product.group_buy_unit_size || 0.0 + end + + def product_name(line_items) + line_items.first.product.name + end + + def product_supplier_name(line_items) + line_items.first.product.supplier.name + end + + def remainder(line_items) + remainder = total_available(line_items) - total_amount(line_items) + remainder >= 0 ? remainder : '' + end + + def scaled_final_weight_volume(line_item) + (line_item.final_weight_volume || 0) / (line_item.product.variant_unit_scale || 1) + end + + def scaled_unit_value(variant) + (variant.unit_value || 0) / (variant.product.variant_unit_scale || 1) + end + + def total_amount(line_items) + line_items.sum { |li| scaled_final_weight_volume(li) } + end + + def total_available(line_items) + units_required(line_items) * group_buy_unit_size(line_items) + end + + def total_max_quantity(line_items) + line_items.sum { |line_item| line_item.max_quantity || 0 } + end + + def total_quantity(line_items) + line_items.sum(&:quantity) + end + + def total_label(_line_items) + I18n.t('admin.reports.total') + end + + def units_required(line_items) + if group_buy_unit_size(line_items).zero? + 0 + else + ( total_amount(line_items) / group_buy_unit_size(line_items) ).ceil + end + end + + def variant_product_group_buy_unit_size_f(line_items) + group_buy_unit_size(line_items) + end + + def variant_product_name(line_items) + line_items.first.variant.product.name + end + + def variant_product_supplier_name(line_items) + line_items.first.variant.product.supplier.name + end + + def weight_from_unit_value(line_items) + line_items.first.weight_from_unit_value || 0 + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_supplier_report.rb b/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_supplier_report.rb new file mode 100644 index 0000000000..0f57c58f6a --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/bulk_coop/bulk_coop_supplier_report.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module OrderManagement + module Reports + module BulkCoop + class BulkCoopSupplierReport + def header + [ + I18n.t(:report_header_supplier), + I18n.t(:report_header_product), + I18n.t(:report_header_bulk_unit_size), + I18n.t(:report_header_variant), + I18n.t(:report_header_variant_value), + I18n.t(:report_header_variant_unit), + I18n.t(:report_header_weight), + I18n.t(:report_header_sum_total), + I18n.t(:report_header_units_required), + I18n.t(:report_header_unallocated), + I18n.t(:report_header_max_quantity_excess), + ] + end + + def rules + [ + { group_by: proc { |line_item| line_item.product.supplier }, + sort_by: proc { |supplier| supplier.name } }, + { group_by: proc { |line_item| line_item.product }, + sort_by: proc { |product| product.name }, + summary_columns: [ + :variant_product_supplier_name, + :variant_product_name, + :variant_product_group_buy_unit_size_f, + :empty_cell, + :empty_cell, + :empty_cell, + :empty_cell, + :total_amount, + :units_required, + :remainder, + :max_quantity_excess + ] }, + { group_by: proc { |line_item| line_item.full_name }, + sort_by: proc { |full_name| full_name } } + ] + end + + def columns + [ + :variant_product_supplier_name, + :variant_product_name, + :variant_product_group_buy_unit_size_f, + :full_name, + :option_value_value, + :option_value_unit, + :weight_from_unit_value, + :total_amount, + :empty_cell, + :empty_cell, + :empty_cell + ] + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/bulk_coop/parameters.rb b/engines/order_management/app/services/order_management/reports/bulk_coop/parameters.rb new file mode 100644 index 0000000000..2f5a48d296 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/bulk_coop/parameters.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module OrderManagement + module Reports + module BulkCoop + class Parameters < ::Reports::Parameters::Base + extend ActiveModel::Naming + extend ActiveModel::Translation + include ActiveModel::Validations + + attr_accessor :start_at, :end_at, :distributor_ids, :report_type + + before_validation :cleanup_arrays + + validates :start_at, :end_at, date_time_string: true + validates :distributor_ids, integer_array: true + validates_inclusion_of :report_type, in: BulkCoopReport::REPORT_TYPES.map(&:to_s) + + validate :require_valid_datetime_range + + def initialize(attributes = {}) + self.distributor_ids = [] + + super(attributes) + end + + def authorize!(permissions) + authorizer = Authorizer.new(self, permissions) + authorizer.authorize! + end + + protected + + # Remove the blank strings that Rails multiple selects add by default to + # make sure that blank lists are still submitted to the server as arrays + # instead of nil. + # + # https://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-select + def cleanup_arrays + distributor_ids.reject!(&:blank?) + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/bulk_coop/permissions.rb b/engines/order_management/app/services/order_management/reports/bulk_coop/permissions.rb new file mode 100644 index 0000000000..ca2a38467c --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/bulk_coop/permissions.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module OrderManagement + module Reports + module BulkCoop + class Permissions < ::Reports::Permissions + def allowed_distributors + @allowed_distributors ||= Enterprise.is_distributor.managed_by(user) + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/bulk_coop/renderers/csv_renderer.rb b/engines/order_management/app/services/order_management/reports/bulk_coop/renderers/csv_renderer.rb new file mode 100644 index 0000000000..f848edbe71 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/bulk_coop/renderers/csv_renderer.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module OrderManagement + module Reports + module BulkCoop + module Renderers + class CsvRenderer < ::Reports::Renderers::Base + def render(context) + context.send_data(generate, filename: filename) + end + + def generate + CSV.generate do |csv| + csv << report_data.header + + report_data.list.each do |data| + csv << data + end + end + end + + private + + def filename + timestamp = Time.zone.now.strftime("%Y%m%d") + "#{report_data.parameters[:report_type]}_#{timestamp}.csv" + end + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/bulk_coop/renderers/html_renderer.rb b/engines/order_management/app/services/order_management/reports/bulk_coop/renderers/html_renderer.rb new file mode 100644 index 0000000000..03f7934e06 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/bulk_coop/renderers/html_renderer.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module OrderManagement + module Reports + module BulkCoop + module Renderers + class HtmlRenderer < ::Reports::Renderers::Base + def render(context) + context.instance_variable_set :@renderer, self + context.render(action: :create, renderer: self) + end + + def header + report_data.header + end + + def data_rows + report_data.list + end + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/bulk_coop/report_service.rb b/engines/order_management/app/services/order_management/reports/bulk_coop/report_service.rb new file mode 100644 index 0000000000..ac7f965218 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/bulk_coop/report_service.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'open_food_network/order_grouper' + +module OrderManagement + module Reports + module BulkCoop + class ReportService + attr_accessor :permissions, :parameters, :user + + def initialize(permissions, parameters, user) + @permissions = permissions + @parameters = parameters + @user = user + @report = BulkCoopReport.new(user, parameters, true) + end + + def header + @report.header + end + + def list + order_grouper = OpenFoodNetwork::OrderGrouper.new @report.rules, @report.columns, @report + order_grouper.table(@report.table_items) + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/authorizer.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/authorizer.rb index e129905dc6..51b1393630 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/authorizer.rb +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/authorizer.rb @@ -2,11 +2,6 @@ module OrderManagement module Reports module EnterpriseFeeSummary class Authorizer < ::Reports::Authorizer - def self.parameter_not_allowed_error_message - i18n_scope = "order_management.reports.enterprise_fee_summary" - I18n.t("parameter_not_allowed_error", scope: i18n_scope) - end - def authorize! authorize_by_distribution! authorize_by_fee! @@ -25,14 +20,6 @@ module OrderManagement require_ids_allowed(parameters.shipping_method_ids, permissions.allowed_shipping_methods) require_ids_allowed(parameters.payment_method_ids, permissions.allowed_payment_methods) end - - def require_ids_allowed(array, allowed_objects) - error_klass = ::Reports::Authorizer::ParameterNotAllowedError - error_message = self.class.parameter_not_allowed_error_message - ids_allowed = (array - allowed_objects.map(&:id).map(&:to_s)).blank? - - raise error_klass, error_message unless ids_allowed - end end end end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/parameters.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/parameters.rb index cc12d9be1f..938feeea75 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/parameters.rb +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/parameters.rb @@ -19,11 +19,6 @@ module OrderManagement validate :require_valid_datetime_range - def self.date_end_before_start_error_message - i18n_scope = "order_management.reports.enterprise_fee_summary" - I18n.t("date_end_before_start_error", scope: i18n_scope) - end - def initialize(attributes = {}) self.distributor_ids = [] self.producer_ids = [] @@ -42,13 +37,6 @@ module OrderManagement protected - def require_valid_datetime_range - return if start_at.blank? || end_at.blank? - - error_message = self.class.date_end_before_start_error_message - errors.add(:end_at, error_message) unless start_at < end_at - end - # Remove the blank strings that Rails multiple selects add by default to # make sure that blank lists are still submitted to the server as arrays # instead of nil. diff --git a/engines/order_management/app/services/order_management/stock/adjuster.rb b/engines/order_management/app/services/order_management/stock/adjuster.rb new file mode 100644 index 0000000000..ed1337a16a --- /dev/null +++ b/engines/order_management/app/services/order_management/stock/adjuster.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +# Used by Prioritizer to adjust item quantities +# see prioritizer_spec for use cases +module OrderManagement + module Stock + class Adjuster + attr_accessor :variant, :need, :status + + def initialize(variant, quantity, status) + @variant = variant + @need = quantity + @status = status + end + + def adjust(item) + if item.quantity >= need + item.quantity = need + @need = 0 + elsif item.quantity < need + @need -= item.quantity + end + end + + def fulfilled? + @need.zero? + end + end + end +end diff --git a/engines/order_management/app/services/order_management/stock/coordinator.rb b/engines/order_management/app/services/order_management/stock/coordinator.rb new file mode 100644 index 0000000000..64bbbc8cbc --- /dev/null +++ b/engines/order_management/app/services/order_management/stock/coordinator.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module OrderManagement + module Stock + class Coordinator + attr_reader :order + + def initialize(order) + @order = order + end + + def packages + packages = build_packages + packages = prioritize_packages(packages) + estimate_packages(packages) + end + + # Build package with default stock location + # No need to check items are in the stock location, + # there is only one stock location so the items will be on that stock location. + # + # Returns an array with a single Package for the default stock location + def build_packages + packer = build_packer(order) + [packer.package] + end + + private + + def prioritize_packages(packages) + prioritizer = OrderManagement::Stock::Prioritizer.new(order, packages) + prioritizer.prioritized_packages + end + + def estimate_packages(packages) + estimator = OrderManagement::Stock::Estimator.new(order) + packages.each do |package| + package.shipping_rates = estimator.shipping_rates(package) + end + packages + end + + def build_packer(order) + stock_location = DefaultStockLocation.find_or_create + OrderManagement::Stock::Packer.new(stock_location, order) + end + end + end +end diff --git a/engines/order_management/app/services/order_management/stock/estimator.rb b/engines/order_management/app/services/order_management/stock/estimator.rb new file mode 100644 index 0000000000..c97d9c3b99 --- /dev/null +++ b/engines/order_management/app/services/order_management/stock/estimator.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module OrderManagement + module Stock + class Estimator + attr_reader :order, :currency + + def initialize(order) + @order = order + @currency = order.currency + end + + def shipping_rates(package, frontend_only = true) + shipping_rates = [] + shipping_methods = shipping_methods(package) + return [] unless shipping_methods + + shipping_methods.each do |shipping_method| + cost = calculate_cost(shipping_method, package) + shipping_rates << shipping_method.shipping_rates.new(cost: cost) unless cost.nil? + end + + shipping_rates.sort_by! { |r| r.cost || 0 } + + unless shipping_rates.empty? + if frontend_only + shipping_rates.each do |rate| + if rate.shipping_method.frontend? + rate.selected = true + break + end + end + else + shipping_rates.first.selected = true + end + end + + shipping_rates + end + + private + + def shipping_methods(package) + shipping_methods = package.shipping_methods + shipping_methods.delete_if { |ship_method| !ship_method.calculator.available?(package) } + shipping_methods.delete_if { |ship_method| !ship_method.include?(order.ship_address) } + shipping_methods.delete_if { |ship_method| + !(ship_method.calculator.preferences[:currency].nil? || + ship_method.calculator.preferences[:currency] == currency) + } + shipping_methods + end + + def calculate_cost(shipping_method, package) + shipping_method.calculator.compute(package) + end + end + end +end diff --git a/engines/order_management/app/services/order_management/stock/package.rb b/engines/order_management/app/services/order_management/stock/package.rb new file mode 100644 index 0000000000..f3bfaf38f0 --- /dev/null +++ b/engines/order_management/app/services/order_management/stock/package.rb @@ -0,0 +1,141 @@ +# frozen_string_literal: true + +module OrderManagement + module Stock + class Package + ContentItem = Struct.new(:variant, :quantity, :state) + + attr_reader :stock_location, :order, :contents + attr_accessor :shipping_rates + + def initialize(stock_location, order, contents = []) + @stock_location = stock_location + @order = order + @contents = contents + @shipping_rates = [] + end + + def add(variant, quantity, state = :on_hand) + contents << ContentItem.new(variant, quantity, state) + end + + def weight + contents.sum { |item| item.variant.weight * item.quantity } + end + + def on_hand + contents.select { |item| item.state == :on_hand } + end + + def backordered + contents.select { |item| item.state == :backordered } + end + + def find_item(variant, state = :on_hand) + contents.select do |item| + item.variant == variant && + item.state == state + end.first + end + + def quantity(state = nil) + case state + when :on_hand + on_hand.sum(&:quantity) + when :backordered + backordered.sum(&:quantity) + else + contents.sum(&:quantity) + end + end + + def empty? + quantity.zero? + end + + def flattened + flat = [] + contents.each do |item| + item.quantity.times do + flat << ContentItem.new(item.variant, 1, item.state) + end + end + flat + end + + def flattened=(flattened) + contents.clear + flattened.each do |item| + current_item = find_item(item.variant, item.state) + if current_item + current_item.quantity += 1 + else + add(item.variant, item.quantity, item.state) + end + end + end + + def currency + # TODO calculate from first variant? + end + + # Returns all existing shipping categories. + # It disables the matching of product shipping category with shipping method's category + # It allows checkout of products with categories that are not the ship method's categories + # + # @return [Array] + def shipping_categories + Spree::ShippingCategory.all + end + + # Skips the methods that are not used by the order's distributor + # + # @return [Array] + def shipping_methods + available_shipping_methods = shipping_categories.flat_map(&:shipping_methods).uniq.to_a + + available_shipping_methods.keep_if do |shipping_method| + ships_with?(order.distributor.shipping_methods.to_a, shipping_method) + end + end + + def inspect + out = "#{order} - " + out << contents.map do |content_item| + "#{content_item.variant.name} #{content_item.quantity} #{content_item.state}" + end.join('/') + out + end + + def to_shipment + shipment = Spree::Shipment.new + shipment.order = order + shipment.stock_location = stock_location + shipment.shipping_rates = shipping_rates + + contents.each do |item| + item.quantity.times do + unit = shipment.inventory_units.build + unit.pending = true + unit.order = order + unit.variant = item.variant + unit.state = item.state.to_s + end + end + + shipment + end + + private + + # Checks whether the given distributor provides the specified shipping method + # + # @param shipping_methods [Array] + # @param shipping_method [Spree::ShippingMethod] + # @return [Boolean] + def ships_with?(shipping_methods, shipping_method) + shipping_methods.include?(shipping_method) + end + end + end +end diff --git a/engines/order_management/app/services/order_management/stock/packer.rb b/engines/order_management/app/services/order_management/stock/packer.rb new file mode 100644 index 0000000000..49141d3fef --- /dev/null +++ b/engines/order_management/app/services/order_management/stock/packer.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module OrderManagement + module Stock + class Packer + attr_reader :stock_location, :order + + def initialize(stock_location, order) + @stock_location = stock_location + @order = order + end + + def package + package = OrderManagement::Stock::Package.new(stock_location, order) + order.line_items.each do |line_item| + next unless stock_location.stock_item(line_item.variant) + + on_hand, backordered = stock_location.fill_status(line_item.variant, line_item.quantity) + package.add line_item.variant, on_hand, :on_hand if on_hand.positive? + package.add line_item.variant, backordered, :backordered if backordered.positive? + end + package + end + end + end +end diff --git a/engines/order_management/app/services/order_management/stock/prioritizer.rb b/engines/order_management/app/services/order_management/stock/prioritizer.rb new file mode 100644 index 0000000000..9e8eea4289 --- /dev/null +++ b/engines/order_management/app/services/order_management/stock/prioritizer.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module OrderManagement + module Stock + class Prioritizer + attr_reader :packages, :order + + def initialize(order, packages, adjuster_class = OrderManagement::Stock::Adjuster) + @order = order + @packages = packages + @adjuster_class = adjuster_class + end + + def prioritized_packages + adjust_packages + prune_packages + packages + end + + private + + def adjust_packages + order.line_items.each do |line_item| + adjuster = @adjuster_class.new(line_item.variant, line_item.quantity, :on_hand) + + visit_packages(adjuster) + + adjuster.status = :backordered + visit_packages(adjuster) + end + end + + def visit_packages(adjuster) + packages.each do |package| + item = package.find_item adjuster.variant, adjuster.status + adjuster.adjust(item) if item + end + end + + def prune_packages + packages.reject!(&:empty?) + end + end + end +end diff --git a/engines/order_management/app/services/reports/authorizer.rb b/engines/order_management/app/services/reports/authorizer.rb index 279952249a..1f232b0160 100644 --- a/engines/order_management/app/services/reports/authorizer.rb +++ b/engines/order_management/app/services/reports/authorizer.rb @@ -8,5 +8,20 @@ module Reports @parameters = parameters @permissions = permissions end + + def self.parameter_not_allowed_error_message + i18n_scope = "order_management.reports.enterprise_fee_summary" + I18n.t("parameter_not_allowed_error", scope: i18n_scope) + end + + private + + def require_ids_allowed(array, allowed_objects) + error_klass = ::Reports::Authorizer::ParameterNotAllowedError + error_message = self.class.parameter_not_allowed_error_message + ids_allowed = (array - allowed_objects.map(&:id).map(&:to_s)).blank? + + raise error_klass, error_message unless ids_allowed + end end end diff --git a/engines/order_management/app/services/reports/parameters/base.rb b/engines/order_management/app/services/reports/parameters/base.rb index 8debc06b1d..31148fd47c 100644 --- a/engines/order_management/app/services/reports/parameters/base.rb +++ b/engines/order_management/app/services/reports/parameters/base.rb @@ -12,8 +12,22 @@ module Reports end end + def self.date_end_before_start_error_message + i18n_scope = "order_management.reports.enterprise_fee_summary" + I18n.t("date_end_before_start_error", scope: i18n_scope) + end + # The parameters are never persisted. def to_key; end + + protected + + def require_valid_datetime_range + return if start_at.blank? || end_at.blank? + + error_message = self.class.date_end_before_start_error_message + errors.add(:end_at, error_message) unless start_at < end_at + end end end end diff --git a/engines/order_management/app/views/order_management/reports/enterprise_fee_summaries/_report.html.haml b/engines/order_management/app/views/order_management/reports/_report.html.haml similarity index 100% rename from engines/order_management/app/views/order_management/reports/enterprise_fee_summaries/_report.html.haml rename to engines/order_management/app/views/order_management/reports/_report.html.haml diff --git a/engines/order_management/app/views/order_management/reports/bulk_coop/_filters.html.haml b/engines/order_management/app/views/order_management/reports/bulk_coop/_filters.html.haml new file mode 100644 index 0000000000..012d910070 --- /dev/null +++ b/engines/order_management/app/views/order_management/reports/bulk_coop/_filters.html.haml @@ -0,0 +1,31 @@ += form_for @report_parameters, as: :report, url: main_app.order_management_reports_bulk_coop_path, method: :post do |f| + .row.date-range-filter + .sixteen.columns.alpha + = label_tag nil, t(".date_range") + %br + + = f.label :start_at, class: "inline" + = f.text_field :start_at, class: "datetimepicker datepicker-from" + + %span.range-divider + %i.icon-arrow-right + + = f.text_field :end_at, class: "datetimepicker datepicker-to" + = f.label :end_at, class: "inline" + + .row + .sixteen.columns.alpha + = f.label :distributor_ids + = f.collection_select(:distributor_ids, @permissions.allowed_distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true}) + + .row + .sixteen.columns.alpha + = f.label :report_type + = f.collection_select(:report_type, OrderManagement::Reports::BulkCoop::BulkCoopReport::REPORT_TYPES.map { |report_type| [t(".#{report_type}"), report_type] }, :last, :first, {}, {class: "select2 fullwidth", multiple: false}) + + .row + .sixteen.columns.alpha + = check_box_tag :report_format, "csv", false, id: "report_format_csv" + = label_tag :report_format_csv, t(".report_format_csv") + + = button t(".generate_report") diff --git a/engines/order_management/app/views/order_management/reports/bulk_coop/_report.html.haml b/engines/order_management/app/views/order_management/reports/bulk_coop/_report.html.haml new file mode 100644 index 0000000000..c4e2e5fc76 --- /dev/null +++ b/engines/order_management/app/views/order_management/reports/bulk_coop/_report.html.haml @@ -0,0 +1,20 @@ +- if @report.present? + %table#bulk_coop_report.report__table + %thead + %tr + - @renderer.header.each do |heading| + %th= heading + + %tbody + - @renderer.data_rows.each do |row| + %tr + - row.each do |cell_value| + %td= cell_value + + - if @renderer.data_rows.empty? + %tr + %td{colspan: @renderer.header.length}= t('.none') +- else + %p.report__message + = t(".select_and_search") + diff --git a/engines/order_management/app/views/order_management/reports/bulk_coop/create.html.haml b/engines/order_management/app/views/order_management/reports/bulk_coop/create.html.haml new file mode 100644 index 0000000000..06938dd67d --- /dev/null +++ b/engines/order_management/app/views/order_management/reports/bulk_coop/create.html.haml @@ -0,0 +1,2 @@ += render "filters" += render "order_management/reports/report" diff --git a/engines/order_management/app/views/order_management/reports/bulk_coop/new.html.haml b/engines/order_management/app/views/order_management/reports/bulk_coop/new.html.haml new file mode 100644 index 0000000000..790853ca1f --- /dev/null +++ b/engines/order_management/app/views/order_management/reports/bulk_coop/new.html.haml @@ -0,0 +1 @@ += render "filters" diff --git a/engines/order_management/app/views/order_management/reports/enterprise_fee_summaries/create.html.haml b/engines/order_management/app/views/order_management/reports/enterprise_fee_summaries/create.html.haml index 6d8f0c79ab..06938dd67d 100644 --- a/engines/order_management/app/views/order_management/reports/enterprise_fee_summaries/create.html.haml +++ b/engines/order_management/app/views/order_management/reports/enterprise_fee_summaries/create.html.haml @@ -1,2 +1,2 @@ = render "filters" -= render "report" += render "order_management/reports/report" diff --git a/engines/order_management/config/routes.rb b/engines/order_management/config/routes.rb index 79706c635a..405219634b 100644 --- a/engines/order_management/config/routes.rb +++ b/engines/order_management/config/routes.rb @@ -1,6 +1,7 @@ Openfoodnetwork::Application.routes.prepend do namespace :order_management do namespace :reports do + resource :bulk_coop, only: [:new, :create], controller: :bulk_coop resource :enterprise_fee_summary, only: [:new, :create] end end diff --git a/engines/order_management/spec/controllers/order_management/reports/bulk_coop_controller_spec.rb b/engines/order_management/spec/controllers/order_management/reports/bulk_coop_controller_spec.rb new file mode 100644 index 0000000000..4476edd9cf --- /dev/null +++ b/engines/order_management/spec/controllers/order_management/reports/bulk_coop_controller_spec.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe OrderManagement::Reports::BulkCoopController, type: :controller do + let(:report_klass) { OrderManagement::Reports::BulkCoop } + + let!(:distributor) { create(:distributor_enterprise) } + + let(:current_user) { distributor.owner } + + before do + allow(controller).to receive(:spree_current_user) { current_user } + end + + describe "#new" do + it "renders the report form" do + get :new + + expect(response).to be_success + expect(response).to render_template(new_template_path) + end + end + + describe "#create" do + context "when the parameters are valid" do + it "sends the generated report in the correct format" do + post :create, report: { + start_at: "2018-10-09 07:30:00", + report_type: "bulk_coop_supplier_report" + }, report_format: "csv" + + expect(response).to be_success + expect(response.body).not_to be_blank + expect(response.header["Content-Type"]).to eq("text/csv") + end + end + + context "when the parameters are invalid" do + it "renders the report form with an error" do + post :create, report: { + start_at: "invalid_date", + report_type: "bulk_coop_supplier_report" + }, report_format: "csv" + + expect(flash[:error]).to eq(I18n.t("invalid_filter_parameters", scope: i18n_scope)) + expect(response).to render_template(new_template_path) + end + end + + context "when some parameters are now allowed" do + let!(:distributor) { create(:distributor_enterprise) } + let!(:other_distributor) { create(:distributor_enterprise) } + + let(:current_user) { distributor.owner } + + it "renders the report form with an error" do + post :create, report: { + distributor_ids: [other_distributor.id], + report_type: "bulk_coop_supplier_report" + }, report_format: "csv" + + expect(flash[:error]).to eq(report_klass::Authorizer.parameter_not_allowed_error_message) + expect(response).to render_template(new_template_path) + end + end + + describe "filtering results based on permissions" do + let!(:distributor) { create(:distributor_enterprise) } + let!(:other_distributor) { create(:distributor_enterprise) } + + let(:current_user) { distributor.owner } + + it "applies permissions to report" do + post :create, report: {}, report_format: "csv" + + expect(assigns(:permissions).allowed_distributors.to_a).to eq([distributor]) + end + end + end + + private + + def default_report_params + { + report_type: "bulk_coop_supplier_report" + } + end + + def i18n_scope + "order_management.reports.enterprise_fee_summary" + end + + def new_template_path + "order_management/reports/bulk_coop/new" + end +end diff --git a/engines/order_management/spec/features/order_management/reports/bulk_coop_spec.rb b/engines/order_management/spec/features/order_management/reports/bulk_coop_spec.rb new file mode 100644 index 0000000000..29caf17a15 --- /dev/null +++ b/engines/order_management/spec/features/order_management/reports/bulk_coop_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "spec_helper" + +feature "bulk coop" do + include AuthenticationWorkflow + include WebHelper + + scenario "bulk co-op report" do + quick_login_as_admin + visit spree.admin_reports_path + click_link 'Bulk Co-Op' + click_button 'Generate Report' + + expect(page).to have_content 'Supplier' + end +end diff --git a/engines/order_management/spec/services/order_management/reports/bulk_coop/bulk_coop_report_spec.rb b/engines/order_management/spec/services/order_management/reports/bulk_coop/bulk_coop_report_spec.rb new file mode 100644 index 0000000000..8f45355ee6 --- /dev/null +++ b/engines/order_management/spec/services/order_management/reports/bulk_coop/bulk_coop_report_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe OrderManagement::Reports::BulkCoop::BulkCoopReport do + describe "fetching orders" do + let(:d1) { create(:distributor_enterprise) } + let(:oc1) { create(:simple_order_cycle) } + let(:o1) { create(:order, completed_at: 1.day.ago, order_cycle: oc1, distributor: d1) } + let(:li1) { build(:line_item_with_shipment) } + + before { o1.line_items << li1 } + + context "as a site admin" do + let(:user) { create(:admin_user) } + subject { OrderManagement::Reports::BulkCoop::BulkCoopReport.new user, {}, true } + + it "fetches completed orders" do + o2 = create(:order) + o2.line_items << build(:line_item) + expect(subject.table_items).to eq([li1]) + end + + it "does not show cancelled orders" do + o2 = create(:order, state: "canceled", completed_at: 1.day.ago) + o2.line_items << build(:line_item_with_shipment) + expect(subject.table_items).to eq([li1]) + end + end + + context "as a manager of a supplier" do + let!(:user) { create(:user) } + subject { OrderManagement::Reports::BulkCoop::BulkCoopReport.new user, {}, true } + + let(:s1) { create(:supplier_enterprise) } + + before do + s1.enterprise_roles.create!(user: user) + end + + context "that has granted P-OC to the distributor" do + let(:o2) do + create(:order, distributor: d1, completed_at: 1.day.ago, bill_address: create(:address), + ship_address: create(:address)) + end + let(:li2) do + build(:line_item_with_shipment, product: create(:simple_product, supplier: s1)) + end + + before do + o2.line_items << li2 + create(:enterprise_relationship, parent: s1, child: d1, + permissions_list: [:add_to_order_cycle]) + end + + it "shows line items supplied by my producers, with names hidden" do + expect(subject.table_items).to eq([li2]) + expect(subject.table_items.first.order.bill_address.firstname).to eq("HIDDEN") + end + end + + context "that has not granted P-OC to the distributor" do + let(:o2) do + create(:order, distributor: d1, completed_at: 1.day.ago, bill_address: create(:address), + ship_address: create(:address)) + end + let(:li2) do + build(:line_item_with_shipment, product: create(:simple_product, supplier: s1)) + end + + before do + o2.line_items << li2 + end + + it "does not show line items supplied by my producers" do + expect(subject.table_items).to eq([]) + end + end + end + end +end diff --git a/engines/order_management/spec/services/order_management/stock/coordinator_spec.rb b/engines/order_management/spec/services/order_management/stock/coordinator_spec.rb new file mode 100644 index 0000000000..c2ab44e5f7 --- /dev/null +++ b/engines/order_management/spec/services/order_management/stock/coordinator_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module OrderManagement + module Stock + describe Coordinator do + let!(:order) { create(:order_with_line_items, distributor: create(:distributor_enterprise)) } + + subject { Coordinator.new(order) } + + context "packages" do + it "builds, prioritizes and estimates" do + expect(subject).to receive(:build_packages).ordered + expect(subject).to receive(:prioritize_packages).ordered + expect(subject).to receive(:estimate_packages).ordered + subject.packages + end + end + end + end +end diff --git a/engines/order_management/spec/services/order_management/stock/estimator_spec.rb b/engines/order_management/spec/services/order_management/stock/estimator_spec.rb new file mode 100644 index 0000000000..6d14903cbd --- /dev/null +++ b/engines/order_management/spec/services/order_management/stock/estimator_spec.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module OrderManagement + module Stock + describe Estimator do + let!(:shipping_method) { create(:shipping_method, zones: [Spree::Zone.global] ) } + let(:package) { build(:stock_package_fulfilled) } + let(:order) { package.order } + subject { Estimator.new(order) } + + context "#shipping rates" do + before(:each) do + shipping_method.zones.first.members.create(zoneable: order.ship_address.country) + allow_any_instance_of(Spree::ShippingMethod). + to receive_message_chain(:calculator, :available?).and_return(true) + allow_any_instance_of(Spree::ShippingMethod). + to receive_message_chain(:calculator, :compute).and_return(4.00) + allow_any_instance_of(Spree::ShippingMethod). + to receive_message_chain(:calculator, :preferences). + and_return(currency: order.currency) + allow_any_instance_of(Spree::ShippingMethod). + to receive_message_chain(:calculator, :marked_for_destruction?) + + allow(package).to receive_messages(shipping_methods: [shipping_method]) + end + + context "the order's ship address is in the same zone" do + it "returns shipping rates from a shipping method" do + shipping_rates = subject.shipping_rates(package) + expect(shipping_rates.first.cost).to eq 4.00 + end + end + + context "the order's ship address is in a different zone" do + it "still returns shipping rates from a shipping method" do + shipping_method.zones.each{ |z| z.members.delete_all } + shipping_rates = subject.shipping_rates(package) + expect(shipping_rates.first.cost).to eq 4.00 + end + end + + context "the calculator is not available for that order" do + it "does not return shipping rates from a shipping method" do + allow_any_instance_of(Spree::ShippingMethod). + to receive_message_chain(:calculator, :available?).and_return(false) + shipping_rates = subject.shipping_rates(package) + expect(shipping_rates).to eq [] + end + end + + context "the currency matches the order's currency" do + it "returns shipping rates from a shipping method" do + shipping_rates = subject.shipping_rates(package) + expect(shipping_rates.first.cost).to eq 4.00 + end + end + + context "the currency is different than the order's currency" do + it "does not return shipping rates from a shipping method" do + order.currency = "USD" + shipping_rates = subject.shipping_rates(package) + expect(shipping_rates).to eq [] + end + end + + it "sorts shipping rates by cost" do + shipping_methods = 3.times.map { create(:shipping_method) } + allow(shipping_methods[0]). + to receive_message_chain(:calculator, :compute).and_return(5.00) + allow(shipping_methods[1]). + to receive_message_chain(:calculator, :compute).and_return(3.00) + allow(shipping_methods[2]). + to receive_message_chain(:calculator, :compute).and_return(4.00) + + allow(subject).to receive(:shipping_methods).and_return(shipping_methods) + + expected_costs = %w[3.00 4.00 5.00].map(&BigDecimal.method(:new)) + expect(subject.shipping_rates(package).map(&:cost)).to eq expected_costs + end + + context "general shipping methods" do + let(:shipping_methods) { 2.times.map { create(:shipping_method) } } + + it "selects the most affordable shipping rate" do + allow(shipping_methods[0]). + to receive_message_chain(:calculator, :compute).and_return(5.00) + allow(shipping_methods[1]). + to receive_message_chain(:calculator, :compute).and_return(3.00) + + allow(subject).to receive(:shipping_methods).and_return(shipping_methods) + + shipping_rates = subject.shipping_rates(package) + expect(shipping_rates.sort_by(&:cost).map(&:selected)).to eq [true, false] + end + + it "selects the cheapest shipping rate and doesn't raise exception over nil cost" do + allow(shipping_methods[0]). + to receive_message_chain(:calculator, :compute).and_return(1.00) + allow(shipping_methods[1]). + to receive_message_chain(:calculator, :compute).and_return(nil) + + allow(subject).to receive(:shipping_methods).and_return(shipping_methods) + + subject.shipping_rates(package) + end + end + + context "involves backend only shipping methods" do + let(:backend_method) { create(:shipping_method, display_on: "back_end") } + let(:generic_method) { create(:shipping_method) } + + # regression for #3287 + it "doesn't select backend rates even if they're more affordable" do + allow(backend_method).to receive_message_chain(:calculator, :compute).and_return(0.00) + allow(generic_method).to receive_message_chain(:calculator, :compute).and_return(5.00) + + allow(subject). + to receive(:shipping_methods).and_return([backend_method, generic_method]) + + expect(subject.shipping_rates(package).map(&:selected)).to eq [false, true] + end + end + end + end + end +end diff --git a/engines/order_management/spec/services/order_management/stock/package_spec.rb b/engines/order_management/spec/services/order_management/stock/package_spec.rb new file mode 100644 index 0000000000..205d50ae1a --- /dev/null +++ b/engines/order_management/spec/services/order_management/stock/package_spec.rb @@ -0,0 +1,173 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module OrderManagement + module Stock + describe Package do + context "base tests" do + let(:variant) { build(:variant, weight: 25.0) } + let(:stock_location) { build(:stock_location) } + let(:distributor) { create(:enterprise) } + let(:order) { build(:order, distributor: distributor) } + + subject { Package.new(stock_location, order) } + + it 'calculates the weight of all the contents' do + subject.add variant, 4 + expect(subject.weight).to eq 100.0 + end + + it 'filters by on_hand and backordered' do + subject.add variant, 4, :on_hand + subject.add variant, 3, :backordered + expect(subject.on_hand.count).to eq 1 + expect(subject.backordered.count).to eq 1 + end + + it 'calculates the quantity by state' do + subject.add variant, 4, :on_hand + subject.add variant, 3, :backordered + + expect(subject.quantity).to eq 7 + expect(subject.quantity(:on_hand)).to eq 4 + expect(subject.quantity(:backordered)).to eq 3 + end + + it 'returns nil for content item not found' do + item = subject.find_item(variant, :on_hand) + expect(item).to be_nil + end + + it 'finds content item for a variant' do + subject.add variant, 4, :on_hand + item = subject.find_item(variant, :on_hand) + expect(item.quantity).to eq 4 + end + + it 'get flattened contents' do + subject.add variant, 4, :on_hand + subject.add variant, 2, :backordered + flattened = subject.flattened + expect(flattened.select { |i| i.state == :on_hand }.size).to eq 4 + expect(flattened.select { |i| i.state == :backordered }.size).to eq 2 + end + + it 'set contents from flattened' do + flattened = [Package::ContentItem.new(variant, 1, :on_hand), + Package::ContentItem.new(variant, 1, :on_hand), + Package::ContentItem.new(variant, 1, :backordered), + Package::ContentItem.new(variant, 1, :backordered)] + + subject.flattened = flattened + expect(subject.on_hand.size).to eq 1 + expect(subject.on_hand.first.quantity).to eq 2 + + expect(subject.backordered.size).to eq 1 + end + + # Contains regression test for #2804 + it 'builds a list of shipping methods from all categories' do + shipping_method1 = create(:shipping_method, distributors: [distributor]) + shipping_method2 = create(:shipping_method, distributors: [distributor]) + variant1 = create(:variant, + shipping_category: shipping_method1.shipping_categories.first) + variant2 = create(:variant, + shipping_category: shipping_method2.shipping_categories.first) + variant3 = create(:variant, shipping_category: nil) + contents = [Package::ContentItem.new(variant1, 1), + Package::ContentItem.new(variant1, 1), + Package::ContentItem.new(variant2, 1), + Package::ContentItem.new(variant3, 1)] + + package = Package.new(stock_location, order, contents) + expect(package.shipping_methods.size).to eq 2 + end + + it "can convert to a shipment" do + flattened = [Package::ContentItem.new(variant, 2, :on_hand), + Package::ContentItem.new(variant, 1, :backordered)] + subject.flattened = flattened + + shipping_method = build(:shipping_method) + subject.shipping_rates = [ + Spree::ShippingRate.new(shipping_method: shipping_method, cost: 10.00, selected: true) + ] + + shipment = subject.to_shipment + expect(shipment.order).to eq subject.order + expect(shipment.stock_location).to eq subject.stock_location + expect(shipment.inventory_units.size).to eq 3 + + first_unit = shipment.inventory_units.first + expect(first_unit.variant).to eq variant + expect(first_unit.state).to eq 'on_hand' + expect(first_unit.order).to eq subject.order + expect(first_unit).to be_pending + + last_unit = shipment.inventory_units.last + expect(last_unit.variant).to eq variant + expect(last_unit.state).to eq 'backordered' + expect(last_unit.order).to eq subject.order + + expect(shipment.shipping_method).to eq shipping_method + end + end + + context "#shipping_methods and #shipping_categories" do + let(:stock_location) { double(:stock_location) } + + subject(:package) { Package.new(stock_location, order, contents) } + + let(:enterprise) { create(:enterprise) } + let(:other_enterprise) { create(:enterprise) } + + let(:order) { build(:order, distributor: enterprise) } + + let(:variant1) do + instance_double( + Spree::Variant, + shipping_category: shipping_method1.shipping_categories.first + ) + end + let(:variant2) do + instance_double( + Spree::Variant, + shipping_category: shipping_method2.shipping_categories.first + ) + end + let(:variant3) do + instance_double(Spree::Variant, shipping_category: nil) + end + + let(:contents) do + [ + Package::ContentItem.new(variant1, 1), + Package::ContentItem.new(variant1, 1), + Package::ContentItem.new(variant2, 1), + Package::ContentItem.new(variant3, 1) + ] + end + + let(:shipping_method1) { create(:shipping_method, distributors: [enterprise]) } + let(:shipping_method2) { create(:shipping_method, distributors: [other_enterprise]) } + + describe '#shipping_methods' do + it 'does not return shipping methods not used by the package\'s order distributor' do + expect(package.shipping_methods).to eq [shipping_method1] + end + end + + describe '#shipping_categories' do + it "returns ship categories that are not the ship categories of the order's products" do + package + other_shipping_category = Spree::ShippingCategory.create(name: "Custom") + + expect(package.shipping_categories).to eq [shipping_method1.shipping_categories.first, + other_shipping_category] + end + end + end + end + end +end diff --git a/engines/order_management/spec/services/order_management/stock/packer_spec.rb b/engines/order_management/spec/services/order_management/stock/packer_spec.rb new file mode 100644 index 0000000000..1100090af0 --- /dev/null +++ b/engines/order_management/spec/services/order_management/stock/packer_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module OrderManagement + module Stock + describe Packer do + let(:order) { create(:order_with_line_items, line_items_count: 5) } + let(:stock_location) { create(:stock_location) } + + subject { Packer.new(stock_location, order) } + + before { order.line_items.first.variant.update(weight: 1) } + + it 'builds a package with all the items' do + package = subject.package + + expect(package.contents.size).to eq 5 + expect(package.weight).to be_positive + end + + it 'variants are added as backordered without enough on_hand' do + expect(stock_location).to receive(:fill_status).exactly(5).times.and_return([2, 3]) + + package = subject.package + expect(package.on_hand.size).to eq 5 + expect(package.backordered.size).to eq 5 + end + end + end +end diff --git a/engines/order_management/spec/services/order_management/stock/prioritizer_spec.rb b/engines/order_management/spec/services/order_management/stock/prioritizer_spec.rb new file mode 100644 index 0000000000..b1783eb734 --- /dev/null +++ b/engines/order_management/spec/services/order_management/stock/prioritizer_spec.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module OrderManagement + module Stock + describe Prioritizer do + let(:order) { create(:order_with_line_items, line_items_count: 2) } + let(:stock_location) { build(:stock_location) } + let(:variant1) { order.line_items[0].variant } + let(:variant2) { order.line_items[1].variant } + + def pack + package = Package.new(order, stock_location) + yield(package) if block_given? + package + end + + it 'keeps a single package' do + package1 = pack do |package| + package.add variant1, 1, :on_hand + package.add variant2, 1, :on_hand + end + + packages = [package1] + prioritizer = Prioritizer.new(order, packages) + packages = prioritizer.prioritized_packages + expect(packages.size).to eq 1 + end + + it 'removes duplicate packages' do + package1 = pack do |package| + package.add variant1, 1, :on_hand + package.add variant2, 1, :on_hand + end + package2 = pack do |package| + package.add variant1, 1, :on_hand + package.add variant2, 1, :on_hand + end + + packages = [package1, package2] + prioritizer = Prioritizer.new(order, packages) + packages = prioritizer.prioritized_packages + expect(packages.size).to eq 1 + end + + it 'split over 2 packages' do + package1 = pack do |package| + package.add variant1, 1, :on_hand + end + package2 = pack do |package| + package.add variant2, 1, :on_hand + end + + packages = [package1, package2] + prioritizer = Prioritizer.new(order, packages) + packages = prioritizer.prioritized_packages + expect(packages.size).to eq 2 + end + + it '1st has some, 2nd has remaining' do + allow(order.line_items[0]).to receive_messages(quantity: 5) + package1 = pack do |package| + package.add variant1, 2, :on_hand + end + package2 = pack do |package| + package.add variant1, 5, :on_hand + end + + packages = [package1, package2] + prioritizer = Prioritizer.new(order, packages) + packages = prioritizer.prioritized_packages + expect(packages.count).to eq 2 + expect(packages[0].quantity).to eq 2 + expect(packages[1].quantity).to eq 3 + end + + it '1st has backorder, 2nd has some' do + allow(order.line_items[0]).to receive_messages(quantity: 5) + package1 = pack do |package| + package.add variant1, 5, :backordered + end + package2 = pack do |package| + package.add variant1, 2, :on_hand + end + + packages = [package1, package2] + prioritizer = Prioritizer.new(order, packages) + packages = prioritizer.prioritized_packages + + expect(packages[0].quantity(:backordered)).to eq 3 + expect(packages[1].quantity(:on_hand)).to eq 2 + end + + it '1st has backorder, 2nd has all' do + allow(order.line_items[0]).to receive_messages(quantity: 5) + package1 = pack do |package| + package.add variant1, 3, :backordered + package.add variant2, 1, :on_hand + end + package2 = pack do |package| + package.add variant1, 5, :on_hand + end + + packages = [package1, package2] + prioritizer = Prioritizer.new(order, packages) + packages = prioritizer.prioritized_packages + expect(packages[0].quantity(:backordered)).to eq 0 + expect(packages[1].quantity(:on_hand)).to eq 5 + end + end + end +end diff --git a/engines/order_management/spec/services/order_management/subscriptions/estimator_spec.rb b/engines/order_management/spec/services/order_management/subscriptions/estimator_spec.rb index 9e7bd15c06..ad14253a98 100644 --- a/engines/order_management/spec/services/order_management/subscriptions/estimator_spec.rb +++ b/engines/order_management/spec/services/order_management/subscriptions/estimator_spec.rb @@ -99,11 +99,11 @@ module OrderManagement context "using flat rate calculators" do let(:shipping_method) { create(:shipping_method, - calculator: Spree::Calculator::FlatRate.new(preferred_amount: 12.34)) + calculator: Calculator::FlatRate.new(preferred_amount: 12.34)) } let(:payment_method) { create(:payment_method, - calculator: Spree::Calculator::FlatRate.new(preferred_amount: 9.12)) + calculator: Calculator::FlatRate.new(preferred_amount: 9.12)) } it "calculates fees based on the rates provided" do @@ -116,13 +116,13 @@ module OrderManagement context "using flat percent item total calculators" do let(:shipping_method) { create(:shipping_method, - calculator: Spree::Calculator::FlatPercentItemTotal.new( + calculator: Calculator::FlatPercentItemTotal.new( preferred_flat_percent: 10 )) } let(:payment_method) { create(:payment_method, - calculator: Spree::Calculator::FlatPercentItemTotal.new( + calculator: Calculator::FlatPercentItemTotal.new( preferred_flat_percent: 20 )) } @@ -154,11 +154,11 @@ module OrderManagement context "using per item calculators" do let(:shipping_method) { create(:shipping_method, - calculator: Spree::Calculator::PerItem.new(preferred_amount: 1.2)) + calculator: Calculator::PerItem.new(preferred_amount: 1.2)) } let(:payment_method) { create(:payment_method, - calculator: Spree::Calculator::PerItem.new(preferred_amount: 0.3)) + calculator: Calculator::PerItem.new(preferred_amount: 0.3)) } it "calculates fees based on the number of items and rate provided" do diff --git a/engines/web/app/assets/stylesheets/web/all.css.scss b/engines/web/app/assets/stylesheets/web/all.scss similarity index 100% rename from engines/web/app/assets/stylesheets/web/all.css.scss rename to engines/web/app/assets/stylesheets/web/all.scss diff --git a/engines/web/app/assets/stylesheets/web/pages/cookies_banner.css.scss b/engines/web/app/assets/stylesheets/web/pages/cookies_banner.scss similarity index 100% rename from engines/web/app/assets/stylesheets/web/pages/cookies_banner.css.scss rename to engines/web/app/assets/stylesheets/web/pages/cookies_banner.scss diff --git a/engines/web/app/assets/stylesheets/web/pages/cookies_policy_modal.css.scss b/engines/web/app/assets/stylesheets/web/pages/cookies_policy_modal.scss similarity index 100% rename from engines/web/app/assets/stylesheets/web/pages/cookies_policy_modal.css.scss rename to engines/web/app/assets/stylesheets/web/pages/cookies_policy_modal.scss diff --git a/knapsack_rspec_report.json b/knapsack_rspec_report.json index 3d054ce4d9..91499ba40e 100644 --- a/knapsack_rspec_report.json +++ b/knapsack_rspec_report.json @@ -1,339 +1,394 @@ { - "spec/features/admin/orders_spec.rb": 209.85864853858948, - "spec/features/admin/variant_overrides_spec.rb": 103.19363331794739, - "spec/features/admin/bulk_order_management_spec.rb": 128.97811818122864, - "spec/features/consumer/shopping/checkout_spec.rb": 89.76498866081238, - "spec/features/admin/product_import_spec.rb": 84.0241048336029, - "spec/features/admin/order_cycles_spec.rb": 148.29575443267822, - "spec/features/consumer/shopping/variant_overrides_spec.rb": 92.9798493385315, - "spec/features/consumer/authentication_spec.rb": 56.12566685676575, - "spec/features/admin/subscriptions_spec.rb": 90.58396697044373, - "spec/features/admin/bulk_product_update_spec.rb": 143.26294326782227, - "spec/features/consumer/shopping/shopping_spec.rb": 67.1083915233612, - "spec/features/admin/enterprises_spec.rb": 39.2133104801178, - "spec/features/admin/payment_method_spec.rb": 50.03155517578125, - "spec/features/admin/tag_rules_spec.rb": 30.245259761810303, - "spec/models/order_cycle_spec.rb": 41.354451417922974, - "spec/features/consumer/shopping/orders_spec.rb": 25.612257957458496, - "spec/features/admin/enterprise_relationships_spec.rb": 44.3502836227417, - "spec/models/product_importer_spec.rb": 39.65096569061279, - "spec/features/admin/shipping_methods_spec.rb": 38.50179934501648, - "spec/models/spree/order_spec.rb": 30.600374937057495, - "spec/features/admin/enterprise_roles_spec.rb": 25.807589530944824, - "spec/features/admin/customers_spec.rb": 31.028107404708862, - "spec/features/consumer/shops_spec.rb": 27.150686264038086, - "spec/controllers/api/orders_controller_spec.rb": 43.287739515304565, - "spec/features/admin/products_spec.rb": 16.402274131774902, - "spec/services/order_syncer_spec.rb": 30.67478346824646, - "spec/helpers/injection_helper_spec.rb": 28.66439199447632, - "spec/features/admin/reports_spec.rb": 21.09156608581543, - "spec/controllers/admin/subscriptions_controller_spec.rb": 28.021868228912354, - "spec/controllers/api/products_controller_spec.rb": 13.549290895462036, - "spec/features/admin/enterprise_fees_spec.rb": 17.234047651290894, - "spec/features/consumer/registration_spec.rb": 14.555023193359375, - "spec/features/consumer/cookies_spec.rb": 31.677974462509155, - "spec/jobs/subscription_confirm_job_spec.rb": 23.378596305847168, - "spec/features/consumer/shopping/cart_spec.rb": 21.239376544952393, - "spec/features/consumer/groups_spec.rb": 13.469002485275269, - "spec/models/spree/line_item_spec.rb": 29.30591082572937, - "spec/controllers/spree/orders_controller_spec.rb": 18.490586280822754, - "spec/lib/open_food_network/order_cycle_permissions_spec.rb": 18.68953037261963, - "spec/controllers/api/shipments_controller_spec.rb": 16.687517642974854, - "spec/features/admin/adjustments_spec.rb": 17.467090368270874, - "spec/controllers/admin/bulk_line_items_controller_spec.rb": 23.035269260406494, - "spec/features/consumer/shopping/embedded_shopfronts_spec.rb": 19.298624753952026, - "spec/features/admin/enterprises/images_spec.rb": 11.094250679016113, - "spec/controllers/spree/admin/reports_controller_spec.rb": 14.215487003326416, - "spec/features/consumer/account_spec.rb": 12.601296663284302, - "spec/models/spree/product_spec.rb": 13.086605787277222, - "spec/mailers/producer_mailer_spec.rb": 15.4656503200531, - "spec/mailers/subscription_mailer_spec.rb": 14.124800682067871, - "spec/features/consumer/shopping/embedded_groups_spec.rb": 12.80406141281128, - "spec/features/consumer/producers_spec.rb": 13.81577444076538, - "spec/models/exchange_spec.rb": 4.696550130844116, - "spec/jobs/subscription_placement_job_spec.rb": 12.984622478485107, - "spec/features/admin/schedules_spec.rb": 10.006147146224976, - "spec/features/consumer/multilingual_spec.rb": 10.963010787963867, - "spec/features/admin/variants_spec.rb": 15.094644784927368, - "spec/controllers/api/variants_controller_spec.rb": 10.557931661605835, - "spec/models/spree/variant_spec.rb": 13.415100574493408, - "spec/features/consumer/shopping/checkout_auth_spec.rb": 12.420539855957031, - "spec/features/admin/reports/enterprise_fee_summaries_spec.rb": 14.304540395736694, - "spec/features/admin/enterprises/index_spec.rb": 8.40625262260437, - "spec/lib/open_food_network/permissions_spec.rb": 6.9782798290252686, - "spec/models/proxy_order_spec.rb": 8.073801279067993, - "spec/lib/open_food_network/enterprise_fee_calculator_spec.rb": 5.851940155029297, - "spec/features/admin/enterprise_groups_spec.rb": 13.105037212371826, - "spec/services/order_factory_spec.rb": 8.655417680740356, - "spec/models/spree/ability_spec.rb": 10.093905210494995, - "spec/controllers/line_items_controller_spec.rb": 9.24480938911438, - "spec/lib/open_food_network/proxy_order_syncer_spec.rb": 6.615863084793091, - "spec/features/admin/authentication_spec.rb": 2.6402037143707275, - "spec/lib/open_food_network/scope_variant_to_hub_spec.rb": 5.528027057647705, - "spec/models/enterprise_spec.rb": 7.465655565261841, - "spec/controllers/admin/proxy_orders_controller_spec.rb": 10.103626012802124, - "spec/features/consumer/shopping/checkout_paypal_spec.rb": 5.646831035614014, - "spec/models/enterprise_relationship_spec.rb": 5.4444804191589355, - "spec/lib/open_food_network/orders_and_fulfillments_report_spec.rb": 8.639384031295776, - "spec/lib/open_food_network/user_balance_calculator_spec.rb": 6.210071563720703, - "spec/lib/open_food_network/order_cycle_form_applicator_spec.rb": 6.225666046142578, - "spec/lib/open_food_network/address_finder_spec.rb": 8.498907804489136, - "spec/requests/checkout/stripe_connect_spec.rb": 7.812800645828247, - "spec/models/spree/adjustment_spec.rb": 7.6640002727508545, - "spec/features/consumer/footer_links_spec.rb": 5.052169561386108, - "spec/models/variant_override_spec.rb": 5.968475580215454, - "spec/controllers/api/logos_controller_spec.rb": 1.4243378639221191, - "spec/controllers/spree/admin/orders_controller_spec.rb": 5.91344141960144, - "spec/mailers/order_mailer_spec.rb": 5.1312196254730225, - "spec/lib/open_food_network/packing_report_spec.rb": 5.236347436904907, - "spec/services/advance_order_service_spec.rb": 4.330220460891724, - "spec/features/consumer/shopping/products_spec.rb": 4.986042261123657, - "spec/models/spree/property_spec.rb": 4.247157335281372, - "spec/controllers/api/product_images_controller_spec.rb": 1.5374972820281982, - "spec/models/spree/stock/availability_validator_spec.rb": 4.662952899932861, - "spec/lib/open_food_network/bulk_coop_report_spec.rb": 6.051215648651123, - "spec/services/subscription_estimator_spec.rb": 4.492072582244873, - "spec/models/enterprise_fee_spec.rb": 1.6908893585205078, - "spec/features/consumer/account/settings_spec.rb": 3.6030097007751465, - "spec/controllers/spree/admin/orders/customer_details_controller_spec.rb": 4.426133871078491, - "spec/controllers/spree/users_controller_spec.rb": 3.8197484016418457, - "spec/features/admin/overview_spec.rb": 4.293005704879761, - "spec/controllers/spree/admin/variants_controller_spec.rb": 3.284226894378662, - "spec/features/admin/users_spec.rb": 7.302890300750732, - "spec/controllers/admin/variant_overrides_controller_spec.rb": 3.715315341949463, - "spec/lib/open_food_network/products_and_inventory_report_spec.rb": 3.820003032684326, - "spec/controllers/admin/enterprises_controller_spec.rb": 3.8785862922668457, - "spec/models/spree/payment_spec.rb": 3.9647903442382812, - "spec/features/admin/reports/packing_report_spec.rb": 2.331310272216797, - "spec/models/concerns/variant_stock_spec.rb": 3.1730740070343018, - "spec/serializers/api/order_serializer_spec.rb": 3.8849294185638428, - "spec/models/spree/image_spec.rb": 0.010381937026977539, - "spec/features/admin/multilingual_spec.rb": 3.5513908863067627, - "spec/jobs/order_cycle_notification_job_spec.rb": 1.3160836696624756, - "spec/controllers/spree/admin/products_controller_spec.rb": 2.776277542114258, - "spec/lib/open_food_network/group_buy_report_spec.rb": 2.9200246334075928, - "spec/controllers/shop_controller_spec.rb": 1.094660997390747, - "spec/features/admin/payments_spec.rb": 2.9398913383483887, - "spec/lib/open_food_network/order_cycle_management_report_spec.rb": 3.1769142150878906, - "spec/models/calculator/weight_spec.rb": 2.7677314281463623, - "spec/services/invoice_renderer_spec.rb": 2.9274940490722656, - "spec/controllers/api/promo_images_controller_spec.rb": 1.8630146980285645, - "spec/features/consumer/account/cards_spec.rb": 3.3731846809387207, - "spec/services/subscription_variants_service_spec.rb": 2.44708251953125, - "spec/controllers/spree/admin/payments_controller_spec.rb": 2.7492406368255615, - "spec/lib/open_food_network/customers_report_spec.rb": 2.010512351989746, - "spec/helpers/enterprises_helper_spec.rb": 2.575876474380493, - "spec/models/spree/calculator_spec.rb": 2.28029465675354, - "spec/controllers/admin/order_cycles_controller_spec.rb": 2.5031261444091797, - "spec/serializers/api/admin/exchange_serializer_spec.rb": 2.1571648120880127, - "spec/models/enterprise_caching_spec.rb": 2.0464723110198975, - "spec/lib/open_food_network/lettuce_share_report_spec.rb": 2.6065244674682617, - "spec/lib/open_food_network/scope_variants_to_search_spec.rb": 2.0106847286224365, - "spec/controllers/admin/schedules_controller_spec.rb": 2.1028177738189697, - "spec/services/bulk_invoice_service_spec.rb": 2.135669708251953, - "spec/lib/open_food_network/subscription_payment_updater_spec.rb": 2.519094228744507, - "spec/controllers/admin/subscription_line_items_controller_spec.rb": 1.8447153568267822, - "spec/requests/checkout/failed_checkout_spec.rb": 1.9410381317138672, - "spec/controllers/admin/inventory_items_controller_spec.rb": 2.0562922954559326, - "spec/serializers/api/admin/enterprise_serializer_spec.rb": 0.5516650676727295, - "spec/models/product_import/inventory_reset_strategy_spec.rb": 1.9712626934051514, - "spec/controllers/enterprises_controller_spec.rb": 2.159472703933716, - "spec/models/product_import/products_reset_strategy_spec.rb": 1.8046910762786865, - "spec/serializers/api/current_order_serializer_spec.rb": 1.6418733596801758, - "spec/models/spree/shipping_method_spec.rb": 1.5293469429016113, - "spec/views/spree/admin/orders/edit.html.haml_spec.rb": 1.4302563667297363, - "spec/services/tax_rate_finder_spec.rb": 1.765697717666626, - "spec/models/producer_property_spec.rb": 1.8772265911102295, - "spec/services/order_cycle_distributed_products_spec.rb": 2.5216150283813477, - "spec/services/variants_stock_levels_spec.rb": 1.4711685180664062, - "spec/serializers/api/enterprise_shopfront_serializer_spec.rb": 1.4934852123260498, - "spec/controllers/cart_controller_spec.rb": 1.4728140830993652, - "spec/models/concerns/order_shipment_spec.rb": 1.9231147766113281, - "spec/services/subscription_form_spec.rb": 1.5087761878967285, - "spec/controllers/checkout_controller_spec.rb": 1.513655424118042, - "spec/requests/checkout/paypal_spec.rb": 1.292949914932251, - "spec/models/spree/product_set_spec.rb": 1.7520904541015625, - "spec/serializers/api/cached_enterprise_serializer_spec.rb": 1.1505959033966064, - "spec/controllers/spree/admin/overview_controller_spec.rb": 0.47139525413513184, - "spec/models/spree/taxon_spec.rb": 0.7411928176879883, - "spec/services/subscription_validator_spec.rb": 1.417020559310913, - "spec/services/cart_service_spec.rb": 0.9984734058380127, - "spec/models/concerns/product_stock_spec.rb": 1.0030314922332764, - "spec/lib/open_food_network/tag_rule_applicator_spec.rb": 1.2484149932861328, - "spec/controllers/spree/admin/shipping_methods_controller_spec.rb": 1.0473082065582275, - "spec/services/exchange_variant_bulk_updater_spec.rb": 1.1361675262451172, - "spec/features/admin/content_spec.rb": 1.1164579391479492, - "spec/models/spree/order/checkout_spec.rb": 0.9971451759338379, - "spec/models/spree/shipment_spec.rb": 0.8724184036254883, - "spec/controllers/admin/customers_controller_spec.rb": 0.8800466060638428, - "spec/controllers/shops_controller_spec.rb": 0.8274595737457275, - "spec/serializers/api/admin/variant_serializer_spec.rb": 0.8609609603881836, - "spec/lib/open_food_network/order_and_distributor_report_spec.rb": 0.6863596439361572, - "spec/serializers/api/admin/for_order_cycle/enterprise_serializer_spec.rb": 0.7560875415802002, - "spec/controllers/api/taxons_controller_spec.rb": 0.5672154426574707, - "spec/controllers/admin/manager_invitations_controller_spec.rb": 0.8336856365203857, - "spec/lib/open_food_network/enterprise_fee_applicator_spec.rb": 0.5529074668884277, - "spec/models/spree/user_spec.rb": 0.8885831832885742, - "spec/models/spree/price_spec.rb": 0.2001354694366455, - "spec/services/restart_checkout_spec.rb": 0.8455610275268555, - "spec/requests/embedded_shopfronts_headers_spec.rb": 0.730147123336792, - "spec/controllers/api/enterprises_controller_spec.rb": 0.7297313213348389, - "spec/services/subscriptions_count_spec.rb": 1.0329487323760986, - "spec/models/subscription_spec.rb": 0.8652608394622803, - "spec/services/order_cycle_form_spec.rb": 0.6517574787139893, - "spec/features/admin/enterprise_user_spec.rb": 0.7582378387451172, - "spec/controllers/spree/admin/search_controller_spec.rb": 0.6782004833221436, - "spec/controllers/spree/admin/payment_methods_controller_spec.rb": 1.032891035079956, - "spec/helpers/admin/subscriptions_helper_spec.rb": 0.6923768520355225, - "spec/models/spree/classification_spec.rb": 0.1685178279876709, - "spec/models/spree/calculator/flat_percent_item_total_spec.rb": 0.4571397304534912, - "spec/models/spree/calculator/price_sack_spec.rb": 0.5336060523986816, - "spec/serializers/api/admin/for_order_cycle/supplied_product_serializer_spec.rb": 0.42862772941589355, - "spec/models/spree/payment_method_spec.rb": 0.4095613956451416, - "spec/models/order_updater_spec.rb": 0.6351447105407715, - "spec/controllers/spree/admin/adjustments_controller_spec.rb": 0.608527421951294, - "spec/models/spree/calculator/flexi_rate_spec.rb": 0.4524409770965576, - "spec/services/search_orders_spec.rb": 0.21066808700561523, - "spec/helpers/admin/orders_helper_spec.rb": 0.452254056930542, - "spec/lib/open_food_network/option_value_namer_spec.rb": 0.09297728538513184, - "spec/controllers/admin/stripe_accounts_controller_spec.rb": 0.48218297958374023, - "spec/features/admin/tax_settings_spec.rb": 0.39211606979370117, - "spec/models/spree/gateway_tagging_spec.rb": 0.4623403549194336, - "spec/serializers/api/admin/product_serializer_spec.rb": 0.16631841659545898, - "spec/controllers/admin/tag_rules_controller_spec.rb": 0.1727132797241211, - "spec/serializers/api/admin/variant_override_serializer_spec.rb": 0.21732354164123535, - "spec/controllers/user_confirmations_controller_spec.rb": 0.3694608211517334, - "spec/helpers/order_cycles_helper_spec.rb": 0.37854909896850586, - "spec/helpers/i18n_helper_spec.rb": 0.3624556064605713, - "spec/models/stripe_account_spec.rb": 0.19968080520629883, - "spec/models/tag_rule/filter_payment_methods_spec.rb": 0.20981264114379883, - "spec/controllers/spree/admin/reports/enterprise_fee_summaries_controller_spec.rb": 0.3504636287689209, - "spec/controllers/api/customers_controller_spec.rb": 0.29506921768188477, - "spec/models/customer_spec.rb": 0.2992515563964844, - "spec/models/enterprise_group_spec.rb": 0.2686808109283447, - "spec/models/adjustment_metadata_spec.rb": 0.15410161018371582, - "spec/mailers/enterprise_mailer_spec.rb": 0.2028944492340088, - "spec/features/consumer/confirm_invitation_spec.rb": 0.1965491771697998, - "spec/models/column_preference_spec.rb": 0.18862366676330566, - "spec/controllers/admin/stripe_connect_settings_controller_spec.rb": 0.21590971946716309, - "spec/controllers/spree/checkout_controller_spec.rb": 0.25676560401916504, - "spec/models/model_set_spec.rb": 0.19089746475219727, - "spec/lib/open_food_network/users_and_enterprises_report_spec.rb": 0.37776756286621094, - "spec/serializers/api/shipping_method_serializer_spec.rb": 0.26949477195739746, - "spec/models/tag_rule/filter_order_cycles_spec.rb": 0.14159250259399414, - "spec/lib/stripe/account_connector_spec.rb": 0.27176880836486816, - "spec/models/spree/calculator/per_item_spec.rb": 0.22195911407470703, - "spec/controllers/admin/column_preferences_controller_spec.rb": 0.14211821556091309, - "spec/controllers/spree/admin/base_controller_spec.rb": 0.25708532333374023, - "spec/controllers/spree/credit_cards_controller_spec.rb": 0.32630300521850586, - "spec/lib/open_food_network/subscription_summary_spec.rb": 0.1385807991027832, - "spec/lib/open_food_network/property_merge_spec.rb": 0.24811196327209473, - "spec/models/spree/tax_rate_spec.rb": 0.25828003883361816, - "spec/mailers/user_mailer_spec.rb": 0.2304532527923584, - "spec/controllers/spree/paypal_controller_spec.rb": 0.24924159049987793, - "spec/serializers/api/variant_serializer_spec.rb": 0.18966460227966309, - "spec/models/tag_rule/filter_shipping_methods_spec.rb": 0.26709508895874023, - "spec/features/admin/image_settings_spec.rb": 0.2686142921447754, - "spec/models/stock/package_spec.rb": 0.2378373146057129, - "spec/features/admin/external_services_spec.rb": 0.25201988220214844, - "spec/models/tag_rule/filter_products_spec.rb": 0.16270112991333008, - "spec/controllers/stripe/callbacks_controller_spec.rb": 0.32970166206359863, - "spec/controllers/spree/admin/users_controller_spec.rb": 0.18564677238464355, - "spec/models/spree/credit_card_spec.rb": 0.1279582977294922, - "spec/serializers/api/admin/index_enterprise_serializer_spec.rb": 0.12614202499389648, - "spec/serializers/api/admin/customer_serializer_spec.rb": 0.13819241523742676, - "spec/models/product_import/settings_spec.rb": 0.0827949047088623, - "spec/controllers/user_passwords_controller_spec.rb": 0.12400317192077637, - "spec/controllers/user_registrations_controller_spec.rb": 0.13727235794067383, - "spec/helpers/checkout_helper_spec.rb": 0.08210468292236328, - "spec/views/spree/admin/orders/index.html.haml_spec.rb": 0.13492274284362793, - "spec/serializers/api/group_list_serializer_spec.rb": 0.14428400993347168, - "spec/serializers/api/enterprise_serializer_spec.rb": 0.12647700309753418, - "spec/controllers/stripe/webhooks_controller_spec.rb": 0.1504201889038086, - "spec/controllers/groups_controller_spec.rb": 0.0957021713256836, - "spec/lib/open_food_network/xero_invoices_report_spec.rb": 0.11310100555419922, - "spec/models/tag_rule/discount_order_spec.rb": 0.10718297958374023, - "spec/helpers/spree/orders_helper_spec.rb": 0.11443710327148438, - "spec/lib/spree/product_filters_spec.rb": 0.09136056900024414, - "spec/models/spree/addresses_spec.rb": 0.08072257041931152, - "spec/controllers/api/enterprise_fees_controller_spec.rb": 0.1456611156463623, - "spec/serializers/api/enterprise_shopfront_list_serializer_spec.rb": 0.13787007331848145, - "spec/features/consumer/sitemap_spec.rb": 0.1070859432220459, - "spec/lib/open_food_network/sales_tax_report_spec.rb": 0.11518430709838867, - "spec/lib/open_food_network/i18n_config_spec.rb": 0.10601115226745605, - "spec/services/default_stock_location_spec.rb": 0.1458897590637207, - "spec/controllers/registration_controller_spec.rb": 0.11335277557373047, - "spec/serializers/api/order_cycle_serializer_spec.rb": 0.09752154350280762, - "spec/helpers/shared_helper_spec.rb": 0.06739163398742676, - "spec/controllers/api/taxonomies_controller_spec.rb": 0.05540919303894043, - "spec/serializers/api/admin/subscription_customer_serializer_spec.rb": 0.049886465072631836, - "spec/helpers/navigation_helper_spec.rb": 0.045441389083862305, - "spec/models/spree/gateway/stripe_connect_spec.rb": 0.049462080001831055, - "spec/helpers/spree/admin/orders_helper_spec.rb": 0.050534963607788086, - "spec/helpers/shop_helper_spec.rb": 0.044415950775146484, - "spec/validators/date_time_string_validator_spec.rb": 0.030704498291015625, - "spec/lib/open_food_network/subscription_summarizer_spec.rb": 0.03782463073730469, - "spec/lib/stripe/webhook_handler_spec.rb": 0.0348663330078125, - "spec/jobs/welcome_enterprise_job_spec.rb": 0.04198408126831055, - "spec/validators/integer_array_validator_spec.rb": 0.033490896224975586, - "spec/models/tag_rule_spec.rb": 0.04035758972167969, - "spec/controllers/spree/user_sessions_controller_spec.rb": 0.056906700134277344, - "spec/helpers/html_helper_spec.rb": 0.04384016990661621, - "spec/lib/spree/localized_number_spec.rb": 0.03789019584655762, - "spec/lib/open_food_network/order_grouper_spec.rb": 0.03744339942932129, - "spec/models/spree/preferences/file_configuration_spec.rb": 0.03453230857849121, - "spec/jobs/confirm_order_job_spec.rb": 0.03809309005737305, - "spec/lib/open_food_network/reports/report_spec.rb": 0.02672100067138672, - "spec/lib/open_food_network/enterprise_issue_validator_spec.rb": 0.03448891639709473, - "spec/services/embedded_page_service_spec.rb": 0.03394007682800293, - "spec/controllers/api/statuses_controller_spec.rb": 0.02658700942993164, - "spec/models/product_import/reset_absent_spec.rb": 0.029580354690551758, - "spec/models/product_import/entry_processor_spec.rb": 0.030652284622192383, - "spec/controllers/spree/store_controller_spec.rb": 0.05485796928405762, - "spec/models/subscription_line_item_spec.rb": 0.029042720794677734, - "spec/controllers/base_controller_spec.rb": 0.03686809539794922, - "spec/services/reset_order_service_spec.rb": 0.021564006805419922, - "spec/services/order_cycle_distributed_variants_spec.rb": 0.0191500186920166, - "spec/models/calculator/flat_percent_per_item_spec.rb": 0.02089238166809082, - "spec/lib/open_food_network/reports/rule_spec.rb": 0.01708674430847168, - "spec/requests/large_request_spec.rb": 0.01527857780456543, - "spec/jobs/confirm_signup_job_spec.rb": 0.01926112174987793, - "spec/lib/open_food_network/referer_parser_spec.rb": 0.019036531448364258, - "spec/lib/open_food_network/feature_toggle_spec.rb": 0.01768207550048828, - "spec/helpers/groups_helper_spec.rb": 0.010357141494750977, - "spec/serializers/api/credit_card_serializer_spec.rb": 0.013578414916992188, - "spec/helpers/products_helper_spec.rb": 0.01333308219909668, - "spec/jobs/heartbeat_job_spec.rb": 0.013099193572998047, - "spec/models/spree/calculator/flat_rate_spec.rb": 0.01121664047241211, - "spec/services/mail_configuration_spec.rb": 0.010842561721801758, - "spec/helpers/serializer_helper_spec.rb": 0.013910770416259766, - "spec/helpers/spree/admin/base_helper_spec.rb": 0.006238222122192383, - "spec/models/content_configuration_spec.rb": 0.005331277847290039, - "spec/services/upload_sanitizer_spec.rb": 0.005086660385131836, - "spec/lib/open_food_network/error_logger_spec.rb": 0.005143880844116211, - "spec/config/application_spec.rb": 0.004831075668334961, - "spec/lib/open_food_network/reports/row_spec.rb": 0.004848003387451172, - "spec/controllers/api/base_controller_spec.rb": 0.2383410930633545, - "spec/controllers/api/order_cycles_controller_spec.rb": 8.10491132736206, - "spec/controllers/spree/admin/image_settings_controller_spec.rb": 0.20762252807617188, - "spec/controllers/spree/admin/mail_methods_controller_spec.rb": 0.2593998908996582, - "spec/features/admin/configuration/general_settings_spec.rb": 0.512702465057373, - "spec/features/admin/configuration/image_settings_spec.rb": 1.0649611949920654, - "spec/features/admin/configuration/mail_methods_spec.rb": 0.5447854995727539, - "spec/features/admin/configuration/states_spec.rb": 8.655013084411621, - "spec/features/admin/configuration/tax_categories_spec.rb": 1.3367998600006104, - "spec/features/admin/configuration/tax_rates_spec.rb": 0.6975750923156738, - "spec/features/admin/configuration/taxonomies_spec.rb": 1.427445650100708, - "spec/features/admin/configuration/zones_spec.rb": 0.8336474895477295, - "spec/lib/open_food_network/orders_and_fulfillments_report/customer_totals_report_spec.rb": 0.9327311515808105, - "spec/lib/open_food_network/orders_and_fulfillments_report/distributor_totals_by_supplier_report_spec.rb": 0.8202314376831055, - "spec/lib/open_food_network/orders_and_fulfillments_report/supplier_totals_by_distributor_report_spec.rb": 0.8403346538543701, - "spec/lib/open_food_network/orders_and_fulfillments_report/supplier_totals_report_spec.rb": 0.9129531383514404, - "spec/lib/tasks/enterprises_rake_spec.rb": 0.0565638542175293, - "spec/lib/tasks/users_rake_spec.rb": 0.2098069190979004, - "spec/serializers/api/admin/subscription_line_item_serializer_spec.rb": 0.6548449993133545, - "spec/serializers/api/product_serializer_spec.rb": 7.2200376987457275, - "spec/services/default_shipping_category_spec.rb": 0.03317761421203613, - "spec/services/product_tag_rules_filterer_spec.rb": 0.9287967681884766, - "spec/services/products_renderer_spec.rb": 4.769314765930176 + "spec/features/admin/variant_overrides_spec.rb": 99.30756449699402, + "spec/features/admin/product_import_spec.rb": 73.07663607597351, + "spec/features/admin/enterprise_relationships_spec.rb": 41.326045751571655, + "spec/features/admin/enterprises_spec.rb": 32.637606143951416, + "spec/models/spree/order_spec.rb": 27.992523431777954, + "spec/controllers/admin/subscriptions_controller_spec.rb": 27.568928956985474, + "spec/jobs/subscription_confirm_job_spec.rb": 21.490560293197632, + "spec/features/consumer/shopping/embedded_shopfronts_spec.rb": 38.76921033859253, + "spec/features/admin/enterprise_fees_spec.rb": 10.35721755027771, + "spec/features/admin/variants_spec.rb": 8.782998323440552, + "spec/features/consumer/producers_spec.rb": 10.305850982666016, + "spec/features/admin/enterprise_groups_spec.rb": 9.281317949295044, + "spec/features/consumer/account_spec.rb": 12.364761114120483, + "spec/controllers/api/variants_controller_spec.rb": 9.698291301727295, + "spec/controllers/admin/proxy_orders_controller_spec.rb": 9.294223308563232, + "spec/controllers/line_items_controller_spec.rb": 9.073998928070068, + "spec/lib/open_food_network/address_finder_spec.rb": 8.379472494125366, + "spec/features/admin/enterprises/index_spec.rb": 4.946447134017944, + "spec/requests/checkout/stripe_connect_spec.rb": 7.5777587890625, + "spec/models/spree/adjustment_spec.rb": 7.560959815979004, + "spec/serializers/api/product_serializer_spec.rb": 8.135705709457397, + "spec/lib/open_food_network/permissions_spec.rb": 3.799001455307007, + "spec/lib/open_food_network/bulk_coop_report_spec.rb": 5.437628269195557, + "spec/models/variant_override_spec.rb": 7.263522624969482, + "spec/features/consumer/shopping/checkout_paypal_spec.rb": 4.054832696914673, + "spec/lib/open_food_network/scope_variant_to_hub_spec.rb": 6.51300835609436, + "spec/mailers/order_mailer_spec.rb": 5.846204519271851, + "spec/features/consumer/footer_links_spec.rb": 2.7448785305023193, + "spec/models/exchange_spec.rb": 5.689413547515869, + "spec/features/admin/overview_spec.rb": 3.0301671028137207, + "spec/models/spree/payment_spec.rb": 4.219077110290527, + "spec/lib/open_food_network/products_and_inventory_report_spec.rb": 4.522545576095581, + "spec/features/admin/multilingual_spec.rb": 1.779911756515503, + "spec/features/consumer/account/cards_spec.rb": 3.4125499725341797, + "spec/models/concerns/variant_stock_spec.rb": 4.260012865066528, + "spec/controllers/spree/admin/products_controller_spec.rb": 2.9444453716278076, + "spec/models/calculator/weight_spec.rb": 4.537610769271851, + "spec/lib/open_food_network/lettuce_share_report_spec.rb": 2.5369696617126465, + "spec/features/admin/reports/packing_report_spec.rb": 2.4766170978546143, + "spec/models/spree/calculator_spec.rb": 2.4739301204681396, + "spec/services/bulk_invoice_service_spec.rb": 2.6490700244903564, + "spec/lib/open_food_network/scope_variants_to_search_spec.rb": 2.6042325496673584, + "spec/lib/open_food_network/customers_report_spec.rb": 2.2221970558166504, + "spec/models/concerns/order_shipment_spec.rb": 1.4907066822052002, + "spec/models/spree/product_set_spec.rb": 1.9434475898742676, + "spec/models/enterprise_fee_spec.rb": 1.8590900897979736, + "spec/models/spree/shipping_method_spec.rb": 1.939526081085205, + "spec/services/variants_stock_levels_spec.rb": 2.701181411743164, + "spec/views/spree/admin/orders/edit.html.haml_spec.rb": 1.4470489025115967, + "spec/features/admin/configuration/tax_categories_spec.rb": 1.29876708984375, + "spec/serializers/api/cached_enterprise_serializer_spec.rb": 1.4465532302856445, + "spec/features/admin/configuration/image_settings_spec.rb": 1.2370245456695557, + "spec/controllers/spree/admin/shipping_methods_controller_spec.rb": 1.7793538570404053, + "spec/services/cart_service_spec.rb": 3.899951696395874, + "spec/lib/open_food_network/orders_and_fulfillments_report/supplier_totals_report_spec.rb": 1.0069975852966309, + "spec/models/spree/user_spec.rb": 0.8939847946166992, + "spec/models/subscription_spec.rb": 0.7737770080566406, + "spec/features/admin/configuration/zones_spec.rb": 2.0647647380828857, + "spec/controllers/shops_controller_spec.rb": 1.1841659545898438, + "spec/models/spree/taxon_spec.rb": 1.4563179016113281, + "spec/helpers/admin/subscriptions_helper_spec.rb": 0.7952499389648438, + "spec/lib/open_food_network/order_and_distributor_report_spec.rb": 1.7040517330169678, + "spec/services/order_cycle_form_spec.rb": 0.8009114265441895, + "spec/lib/open_food_network/enterprise_fee_applicator_spec.rb": 0.640235185623169, + "spec/serializers/api/admin/enterprise_serializer_spec.rb": 0.6820120811462402, + "spec/features/admin/configuration/general_settings_spec.rb": 0.5539102554321289, + "spec/models/spree/calculator/flat_percent_item_total_spec.rb": 0.5510048866271973, + "spec/models/spree/calculator/flexi_rate_spec.rb": 0.5088491439819336, + "spec/models/spree/payment_method_spec.rb": 0.47752952575683594, + "spec/controllers/user_confirmations_controller_spec.rb": 0.8516778945922852, + "spec/helpers/i18n_helper_spec.rb": 0.14914989471435547, + "spec/models/customer_spec.rb": 0.3615994453430176, + "spec/models/enterprise_group_spec.rb": 0.2747635841369629, + "spec/models/spree/tax_rate_spec.rb": 0.2905604839324951, + "spec/controllers/spree/admin/base_controller_spec.rb": 0.1441051959991455, + "spec/lib/open_food_network/property_merge_spec.rb": 0.2783482074737549, + "spec/controllers/api/base_controller_spec.rb": 0.1090250015258789, + "spec/models/spree/calculator/per_item_spec.rb": 0.26129627227783203, + "spec/models/tag_rule/filter_payment_methods_spec.rb": 0.2537047863006592, + "spec/models/spree/price_spec.rb": 0.238206148147583, + "spec/models/stripe_account_spec.rb": 0.2526712417602539, + "spec/serializers/api/variant_serializer_spec.rb": 0.26934289932250977, + "spec/controllers/spree/admin/users_controller_spec.rb": 0.1959519386291504, + "spec/models/spree/classification_spec.rb": 0.19763445854187012, + "spec/models/tag_rule/filter_products_spec.rb": 0.1828017234802246, + "spec/controllers/stripe/webhooks_controller_spec.rb": 0.13119006156921387, + "spec/services/default_stock_location_spec.rb": 0.07834315299987793, + "spec/controllers/admin/column_preferences_controller_spec.rb": 0.1527540683746338, + "spec/models/tag_rule/filter_order_cycles_spec.rb": 0.19031882286071777, + "spec/controllers/user_registrations_controller_spec.rb": 0.14432930946350098, + "spec/models/spree/credit_card_spec.rb": 0.1434488296508789, + "spec/serializers/api/admin/index_enterprise_serializer_spec.rb": 0.15880084037780762, + "spec/controllers/user_passwords_controller_spec.rb": 0.11744856834411621, + "spec/controllers/registration_controller_spec.rb": 0.08824491500854492, + "spec/models/tag_rule/discount_order_spec.rb": 0.1451718807220459, + "spec/lib/open_food_network/i18n_config_spec.rb": 0.06594657897949219, + "spec/serializers/api/order_cycle_serializer_spec.rb": 0.09044694900512695, + "spec/lib/spree/product_filters_spec.rb": 0.11472368240356445, + "spec/models/product_import/settings_spec.rb": 0.08627009391784668, + "spec/helpers/shared_helper_spec.rb": 0.019224166870117188, + "spec/controllers/spree/store_controller_spec.rb": 0.02562713623046875, + "spec/serializers/api/admin/subscription_customer_serializer_spec.rb": 0.058799028396606445, + "spec/helpers/navigation_helper_spec.rb": 0.042215585708618164, + "spec/helpers/html_helper_spec.rb": 0.04345393180847168, + "spec/models/tag_rule_spec.rb": 0.04623818397521973, + "spec/lib/spree/localized_number_spec.rb": 0.03824877738952637, + "spec/controllers/base_controller_spec.rb": 0.022301673889160156, + "spec/models/spree/preferences/file_configuration_spec.rb": 0.0330810546875, + "spec/services/embedded_page_service_spec.rb": 0.032323360443115234, + "spec/services/default_shipping_category_spec.rb": 0.03125119209289551, + "spec/models/product_import/entry_processor_spec.rb": 0.030833005905151367, + "spec/models/subscription_line_item_spec.rb": 0.021193265914916992, + "spec/controllers/api/statuses_controller_spec.rb": 0.02451467514038086, + "spec/lib/open_food_network/referer_parser_spec.rb": 0.015799283981323242, + "spec/lib/open_food_network/reports/rule_spec.rb": 0.01628732681274414, + "spec/helpers/serializer_helper_spec.rb": 0.004682064056396484, + "spec/jobs/heartbeat_job_spec.rb": 0.013271570205688477, + "spec/services/mail_configuration_spec.rb": 0.01050567626953125, + "spec/models/content_configuration_spec.rb": 0.004523277282714844, + "spec/lib/open_food_network/error_logger_spec.rb": 0.004483938217163086, + "spec/services/upload_sanitizer_spec.rb": 0.00407862663269043, + "spec/config/application_spec.rb": 0.004088878631591797, + "engines/order_management/spec/features/order_management/reports/enterprise_fee_summaries_spec.rb": 14.05416202545166, + "engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/permissions_spec.rb": 6.665990352630615, + "engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb": 43.40459585189819, + "engines/order_management/spec/services/order_management/subscriptions/payment_setup_spec.rb": 0.6526827812194824, + "engines/order_management/spec/services/order_management/subscriptions/summarizer_spec.rb": 0.041594743728637695, + "engines/web/spec/features/consumer/cookies_spec.rb": 24.080432891845703, + "spec/controllers/checkout_controller_concurrency_spec.rb": 2.275583505630493, + "spec/controllers/spree/admin/shipping_categories_controller_spec.rb": 0.07861185073852539, + "spec/features/admin/order_cycles/complex_editing_spec.rb": 5.987002611160278, + "spec/features/admin/order_cycles_complex_nav_check_spec.rb": 2.227072238922119, + "spec/features/consumer/caching/darkwarm_caching_spec.rb": 2.8325679302215576, + "spec/lib/stripe/payment_intent_validator_spec.rb": 0.022137880325317383, + "spec/models/spree/stock/quantifier_spec.rb": 0.23374128341674805, + "spec/requests/checkout/stripe_sca_spec.rb": 7.938737869262695, + "spec/services/checkout/form_data_adapter_spec.rb": 0.17598271369934082, + "spec/services/exchange_products_renderer_spec.rb": 3.1031360626220703, + "spec/services/order_cycle_warning_spec.rb": 0.19490528106689453, + "spec/services/permitted_attributes/order_cycle_spec.rb": 0.015558242797851562, + "spec/views/spree/admin/orders/invoice.html.haml_spec.rb": 6.268559455871582, + "spec/features/admin/bulk_order_management_spec.rb": 140.8281648159027, + "spec/features/consumer/shopping/checkout_spec.rb": 99.63996767997742, + "spec/features/admin/payment_method_spec.rb": 47.7765429019928, + "spec/models/product_importer_spec.rb": 58.75986576080322, + "spec/services/order_syncer_spec.rb": 31.290876388549805, + "spec/helpers/injection_helper_spec.rb": 32.275402784347534, + "spec/features/consumer/shopping/orders_spec.rb": 22.38155460357666, + "spec/features/admin/reports_spec.rb": 19.470484495162964, + "spec/features/admin/adjustments_spec.rb": 11.78750228881836, + "spec/mailers/producer_mailer_spec.rb": 29.681770086288452, + "spec/mailers/subscription_mailer_spec.rb": 26.084392070770264, + "spec/models/spree/variant_spec.rb": 14.30089783668518, + "spec/features/consumer/shopping/embedded_groups_spec.rb": 4.744326591491699, + "spec/features/consumer/multilingual_spec.rb": 7.861083507537842, + "spec/features/admin/schedules_spec.rb": 5.3575074672698975, + "spec/lib/open_food_network/orders_and_fulfillments_report_spec.rb": 9.90589451789856, + "spec/models/proxy_order_spec.rb": 7.81204891204834, + "spec/features/admin/users_spec.rb": 4.110132217407227, + "spec/lib/open_food_network/user_balance_calculator_spec.rb": 17.331896781921387, + "spec/lib/open_food_network/enterprise_fee_calculator_spec.rb": 10.376912832260132, + "spec/lib/open_food_network/packing_report_spec.rb": 7.372665643692017, + "spec/services/products_renderer_spec.rb": 6.482128858566284, + "spec/models/spree/stock/availability_validator_spec.rb": 9.495196104049683, + "spec/services/advance_order_service_spec.rb": 7.021219968795776, + "spec/controllers/admin/enterprises_controller_spec.rb": 4.748497247695923, + "spec/controllers/spree/users_controller_spec.rb": 5.6285271644592285, + "spec/features/consumer/account/settings_spec.rb": 3.27730393409729, + "spec/lib/open_food_network/order_cycle_management_report_spec.rb": 4.047335863113403, + "spec/features/admin/payments_spec.rb": 7.85437798500061, + "spec/lib/open_food_network/group_buy_report_spec.rb": 3.6540842056274414, + "spec/features/admin/authentication_spec.rb": 3.2194108963012695, + "spec/helpers/enterprises_helper_spec.rb": 3.27225923538208, + "spec/controllers/admin/order_cycles_controller_spec.rb": 3.0946338176727295, + "spec/serializers/api/admin/exchange_serializer_spec.rb": 3.0946173667907715, + "spec/controllers/admin/schedules_controller_spec.rb": 3.847097158432007, + "spec/models/enterprise_caching_spec.rb": 3.255535364151001, + "spec/requests/checkout/failed_checkout_spec.rb": 2.4441702365875244, + "spec/controllers/api/promo_images_controller_spec.rb": 3.4382574558258057, + "spec/services/tax_rate_finder_spec.rb": 2.9899346828460693, + "spec/controllers/api/product_images_controller_spec.rb": 3.9871389865875244, + "spec/controllers/checkout_controller_spec.rb": 4.339718341827393, + "spec/controllers/cart_controller_spec.rb": 2.3228118419647217, + "spec/controllers/api/logos_controller_spec.rb": 3.2836084365844727, + "spec/lib/open_food_network/tag_rule_applicator_spec.rb": 2.221569299697876, + "spec/services/exchange_variant_bulk_updater_spec.rb": 2.6784279346466064, + "spec/controllers/shop_controller_spec.rb": 3.5295212268829346, + "spec/models/concerns/product_stock_spec.rb": 2.5454351902008057, + "spec/models/spree/order/checkout_spec.rb": 1.958070993423462, + "spec/services/product_tag_rules_filterer_spec.rb": 2.044708013534546, + "spec/models/spree/shipment_spec.rb": 1.6677730083465576, + "spec/serializers/api/admin/variant_serializer_spec.rb": 1.2669260501861572, + "spec/controllers/admin/manager_invitations_controller_spec.rb": 1.1235411167144775, + "spec/features/admin/enterprise_user_spec.rb": 1.4083926677703857, + "spec/requests/embedded_shopfronts_headers_spec.rb": 1.80560302734375, + "spec/features/admin/configuration/tax_rates_spec.rb": 1.076134204864502, + "spec/serializers/api/admin/subscription_line_item_serializer_spec.rb": 1.103409767150879, + "spec/models/order_updater_spec.rb": 0.893103837966919, + "spec/controllers/api/taxons_controller_spec.rb": 1.4529874324798584, + "spec/models/spree/calculator/price_sack_spec.rb": 2.4207956790924072, + "spec/controllers/admin/stripe_accounts_controller_spec.rb": 0.7403256893157959, + "spec/models/spree/gateway_tagging_spec.rb": 0.5006825923919678, + "spec/serializers/api/admin/for_order_cycle/supplied_product_serializer_spec.rb": 0.6309933662414551, + "spec/features/admin/tax_settings_spec.rb": 0.4539029598236084, + "spec/lib/open_food_network/users_and_enterprises_report_spec.rb": 0.45987796783447266, + "spec/controllers/spree/credit_cards_controller_spec.rb": 0.3677220344543457, + "spec/controllers/api/customers_controller_spec.rb": 0.5009043216705322, + "spec/serializers/api/shipping_method_serializer_spec.rb": 0.418184757232666, + "spec/features/admin/image_settings_spec.rb": 0.3251943588256836, + "spec/controllers/spree/admin/mail_methods_controller_spec.rb": 0.1681194305419922, + "spec/controllers/spree/paypal_controller_spec.rb": 0.3564426898956299, + "spec/mailers/user_mailer_spec.rb": 0.25026392936706543, + "spec/serializers/api/admin/variant_override_serializer_spec.rb": 0.3578934669494629, + "spec/services/search_orders_spec.rb": 0.3271763324737549, + "spec/lib/tasks/users_rake_spec.rb": 0.18160343170166016, + "spec/mailers/enterprise_mailer_spec.rb": 0.22394657135009766, + "spec/models/model_set_spec.rb": 0.29787373542785645, + "spec/models/column_preference_spec.rb": 0.2551867961883545, + "spec/controllers/admin/tag_rules_controller_spec.rb": 0.26432085037231445, + "spec/serializers/api/admin/product_serializer_spec.rb": 0.28866004943847656, + "spec/models/adjustment_metadata_spec.rb": 0.2655766010284424, + "spec/controllers/api/enterprise_fees_controller_spec.rb": 0.11628031730651855, + "spec/serializers/api/group_list_serializer_spec.rb": 0.2681000232696533, + "spec/serializers/api/admin/customer_serializer_spec.rb": 0.34451889991760254, + "spec/serializers/api/enterprise_shopfront_list_serializer_spec.rb": 0.20429253578186035, + "spec/views/spree/admin/orders/index.html.haml_spec.rb": 0.26395225524902344, + "spec/serializers/api/enterprise_serializer_spec.rb": 0.23106741905212402, + "spec/lib/open_food_network/sales_tax_report_spec.rb": 0.12182736396789551, + "spec/helpers/spree/orders_helper_spec.rb": 0.03900146484375, + "spec/lib/open_food_network/xero_invoices_report_spec.rb": 0.18147039413452148, + "spec/features/consumer/sitemap_spec.rb": 0.12035298347473145, + "spec/controllers/groups_controller_spec.rb": 0.16557097434997559, + "spec/lib/open_food_network/option_value_namer_spec.rb": 0.13304948806762695, + "spec/models/spree/addresses_spec.rb": 0.11377644538879395, + "spec/lib/tasks/enterprises_rake_spec.rb": 0.0833287239074707, + "spec/controllers/api/taxonomies_controller_spec.rb": 0.08802604675292969, + "spec/helpers/spree/admin/orders_helper_spec.rb": 0.0646059513092041, + "spec/models/spree/gateway/stripe_connect_spec.rb": 0.05724668502807617, + "spec/helpers/shop_helper_spec.rb": 0.3690376281738281, + "spec/jobs/welcome_enterprise_job_spec.rb": 0.08383440971374512, + "spec/jobs/confirm_order_job_spec.rb": 0.06512808799743652, + "spec/lib/open_food_network/order_grouper_spec.rb": 0.05493974685668945, + "spec/lib/stripe/webhook_handler_spec.rb": 0.05923867225646973, + "spec/lib/open_food_network/enterprise_issue_validator_spec.rb": 0.058492183685302734, + "spec/validators/integer_array_validator_spec.rb": 0.04994392395019531, + "spec/validators/date_time_string_validator_spec.rb": 0.05316734313964844, + "spec/models/product_import/reset_absent_spec.rb": 0.04071307182312012, + "spec/lib/open_food_network/reports/report_spec.rb": 0.04329681396484375, + "spec/jobs/confirm_signup_job_spec.rb": 0.03060293197631836, + "spec/services/order_cycle_distributed_variants_spec.rb": 0.02808237075805664, + "spec/lib/open_food_network/feature_toggle_spec.rb": 0.0240786075592041, + "spec/requests/large_request_spec.rb": 0.029397964477539062, + "spec/serializers/api/credit_card_serializer_spec.rb": 0.018352746963500977, + "spec/models/spree/calculator/flat_rate_spec.rb": 0.014808177947998047, + "spec/models/spree/image_spec.rb": 0.014715909957885742, + "spec/helpers/spree/admin/base_helper_spec.rb": 0.008042573928833008, + "spec/lib/open_food_network/reports/row_spec.rb": 0.005358695983886719, + "engines/order_management/spec/controllers/order_management/reports/enterprise_fee_summaries_controller_spec.rb": 0.7100100517272949, + "engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/parameters_spec.rb": 4.190261602401733, + "engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb": 0.006247282028198242, + "engines/order_management/spec/services/order_management/subscriptions/form_spec.rb": 2.317678928375244, + "engines/order_management/spec/services/order_management/subscriptions/stripe_sca_payment_authorize_spec.rb": 0.7113745212554932, + "engines/order_management/spec/services/order_management/subscriptions/variants_list_spec.rb": 5.509528398513794, + "spec/controllers/api/shops_controller_spec.rb": 1.5322284698486328, + "spec/controllers/spree/admin/return_authorizations_controller_spec.rb": 1.8667445182800293, + "spec/features/admin/order_cycles/complex_editing_multiple_product_pages_spec.rb": 8.482699632644653, + "spec/features/admin/order_cycles/simple_spec.rb": 54.873196601867676, + "spec/features/admin/properties_spec.rb": 0.4787609577178955, + "spec/jobs/job_logger_spec.rb": 0.017215251922607422, + "spec/lib/stripe/credit_card_cloner_spec.rb": 0.04944920539855957, + "spec/lib/tasks/data/truncate_data_spec.rb": 6.85896372795105, + "spec/services/cache_service_spec.rb": 0.0431828498840332, + "spec/services/checkout/stripe_redirect_spec.rb": 0.3430206775665283, + "spec/services/order_completion_reset_spec.rb": 0.019972562789916992, + "spec/services/permissions/order_spec.rb": 11.389648914337158, + "spec/services/variant_overrides_indexed_spec.rb": 1.599395990371704, + "spec/features/admin/bulk_product_update_spec.rb": 85.68869709968567, + "spec/features/admin/subscriptions_spec.rb": 66.11615419387817, + "spec/features/consumer/authentication_spec.rb": 52.20323586463928, + "spec/models/order_cycle_spec.rb": 49.266600131988525, + "spec/features/admin/customers_spec.rb": 24.988075256347656, + "spec/models/spree/line_item_spec.rb": 43.67242765426636, + "spec/features/admin/enterprise_roles_spec.rb": 20.88746976852417, + "spec/features/consumer/shopping/cart_spec.rb": 31.941763639450073, + "spec/controllers/spree/orders_controller_spec.rb": 33.53650450706482, + "spec/features/admin/products_spec.rb": 18.70025324821472, + "spec/controllers/spree/admin/reports_controller_spec.rb": 24.616009950637817, + "spec/features/consumer/groups_spec.rb": 8.961438655853271, + "spec/jobs/subscription_placement_job_spec.rb": 19.798776626586914, + "spec/features/admin/enterprises/images_spec.rb": 8.75648546218872, + "spec/models/spree/ability_spec.rb": 18.13743495941162, + "spec/features/admin/configuration/states_spec.rb": 3.9561424255371094, + "spec/controllers/api/order_cycles_controller_spec.rb": 15.353764295578003, + "spec/models/enterprise_spec.rb": 15.155356407165527, + "spec/lib/open_food_network/order_cycle_form_applicator_spec.rb": 9.664437294006348, + "spec/controllers/spree/admin/orders_controller_spec.rb": 11.2395179271698, + "spec/models/enterprise_relationship_spec.rb": 10.401739358901978, + "spec/features/consumer/shopping/products_spec.rb": 4.121779441833496, + "spec/controllers/spree/admin/orders/customer_details_controller_spec.rb": 6.558723211288452, + "spec/serializers/api/order_serializer_spec.rb": 6.36934494972229, + "spec/controllers/admin/variant_overrides_controller_spec.rb": 6.027082681655884, + "spec/controllers/spree/admin/variants_controller_spec.rb": 4.790205717086792, + "spec/services/invoice_renderer_spec.rb": 3.852773666381836, + "spec/controllers/spree/admin/payments_controller_spec.rb": 12.54552435874939, + "spec/services/order_cycle_distributed_products_spec.rb": 4.698481321334839, + "spec/controllers/enterprises_controller_spec.rb": 7.326315879821777, + "spec/controllers/admin/inventory_items_controller_spec.rb": 3.6743903160095215, + "spec/models/product_import/inventory_reset_strategy_spec.rb": 3.342827796936035, + "spec/controllers/admin/subscription_line_items_controller_spec.rb": 3.391206741333008, + "spec/serializers/api/current_order_serializer_spec.rb": 2.8222057819366455, + "spec/serializers/api/enterprise_shopfront_serializer_spec.rb": 2.453338384628296, + "spec/features/admin/configuration/taxonomies_spec.rb": 2.2291758060455322, + "spec/requests/checkout/paypal_spec.rb": 1.9109196662902832, + "spec/features/admin/content_spec.rb": 1.6747770309448242, + "spec/controllers/spree/admin/payment_methods_controller_spec.rb": 1.7733421325683594, + "spec/lib/open_food_network/orders_and_fulfillments_report/customer_totals_report_spec.rb": 5.486632347106934, + "spec/controllers/admin/customers_controller_spec.rb": 1.6756572723388672, + "spec/lib/open_food_network/orders_and_fulfillments_report/supplier_totals_by_distributor_report_spec.rb": 1.494542121887207, + "spec/lib/open_food_network/orders_and_fulfillments_report/distributor_totals_by_supplier_report_spec.rb": 1.536348581314087, + "spec/controllers/api/enterprises_controller_spec.rb": 0.5235552787780762, + "spec/controllers/spree/admin/search_controller_spec.rb": 1.0657844543457031, + "spec/controllers/spree/admin/adjustments_controller_spec.rb": 0.9423079490661621, + "spec/features/admin/configuration/mail_methods_spec.rb": 0.43262410163879395, + "spec/controllers/spree/admin/overview_controller_spec.rb": 0.8039264678955078, + "spec/helpers/admin/orders_helper_spec.rb": 0.7706241607666016, + "spec/helpers/order_cycles_helper_spec.rb": 0.6262946128845215, + "spec/controllers/stripe/callbacks_controller_spec.rb": 0.41001367568969727, + "spec/lib/stripe/account_connector_spec.rb": 0.4642143249511719, + "spec/models/tag_rule/filter_shipping_methods_spec.rb": 0.47301149368286133, + "spec/features/admin/external_services_spec.rb": 0.3893253803253174, + "spec/models/stock/package_spec.rb": 0.43047094345092773, + "spec/controllers/admin/stripe_connect_settings_controller_spec.rb": 0.27138352394104004, + "spec/controllers/spree/admin/image_settings_controller_spec.rb": 0.2584362030029297, + "spec/features/consumer/confirm_invitation_spec.rb": 0.3224337100982666, + "spec/helpers/checkout_helper_spec.rb": 0.13036370277404785, + "spec/helpers/groups_helper_spec.rb": 0.012569427490234375, + "engines/dfc_provider/spec/controllers/dfc_provider/api/products_controller_spec.rb": 2.489386558532715, + "engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/authorizer_spec.rb": 0.3381996154785156, + "engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb": 0.03400373458862305, + "engines/order_management/spec/services/order_management/subscriptions/estimator_spec.rb": 7.829980373382568, + "engines/order_management/spec/services/order_management/subscriptions/stripe_payment_setup_spec.rb": 1.1807715892791748, + "engines/order_management/spec/services/order_management/subscriptions/validator_spec.rb": 2.3976430892944336, + "spec/controllers/api/exchange_products_controller_spec.rb": 11.865624189376831, + "spec/features/admin/order_cycles/complex_editing_exchange_same_enterprise_spec.rb": 2.529176950454712, + "spec/features/admin/order_cycles/list_spec.rb": 11.831752300262451, + "spec/features/admin/order_spec.rb": 171.90774178504944, + "spec/helpers/spree/admin/reports_helper_spec.rb": 5.590947389602661, + "spec/lib/stripe/authorize_response_patcher_spec.rb": 0.015482425689697266, + "spec/lib/tasks/data/truncate_data_rake_spec.rb": 4.825200080871582, + "spec/serializers/api/admin/order_serializer_spec.rb": 3.7998077869415283, + "spec/services/checkout/post_checkout_actions_spec.rb": 0.5116136074066162, + "spec/services/order_checkout_restart_spec.rb": 1.1087470054626465, + "spec/services/order_tax_adjustments_fetcher_spec.rb": 7.905844449996948, + "spec/services/user_default_address_setter_spec.rb": 0.32790184020996094, + "spec/views/spree/shared/_order_details.html.haml_spec.rb": 6.581799507141113, +"spec/features/admin/orders_spec.rb": 37.106088638305664, + "spec/features/consumer/shopping/variant_overrides_spec.rb": 141.61448574066162, + "spec/features/consumer/shopping/shopping_spec.rb": 108.44787383079529, + "spec/controllers/api/orders_controller_spec.rb": 105.54521131515503, + "spec/features/admin/shipping_methods_spec.rb": 43.61902475357056, + "spec/features/admin/tag_rules_spec.rb": 31.836936473846436, + "spec/features/consumer/shops_spec.rb": 32.17615461349487, + "spec/controllers/admin/bulk_line_items_controller_spec.rb": 56.091858863830566, + "spec/lib/open_food_network/order_cycle_permissions_spec.rb": 49.695056438446045, + "spec/controllers/api/shipments_controller_spec.rb": 38.7325336933136, + "spec/features/consumer/registration_spec.rb": 15.266984462738037, + "spec/controllers/api/products_controller_spec.rb": 34.40333008766174, + "spec/models/spree/product_spec.rb": 41.200894832611084, + "spec/features/consumer/shopping/checkout_auth_spec.rb": 15.087469100952148, + "spec/services/order_factory_spec.rb": 21.278146266937256, + "spec/jobs/order_cycle_notification_job_spec.rb": 2.3313727378845215, + "spec/controllers/spree/user_sessions_controller_spec.rb": 0.06848526000976562, + "spec/models/calculator/flat_percent_per_item_spec.rb": 0.03157782554626465, + "engines/catalog/spec/services/catalog/product_import/products_reset_strategy_spec.rb": 4.549654483795166, + "engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/csv_renderer_spec.rb": 0.06129932403564453, + "engines/order_management/spec/services/order_management/subscriptions/count_spec.rb": 1.851973533630371, + "engines/order_management/spec/services/order_management/subscriptions/proxy_order_syncer_spec.rb": 13.164706230163574, + "engines/order_management/spec/services/order_management/subscriptions/summary_spec.rb": 0.3277547359466553, + "engines/web/spec/helpers/cookies_policy_helper_spec.rb": 0.054679155349731445, + "spec/controllers/spree/admin/countries_controller_spec.rb": 0.07967424392700195, + "spec/features/admin/order_cycles/complex_creating_specific_time_spec.rb": 13.867288827896118, + "spec/features/admin/order_cycles/complex_updating_specific_time_spec.rb": 20.47591543197632, + "spec/features/admin/order_print_ticket_spec.rb": 6.32416033744812, + "spec/features/consumer/caching/shops_caching_spec.rb": 10.797699213027954, + "spec/lib/open_food_network/i18n_inflections_spec.rb": 0.04519987106323242, + "spec/lib/stripe/profile_storer_spec.rb": 0.6562683582305908, + "spec/serializers/api/admin/order_cycle_serializer_spec.rb": 10.537176847457886, + "spec/services/checkout/paypal_redirect_spec.rb": 0.1902143955230713, + "spec/services/order_cart_reset_spec.rb": 2.440279245376587, + "spec/services/order_payment_finder_spec.rb": 1.5698633193969727, + "spec/services/permitted_attributes/user_spec.rb": 0.021186351776123047, + "spec/views/spree/admin/payment_methods/index.html.haml_spec.rb": 0.16062402725219727 } diff --git a/lib/open_food_network/bulk_coop_report.rb b/lib/open_food_network/bulk_coop_report.rb deleted file mode 100644 index 0044692601..0000000000 --- a/lib/open_food_network/bulk_coop_report.rb +++ /dev/null @@ -1,140 +0,0 @@ -require 'open_food_network/reports/bulk_coop_supplier_report' -require 'open_food_network/reports/bulk_coop_allocation_report' -require "open_food_network/reports/line_items" - -module OpenFoodNetwork - class BulkCoopReport - attr_reader :params - def initialize(user, params = {}, render_table = false) - @params = params - @user = user - @render_table = render_table - - @supplier_report = Reports::BulkCoopSupplierReport.new - @allocation_report = Reports::BulkCoopAllocationReport.new - end - - def header - case params[:report_type] - when "bulk_coop_supplier_report" - @supplier_report.header - when "bulk_coop_allocation" - @allocation_report.header - when "bulk_coop_packing_sheets" - [I18n.t(:report_header_customer), - I18n.t(:report_header_product), - I18n.t(:report_header_variant), - I18n.t(:report_header_sum_total)] - when "bulk_coop_customer_payments" - [I18n.t(:report_header_customer), - I18n.t(:report_header_date_of_order), - I18n.t(:report_header_total_cost), - I18n.t(:report_header_amount_owing), - I18n.t(:report_header_amount_paid)] - else - [I18n.t(:report_header_supplier), - I18n.t(:report_header_product), - I18n.t(:report_header_product), - I18n.t(:report_header_bulk_unit_size), - I18n.t(:report_header_variant), - I18n.t(:report_header_weight), - I18n.t(:report_header_sum_total), - I18n.t(:report_header_sum_max_total), - I18n.t(:report_header_units_required), - I18n.t(:report_header_remainder)] - end - end - - def search - report_line_items.orders - end - - def table_items - return [] unless @render_table - - report_line_items.list(line_item_includes) - end - - def rules - case params[:report_type] - when "bulk_coop_supplier_report" - @supplier_report.rules - when "bulk_coop_allocation" - @allocation_report.rules - when "bulk_coop_packing_sheets" - [{ group_by: proc { |li| li.product }, - sort_by: proc { |product| product.name } }, - { group_by: proc { |li| li.full_name }, - sort_by: proc { |full_name| full_name } }, - { group_by: proc { |li| li.order }, - sort_by: proc { |order| order.to_s } }] - when "bulk_coop_customer_payments" - [{ group_by: proc { |li| li.order }, - sort_by: proc { |order| order.completed_at } }] - else - [{ group_by: proc { |li| li.product.supplier }, - sort_by: proc { |supplier| supplier.name } }, - { group_by: proc { |li| li.product }, - sort_by: proc { |product| product.name }, - summary_columns: [proc { |lis| lis.first.product.supplier.name }, - proc { |lis| lis.first.product.name }, - proc { |lis| lis.first.product.group_buy_unit_size || 0.0 }, - proc { |_lis| "" }, - proc { |_lis| "" }, - proc { |lis| lis.sum { |li| li.quantity * (li.weight_from_unit_value || 0) } }, - proc { |lis| lis.sum { |li| (li.max_quantity || 0) * (li.weight_from_unit_value || 0) } }, - proc { |lis| ( (lis.first.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| [li.max_quantity || 0, li.quantity || 0].max * (li.weight_from_unit_value || 0) } / lis.first.product.group_buy_unit_size ) ).floor }, - proc { |lis| lis.sum { |li| [li.max_quantity || 0, li.quantity || 0].max * (li.weight_from_unit_value || 0) } - ( ( (lis.first.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| [li.max_quantity || 0, li.quantity || 0].max * (li.weight_from_unit_value || 0) } / lis.first.product.group_buy_unit_size ) ).floor * (lis.first.product.group_buy_unit_size || 0) ) }] }, - { group_by: proc { |li| li.full_name }, - sort_by: proc { |full_name| full_name } }] - end - end - - def columns - case params[:report_type] - when "bulk_coop_supplier_report" - @supplier_report.columns - when "bulk_coop_allocation" - @allocation_report.columns - when "bulk_coop_packing_sheets" - [proc { |lis| lis.first.order.bill_address.firstname + " " + lis.first.order.bill_address.lastname }, - proc { |lis| lis.first.product.name }, - proc { |lis| lis.first.full_name }, - proc { |lis| lis.sum(&:quantity) }] - when "bulk_coop_customer_payments" - [proc { |lis| lis.first.order.bill_address.firstname + " " + lis.first.order.bill_address.lastname }, - proc { |lis| lis.first.order.completed_at.to_s }, - proc { |lis| lis.map(&:order).uniq.sum(&:total) }, - proc { |lis| lis.map(&:order).uniq.sum(&:outstanding_balance) }, - proc { |lis| lis.map(&:order).uniq.sum(&:payment_total) }] - else - [proc { |lis| lis.first.product.supplier.name }, - proc { |lis| lis.first.product.name }, - proc { |lis| lis.first.product.group_buy_unit_size || 0.0 }, - proc { |lis| lis.first.full_name }, - proc { |lis| lis.first.weight_from_unit_value || 0 }, - proc { |lis| lis.sum(&:quantity) }, - proc { |lis| lis.sum { |li| li.max_quantity || 0 } }, - proc { |_lis| "" }, - proc { |_lis| "" }] - end - end - - private - - def line_item_includes - [{ order: [:bill_address], - variant: [{ option_values: :option_type }, { product: :supplier }] }] - end - - def order_permissions - return @order_permissions unless @order_permissions.nil? - - @order_permissions = ::Permissions::Order.new(@user, @params[:q]) - end - - def report_line_items - @report_line_items ||= Reports::LineItems.new(order_permissions, @params) - end - end -end diff --git a/lib/open_food_network/order_grouper.rb b/lib/open_food_network/order_grouper.rb index 5b792bd5d4..8d810254d5 100644 --- a/lib/open_food_network/order_grouper.rb +++ b/lib/open_food_network/order_grouper.rb @@ -1,8 +1,9 @@ module OpenFoodNetwork class OrderGrouper - def initialize(rules, column_constructors) + def initialize(rules, column_constructors, report = nil) @rules = rules @column_constructors = column_constructors + @report = report end def build_tree(items, remaining_rules) @@ -38,11 +39,11 @@ module OpenFoodNetwork def build_table(groups) rows = [] if is_leaf_node(groups) - rows << @column_constructors.map { |column_constructor| column_constructor.call(groups) } + rows << build_row(groups) else groups.each do |key, group| if key == :summary_row - rows << group[:columns].map { |cols| cols.call(group[:items]) } + rows << build_summary_row(group[:columns], group[:items]) else build_table(group).each { |g| rows << g } end @@ -59,6 +60,26 @@ module OpenFoodNetwork private + def build_cell(column_constructor, items) + if column_constructor.is_a?(Symbol) + @report.__send__(column_constructor, items) + else + column_constructor.call(items) + end + end + + def build_row(groups) + @column_constructors.map do |column_constructor| + build_cell(column_constructor, groups) + end + end + + def build_summary_row(summary_row_column_constructors, items) + summary_row_column_constructors.map do |summary_row_column_constructor| + build_cell(summary_row_column_constructor, items) + end + end + def is_leaf_node(node) node.is_a? Array end diff --git a/lib/open_food_network/products_and_inventory_report_base.rb b/lib/open_food_network/products_and_inventory_report_base.rb index d0f4a50de8..c669dc4d6a 100644 --- a/lib/open_food_network/products_and_inventory_report_base.rb +++ b/lib/open_food_network/products_and_inventory_report_base.rb @@ -36,7 +36,6 @@ module OpenFoodNetwork end # Using the `in_stock?` method allows overrides by distributors. - # It also allows the upgrade to Spree 2.0. def filter_on_hand(variants) if params[:report_type] == 'inventory' variants.select(&:in_stock?) diff --git a/lib/open_food_network/reports/bulk_coop_allocation_report.rb b/lib/open_food_network/reports/bulk_coop_allocation_report.rb deleted file mode 100644 index f132e642df..0000000000 --- a/lib/open_food_network/reports/bulk_coop_allocation_report.rb +++ /dev/null @@ -1,64 +0,0 @@ -require 'open_food_network/reports/bulk_coop_report' - -module OpenFoodNetwork::Reports - class BulkCoopAllocationReport < BulkCoopReport - def header - [ - I18n.t(:report_header_customer), - I18n.t(:report_header_product), - I18n.t(:report_header_bulk_unit_size), - I18n.t(:report_header_variant), - I18n.t(:report_header_variant_value), - I18n.t(:report_header_variant_unit), - I18n.t(:report_header_weight), - I18n.t(:report_header_sum_total), - I18n.t(:report_header_total_available), - I18n.t(:report_header_unallocated), - I18n.t(:report_header_max_quantity_excess), - ] - end - - organise do - group(&:product) - sort(&:name) - - summary_row do - column { |_lis| I18n.t('admin.reports.total') } - column { |lis| product_name(lis) } - column { |lis| group_buy_unit_size_f(lis) } - column { |_lis| "" } - column { |_lis| "" } - column { |_lis| "" } - column { |_lis| "" } - column { |lis| total_amount(lis) } - column { |lis| total_available(lis) } - column { |lis| remainder(lis) } - column { |lis| max_quantity_excess(lis) } - end - - organise do - group(&:full_name) - sort { |full_name| full_name } - - organise do - group(&:order) - sort(&:to_s) - end - end - end - - columns do - column { |lis| lis.first.order.bill_address.firstname + " " + lis.first.order.bill_address.lastname } - column { |lis| lis.first.product.name } - column { |lis| lis.first.product.group_buy_unit_size || 0.0 } - column { |lis| lis.first.full_name } - column { |lis| OpenFoodNetwork::OptionValueNamer.new(lis.first).value } - column { |lis| OpenFoodNetwork::OptionValueNamer.new(lis.first).unit } - column { |lis| lis.first.weight_from_unit_value || 0 } - column { |lis| total_amount(lis) } - column { |_lis| "" } - column { |_lis| "" } - column { |_lis| "" } - end - end -end diff --git a/lib/open_food_network/reports/bulk_coop_report.rb b/lib/open_food_network/reports/bulk_coop_report.rb deleted file mode 100644 index d05aded79a..0000000000 --- a/lib/open_food_network/reports/bulk_coop_report.rb +++ /dev/null @@ -1,66 +0,0 @@ -require 'open_food_network/reports/report' - -module OpenFoodNetwork::Reports - class BulkCoopReport < Report - private - - class << self - def supplier_name(lis) - lis.first.variant.product.supplier.name - end - - def product_name(lis) - lis.first.variant.product.name - end - - def group_buy_unit_size(lis) - (lis.first.variant.product.group_buy_unit_size || 0.0) / - (lis.first.product.variant_unit_scale || 1) - end - - def group_buy_unit_size_f(lis) - group_buy_unit_size(lis) - end - - def total_amount(lis) - lis.sum { |li| scaled_final_weight_volume(li) } - end - - def units_required(lis) - if group_buy_unit_size(lis).zero? - 0 - else - ( total_amount(lis) / group_buy_unit_size(lis) ).ceil - end - end - - def total_available(lis) - units_required(lis) * group_buy_unit_size(lis) - end - - def remainder(lis) - remainder = total_available(lis) - total_amount(lis) - remainder >= 0 ? remainder : '' - end - - def max_quantity_excess(lis) - max_quantity_amount(lis) - total_amount(lis) - end - - def max_quantity_amount(lis) - lis.sum do |li| - max_quantity = [li.max_quantity || 0, li.quantity || 0].max - max_quantity * scaled_unit_value(li.variant) - end - end - - def scaled_final_weight_volume(line_item) - (line_item.final_weight_volume || 0) / (line_item.product.variant_unit_scale || 1) - end - - def scaled_unit_value(variant) - (variant.unit_value || 0) / (variant.product.variant_unit_scale || 1) - end - end - end -end diff --git a/lib/open_food_network/reports/bulk_coop_supplier_report.rb b/lib/open_food_network/reports/bulk_coop_supplier_report.rb deleted file mode 100644 index ae4ed3da37..0000000000 --- a/lib/open_food_network/reports/bulk_coop_supplier_report.rb +++ /dev/null @@ -1,64 +0,0 @@ -require 'open_food_network/reports/bulk_coop_report' - -module OpenFoodNetwork::Reports - class BulkCoopSupplierReport < BulkCoopReport - def header - [ - I18n.t(:report_header_supplier), - I18n.t(:report_header_product), - I18n.t(:report_header_bulk_unit_size), - I18n.t(:report_header_variant), - I18n.t(:report_header_variant_value), - I18n.t(:report_header_variant_unit), - I18n.t(:report_header_weight), - I18n.t(:report_header_sum_total), - I18n.t(:report_header_units_required), - I18n.t(:report_header_unallocated), - I18n.t(:report_header_max_quantity_excess), - ] - end - - organise do - group { |li| li.product.supplier } - sort(&:name) - - organise do - group(&:product) - sort(&:name) - - summary_row do - column { |lis| supplier_name(lis) } - column { |lis| product_name(lis) } - column { |lis| group_buy_unit_size_f(lis) } - column { |_lis| "" } - column { |_lis| "" } - column { |_lis| "" } - column { |_lis| "" } - column { |lis| total_amount(lis) } - column { |lis| units_required(lis) } - column { |lis| remainder(lis) } - column { |lis| max_quantity_excess(lis) } - end - - organise do - group(&:full_name) - sort { |full_name| full_name } - end - end - end - - columns do - column { |lis| supplier_name(lis) } - column { |lis| product_name(lis) } - column { |lis| group_buy_unit_size_f(lis) } - column { |lis| lis.first.full_name } - column { |lis| OpenFoodNetwork::OptionValueNamer.new(lis.first).value } - column { |lis| OpenFoodNetwork::OptionValueNamer.new(lis.first).unit } - column { |lis| lis.first.weight_from_unit_value || 0 } - column { |lis| total_amount(lis) } - column { |_lis| '' } - column { |_lis| '' } - column { |_lis| '' } - end - end -end diff --git a/lib/open_food_network/reports/report.rb b/lib/open_food_network/reports/report.rb index 2933b7cec6..2c4e5900f2 100644 --- a/lib/open_food_network/reports/report.rb +++ b/lib/open_food_network/reports/report.rb @@ -31,15 +31,5 @@ module OpenFoodNetwork::Reports def self.header(*columns) self._header = columns end - - def self.columns(&block) - self._columns = Row.new - Blockenspiel.invoke block, _columns - end - - def self.organise(&block) - self._rules_head = Rule.new - Blockenspiel.invoke block, _rules_head - end end end diff --git a/lib/open_food_network/reports/row.rb b/lib/open_food_network/reports/row.rb index 01c8dd192e..35b4bffe33 100644 --- a/lib/open_food_network/reports/row.rb +++ b/lib/open_food_network/reports/row.rb @@ -1,7 +1,5 @@ module OpenFoodNetwork::Reports class Row - include Blockenspiel::DSL - def initialize @columns = [] end diff --git a/lib/open_food_network/reports/rule.rb b/lib/open_food_network/reports/rule.rb index 372c52b249..cfb8b7e67f 100644 --- a/lib/open_food_network/reports/rule.rb +++ b/lib/open_food_network/reports/rule.rb @@ -2,7 +2,6 @@ require 'open_food_network/reports/row' module OpenFoodNetwork::Reports class Rule - include Blockenspiel::DSL attr_reader :next def group(&block) @@ -13,16 +12,6 @@ module OpenFoodNetwork::Reports @sort = block end - def summary_row(&block) - @summary_row = Row.new - Blockenspiel.invoke block, @summary_row - end - - def organise(&block) - @next = Rule.new - Blockenspiel.invoke block, @next - end - def to_h h = { group_by: @group, sort_by: @sort } h[:summary_columns] = @summary_row.to_a if @summary_row diff --git a/lib/spree/authentication_helpers.rb b/lib/spree/authentication_helpers.rb index 49bd97542a..1f26c65f2c 100644 --- a/lib/spree/authentication_helpers.rb +++ b/lib/spree/authentication_helpers.rb @@ -11,7 +11,9 @@ module Spree current_spree_user end - delegate :login_path, to: :spree, prefix: true + def spree_login_path + main_app.login_path + end delegate :signup_path, to: :spree, prefix: true diff --git a/lib/spree/core/controller_helpers.rb b/lib/spree/core/controller_helpers.rb new file mode 100644 index 0000000000..3df5251c9a --- /dev/null +++ b/lib/spree/core/controller_helpers.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Spree + module Core + module ControllerHelpers + def self.included(klass) + klass.class_eval do + include Spree::Core::ControllerHelpers::Common + include Spree::Core::ControllerHelpers::Auth + include Spree::Core::ControllerHelpers::RespondWith + include Spree::Core::ControllerHelpers::Order + end + end + end + end +end diff --git a/lib/spree/core/controller_helpers/auth.rb b/lib/spree/core/controller_helpers/auth.rb new file mode 100644 index 0000000000..c6c18b9be1 --- /dev/null +++ b/lib/spree/core/controller_helpers/auth.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module Spree + module Core + module ControllerHelpers + module Auth + extend ActiveSupport::Concern + + included do + before_filter :ensure_api_key + + rescue_from CanCan::AccessDenied do + unauthorized + end + end + + # Needs to be overriden so that we use Spree's Ability rather than anyone else's. + def current_ability + @current_ability ||= Spree::Ability.new(spree_current_user) + end + + # Redirect as appropriate when an access request fails. The default action is to redirect + # to the login screen. Override this method in your controllers if you want to have + # special behavior in case the user is not authorized to access the requested action. + # For example, a popup window might simply close itself. + def unauthorized + if spree_current_user + flash[:error] = Spree.t(:authorization_failure) + redirect_to '/unauthorized' + else + store_location + redirect_to main_app.root_path(anchor: "login?after_login=#{request.env['PATH_INFO']}") + end + end + + def store_location + # disallow return to login, logout, signup pages + authentication_routes = [:spree_signup_path, :spree_login_path, :spree_logout_path] + disallowed_urls = [] + authentication_routes.each do |route| + if respond_to?(route) + disallowed_urls << __send__(route) + end + end + + disallowed_urls.map!{ |url| url[/\/\w+$/] } + return if disallowed_urls.include?(request.fullpath) + + session['spree_user_return_to'] = request.fullpath.gsub('//', '/') + end + + def redirect_back_or_default(default) + redirect_to(session["spree_user_return_to"] || default) + session["spree_user_return_to"] = nil + end + + # Need to generate an API key for a user due to some actions potentially + # requiring authentication to the Spree API + def ensure_api_key + return unless (user = spree_current_user) + + return unless user.respond_to?(:spree_api_key) && user.spree_api_key.blank? + + user.generate_spree_api_key! + end + end + end + end +end diff --git a/lib/spree/core/controller_helpers/auth_decorator.rb b/lib/spree/core/controller_helpers/auth_decorator.rb deleted file mode 100644 index 7342450c74..0000000000 --- a/lib/spree/core/controller_helpers/auth_decorator.rb +++ /dev/null @@ -1,5 +0,0 @@ -Spree::Core::ControllerHelpers::Auth.class_eval do - def require_login_then_redirect_to(url) - redirect_to main_app.root_path(anchor: "login?after_login=#{url}") - end -end diff --git a/lib/spree/core/controller_helpers/common.rb b/lib/spree/core/controller_helpers/common.rb new file mode 100644 index 0000000000..e88920eaf6 --- /dev/null +++ b/lib/spree/core/controller_helpers/common.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module Spree + module Core + module ControllerHelpers + module Common + extend ActiveSupport::Concern + included do + helper_method :title + helper_method :title= + helper_method :accurate_title + + layout :get_layout + + before_filter :set_user_language + + protected + + # Convenience method for firing instrumentation events with the default payload hash + def fire_event(name, extra_payload = {}) + ActiveSupport::Notifications.instrument(name, default_notification_payload. + merge(extra_payload)) + end + + # Creates the hash that is sent as the payload for all notifications. + # Specific notifications will add additional keys as appropriate. + # This method can be overriden to provide additional data when + # responding to a notification + def default_notification_payload + { user: spree_current_user, order: current_order } + end + + # This can be used in views as well as controllers. + # e.g. <% self.title = 'This is a custom title for this view' %> + attr_writer :title + + def title + title_string = @title.presence || accurate_title + if title_string.present? + if Spree::Config[:always_put_site_name_in_title] + [title_string, default_title].join(' - ') + else + title_string + end + else + default_title + end + end + + def default_title + Spree::Config[:site_name] + end + + # This is a hook for subclasses to provide title + def accurate_title + Spree::Config[:default_seo_title] + end + + def render_404(_exception = nil) + respond_to do |type| + type.html { + render status: :not_found, + file: "#{::Rails.root}/public/404", + formats: [:html], + layout: nil + } + type.all { render status: :not_found, nothing: true } + end + end + + private + + def set_user_language + locale = session[:locale] + locale ||= config_locale if respond_to?(:config_locale, true) + locale ||= Rails.application.config.i18n.default_locale + unless I18n.available_locales.map(&:to_s).include?(locale) + locale ||= I18n.default_locale + end + I18n.locale = locale + end + + # Returns which layout to render. + # The layout to render can be set inside Spree configuration with the +:layout+ option. + # Default layout is: +app/views/spree/layouts/spree_application+ + def get_layout + layout ||= Spree::Config[:layout] + end + end + end + end + end +end diff --git a/lib/spree/core/controller_helpers/order.rb b/lib/spree/core/controller_helpers/order.rb new file mode 100644 index 0000000000..1df01e286a --- /dev/null +++ b/lib/spree/core/controller_helpers/order.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require 'open_food_network/scope_variant_to_hub' + +module Spree + module Core + module ControllerHelpers + module Order + def self.included(base) + base.class_eval do + helper_method :current_order + helper_method :current_currency + before_filter :set_current_order + end + end + + def current_order(create_order_if_necessary = false) + order = spree_current_order(create_order_if_necessary) + + if order + scoper = OpenFoodNetwork::ScopeVariantToHub.new(order.distributor) + order.line_items.each do |li| + scoper.scope(li.variant) + end + end + + order + end + + # The current incomplete session order used in cart and checkout + def spree_current_order(create_order_if_necessary = false) + return @current_order if @current_order + + if session[:order_id] + current_order = Spree::Order.includes(:adjustments) + .find_by(id: session[:order_id], currency: current_currency) + @current_order = current_order unless current_order.try(:completed?) + end + + if create_order_if_necessary && (@current_order.nil? || @current_order.completed?) + @current_order = Spree::Order.new(currency: current_currency) + @current_order.user ||= spree_current_user + # See https://github.com/spree/spree/issues/3346 for reasons why this line is here + @current_order.created_by ||= spree_current_user + @current_order.save! + + # Verify that the user has access to the order (if they are a guest) + if spree_current_user.nil? + session[:access_token] = @current_order.token + end + end + + return unless @current_order + + @current_order.last_ip_address = ip_address + session[:order_id] = @current_order.id + @current_order + end + + def associate_user + @order ||= current_order + if spree_current_user && @order + if @order.user.blank? || @order.email.blank? + @order.associate_user!(spree_current_user) + end + end + + session[:guest_token] = nil + end + + # Do not attempt to merge incomplete and current orders. + # Instead, destroy the incomplete orders. + def set_current_order + return unless (user = spree_current_user) + + last_incomplete_order = user.last_incomplete_spree_order + + if session[:order_id].nil? && last_incomplete_order + session[:order_id] = last_incomplete_order.id + elsif current_order(true) && + last_incomplete_order && + current_order != last_incomplete_order + last_incomplete_order.destroy + end + end + + def current_currency + Spree::Config[:currency] + end + + def ip_address + request.env['HTTP_X_REAL_IP'] || request.env['REMOTE_ADDR'] + end + end + end + end +end diff --git a/lib/spree/core/controller_helpers/order_decorator.rb b/lib/spree/core/controller_helpers/order_decorator.rb deleted file mode 100644 index 6da1b42ee6..0000000000 --- a/lib/spree/core/controller_helpers/order_decorator.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'open_food_network/scope_variant_to_hub' - -Spree::Core::ControllerHelpers::Order.class_eval do - def current_order_with_scoped_variants(create_order_if_necessary = false) - order = current_order_without_scoped_variants(create_order_if_necessary) - - if order - scoper = OpenFoodNetwork::ScopeVariantToHub.new(order.distributor) - order.line_items.each do |li| - scoper.scope(li.variant) - end - end - - order - end - alias_method_chain :current_order, :scoped_variants - - # Override definition in Spree::Core::ControllerHelpers::Order - # Do not attempt to merge incomplete and current orders. Instead, destroy the incomplete orders. - def set_current_order - if user = try_spree_current_user - last_incomplete_order = user.last_incomplete_spree_order - - if session[:order_id].nil? && last_incomplete_order - session[:order_id] = last_incomplete_order.id - - elsif current_order && last_incomplete_order && current_order != last_incomplete_order - last_incomplete_order.destroy - end - end - end -end diff --git a/lib/spree/core/controller_helpers/respond_with.rb b/lib/spree/core/controller_helpers/respond_with.rb new file mode 100644 index 0000000000..6b3e7129ca --- /dev/null +++ b/lib/spree/core/controller_helpers/respond_with.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +module ActionController + class Base + def respond_with(*resources, &block) + if self.class.mimes_for_respond_to.empty? + raise "In order to use respond_with, first you need to declare the formats your " \ + "controller responds to in the class level" + end + + return unless (collector = retrieve_collector_from_mimes(&block)) + + options = resources.size == 1 ? {} : resources.extract_options! + + # Fix spree issues #3531 and #2210 (patch provided by leiyangyou) + if (defined_response = collector.response) && + !Spree::BaseController.spree_responders[self.class.to_s.to_sym].try(:[], + action_name.to_sym) + if action = options.delete(:action) + render action: action + else + defined_response.call + end + else + # The action name is needed for processing + options[:action_name] = action_name.to_sym + # If responder is not specified then pass in Spree::Responder + (options.delete(:responder) || Spree::Responder).call(self, resources, options) + end + end + end +end + +module Spree + module Core + module ControllerHelpers + module RespondWith + extend ActiveSupport::Concern + + included do + cattr_accessor :spree_responders + self.spree_responders = {} + end + + module ClassMethods + def clear_overrides! + self.spree_responders = {} + end + + def respond_override(options = {}) + return if options.blank? + + action_name = options.keys.first + action_value = options.values.first + + if action_name.blank? || action_value.blank? + raise ArgumentError, "invalid values supplied #{options.inspect}" + end + + format_name = action_value.keys.first + format_value = action_value.values.first + + if format_name.blank? || format_value.blank? + raise ArgumentError, "invalid values supplied #{options.inspect}" + end + + if format_value.is_a?(Proc) + options = { + action_name.to_sym => { format_name.to_sym => { success: format_value } } + } + end + + spree_responders.deep_merge!(name.to_sym => options) + end + end + end + end + end +end diff --git a/lib/spree/core/controller_helpers/respond_with_decorator.rb b/lib/spree/core/controller_helpers/respond_with_decorator.rb deleted file mode 100644 index 522247ea01..0000000000 --- a/lib/spree/core/controller_helpers/respond_with_decorator.rb +++ /dev/null @@ -1,28 +0,0 @@ -module ActionController - class Base - def respond_with(*resources, &block) - if self.class.mimes_for_respond_to.empty? - raise "In order to use respond_with, first you need to declare the formats your " \ - "controller responds to in the class level" - end - - if collector = retrieve_collector_from_mimes(&block) - options = resources.size == 1 ? {} : resources.extract_options! - - # Fix spree issues #3531 and #2210 (patch provided by leiyangyou) - if (defined_response = collector.response) && !Spree::BaseController.spree_responders[self.class.to_s.to_sym].try(:[], action_name.to_sym) - if action = options.delete(:action) - render action: action - else - defined_response.call - end - else - # The action name is needed for processing - options[:action_name] = action_name.to_sym - # If responder is not specified then pass in Spree::Responder - (options.delete(:responder) || Spree::Responder).call(self, resources, options) - end - end - end - end -end diff --git a/lib/spree/core/controller_helpers/ssl.rb b/lib/spree/core/controller_helpers/ssl.rb new file mode 100644 index 0000000000..6c923e4977 --- /dev/null +++ b/lib/spree/core/controller_helpers/ssl.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module Spree + module Core + module ControllerHelpers + module SSL + extend ActiveSupport::Concern + + included do + before_filter :force_non_ssl_redirect, if: proc { Spree::Config[:redirect_https_to_http] } + + def self.ssl_allowed(*actions) + class_attribute :ssl_allowed_actions + self.ssl_allowed_actions = actions + end + + def self.ssl_required(*actions) + class_attribute :ssl_required_actions + self.ssl_required_actions = actions + return unless ssl_supported? + + if ssl_required_actions.empty? || Rails.application.config.force_ssl + force_ssl + else + force_ssl only: ssl_required_actions + end + end + + def self.ssl_supported? + return Spree::Config[:allow_ssl_in_production] if Rails.env.production? + return Spree::Config[:allow_ssl_in_staging] if Rails.env.staging? + return unless Rails.env.development? || Rails.env.test? + + Spree::Config[:allow_ssl_in_development_and_test] + end + + private + + # Redirect the existing request to use the HTTP protocol. + # + # ==== Parameters + # * host - Redirect to a different host name + def force_non_ssl_redirect(host = nil) + return true if defined?(ssl_allowed_actions) && + ssl_allowed_actions.include?(action_name.to_sym) + + return unless request.ssl? && + (!defined?(ssl_required_actions) || + !ssl_required_actions.include?(action_name.to_sym)) + + redirect_options = { protocol: 'http://', status: :moved_permanently } + redirect_options.merge!(host: host) if host + redirect_options.merge!(params: request.query_parameters) + flash.keep if respond_to?(:flash) + redirect_to redirect_options + end + end + end + end + end +end diff --git a/lib/spree/core/environment.rb b/lib/spree/core/environment.rb new file mode 100644 index 0000000000..4cd6fb5133 --- /dev/null +++ b/lib/spree/core/environment.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Spree + module Core + class Environment + include EnvironmentExtension + + attr_accessor :calculators, :payment_methods, :preferences + + def initialize + @calculators = Calculators.new + @preferences = Spree::AppConfiguration.new + end + end + end +end diff --git a/lib/tasks/sample_data/payment_method_factory.rb b/lib/tasks/sample_data/payment_method_factory.rb index 27dc5404cf..b8430c3d96 100644 --- a/lib/tasks/sample_data/payment_method_factory.rb +++ b/lib/tasks/sample_data/payment_method_factory.rb @@ -29,7 +29,7 @@ class PaymentMethodFactory enterprise, "Cash on collection", "Pay on collection!", - Spree::Calculator::FlatRate.new + Calculator::FlatRate.new ) end @@ -39,7 +39,7 @@ class PaymentMethodFactory enterprise, "Credit card (fake)", "We charge 1%, but won't ask for your details. ;-)", - Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 1) + Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 1) ) end diff --git a/lib/tasks/sample_data/shipping_method_factory.rb b/lib/tasks/sample_data/shipping_method_factory.rb index 5bfb382318..889765775a 100644 --- a/lib/tasks/sample_data/shipping_method_factory.rb +++ b/lib/tasks/sample_data/shipping_method_factory.rb @@ -39,7 +39,7 @@ class ShippingMethodFactory name: "Home delivery #{enterprise.name}", description: "yummy food delivered at your door", require_ship_address: true, - calculator_type: "Spree::Calculator::FlatRate" + calculator_type: "Calculator::FlatRate" ) delivery.calculator.preferred_amount = 2 delivery.calculator.save! diff --git a/app/assets/images/home/home.jpg b/public/default_images/home.jpg similarity index 100% rename from app/assets/images/home/home.jpg rename to public/default_images/home.jpg diff --git a/app/assets/images/ofn-logo-footer.png b/public/default_images/ofn-logo-footer.png similarity index 100% rename from app/assets/images/ofn-logo-footer.png rename to public/default_images/ofn-logo-footer.png diff --git a/app/assets/images/ofn-logo-mobile.svg b/public/default_images/ofn-logo-mobile.svg similarity index 100% rename from app/assets/images/ofn-logo-mobile.svg rename to public/default_images/ofn-logo-mobile.svg diff --git a/app/assets/images/ofn-logo.png b/public/default_images/ofn-logo.png similarity index 100% rename from app/assets/images/ofn-logo.png rename to public/default_images/ofn-logo.png diff --git a/app/assets/images/noimage/group.png b/public/noimage/group.png similarity index 100% rename from app/assets/images/noimage/group.png rename to public/noimage/group.png diff --git a/app/assets/images/noimage/large.png b/public/noimage/large.png similarity index 100% rename from app/assets/images/noimage/large.png rename to public/noimage/large.png diff --git a/app/assets/images/noimage/mini.png b/public/noimage/mini.png similarity index 100% rename from app/assets/images/noimage/mini.png rename to public/noimage/mini.png diff --git a/app/assets/images/noimage/product.png b/public/noimage/product.png similarity index 100% rename from app/assets/images/noimage/product.png rename to public/noimage/product.png diff --git a/app/assets/images/noimage/small.png b/public/noimage/small.png similarity index 100% rename from app/assets/images/noimage/small.png rename to public/noimage/small.png diff --git a/spec/config/application_spec.rb b/spec/config/application_spec.rb deleted file mode 100644 index 126325a4f1..0000000000 --- a/spec/config/application_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'spec_helper' - -describe Openfoodnetwork::Application, 'configuration' do - let(:config) { described_class.config } - - it "sets Spree::Stock::Splitter::Base as the only stock splitter" do - expect(config.spree.stock_splitters).to eq [Spree::Stock::Splitter::Base] - end -end diff --git a/spec/controllers/admin/bulk_line_items_controller_spec.rb b/spec/controllers/admin/bulk_line_items_controller_spec.rb index 941b44eb0c..553e796558 100644 --- a/spec/controllers/admin/bulk_line_items_controller_spec.rb +++ b/spec/controllers/admin/bulk_line_items_controller_spec.rb @@ -21,7 +21,7 @@ describe Admin::BulkLineItemsController, type: :controller do it "should deny me access to the index action" do spree_get :index, format: :json - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -94,7 +94,7 @@ describe Admin::BulkLineItemsController, type: :controller do end it "does not display line items for which my enterprise is a supplier" do - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -172,7 +172,7 @@ describe Admin::BulkLineItemsController, type: :controller do end it "does not allow access" do - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end diff --git a/spec/controllers/admin/customers_controller_spec.rb b/spec/controllers/admin/customers_controller_spec.rb index b1503d89f7..8e344879a2 100644 --- a/spec/controllers/admin/customers_controller_spec.rb +++ b/spec/controllers/admin/customers_controller_spec.rb @@ -90,7 +90,7 @@ describe Admin::CustomersController, type: :controller do it "prevents me from updating the customer" do spree_put :update, format: :json, id: customer.id, customer: { email: 'new.email@gmail.com' } - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path expect(assigns(:customer)).to eq nil expect(customer.email).to_not eq 'new.email@gmail.com' end @@ -166,7 +166,7 @@ describe Admin::CustomersController, type: :controller do it "prevents me from updating the customer" do spree_get :show, format: :json, id: customer.id - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end end diff --git a/spec/controllers/admin/enterprises_controller_spec.rb b/spec/controllers/admin/enterprises_controller_spec.rb index a95334d292..c8a0a9ac29 100644 --- a/spec/controllers/admin/enterprises_controller_spec.rb +++ b/spec/controllers/admin/enterprises_controller_spec.rb @@ -1,561 +1,559 @@ require 'spec_helper' require 'open_food_network/order_cycle_permissions' -module Admin - describe EnterprisesController, type: :controller do - include AuthenticationWorkflow +describe Admin::EnterprisesController, type: :controller do + include AuthenticationWorkflow - let(:user) { create(:user) } - let(:admin_user) { create(:admin_user) } - let(:distributor_manager) { create(:user, enterprise_limit: 10, enterprises: [distributor]) } - let(:supplier_manager) { create(:user, enterprise_limit: 10, enterprises: [supplier]) } - let(:distributor_owner) { create(:user, enterprise_limit: 10) } - let(:supplier_owner) { create(:user) } + let(:user) { create(:user) } + let(:admin_user) { create(:admin_user) } + let(:distributor_manager) { create(:user, enterprise_limit: 10, enterprises: [distributor]) } + let(:supplier_manager) { create(:user, enterprise_limit: 10, enterprises: [supplier]) } + let(:distributor_owner) { create(:user, enterprise_limit: 10) } + let(:supplier_owner) { create(:user) } - let(:distributor) { create(:distributor_enterprise, owner: distributor_owner ) } - let(:supplier) { create(:supplier_enterprise, owner: supplier_owner) } + let(:distributor) { create(:distributor_enterprise, owner: distributor_owner ) } + let(:supplier) { create(:supplier_enterprise, owner: supplier_owner) } - before { @request.env['HTTP_REFERER'] = 'http://test.com/' } + before { @request.env['HTTP_REFERER'] = 'http://test.com/' } - describe "creating an enterprise" do - let(:country) { Spree::Country.find_by name: 'Australia' } - let(:state) { Spree::State.find_by name: 'Victoria' } - let(:enterprise_params) { { enterprise: { name: 'zzz', permalink: 'zzz', is_primary_producer: '0', address_attributes: { address1: 'a', city: 'a', zipcode: 'a', country_id: country.id, state_id: state.id } } } } + describe "creating an enterprise" do + let(:country) { Spree::Country.find_by name: 'Australia' } + let(:state) { Spree::State.find_by name: 'Victoria' } + let(:enterprise_params) { { enterprise: { name: 'zzz', permalink: 'zzz', is_primary_producer: '0', address_attributes: { address1: 'a', city: 'a', zipcode: 'a', country_id: country.id, state_id: state.id } } } } - it "grants management permission if the current user is an enterprise user" do - allow(controller).to receive_messages spree_current_user: distributor_manager - enterprise_params[:enterprise][:owner_id] = distributor_manager + it "grants management permission if the current user is an enterprise user" do + allow(controller).to receive_messages spree_current_user: distributor_manager + enterprise_params[:enterprise][:owner_id] = distributor_manager + + spree_put :create, enterprise_params + enterprise = Enterprise.find_by name: 'zzz' + expect(response).to redirect_to edit_admin_enterprise_path enterprise + expect(distributor_manager.enterprise_roles.where(enterprise_id: enterprise).first).to be + end + + it "overrides the owner_id submitted by the user (when not super admin)" do + allow(controller).to receive_messages spree_current_user: distributor_manager + enterprise_params[:enterprise][:owner_id] = user + + spree_put :create, enterprise_params + enterprise = Enterprise.find_by name: 'zzz' + expect(response).to redirect_to edit_admin_enterprise_path enterprise + expect(distributor_manager.enterprise_roles.where(enterprise_id: enterprise).first).to be + end + + context "when I already own a hub" do + before { distributor } + + it "creates new non-producers as hubs" do + allow(controller).to receive_messages spree_current_user: distributor_owner + enterprise_params[:enterprise][:owner_id] = distributor_owner spree_put :create, enterprise_params enterprise = Enterprise.find_by name: 'zzz' expect(response).to redirect_to edit_admin_enterprise_path enterprise - expect(distributor_manager.enterprise_roles.where(enterprise_id: enterprise).first).to be + expect(enterprise.sells).to eq('any') end - it "overrides the owner_id submitted by the user (when not super admin)" do - allow(controller).to receive_messages spree_current_user: distributor_manager - enterprise_params[:enterprise][:owner_id] = user + it "creates new producers as sells none" do + allow(controller).to receive_messages spree_current_user: distributor_owner + enterprise_params[:enterprise][:owner_id] = distributor_owner + enterprise_params[:enterprise][:is_primary_producer] = '1' spree_put :create, enterprise_params enterprise = Enterprise.find_by name: 'zzz' expect(response).to redirect_to edit_admin_enterprise_path enterprise - expect(distributor_manager.enterprise_roles.where(enterprise_id: enterprise).first).to be + expect(enterprise.sells).to eq('none') end - context "when I already own a hub" do - before { distributor } + it "doesn't affect the hub status for super admins" do + admin_user.enterprises << create(:distributor_enterprise) - it "creates new non-producers as hubs" do - allow(controller).to receive_messages spree_current_user: distributor_owner - enterprise_params[:enterprise][:owner_id] = distributor_owner + allow(controller).to receive_messages spree_current_user: admin_user + enterprise_params[:enterprise][:owner_id] = admin_user + enterprise_params[:enterprise][:sells] = 'none' - spree_put :create, enterprise_params - enterprise = Enterprise.find_by name: 'zzz' - expect(response).to redirect_to edit_admin_enterprise_path enterprise - expect(enterprise.sells).to eq('any') - end - - it "creates new producers as sells none" do - allow(controller).to receive_messages spree_current_user: distributor_owner - enterprise_params[:enterprise][:owner_id] = distributor_owner - enterprise_params[:enterprise][:is_primary_producer] = '1' - - spree_put :create, enterprise_params - enterprise = Enterprise.find_by name: 'zzz' - expect(response).to redirect_to edit_admin_enterprise_path enterprise - expect(enterprise.sells).to eq('none') - end - - it "doesn't affect the hub status for super admins" do - admin_user.enterprises << create(:distributor_enterprise) - - allow(controller).to receive_messages spree_current_user: admin_user - enterprise_params[:enterprise][:owner_id] = admin_user - enterprise_params[:enterprise][:sells] = 'none' - - spree_put :create, enterprise_params - enterprise = Enterprise.find_by name: 'zzz' - expect(response).to redirect_to edit_admin_enterprise_path enterprise - expect(enterprise.sells).to eq('none') - end - end - - context "when I do not have a hub" do - it "does not create the new enterprise as a hub" do - allow(controller).to receive_messages spree_current_user: supplier_manager - enterprise_params[:enterprise][:owner_id] = supplier_manager - - spree_put :create, enterprise_params - enterprise = Enterprise.find_by name: 'zzz' - expect(enterprise.sells).to eq('none') - end - - it "doesn't affect the hub status for super admins" do - allow(controller).to receive_messages spree_current_user: admin_user - enterprise_params[:enterprise][:owner_id] = admin_user - enterprise_params[:enterprise][:sells] = 'any' - - spree_put :create, enterprise_params - enterprise = Enterprise.find_by name: 'zzz' - expect(enterprise.sells).to eq('any') - end + spree_put :create, enterprise_params + enterprise = Enterprise.find_by name: 'zzz' + expect(response).to redirect_to edit_admin_enterprise_path enterprise + expect(enterprise.sells).to eq('none') end end - describe "updating an enterprise" do - let(:profile_enterprise) { create(:enterprise, sells: 'none') } + context "when I do not have a hub" do + it "does not create the new enterprise as a hub" do + allow(controller).to receive_messages spree_current_user: supplier_manager + enterprise_params[:enterprise][:owner_id] = supplier_manager - context "as manager" do - it "does not allow 'sells' to be changed" do - profile_enterprise.enterprise_roles.build(user: distributor_manager).save - allow(controller).to receive_messages spree_current_user: distributor_manager - enterprise_params = { id: profile_enterprise, enterprise: { sells: 'any' } } - - spree_put :update, enterprise_params - profile_enterprise.reload - expect(profile_enterprise.sells).to eq 'none' - end - - it "does not allow owner to be changed" do - allow(controller).to receive_messages spree_current_user: distributor_manager - update_params = { id: distributor, enterprise: { owner_id: distributor_manager } } - spree_post :update, update_params - - distributor.reload - expect(distributor.owner).to eq distributor_owner - end - - it "does not allow managers to be changed" do - allow(controller).to receive_messages spree_current_user: distributor_manager - update_params = { id: distributor, enterprise: { user_ids: [distributor_owner.id, distributor_manager.id, user.id] } } - spree_post :update, update_params - - distributor.reload - expect(distributor.users).to_not include user - end - - describe "enterprise properties" do - let(:producer) { create(:enterprise) } - let!(:property) { create(:property, name: "A nice name") } - - before do - login_as_enterprise_user [producer] - end - - context "when a submitted property does not already exist" do - it "does not create a new property, or product property" do - spree_put :update, - id: producer, - enterprise: { - producer_properties_attributes: { - '0' => { property_name: 'a different name', value: 'something' } - } - } - expect(Spree::Property.count).to be 1 - expect(ProducerProperty.count).to be 0 - property_names = producer.reload.properties.map(&:name) - expect(property_names).to_not include 'a different name' - end - end - - context "when a submitted property exists" do - it "adds a product property" do - spree_put :update, - id: producer, - enterprise: { - producer_properties_attributes: { - '0' => { property_name: 'A nice name', value: 'something' } - } - } - expect(Spree::Property.count).to be 1 - expect(ProducerProperty.count).to be 1 - property_names = producer.reload.properties.map(&:name) - expect(property_names).to include 'A nice name' - end - end - end - - describe "tag rules" do - let(:enterprise) { create(:distributor_enterprise) } - let!(:tag_rule) { create(:tag_rule, enterprise: enterprise) } - - before do - login_as_enterprise_user [enterprise] - end - - context "discount order rules" do - it "updates the existing rule with new attributes" do - spree_put :update, - id: enterprise, - enterprise: { - tag_rules_attributes: { - '0' => { - id: tag_rule, - type: "TagRule::DiscountOrder", - preferred_customer_tags: "some,new,tags", - calculator_type: "Spree::Calculator::FlatPercentItemTotal", - calculator_attributes: { id: tag_rule.calculator.id, preferred_flat_percent: "15" } - } - } - } - tag_rule.reload - expect(tag_rule.preferred_customer_tags).to eq "some,new,tags" - expect(tag_rule.calculator.preferred_flat_percent).to eq 15 - end - - it "creates new rules with new attributes" do - spree_put :update, - id: enterprise, - enterprise: { - tag_rules_attributes: { - '0' => { - id: "", - type: "TagRule::DiscountOrder", - preferred_customer_tags: "tags,are,awesome", - calculator_type: "Spree::Calculator::FlatPercentItemTotal", - calculator_attributes: { id: "", preferred_flat_percent: "24" } - } - } - } - expect(tag_rule.reload).to be - new_tag_rule = TagRule::DiscountOrder.last - expect(new_tag_rule.preferred_customer_tags).to eq "tags,are,awesome" - expect(new_tag_rule.calculator.preferred_flat_percent).to eq 24 - end - end - end + spree_put :create, enterprise_params + enterprise = Enterprise.find_by name: 'zzz' + expect(enterprise.sells).to eq('none') end - context "as owner" do - it "allows 'sells' to be changed" do - allow(controller).to receive_messages spree_current_user: profile_enterprise.owner - enterprise_params = { id: profile_enterprise, enterprise: { sells: 'any' } } + it "doesn't affect the hub status for super admins" do + allow(controller).to receive_messages spree_current_user: admin_user + enterprise_params[:enterprise][:owner_id] = admin_user + enterprise_params[:enterprise][:sells] = 'any' - spree_put :update, enterprise_params - profile_enterprise.reload - expect(profile_enterprise.sells).to eq 'any' - end - - it "allows owner to be changed" do - allow(controller).to receive_messages spree_current_user: distributor_owner - update_params = { id: distributor, enterprise: { owner_id: distributor_manager } } - spree_post :update, update_params - - distributor.reload - expect(distributor.owner).to eq distributor_manager - end - - it "allows managers to be changed" do - allow(controller).to receive_messages spree_current_user: distributor_owner - update_params = { id: distributor, enterprise: { user_ids: [distributor_owner.id, distributor_manager.id, user.id] } } - spree_post :update, update_params - - distributor.reload - expect(distributor.users).to include user - end - end - - context "as super admin" do - it "allows 'sells' to be changed" do - allow(controller).to receive_messages spree_current_user: admin_user - enterprise_params = { id: profile_enterprise, enterprise: { sells: 'any' } } - - spree_put :update, enterprise_params - profile_enterprise.reload - expect(profile_enterprise.sells).to eq 'any' - end - - it "allows owner to be changed" do - allow(controller).to receive_messages spree_current_user: admin_user - update_params = { id: distributor, enterprise: { owner_id: distributor_manager } } - spree_post :update, update_params - - distributor.reload - expect(distributor.owner).to eq distributor_manager - end - - it "allows managers to be changed" do - allow(controller).to receive_messages spree_current_user: admin_user - update_params = { id: distributor, enterprise: { user_ids: [distributor_owner.id, distributor_manager.id, user.id] } } - spree_post :update, update_params - - distributor.reload - expect(distributor.users).to include user - end + spree_put :create, enterprise_params + enterprise = Enterprise.find_by name: 'zzz' + expect(enterprise.sells).to eq('any') end end + end - describe "register" do - let(:enterprise) { create(:enterprise, sells: 'none') } + describe "updating an enterprise" do + let(:profile_enterprise) { create(:enterprise, sells: 'none') } + + context "as manager" do + it "does not allow 'sells' to be changed" do + profile_enterprise.enterprise_roles.build(user: distributor_manager).save + allow(controller).to receive_messages spree_current_user: distributor_manager + enterprise_params = { id: profile_enterprise, enterprise: { sells: 'any' } } + + spree_put :update, enterprise_params + profile_enterprise.reload + expect(profile_enterprise.sells).to eq 'none' + end + + it "does not allow owner to be changed" do + allow(controller).to receive_messages spree_current_user: distributor_manager + update_params = { id: distributor, enterprise: { owner_id: distributor_manager } } + spree_post :update, update_params + + distributor.reload + expect(distributor.owner).to eq distributor_owner + end + + it "does not allow managers to be changed" do + allow(controller).to receive_messages spree_current_user: distributor_manager + update_params = { id: distributor, enterprise: { user_ids: [distributor_owner.id, distributor_manager.id, user.id] } } + spree_post :update, update_params + + distributor.reload + expect(distributor.users).to_not include user + end + + describe "enterprise properties" do + let(:producer) { create(:enterprise) } + let!(:property) { create(:property, name: "A nice name") } - context "as a normal user" do before do - allow(controller).to receive_messages spree_current_user: distributor_manager + login_as_enterprise_user [producer] end - it "does not allow access" do - spree_post :register, id: enterprise.id, sells: 'none' - expect(response).to redirect_to spree.unauthorized_path + context "when a submitted property does not already exist" do + it "does not create a new property, or product property" do + spree_put :update, + id: producer, + enterprise: { + producer_properties_attributes: { + '0' => { property_name: 'a different name', value: 'something' } + } + } + expect(Spree::Property.count).to be 1 + expect(ProducerProperty.count).to be 0 + property_names = producer.reload.properties.map(&:name) + expect(property_names).to_not include 'a different name' + end + end + + context "when a submitted property exists" do + it "adds a product property" do + spree_put :update, + id: producer, + enterprise: { + producer_properties_attributes: { + '0' => { property_name: 'A nice name', value: 'something' } + } + } + expect(Spree::Property.count).to be 1 + expect(ProducerProperty.count).to be 1 + property_names = producer.reload.properties.map(&:name) + expect(property_names).to include 'A nice name' + end end end - context "as a manager" do + describe "tag rules" do + let(:enterprise) { create(:distributor_enterprise) } + let!(:tag_rule) { create(:tag_rule, enterprise: enterprise) } + before do - allow(controller).to receive_messages spree_current_user: distributor_manager - enterprise.enterprise_roles.build(user: distributor_manager).save + login_as_enterprise_user [enterprise] end - it "does not allow access" do - spree_post :register, id: enterprise.id, sells: 'none' - expect(response).to redirect_to spree.unauthorized_path - end - end - - context "as an owner" do - before do - allow(controller).to receive_messages spree_current_user: enterprise.owner - end - - context "setting 'sells' to 'none'" do - it "is allowed" do - spree_post :register, id: enterprise, sells: 'none' - expect(response).to redirect_to spree.admin_dashboard_path - expect(flash[:success]).to eq "Congratulations! Registration for #{enterprise.name} is complete!" - expect(enterprise.reload.sells).to eq 'none' - end - end - - context "setting producer_profile_only" do - it "is ignored" do - spree_post :register, id: enterprise, sells: 'none', producer_profile_only: true - expect(response).to redirect_to spree.admin_dashboard_path - expect(enterprise.reload.producer_profile_only).to be false - end - end - - context "setting 'sells' to 'own'" do - before do - enterprise.sells = 'none' - enterprise.save! + context "discount order rules" do + it "updates the existing rule with new attributes" do + spree_put :update, + id: enterprise, + enterprise: { + tag_rules_attributes: { + '0' => { + id: tag_rule, + type: "TagRule::DiscountOrder", + preferred_customer_tags: "some,new,tags", + calculator_type: "Calculator::FlatPercentItemTotal", + calculator_attributes: { id: tag_rule.calculator.id, preferred_flat_percent: "15" } + } + } + } + tag_rule.reload + expect(tag_rule.preferred_customer_tags).to eq "some,new,tags" + expect(tag_rule.calculator.preferred_flat_percent).to eq 15 end - it "is allowed" do - spree_post :register, id: enterprise, sells: 'own' - expect(response).to redirect_to spree.admin_dashboard_path - expect(flash[:success]).to eq "Congratulations! Registration for #{enterprise.name} is complete!" - expect(enterprise.reload.sells).to eq 'own' - end - end - - context "setting 'sells' to any" do - it "is allowed" do - spree_post :register, id: enterprise, sells: 'any' - expect(response).to redirect_to spree.admin_dashboard_path - expect(flash[:success]).to eq "Congratulations! Registration for #{enterprise.name} is complete!" - expect(enterprise.reload.sells).to eq 'any' - end - end - - context "settiing 'sells' to 'unspecified'" do - it "is not allowed" do - spree_post :register, id: enterprise, sells: 'unspecified' - expect(response).to render_template :welcome - expect(flash[:error]).to eq "Please select a package" + it "creates new rules with new attributes" do + spree_put :update, + id: enterprise, + enterprise: { + tag_rules_attributes: { + '0' => { + id: "", + type: "TagRule::DiscountOrder", + preferred_customer_tags: "tags,are,awesome", + calculator_type: "Calculator::FlatPercentItemTotal", + calculator_attributes: { id: "", preferred_flat_percent: "24" } + } + } + } + expect(tag_rule.reload).to be + new_tag_rule = TagRule::DiscountOrder.last + expect(new_tag_rule.preferred_customer_tags).to eq "tags,are,awesome" + expect(new_tag_rule.calculator.preferred_flat_percent).to eq 24 end end end end - describe "bulk updating enterprises" do - let!(:original_owner) do - user = create_enterprise_user - user.enterprise_limit = 2 - user.save! - user - end - let!(:new_owner) do - user = create_enterprise_user - user.enterprise_limit = 2 - user.save! - user - end - let!(:profile_enterprise1) { create(:enterprise, sells: 'none', owner: original_owner ) } - let!(:profile_enterprise2) { create(:enterprise, sells: 'none', owner: original_owner ) } + context "as owner" do + it "allows 'sells' to be changed" do + allow(controller).to receive_messages spree_current_user: profile_enterprise.owner + enterprise_params = { id: profile_enterprise, enterprise: { sells: 'any' } } - context "as manager" do - it "does not allow 'sells' or 'owner' to be changed" do - profile_enterprise1.enterprise_roles.build(user: new_owner).save - profile_enterprise2.enterprise_roles.build(user: new_owner).save - allow(controller).to receive_messages spree_current_user: new_owner - bulk_enterprise_params = { enterprise_set: { collection_attributes: { '0' => { id: profile_enterprise1.id, sells: 'any', owner_id: new_owner.id }, '1' => { id: profile_enterprise2.id, sells: 'any', owner_id: new_owner.id } } } } - - spree_put :bulk_update, bulk_enterprise_params - profile_enterprise1.reload - profile_enterprise2.reload - expect(profile_enterprise1.sells).to eq 'none' - expect(profile_enterprise2.sells).to eq 'none' - expect(profile_enterprise1.owner).to eq original_owner - expect(profile_enterprise2.owner).to eq original_owner - end - - it "cuts down the list of enterprises displayed when error received on bulk update" do - allow_any_instance_of(EnterpriseSet).to receive(:save) { false } - profile_enterprise1.enterprise_roles.build(user: new_owner).save - allow(controller).to receive_messages spree_current_user: new_owner - bulk_enterprise_params = { enterprise_set: { collection_attributes: { '0' => { id: profile_enterprise1.id, visible: 'false' } } } } - spree_put :bulk_update, bulk_enterprise_params - expect(assigns(:enterprise_set).collection).to eq [profile_enterprise1] - end + spree_put :update, enterprise_params + profile_enterprise.reload + expect(profile_enterprise.sells).to eq 'any' end - context "as the owner of an enterprise" do - it "allows 'sells' and 'owner' to be changed" do - allow(controller).to receive_messages spree_current_user: original_owner - bulk_enterprise_params = { enterprise_set: { collection_attributes: { '0' => { id: profile_enterprise1.id, sells: 'any', owner_id: new_owner.id }, '1' => { id: profile_enterprise2.id, sells: 'any', owner_id: new_owner.id } } } } + it "allows owner to be changed" do + allow(controller).to receive_messages spree_current_user: distributor_owner + update_params = { id: distributor, enterprise: { owner_id: distributor_manager } } + spree_post :update, update_params - spree_put :bulk_update, bulk_enterprise_params - profile_enterprise1.reload - profile_enterprise2.reload - expect(profile_enterprise1.sells).to eq 'any' - expect(profile_enterprise2.sells).to eq 'any' - expect(profile_enterprise1.owner).to eq original_owner - expect(profile_enterprise2.owner).to eq original_owner - end + distributor.reload + expect(distributor.owner).to eq distributor_manager end - context "as super admin" do - it "allows 'sells' and 'owner' to be changed" do - profile_enterprise1.enterprise_roles.build(user: new_owner).save - profile_enterprise2.enterprise_roles.build(user: new_owner).save - allow(controller).to receive_messages spree_current_user: admin_user - bulk_enterprise_params = { enterprise_set: { collection_attributes: { '0' => { id: profile_enterprise1.id, sells: 'any', owner_id: new_owner.id }, '1' => { id: profile_enterprise2.id, sells: 'any', owner_id: new_owner.id } } } } + it "allows managers to be changed" do + allow(controller).to receive_messages spree_current_user: distributor_owner + update_params = { id: distributor, enterprise: { user_ids: [distributor_owner.id, distributor_manager.id, user.id] } } + spree_post :update, update_params - spree_put :bulk_update, bulk_enterprise_params - profile_enterprise1.reload - profile_enterprise2.reload - expect(profile_enterprise1.sells).to eq 'any' - expect(profile_enterprise2.sells).to eq 'any' - expect(profile_enterprise1.owner).to eq new_owner - expect(profile_enterprise2.owner).to eq new_owner - end + distributor.reload + expect(distributor.users).to include user end end - describe "for_order_cycle" do - let!(:user) { create_enterprise_user } - let!(:enterprise) { create(:enterprise, sells: 'any', owner: user) } - let(:permission_mock) { double(:permission) } + context "as super admin" do + it "allows 'sells' to be changed" do + allow(controller).to receive_messages spree_current_user: admin_user + enterprise_params = { id: profile_enterprise, enterprise: { sells: 'any' } } + + spree_put :update, enterprise_params + profile_enterprise.reload + expect(profile_enterprise.sells).to eq 'any' + end + + it "allows owner to be changed" do + allow(controller).to receive_messages spree_current_user: admin_user + update_params = { id: distributor, enterprise: { owner_id: distributor_manager } } + spree_post :update, update_params + + distributor.reload + expect(distributor.owner).to eq distributor_manager + end + + it "allows managers to be changed" do + allow(controller).to receive_messages spree_current_user: admin_user + update_params = { id: distributor, enterprise: { user_ids: [distributor_owner.id, distributor_manager.id, user.id] } } + spree_post :update, update_params + + distributor.reload + expect(distributor.users).to include user + end + end + end + + describe "register" do + let(:enterprise) { create(:enterprise, sells: 'none') } + + context "as a normal user" do + before do + allow(controller).to receive_messages spree_current_user: distributor_manager + end + + it "does not allow access" do + spree_post :register, id: enterprise.id, sells: 'none' + expect(response).to redirect_to unauthorized_path + end + end + + context "as a manager" do + before do + allow(controller).to receive_messages spree_current_user: distributor_manager + enterprise.enterprise_roles.build(user: distributor_manager).save + end + + it "does not allow access" do + spree_post :register, id: enterprise.id, sells: 'none' + expect(response).to redirect_to unauthorized_path + end + end + + context "as an owner" do + before do + allow(controller).to receive_messages spree_current_user: enterprise.owner + end + + context "setting 'sells' to 'none'" do + it "is allowed" do + spree_post :register, id: enterprise, sells: 'none' + expect(response).to redirect_to spree.admin_dashboard_path + expect(flash[:success]).to eq "Congratulations! Registration for #{enterprise.name} is complete!" + expect(enterprise.reload.sells).to eq 'none' + end + end + + context "setting producer_profile_only" do + it "is ignored" do + spree_post :register, id: enterprise, sells: 'none', producer_profile_only: true + expect(response).to redirect_to spree.admin_dashboard_path + expect(enterprise.reload.producer_profile_only).to be false + end + end + + context "setting 'sells' to 'own'" do + before do + enterprise.sells = 'none' + enterprise.save! + end + + it "is allowed" do + spree_post :register, id: enterprise, sells: 'own' + expect(response).to redirect_to spree.admin_dashboard_path + expect(flash[:success]).to eq "Congratulations! Registration for #{enterprise.name} is complete!" + expect(enterprise.reload.sells).to eq 'own' + end + end + + context "setting 'sells' to any" do + it "is allowed" do + spree_post :register, id: enterprise, sells: 'any' + expect(response).to redirect_to spree.admin_dashboard_path + expect(flash[:success]).to eq "Congratulations! Registration for #{enterprise.name} is complete!" + expect(enterprise.reload.sells).to eq 'any' + end + end + + context "settiing 'sells' to 'unspecified'" do + it "is not allowed" do + spree_post :register, id: enterprise, sells: 'unspecified' + expect(response).to render_template :welcome + expect(flash[:error]).to eq "Please select a package" + end + end + end + end + + describe "bulk updating enterprises" do + let!(:original_owner) do + user = create_enterprise_user + user.enterprise_limit = 2 + user.save! + user + end + let!(:new_owner) do + user = create_enterprise_user + user.enterprise_limit = 2 + user.save! + user + end + let!(:profile_enterprise1) { create(:enterprise, sells: 'none', owner: original_owner ) } + let!(:profile_enterprise2) { create(:enterprise, sells: 'none', owner: original_owner ) } + + context "as manager" do + it "does not allow 'sells' or 'owner' to be changed" do + profile_enterprise1.enterprise_roles.build(user: new_owner).save + profile_enterprise2.enterprise_roles.build(user: new_owner).save + allow(controller).to receive_messages spree_current_user: new_owner + bulk_enterprise_params = { enterprise_set: { collection_attributes: { '0' => { id: profile_enterprise1.id, sells: 'any', owner_id: new_owner.id }, '1' => { id: profile_enterprise2.id, sells: 'any', owner_id: new_owner.id } } } } + + spree_put :bulk_update, bulk_enterprise_params + profile_enterprise1.reload + profile_enterprise2.reload + expect(profile_enterprise1.sells).to eq 'none' + expect(profile_enterprise2.sells).to eq 'none' + expect(profile_enterprise1.owner).to eq original_owner + expect(profile_enterprise2.owner).to eq original_owner + end + + it "cuts down the list of enterprises displayed when error received on bulk update" do + allow_any_instance_of(EnterpriseSet).to receive(:save) { false } + profile_enterprise1.enterprise_roles.build(user: new_owner).save + allow(controller).to receive_messages spree_current_user: new_owner + bulk_enterprise_params = { enterprise_set: { collection_attributes: { '0' => { id: profile_enterprise1.id, visible: 'false' } } } } + spree_put :bulk_update, bulk_enterprise_params + expect(assigns(:enterprise_set).collection).to eq [profile_enterprise1] + end + end + + context "as the owner of an enterprise" do + it "allows 'sells' and 'owner' to be changed" do + allow(controller).to receive_messages spree_current_user: original_owner + bulk_enterprise_params = { enterprise_set: { collection_attributes: { '0' => { id: profile_enterprise1.id, sells: 'any', owner_id: new_owner.id }, '1' => { id: profile_enterprise2.id, sells: 'any', owner_id: new_owner.id } } } } + + spree_put :bulk_update, bulk_enterprise_params + profile_enterprise1.reload + profile_enterprise2.reload + expect(profile_enterprise1.sells).to eq 'any' + expect(profile_enterprise2.sells).to eq 'any' + expect(profile_enterprise1.owner).to eq original_owner + expect(profile_enterprise2.owner).to eq original_owner + end + end + + context "as super admin" do + it "allows 'sells' and 'owner' to be changed" do + profile_enterprise1.enterprise_roles.build(user: new_owner).save + profile_enterprise2.enterprise_roles.build(user: new_owner).save + allow(controller).to receive_messages spree_current_user: admin_user + bulk_enterprise_params = { enterprise_set: { collection_attributes: { '0' => { id: profile_enterprise1.id, sells: 'any', owner_id: new_owner.id }, '1' => { id: profile_enterprise2.id, sells: 'any', owner_id: new_owner.id } } } } + + spree_put :bulk_update, bulk_enterprise_params + profile_enterprise1.reload + profile_enterprise2.reload + expect(profile_enterprise1.sells).to eq 'any' + expect(profile_enterprise2.sells).to eq 'any' + expect(profile_enterprise1.owner).to eq new_owner + expect(profile_enterprise2.owner).to eq new_owner + end + end + end + + describe "for_order_cycle" do + let!(:user) { create_enterprise_user } + let!(:enterprise) { create(:enterprise, sells: 'any', owner: user) } + let(:permission_mock) { double(:permission) } + + before do + # As a user with permission + allow(controller).to receive_messages spree_current_user: user + allow(OrderCycle).to receive_messages find_by: "existing OrderCycle" + allow(Enterprise).to receive_messages find_by: "existing Enterprise" + allow(OrderCycle).to receive_messages new: "new OrderCycle" + + allow(OpenFoodNetwork::OrderCyclePermissions).to receive(:new) { permission_mock } + allow(permission_mock).to receive(:visible_enterprises) { [] } + allow(ActiveModel::ArraySerializer).to receive(:new) { "" } + end + + context "when no order_cycle or coordinator is provided in params" do + before { spree_get :for_order_cycle, format: :json } + it "initializes permissions with nil" do + expect(OpenFoodNetwork::OrderCyclePermissions).to have_received(:new).with(user, nil) + end + end + + context "when an order_cycle_id is provided in params" do + before { spree_get :for_order_cycle, format: :json, order_cycle_id: 1 } + it "initializes permissions with the existing OrderCycle" do + expect(OpenFoodNetwork::OrderCyclePermissions).to have_received(:new).with(user, "existing OrderCycle") + end + end + + context "when a coordinator is provided in params" do + before { spree_get :for_order_cycle, format: :json, coordinator_id: 1 } + it "initializes permissions with a new OrderCycle" do + expect(OpenFoodNetwork::OrderCyclePermissions).to have_received(:new).with(user, "new OrderCycle") + end + end + + context "when both an order cycle and a coordinator are provided in params" do + before { spree_get :for_order_cycle, format: :json, order_cycle_id: 1, coordinator_id: 1 } + it "initializes permissions with the existing OrderCycle" do + expect(OpenFoodNetwork::OrderCyclePermissions).to have_received(:new).with(user, "existing OrderCycle") + end + end + end + + describe "visible" do + let!(:user) { create(:user, enterprise_limit: 10) } + let!(:visible_enterprise) { create(:enterprise, sells: 'any', owner: user) } + let!(:not_visible_enterprise) { create(:enterprise, sells: 'any') } + + before do + # As a user with permission + allow(controller).to receive_messages spree_current_user: user + + # :create_variant_overrides does not affect visiblity (at time of writing) + create(:enterprise_relationship, parent: not_visible_enterprise, child: visible_enterprise, permissions_list: [:create_variant_overrides]) + end + + it "uses permissions to determine which enterprises are visible and should be rendered" do + expect(controller).to receive(:render_as_json).with([visible_enterprise], ams_prefix: 'basic', spree_current_user: user).and_call_original + spree_get :visible, format: :json + end + end + + describe "index" do + context "as super admin" do + let(:super_admin) { create(:admin_user) } + let!(:user) { create_enterprise_user(enterprise_limit: 10) } + let!(:enterprise1) { create(:enterprise, sells: 'any', owner: user) } + let!(:enterprise2) { create(:enterprise, sells: 'own', owner: user) } + let!(:enterprise3) { create(:enterprise, sells: 'any', owner: create_enterprise_user ) } before do - # As a user with permission - allow(controller).to receive_messages spree_current_user: user - allow(OrderCycle).to receive_messages find_by: "existing OrderCycle" - allow(Enterprise).to receive_messages find_by: "existing Enterprise" - allow(OrderCycle).to receive_messages new: "new OrderCycle" - - allow(OpenFoodNetwork::OrderCyclePermissions).to receive(:new) { permission_mock } - allow(permission_mock).to receive(:visible_enterprises) { [] } - allow(ActiveModel::ArraySerializer).to receive(:new) { "" } + allow(controller).to receive_messages spree_current_user: super_admin end - context "when no order_cycle or coordinator is provided in params" do - before { spree_get :for_order_cycle, format: :json } - it "initializes permissions with nil" do - expect(OpenFoodNetwork::OrderCyclePermissions).to have_received(:new).with(user, nil) + context "html" do + it "returns all enterprises" do + spree_get :index, format: :html + expect(assigns(:collection)).to include enterprise1, enterprise2, enterprise3 end end - context "when an order_cycle_id is provided in params" do - before { spree_get :for_order_cycle, format: :json, order_cycle_id: 1 } - it "initializes permissions with the existing OrderCycle" do - expect(OpenFoodNetwork::OrderCyclePermissions).to have_received(:new).with(user, "existing OrderCycle") - end - end - - context "when a coordinator is provided in params" do - before { spree_get :for_order_cycle, format: :json, coordinator_id: 1 } - it "initializes permissions with a new OrderCycle" do - expect(OpenFoodNetwork::OrderCyclePermissions).to have_received(:new).with(user, "new OrderCycle") - end - end - - context "when both an order cycle and a coordinator are provided in params" do - before { spree_get :for_order_cycle, format: :json, order_cycle_id: 1, coordinator_id: 1 } - it "initializes permissions with the existing OrderCycle" do - expect(OpenFoodNetwork::OrderCyclePermissions).to have_received(:new).with(user, "existing OrderCycle") + context "json" do + it "returns all enterprises" do + spree_get :index, format: :json + expect(assigns(:collection)).to include enterprise1, enterprise2, enterprise3 end end end - describe "visible" do - let!(:user) { create(:user, enterprise_limit: 10) } - let!(:visible_enterprise) { create(:enterprise, sells: 'any', owner: user) } - let!(:not_visible_enterprise) { create(:enterprise, sells: 'any') } + context "as an enterprise user" do + let!(:user) { create_enterprise_user(enterprise_limit: 10) } + let!(:enterprise1) { create(:enterprise, sells: 'any', owner: user) } + let!(:enterprise2) { create(:enterprise, sells: 'own', owner: user) } + let!(:enterprise3) { create(:enterprise, sells: 'any', owner: create_enterprise_user ) } before do - # As a user with permission allow(controller).to receive_messages spree_current_user: user - - # :create_variant_overrides does not affect visiblity (at time of writing) - create(:enterprise_relationship, parent: not_visible_enterprise, child: visible_enterprise, permissions_list: [:create_variant_overrides]) end - it "uses permissions to determine which enterprises are visible and should be rendered" do - expect(controller).to receive(:render_as_json).with([visible_enterprise], ams_prefix: 'basic', spree_current_user: user).and_call_original - spree_get :visible, format: :json - end - end - - describe "index" do - context "as super admin" do - let(:super_admin) { create(:admin_user) } - let!(:user) { create_enterprise_user(enterprise_limit: 10) } - let!(:enterprise1) { create(:enterprise, sells: 'any', owner: user) } - let!(:enterprise2) { create(:enterprise, sells: 'own', owner: user) } - let!(:enterprise3) { create(:enterprise, sells: 'any', owner: create_enterprise_user ) } - - before do - allow(controller).to receive_messages spree_current_user: super_admin - end - - context "html" do - it "returns all enterprises" do - spree_get :index, format: :html - expect(assigns(:collection)).to include enterprise1, enterprise2, enterprise3 - end - end - - context "json" do - it "returns all enterprises" do - spree_get :index, format: :json - expect(assigns(:collection)).to include enterprise1, enterprise2, enterprise3 - end + context "html" do + it "returns an empty @collection" do + spree_get :index, format: :html + expect(assigns(:collection)).to eq [] end end - context "as an enterprise user" do - let!(:user) { create_enterprise_user(enterprise_limit: 10) } - let!(:enterprise1) { create(:enterprise, sells: 'any', owner: user) } - let!(:enterprise2) { create(:enterprise, sells: 'own', owner: user) } - let!(:enterprise3) { create(:enterprise, sells: 'any', owner: create_enterprise_user ) } - - before do - allow(controller).to receive_messages spree_current_user: user - end - - context "html" do - it "returns an empty @collection" do - spree_get :index, format: :html - expect(assigns(:collection)).to eq [] - end - end - - context "json" do - it "scopes @collection to enterprises editable by the user" do - spree_get :index, format: :json - expect(assigns(:collection)).to include enterprise1, enterprise2 - expect(assigns(:collection)).to_not include enterprise3 - end + context "json" do + it "scopes @collection to enterprises editable by the user" do + spree_get :index, format: :json + expect(assigns(:collection)).to include enterprise1, enterprise2 + expect(assigns(:collection)).to_not include enterprise3 end end end diff --git a/spec/controllers/admin/inventory_items_controller_spec.rb b/spec/controllers/admin/inventory_items_controller_spec.rb index 741f329663..9fce5711a6 100644 --- a/spec/controllers/admin/inventory_items_controller_spec.rb +++ b/spec/controllers/admin/inventory_items_controller_spec.rb @@ -21,7 +21,7 @@ describe Admin::InventoryItemsController, type: :controller do it "redirects to unauthorized" do spree_post :create, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -33,7 +33,7 @@ describe Admin::InventoryItemsController, type: :controller do context "but the producer has not granted VO permission" do it "redirects to unauthorized" do spree_post :create, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -84,7 +84,7 @@ describe Admin::InventoryItemsController, type: :controller do it "redirects to unauthorized" do spree_put :update, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -96,7 +96,7 @@ describe Admin::InventoryItemsController, type: :controller do context "but the producer has not granted VO permission" do it "redirects to unauthorized" do spree_put :update, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end diff --git a/spec/controllers/admin/order_cycles_controller_spec.rb b/spec/controllers/admin/order_cycles_controller_spec.rb index 2ba98c9f8e..0175cc1d28 100644 --- a/spec/controllers/admin/order_cycles_controller_spec.rb +++ b/spec/controllers/admin/order_cycles_controller_spec.rb @@ -178,10 +178,22 @@ module Admin it "returns an error message" do spree_put :update, params + json_response = JSON.parse(response.body) expect(json_response['errors']).to be end end + + it "can update preference product_selection_from_coordinator_inventory_only" do + expect(OrderCycleForm).to receive(:new). + with(order_cycle, + { "preferred_product_selection_from_coordinator_inventory_only" => true }, + anything) { form_mock } + allow(form_mock).to receive(:save) { true } + + spree_put :update, params. + merge(order_cycle: { preferred_product_selection_from_coordinator_inventory_only: true }) + end end end diff --git a/spec/controllers/admin/proxy_orders_controller_spec.rb b/spec/controllers/admin/proxy_orders_controller_spec.rb index 1d612e2c57..0bc63e3cb8 100644 --- a/spec/controllers/admin/proxy_orders_controller_spec.rb +++ b/spec/controllers/admin/proxy_orders_controller_spec.rb @@ -20,7 +20,7 @@ describe Admin::ProxyOrdersController, type: :controller do context 'as a regular user' do it 'redirects to unauthorized' do spree_put :cancel, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -31,7 +31,7 @@ describe Admin::ProxyOrdersController, type: :controller do it 'redirects to unauthorized' do spree_put :cancel, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -89,7 +89,7 @@ describe Admin::ProxyOrdersController, type: :controller do context 'as a regular user' do it 'redirects to unauthorized' do spree_put :resume, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -100,7 +100,7 @@ describe Admin::ProxyOrdersController, type: :controller do it 'redirects to unauthorized' do spree_put :resume, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end diff --git a/spec/controllers/admin/schedules_controller_spec.rb b/spec/controllers/admin/schedules_controller_spec.rb index 99b73f4e03..8a0ac86c78 100644 --- a/spec/controllers/admin/schedules_controller_spec.rb +++ b/spec/controllers/admin/schedules_controller_spec.rb @@ -106,7 +106,7 @@ describe Admin::SchedulesController, type: :controller do it "prevents me from updating the schedule" do spree_put :update, format: :json, id: coordinated_schedule.id, schedule: { name: "my awesome schedule" } - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path expect(assigns(:schedule)).to eq nil expect(coordinated_schedule.name).to_not eq "my awesome schedule" end diff --git a/spec/controllers/admin/stripe_accounts_controller_spec.rb b/spec/controllers/admin/stripe_accounts_controller_spec.rb index 9a6854ae85..c1b0890316 100644 --- a/spec/controllers/admin/stripe_accounts_controller_spec.rb +++ b/spec/controllers/admin/stripe_accounts_controller_spec.rb @@ -46,7 +46,7 @@ describe Admin::StripeAccountsController, type: :controller do it "redirects to unauthorized" do spree_delete :destroy, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -93,7 +93,7 @@ describe Admin::StripeAccountsController, type: :controller do it "redirects to unauthorized" do spree_get :status, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end diff --git a/spec/controllers/admin/stripe_connect_settings_controller_spec.rb b/spec/controllers/admin/stripe_connect_settings_controller_spec.rb index 11cebea66e..73331a151a 100644 --- a/spec/controllers/admin/stripe_connect_settings_controller_spec.rb +++ b/spec/controllers/admin/stripe_connect_settings_controller_spec.rb @@ -14,7 +14,7 @@ describe Admin::StripeConnectSettingsController, type: :controller do it "does not allow access" do spree_get :edit - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -76,7 +76,7 @@ describe Admin::StripeConnectSettingsController, type: :controller do it "does not allow access" do spree_get :update, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end diff --git a/spec/controllers/admin/subscriptions_controller_spec.rb b/spec/controllers/admin/subscriptions_controller_spec.rb index c7f0e207a4..7eb11807bf 100644 --- a/spec/controllers/admin/subscriptions_controller_spec.rb +++ b/spec/controllers/admin/subscriptions_controller_spec.rb @@ -18,7 +18,7 @@ describe Admin::SubscriptionsController, type: :controller do context 'as a regular user' do it 'redirects to unauthorized' do spree_get :index, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -55,7 +55,7 @@ describe Admin::SubscriptionsController, type: :controller do context 'as a regular user' do it 'redirects to unauthorized' do spree_get :index, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -120,7 +120,7 @@ describe Admin::SubscriptionsController, type: :controller do it 'redirects to unauthorized' do spree_post :create, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -272,7 +272,7 @@ describe Admin::SubscriptionsController, type: :controller do it 'redirects to unauthorized' do spree_post :update, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -390,7 +390,7 @@ describe Admin::SubscriptionsController, type: :controller do context 'as a regular user' do it 'redirects to unauthorized' do spree_put :cancel, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -401,7 +401,7 @@ describe Admin::SubscriptionsController, type: :controller do it 'redirects to unauthorized' do spree_put :cancel, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -489,7 +489,7 @@ describe Admin::SubscriptionsController, type: :controller do context 'as a regular user' do it 'redirects to unauthorized' do spree_put :pause, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -500,7 +500,7 @@ describe Admin::SubscriptionsController, type: :controller do it 'redirects to unauthorized' do spree_put :pause, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -588,7 +588,7 @@ describe Admin::SubscriptionsController, type: :controller do context 'as a regular user' do it 'redirects to unauthorized' do spree_put :unpause, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -599,7 +599,7 @@ describe Admin::SubscriptionsController, type: :controller do it 'redirects to unauthorized' do spree_put :unpause, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end diff --git a/spec/controllers/admin/tag_rules_controller_spec.rb b/spec/controllers/admin/tag_rules_controller_spec.rb index b4da011923..5d654958a4 100644 --- a/spec/controllers/admin/tag_rules_controller_spec.rb +++ b/spec/controllers/admin/tag_rules_controller_spec.rb @@ -19,7 +19,7 @@ describe Admin::TagRulesController, type: :controller do it "redirects to unauthorized" do spree_delete :destroy, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end diff --git a/spec/controllers/admin/variant_overrides_controller_spec.rb b/spec/controllers/admin/variant_overrides_controller_spec.rb index c75a265cd1..4690dba35b 100644 --- a/spec/controllers/admin/variant_overrides_controller_spec.rb +++ b/spec/controllers/admin/variant_overrides_controller_spec.rb @@ -22,7 +22,7 @@ describe Admin::VariantOverridesController, type: :controller do it "redirects to unauthorized" do spree_put :bulk_update, format: format, variant_overrides: variant_override_params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -34,7 +34,7 @@ describe Admin::VariantOverridesController, type: :controller do context "but the producer has not granted VO permission" do it "redirects to unauthorized" do spree_put :bulk_update, format: format, variant_overrides: variant_override_params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -78,7 +78,7 @@ describe Admin::VariantOverridesController, type: :controller do it "allows to update other variant overrides" do spree_put :bulk_update, format: format, variant_overrides: variant_override_params - expect(response).to_not redirect_to spree.unauthorized_path + expect(response).to_not redirect_to unauthorized_path variant_override.reload expect(variant_override.price).to eq 123.45 end @@ -111,7 +111,7 @@ describe Admin::VariantOverridesController, type: :controller do it "redirects to unauthorized" do spree_put :bulk_reset, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -123,7 +123,7 @@ describe Admin::VariantOverridesController, type: :controller do context "where the producer has not granted create_variant_overrides permission to the hub" do it "restricts access" do spree_put :bulk_reset, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end diff --git a/spec/controllers/api/base_controller_spec.rb b/spec/controllers/api/base_controller_spec.rb index d501aa98fb..1ebd3eca15 100644 --- a/spec/controllers/api/base_controller_spec.rb +++ b/spec/controllers/api/base_controller_spec.rb @@ -14,7 +14,7 @@ describe Api::BaseController do context "signed in as a user using an authentication extension" do before do - allow(controller).to receive_messages try_spree_current_user: + allow(controller).to receive_messages spree_current_user: double(email: "ofn@example.com") end diff --git a/spec/controllers/checkout_controller_concurrency_spec.rb b/spec/controllers/checkout_controller_concurrency_spec.rb index 51eefe98ae..266f9a4fbe 100644 --- a/spec/controllers/checkout_controller_concurrency_spec.rb +++ b/spec/controllers/checkout_controller_concurrency_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' # This is the first example of testing concurrency in the Open Food Network. @@ -15,6 +17,20 @@ describe CheckoutController, concurrency: true, type: :controller do let(:payment_method) { create(:payment_method, distributors: [distributor]) } let(:breakpoint) { Mutex.new } + let(:address_params) { address.attributes.except("id") } + let(:order_params) { + { + "payments_attributes" => [ + { + "payment_method_id" => payment_method.id, + "amount" => order.total + } + ], + "bill_address_attributes" => address_params, + "ship_address_attributes" => address_params, + } + } + before do # Create a valid order ready for checkout: create(:shipping_method, distributors: [distributor]) @@ -26,7 +42,9 @@ describe CheckoutController, concurrency: true, type: :controller do allow(controller).to receive(:spree_current_user).and_return(order.user) allow(controller).to receive(:current_distributor).and_return(order.distributor) allow(controller).to receive(:current_order_cycle).and_return(order.order_cycle) + end + it "handles two concurrent orders successfully" do # New threads start running straight away. The breakpoint is after loading # the order and before advancing the order's state and making payments. breakpoint.lock @@ -36,21 +54,6 @@ describe CheckoutController, concurrency: true, type: :controller do # I did not find out how to call the original code otherwise. ActiveSupport::Notifications.instrument("spree.checkout.update") end - end - - it "waits for concurrent checkouts" do - # Basic data the user submits during checkout: - address_params = address.attributes.except("id") - order_params = { - "payments_attributes" => [ - { - "payment_method_id" => payment_method.id, - "amount" => order.total - } - ], - "bill_address_attributes" => address_params, - "ship_address_attributes" => address_params, - } # Starting two checkout threads. The controller code will determine if # these two threads are synchronised correctly or run into a race condition. diff --git a/spec/controllers/spree/admin/adjustments_controller_spec.rb b/spec/controllers/spree/admin/adjustments_controller_spec.rb index fc60008c00..4ad02b73c7 100644 --- a/spec/controllers/spree/admin/adjustments_controller_spec.rb +++ b/spec/controllers/spree/admin/adjustments_controller_spec.rb @@ -8,7 +8,7 @@ module Spree describe "setting included tax" do let(:order) { create(:order) } - let(:tax_rate) { create(:tax_rate, amount: 0.1, calculator: Spree::Calculator::DefaultTax.new) } + let(:tax_rate) { create(:tax_rate, amount: 0.1, calculator: Calculator::DefaultTax.new) } describe "creating an adjustment" do it "sets included tax to zero when no tax rate is specified" do diff --git a/spec/controllers/spree/admin/mail_methods_controller_spec.rb b/spec/controllers/spree/admin/mail_methods_controller_spec.rb index 422491660e..1a706462a7 100644 --- a/spec/controllers/spree/admin/mail_methods_controller_spec.rb +++ b/spec/controllers/spree/admin/mail_methods_controller_spec.rb @@ -18,8 +18,10 @@ describe Spree::Admin::MailMethodsController do spree_api_key: 'fake', id: nil, owned_groups: nil) - allow(user).to receive_messages(enterprises: [create(:enterprise)], has_spree_role?: true) - allow(controller).to receive_messages(try_spree_current_user: user) + allow(user).to receive_messages(enterprises: [create(:enterprise)], + has_spree_role?: true, + locale: nil) + allow(controller).to receive_messages(spree_current_user: user) Spree::Config[:enable_mail_delivery] = "1" ActionMailer::Base.perform_deliveries = true diff --git a/spec/controllers/spree/admin/orders/payments/payments_controller_refunds_spec.rb b/spec/controllers/spree/admin/orders/payments/payments_controller_refunds_spec.rb new file mode 100644 index 0000000000..08e923b5c3 --- /dev/null +++ b/spec/controllers/spree/admin/orders/payments/payments_controller_refunds_spec.rb @@ -0,0 +1,273 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Spree::Admin::PaymentsController, type: :controller do + let!(:shop) { create(:enterprise) } + let!(:user) { shop.owner } + let!(:order) { create(:order, distributor: shop, state: 'complete') } + let!(:line_item) { create(:line_item, order: order, price: 5.0) } + + before do + allow(controller).to receive(:spree_current_user) { user } + order.reload.update_totals + end + + context "Stripe Connect" do + context "requesting a refund on a payment" do + let(:params) { { id: payment.id, order_id: order.number, e: :void } } + + # Required for the respond override in the controller decorator to work + before { @request.env['HTTP_REFERER'] = spree.admin_order_payments_url(payment) } + + context "that was processed by stripe" do + let!(:payment_method) { create(:stripe_payment_method, distributors: [shop]) } + let!(:payment) do + create(:payment, order: order, state: 'completed', payment_method: payment_method, + response_code: 'ch_1a2b3c', amount: order.total) + end + + before do + allow(Stripe).to receive(:api_key) { "sk_test_12345" } + end + + context "where the request succeeds" do + before do + stub_request(:post, "https://api.stripe.com/v1/charges/ch_1a2b3c/refunds"). + with(basic_auth: ["sk_test_12345", ""]). + to_return(status: 200, + body: JSON.generate(id: 're_123', object: 'refund', status: 'succeeded') ) + end + + it "voids the payment" do + order.reload + expect(order.payment_total).to_not eq 0 + expect(order.outstanding_balance).to eq 0 + spree_put :fire, params + expect(payment.reload.state).to eq 'void' + order.reload + expect(order.payment_total).to eq 0 + expect(order.outstanding_balance).to_not eq 0 + end + end + + context "where the request fails" do + before do + stub_request(:post, "https://api.stripe.com/v1/charges/ch_1a2b3c/refunds"). + with(basic_auth: ["sk_test_12345", ""]). + to_return(status: 200, body: JSON.generate(error: { message: "Bup-bow!" }) ) + end + + it "does not void the payment" do + order.reload + expect(order.payment_total).to_not eq 0 + expect(order.outstanding_balance).to eq 0 + spree_put :fire, params + expect(payment.reload.state).to eq 'completed' + order.reload + expect(order.payment_total).to_not eq 0 + expect(order.outstanding_balance).to eq 0 + expect(flash[:error]).to eq "Bup-bow!" + end + end + end + end + + context "requesting a partial credit on a payment" do + let(:params) { { id: payment.id, order_id: order.number, e: :credit } } + + # Required for the respond override in the controller decorator to work + before { @request.env['HTTP_REFERER'] = spree.admin_order_payments_url(payment) } + + context "that was processed by stripe" do + let!(:payment_method) { create(:stripe_payment_method, distributors: [shop]) } + let!(:payment) do + create(:payment, order: order, state: 'completed', payment_method: payment_method, + response_code: 'ch_1a2b3c', amount: order.total + 5) + end + + before do + allow(Stripe).to receive(:api_key) { "sk_test_12345" } + end + + context "where the request succeeds" do + before do + stub_request(:post, "https://api.stripe.com/v1/charges/ch_1a2b3c/refunds"). + with(basic_auth: ["sk_test_12345", ""]). + to_return(status: 200, + body: JSON.generate(id: 're_123', object: 'refund', status: 'succeeded') ) + end + + it "partially refunds the payment" do + order.reload + expect(order.payment_total).to eq order.total + 5 + expect(order.outstanding_balance).to eq(-5) + spree_put :fire, params + expect(payment.reload.state).to eq 'completed' + order.reload + expect(order.payment_total).to eq order.total + expect(order.outstanding_balance).to eq 0 + end + end + + context "where the request fails" do + before do + stub_request(:post, "https://api.stripe.com/v1/charges/ch_1a2b3c/refunds"). + with(basic_auth: ["sk_test_12345", ""]). + to_return(status: 200, body: JSON.generate(error: { message: "Bup-bow!" }) ) + end + + it "does not void the payment" do + order.reload + expect(order.payment_total).to eq order.total + 5 + expect(order.outstanding_balance).to eq(-5) + spree_put :fire, params + expect(payment.reload.state).to eq 'completed' + order.reload + expect(order.payment_total).to eq order.total + 5 + expect(order.outstanding_balance).to eq(-5) + expect(flash[:error]).to eq "Bup-bow!" + end + end + end + end + end + + context "StripeSCA" do + context "requesting a refund on a payment" do + let(:params) { { id: payment.id, order_id: order.number, e: :void } } + + # Required for the respond override in the controller decorator to work + before { @request.env['HTTP_REFERER'] = spree.admin_order_payments_url(payment) } + + context "that was processed by stripe" do + let!(:payment_method) { create(:stripe_sca_payment_method, distributors: [shop]) } + let!(:payment) do + create(:payment, order: order, state: 'completed', payment_method: payment_method, + response_code: 'pi_123', amount: order.total) + end + let(:stripe_account) { create(:stripe_account, enterprise: shop) } + + before do + allow(Stripe).to receive(:api_key) { "sk_test_12345" } + allow(StripeAccount).to receive(:find_by) { stripe_account } + + # Retrieves payment intent info + stub_request(:get, "https://api.stripe.com/v1/payment_intents/pi_123") + .with(headers: { 'Stripe-Account' => 'abc123' }) + .to_return({ status: 200, body: JSON.generate( + amount_received: 2000, + charges: { data: [{ id: "ch_1a2b3c" }] } + ) }) + end + + context "where the request succeeds" do + before do + # Issues the refund + stub_request(:post, "https://api.stripe.com/v1/charges/ch_1a2b3c/refunds"). + with(basic_auth: ["sk_test_12345", ""]). + to_return(status: 200, + body: JSON.generate(id: 're_123', object: 'refund', status: 'succeeded') ) + end + + it "voids the payment" do + order.reload + expect(order.payment_total).to_not eq 0 + expect(order.outstanding_balance).to eq 0 + spree_put :fire, params + expect(payment.reload.state).to eq 'void' + order.reload + expect(order.payment_total).to eq 0 + expect(order.outstanding_balance).to_not eq 0 + end + end + + context "where the request fails" do + before do + stub_request(:post, "https://api.stripe.com/v1/charges/ch_1a2b3c/refunds"). + with(basic_auth: ["sk_test_12345", ""]). + to_return(status: 200, body: JSON.generate(error: { message: "Bup-bow!" }) ) + end + + it "does not void the payment" do + order.reload + expect(order.payment_total).to_not eq 0 + expect(order.outstanding_balance).to eq 0 + spree_put :fire, params + expect(payment.reload.state).to eq 'completed' + order.reload + expect(order.payment_total).to_not eq 0 + expect(order.outstanding_balance).to eq 0 + expect(flash[:error]).to eq "Bup-bow!" + end + end + end + end + + context "requesting a partial credit on a payment" do + let(:params) { { id: payment.id, order_id: order.number, e: :credit } } + + # Required for the respond override in the controller decorator to work + before { @request.env['HTTP_REFERER'] = spree.admin_order_payments_url(payment) } + + context "that was processed by stripe" do + let!(:payment_method) { create(:stripe_sca_payment_method, distributors: [shop]) } + let!(:payment) do + create(:payment, order: order, state: 'completed', payment_method: payment_method, + response_code: 'pi_123', amount: order.total + 5) + end + + before do + allow(Stripe).to receive(:api_key) { "sk_test_12345" } + + # Retrieves payment intent info + stub_request(:get, "https://api.stripe.com/v1/payment_intents/pi_123") + .to_return({ status: 200, body: JSON.generate( + amount_received: 2000, + charges: { data: [{ id: "ch_1a2b3c" }] } + ) }) + end + + context "where the request succeeds" do + before do + stub_request(:post, "https://api.stripe.com/v1/charges/ch_1a2b3c/refunds"). + with(basic_auth: ["sk_test_12345", ""]). + to_return(status: 200, + body: JSON.generate(id: 're_123', object: 'refund', status: 'succeeded') ) + end + + it "partially refunds the payment" do + order.reload + expect(order.payment_total).to eq order.total + 5 + expect(order.outstanding_balance).to eq(-5) + spree_put :fire, params + expect(payment.reload.state).to eq 'completed' + order.reload + expect(order.payment_total).to eq order.total + expect(order.outstanding_balance).to eq 0 + end + end + + context "where the request fails" do + before do + stub_request(:post, "https://api.stripe.com/v1/charges/ch_1a2b3c/refunds"). + with(basic_auth: ["sk_test_12345", ""]). + to_return(status: 200, body: JSON.generate(error: { message: "Bup-bow!" }) ) + end + + it "does not void the payment" do + order.reload + expect(order.payment_total).to eq order.total + 5 + expect(order.outstanding_balance).to eq(-5) + spree_put :fire, params + expect(payment.reload.state).to eq 'completed' + order.reload + expect(order.payment_total).to eq order.total + 5 + expect(order.outstanding_balance).to eq(-5) + expect(flash[:error]).to eq "Bup-bow!" + end + end + end + end + end +end diff --git a/spec/controllers/spree/admin/payments_controller_spec.rb b/spec/controllers/spree/admin/orders/payments/payments_controller_spec.rb similarity index 52% rename from spec/controllers/spree/admin/payments_controller_spec.rb rename to spec/controllers/spree/admin/orders/payments/payments_controller_spec.rb index 6c7c2eeb50..6932efa236 100644 --- a/spec/controllers/spree/admin/payments_controller_spec.rb +++ b/spec/controllers/spree/admin/orders/payments/payments_controller_spec.rb @@ -138,128 +138,4 @@ describe Spree::Admin::PaymentsController, type: :controller do end end end - - context "as an enterprise user" do - before do - order.reload.update_totals - end - - context "requesting a refund on a payment" do - let(:params) { { id: payment.id, order_id: order.number, e: :void } } - - # Required for the respond override in the controller decorator to work - before { @request.env['HTTP_REFERER'] = spree.admin_order_payments_url(payment) } - - context "that was processed by stripe" do - let!(:payment_method) { create(:stripe_payment_method, distributors: [shop]) } - let!(:payment) do - create(:payment, order: order, state: 'completed', payment_method: payment_method, - response_code: 'ch_1a2b3c', amount: order.total) - end - - before do - allow(Stripe).to receive(:api_key) { "sk_test_12345" } - end - - context "where the request succeeds" do - before do - stub_request(:post, "https://api.stripe.com/v1/charges/ch_1a2b3c/refunds"). - with(basic_auth: ["sk_test_12345", ""]). - to_return(status: 200, - body: JSON.generate(id: 're_123', object: 'refund', status: 'succeeded') ) - end - - it "voids the payment" do - order.reload - expect(order.payment_total).to_not eq 0 - expect(order.outstanding_balance).to eq 0 - spree_put :fire, params - expect(payment.reload.state).to eq 'void' - order.reload - expect(order.payment_total).to eq 0 - expect(order.outstanding_balance).to_not eq 0 - end - end - - context "where the request fails" do - before do - stub_request(:post, "https://api.stripe.com/v1/charges/ch_1a2b3c/refunds"). - with(basic_auth: ["sk_test_12345", ""]). - to_return(status: 200, body: JSON.generate(error: { message: "Bup-bow!" }) ) - end - - it "does not void the payment" do - order.reload - expect(order.payment_total).to_not eq 0 - expect(order.outstanding_balance).to eq 0 - spree_put :fire, params - expect(payment.reload.state).to eq 'completed' - order.reload - expect(order.payment_total).to_not eq 0 - expect(order.outstanding_balance).to eq 0 - expect(flash[:error]).to eq "Bup-bow!" - end - end - end - end - - context "requesting a partial credit on a payment" do - let(:params) { { id: payment.id, order_id: order.number, e: :credit } } - - # Required for the respond override in the controller decorator to work - before { @request.env['HTTP_REFERER'] = spree.admin_order_payments_url(payment) } - - context "that was processed by stripe" do - let!(:payment_method) { create(:stripe_payment_method, distributors: [shop]) } - let!(:payment) do - create(:payment, order: order, state: 'completed', payment_method: payment_method, - response_code: 'ch_1a2b3c', amount: order.total + 5) - end - - before do - allow(Stripe).to receive(:api_key) { "sk_test_12345" } - end - - context "where the request succeeds" do - before do - stub_request(:post, "https://api.stripe.com/v1/charges/ch_1a2b3c/refunds"). - with(basic_auth: ["sk_test_12345", ""]). - to_return(status: 200, - body: JSON.generate(id: 're_123', object: 'refund', status: 'succeeded') ) - end - - it "partially refunds the payment" do - order.reload - expect(order.payment_total).to eq order.total + 5 - expect(order.outstanding_balance).to eq(-5) - spree_put :fire, params - expect(payment.reload.state).to eq 'completed' - order.reload - expect(order.payment_total).to eq order.total - expect(order.outstanding_balance).to eq 0 - end - end - - context "where the request fails" do - before do - stub_request(:post, "https://api.stripe.com/v1/charges/ch_1a2b3c/refunds"). - with(basic_auth: ["sk_test_12345", ""]). - to_return(status: 200, body: JSON.generate(error: { message: "Bup-bow!" }) ) - end - - it "does not void the payment" do - order.reload - expect(order.payment_total).to eq order.total + 5 - expect(order.outstanding_balance).to eq(-5) - spree_put :fire, params - expect(payment.reload.state).to eq 'completed' - order.reload - expect(order.payment_total).to eq order.total + 5 - expect(order.outstanding_balance).to eq(-5) - expect(flash[:error]).to eq "Bup-bow!" - end - end - end - end - end end diff --git a/spec/controllers/spree/admin/orders_controller_spec.rb b/spec/controllers/spree/admin/orders_controller_spec.rb index 8ab20c385a..5d0401c6cc 100644 --- a/spec/controllers/spree/admin/orders_controller_spec.rb +++ b/spec/controllers/spree/admin/orders_controller_spec.rb @@ -113,7 +113,7 @@ describe Spree::Admin::OrdersController, type: :controller do it "should deny me access to the index action" do spree_get :index - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -140,7 +140,7 @@ describe Spree::Admin::OrdersController, type: :controller do it "should prevent me from sending order invoices" do spree_get :invoice, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -150,7 +150,7 @@ describe Spree::Admin::OrdersController, type: :controller do it "should prevent me from sending order invoices" do spree_get :invoice, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -198,7 +198,7 @@ describe Spree::Admin::OrdersController, type: :controller do it "should prevent me from sending order invoices" do spree_get :print, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -207,7 +207,7 @@ describe Spree::Admin::OrdersController, type: :controller do before { allow(controller).to receive(:spree_current_user) { user } } it "should prevent me from sending order invoices" do spree_get :print, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end diff --git a/spec/controllers/spree/admin/payment_methods_controller_spec.rb b/spec/controllers/spree/admin/payment_methods_controller_spec.rb index dfc7dd307e..3e752170c7 100644 --- a/spec/controllers/spree/admin/payment_methods_controller_spec.rb +++ b/spec/controllers/spree/admin/payment_methods_controller_spec.rb @@ -39,6 +39,20 @@ module Spree expect(response).to redirect_to spree.edit_admin_payment_method_path(assigns(:payment_method)) end + it "can save Pin Payment payment method details" do + expect { + spree_post :create, payment_method: { + name: "Test Method", type: "Spree::Gateway::Pin", distributor_ids: [enterprise.id], + preferred_server: "test", preferred_api_key: "apikey123", preferred_test_mode: "1" + } + }.to change(Spree::PaymentMethod, :count).by(1) + + payment_method = Spree::PaymentMethod.last + expect(payment_method.preferences[:server]).to eq "test" + expect(payment_method.preferences[:api_key]).to eq "apikey123" + expect(payment_method.preferences[:test_mode]).to eq true + end + it "can not create a payment method of an invalid type" do expect { spree_post :create, payment_method: { name: "Invalid Payment Method", type: "Spree::InvalidType", distributor_ids: [enterprise.id] } diff --git a/spec/controllers/spree/admin/products_controller_spec.rb b/spec/controllers/spree/admin/products_controller_spec.rb index e8ae658e9e..efc6ce9b5b 100644 --- a/spec/controllers/spree/admin/products_controller_spec.rb +++ b/spec/controllers/spree/admin/products_controller_spec.rb @@ -16,7 +16,7 @@ describe Spree::Admin::ProductsController, type: :controller do end it "denies access" do - expect(response).to redirect_to spree.unauthorized_url + expect(response).to redirect_to unauthorized_path end it "does not update any product" do diff --git a/spec/controllers/spree/admin/reports_controller_spec.rb b/spec/controllers/spree/admin/reports_controller_spec.rb index 3fccd41d43..8cfda5fe36 100644 --- a/spec/controllers/spree/admin/reports_controller_spec.rb +++ b/spec/controllers/spree/admin/reports_controller_spec.rb @@ -93,18 +93,6 @@ describe Spree::Admin::ReportsController, type: :controller do end end - describe 'Bulk Coop' do - let!(:present_objects) { [orderA1, orderA2, orderB1, orderB2] } - - it "only shows orders that I have access to" do - spree_post :bulk_coop, q: {} - - expect(resulting_orders).to include(orderA1, orderB1) - expect(resulting_orders).not_to include(orderA2) - expect(resulting_orders).not_to include(orderB2) - end - end - describe 'Payments' do let!(:present_objects) { [orderA1, orderA2, orderB1, orderB2] } @@ -156,31 +144,6 @@ describe Spree::Admin::ReportsController, type: :controller do end end - describe 'Bulk Coop' do - context "where I have granted P-OC to the distributor" do - let!(:present_objects) { [orderA1, orderA2] } - - before do - create(:enterprise_relationship, parent: supplier1, child: distributor1, permissions_list: [:add_to_order_cycle]) - end - - it "only shows product line items that I am supplying" do - spree_post :bulk_coop, q: {} - - expect(resulting_products).to include product1 - expect(resulting_products).not_to include product2, product3 - end - end - - context "where I have not granted P-OC to the distributor" do - it "shows product line items that I am supplying" do - spree_post :bulk_coop - - expect(resulting_products).not_to include product1, product2, product3 - end - end - end - describe 'Orders & Fulfillment' do let!(:present_objects) { [orderA1, orderA2] } diff --git a/spec/controllers/spree/admin/shipping_methods_controller_spec.rb b/spec/controllers/spree/admin/shipping_methods_controller_spec.rb index 531d01c11e..105d55563c 100644 --- a/spec/controllers/spree/admin/shipping_methods_controller_spec.rb +++ b/spec/controllers/spree/admin/shipping_methods_controller_spec.rb @@ -50,7 +50,7 @@ describe Spree::Admin::ShippingMethodsController, type: :controller do end it "updates details of a FlexiRate calculator" do - shipping_method.calculator = Spree::Calculator::FlexiRate.new(calculable: shipping_method) + shipping_method.calculator = Calculator::FlexiRate.new(calculable: shipping_method) params[:shipping_method][:calculator_attributes][:preferred_first_item] = 10 params[:shipping_method][:calculator_attributes][:preferred_additional_item] = 20 params[:shipping_method][:calculator_attributes][:preferred_max_items] = 30 @@ -63,7 +63,7 @@ describe Spree::Admin::ShippingMethodsController, type: :controller do end it "updates details of a PriceSack calculator" do - shipping_method.calculator = Spree::Calculator::PriceSack.new(calculable: shipping_method) + shipping_method.calculator = Calculator::PriceSack.new(calculable: shipping_method) params[:shipping_method][:calculator_attributes][:preferred_minimal_amount] = 10 params[:shipping_method][:calculator_attributes][:preferred_normal_amount] = 20 params[:shipping_method][:calculator_attributes][:preferred_discount_amount] = 30 diff --git a/spec/controllers/spree/credit_cards_controller_spec.rb b/spec/controllers/spree/credit_cards_controller_spec.rb index e8996433f3..2c4f6a3a15 100644 --- a/spec/controllers/spree/credit_cards_controller_spec.rb +++ b/spec/controllers/spree/credit_cards_controller_spec.rb @@ -88,7 +88,7 @@ describe Spree::CreditCardsController, type: :controller do context "but the card is not owned by the user" do it "redirects to unauthorized" do spree_put :update, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end @@ -135,7 +135,7 @@ describe Spree::CreditCardsController, type: :controller do context "but the card is not owned by the user" do it "redirects to unauthorized" do spree_delete :destroy, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end diff --git a/spec/controllers/spree/orders_controller_spec.rb b/spec/controllers/spree/orders_controller_spec.rb index cee76bdc39..1f74a3915f 100644 --- a/spec/controllers/spree/orders_controller_spec.rb +++ b/spec/controllers/spree/orders_controller_spec.rb @@ -58,7 +58,7 @@ describe Spree::OrdersController, type: :controller do it "redirects to unauthorized" do spree_get :show, id: order.number - expect(response.status).to eq(401) + expect(response).to redirect_to unauthorized_path end end @@ -415,9 +415,11 @@ describe Spree::OrdersController, type: :controller do let(:params) { { id: order.number } } context "when the user does not have permission to cancel the order" do + before { allow(controller).to receive(:spree_current_user) { create(:user) } } + it "responds with unauthorized" do spree_put :cancel, params - expect(response).to render_template 'shared/unauthorized' + expect(response).to redirect_to unauthorized_path end end diff --git a/spec/controllers/spree/store_controller_spec.rb b/spec/controllers/spree/store_controller_spec.rb deleted file mode 100644 index b04ac0167e..0000000000 --- a/spec/controllers/spree/store_controller_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'spec_helper' - -describe Spree::StoreController, type: :controller do - controller(Spree::StoreController) do - before_filter :unauthorized - def index - render text: "" - end - end - it "redirects to home when unauthorized" do - get :index - expect(response).to render_template("shared/unauthorized", layout: 'darkswarm') - end -end diff --git a/spec/controllers/stripe/callbacks_controller_spec.rb b/spec/controllers/stripe/callbacks_controller_spec.rb index 33f7bd5b76..50c5433767 100644 --- a/spec/controllers/stripe/callbacks_controller_spec.rb +++ b/spec/controllers/stripe/callbacks_controller_spec.rb @@ -30,7 +30,7 @@ describe Stripe::CallbacksController, type: :controller do it "redirects to unauthorized" do spree_get :index, params - expect(response).to redirect_to spree.unauthorized_path + expect(response).to redirect_to unauthorized_path end end diff --git a/spec/factories/calculated_adjustment_factory.rb b/spec/factories/calculated_adjustment_factory.rb index 3e9c6f5cc2..5654b62746 100644 --- a/spec/factories/calculated_adjustment_factory.rb +++ b/spec/factories/calculated_adjustment_factory.rb @@ -1,5 +1,5 @@ FactoryBot.define do - factory :calculator_flat_rate, class: Spree::Calculator::FlatRate do + factory :calculator_flat_rate, class: Calculator::FlatRate do preferred_amount { generate(:calculator_amount) } end end diff --git a/spec/factories/calculator_factory.rb b/spec/factories/calculator_factory.rb index 25dd9b0a13..0c8acaf3e5 100644 --- a/spec/factories/calculator_factory.rb +++ b/spec/factories/calculator_factory.rb @@ -1,6 +1,6 @@ FactoryBot.define do sequence(:calculator_amount) - factory :calculator_per_item, class: Spree::Calculator::PerItem do + factory :calculator_per_item, class: Calculator::PerItem do preferred_amount { generate(:calculator_amount) } end diff --git a/spec/factories/product_factory.rb b/spec/factories/product_factory.rb index e546abbac4..3ca3fdb77e 100644 --- a/spec/factories/product_factory.rb +++ b/spec/factories/product_factory.rb @@ -36,7 +36,7 @@ FactoryBot.define do create(:tax_rate, amount: proxy.tax_rate_amount, tax_category: product.tax_category, included_in_price: true, - calculator: Spree::Calculator::DefaultTax.new, + calculator: Calculator::DefaultTax.new, zone: proxy.zone, name: proxy.tax_rate_name) end diff --git a/spec/factories/shipping_method_factory.rb b/spec/factories/shipping_method_factory.rb index 516dfb6b66..87cec39be9 100644 --- a/spec/factories/shipping_method_factory.rb +++ b/spec/factories/shipping_method_factory.rb @@ -9,13 +9,13 @@ FactoryBot.define do end trait :flat_rate do - calculator { Spree::Calculator::FlatRate.new(preferred_amount: 50.0) } + calculator { Calculator::FlatRate.new(preferred_amount: 50.0) } end trait :expensive_name do name { "Shipping" } description { "Expensive" } - calculator { Spree::Calculator::FlatRate.new(preferred_amount: 100.55) } + calculator { Calculator::FlatRate.new(preferred_amount: 100.55) } end trait :distributor do diff --git a/spec/factories/tag_rule_factory.rb b/spec/factories/tag_rule_factory.rb index 9663913820..4a4f5b1130 100644 --- a/spec/factories/tag_rule_factory.rb +++ b/spec/factories/tag_rule_factory.rb @@ -18,7 +18,7 @@ FactoryBot.define do factory :tag_rule, class: TagRule::DiscountOrder do enterprise { FactoryBot.create :distributor_enterprise } before(:create) do |tr| - tr.calculator = Spree::Calculator::FlatPercentItemTotal.new(calculable: tr) + tr.calculator = Calculator::FlatPercentItemTotal.new(calculable: tr) end end end diff --git a/spec/features/admin/authentication_spec.rb b/spec/features/admin/authentication_spec.rb index 6ff46d15a4..a7add975f2 100644 --- a/spec/features/admin/authentication_spec.rb +++ b/spec/features/admin/authentication_spec.rb @@ -9,17 +9,14 @@ feature "Authentication", js: true do let!(:enterprise) { create(:enterprise, owner: user) } # Required for access to admin scenario "logging into admin redirects home, then back to admin" do - # This is the first admin spec, so give a little extra load time for slow systems - Capybara.using_wait_time(120) do - visit spree.admin_dashboard_path + visit spree.admin_dashboard_path - fill_in "Email", with: user.email - fill_in "Password", with: user.password - click_login_button - expect(page).to have_content "DASHBOARD" - expect(page).to have_current_path spree.admin_dashboard_path - expect(page).to have_no_content "CONFIGURATION" - end + fill_in "Email", with: user.email + fill_in "Password", with: user.password + click_login_button + expect(page).to have_content "DASHBOARD" + expect(page).to have_current_path spree.admin_dashboard_path + expect(page).to have_no_content "CONFIGURATION" end scenario "viewing my account" do diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index 09bdf68fca..c0b55ed224 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -770,7 +770,7 @@ feature ' expect(page).to have_selector "td.image" # Shows default image when no image set - expect(page).to have_css "img[src='/assets/noimage/mini.png']" + expect(page).to have_css "img[src='/noimage/mini.png']" @old_thumb_src = page.find("a.image-modal img")['src'] # Click image diff --git a/spec/features/admin/configuration/taxonomies_spec.rb b/spec/features/admin/configuration/taxonomies_spec.rb index 6947a9a0b3..6e871e3529 100644 --- a/spec/features/admin/configuration/taxonomies_spec.rb +++ b/spec/features/admin/configuration/taxonomies_spec.rb @@ -14,8 +14,10 @@ describe "Taxonomies" do create(:taxonomy, name: 'Brand') create(:taxonomy, name: 'Categories') click_link "Taxonomies" - within_row(1) { expect(page).to have_content("Brand") } - within_row(2) { expect(page).to have_content("Categories") } + within("table.index tbody") do + expect(page).to have_content("Brand") + expect(page).to have_content("Categories") + end end end diff --git a/spec/features/admin/order_cycles/complex_updating_specific_time_spec.rb b/spec/features/admin/order_cycles/complex_updating_specific_time_spec.rb index 258caa1080..2094a83f7e 100644 --- a/spec/features/admin/order_cycles/complex_updating_specific_time_spec.rb +++ b/spec/features/admin/order_cycles/complex_updating_specific_time_spec.rb @@ -69,7 +69,8 @@ feature ' select 'My supplier', from: 'new_supplier_id' click_button 'Add supplier' expect(page).to have_selector("table.exchanges tr.supplier", text: "My supplier") - page.all("table.exchanges tr.supplier td.products").each(&:click) + + open_all_exchange_product_tabs expect(page).to have_selector "#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true page.find("#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true).click # uncheck (with visible:true filter) @@ -87,6 +88,7 @@ feature ' select 'Supplier fee 2', from: 'order_cycle_incoming_exchange_2_enterprise_fees_0_enterprise_fee_id' click_button 'Save and Next' + expect(page).to have_content 'Your order cycle has been updated.' # And I add a distributor and some products select 'My distributor', from: 'new_distributor_id' @@ -105,12 +107,7 @@ feature ' find(:css, "tags-input .tags input").set "wholesale\n" end - exchange_rows = page.all("table.exchanges tbody") - exchange_rows.each do |exchange_row| - exchange_row.find("td.products").click - # Wait for the products panel to be visible. - expect(exchange_row).to have_selector "tr", count: 2 - end + open_all_exchange_product_tabs uncheck "order_cycle_outgoing_exchange_2_variants_#{v1.id}" check "order_cycle_outgoing_exchange_2_variants_#{v2.id}" @@ -165,4 +162,15 @@ feature ' def wait_for_edit_form_to_load_order_cycle(order_cycle) expect(page).to have_field "order_cycle_name", with: order_cycle.name end + + def open_all_exchange_product_tabs + exchange_rows = page.all("table.exchanges tbody") + exchange_rows.each do |exchange_row| + exchange_row.find("td.products").click + within(exchange_row) do + # Wait for the products panel to be visible. + expect(page).to have_selector ".exchange-products" + end + end + end end diff --git a/spec/features/admin/order_spec.rb b/spec/features/admin/order_spec.rb index 0017d6b72a..009092686b 100644 --- a/spec/features/admin/order_spec.rb +++ b/spec/features/admin/order_spec.rb @@ -285,7 +285,7 @@ feature ' from: 'selected_shipping_rate_id' find('.save-method').click - expect(page).to have_content different_shipping_method_for_distributor1.name + expect(page).to have_content "Shipping: #{different_shipping_method_for_distributor1.name}" end end diff --git a/spec/features/admin/payments_spec.rb b/spec/features/admin/payments_spec.rb index 2ea666f121..adce888db2 100644 --- a/spec/features/admin/payments_spec.rb +++ b/spec/features/admin/payments_spec.rb @@ -21,7 +21,7 @@ feature ' # This calculator doesn't handle a `nil` order well. # That has been useful in finding bugs. ;-) - payment_method.calculator = Spree::Calculator::FlatPercentItemTotal.new + payment_method.calculator = Calculator::FlatPercentItemTotal.new payment_method.save! end diff --git a/spec/features/admin/reports_spec.rb b/spec/features/admin/reports_spec.rb index 97b677ed98..b8eb70f010 100644 --- a/spec/features/admin/reports_spec.rb +++ b/spec/features/admin/reports_spec.rb @@ -155,15 +155,6 @@ feature ' expect(page).to have_content 'Order date' end - scenario "bulk co-op report" do - quick_login_as_admin - visit spree.admin_reports_path - click_link 'Bulk Co-Op' - click_button 'Search' - - expect(page).to have_content 'Supplier' - end - scenario "payments reports" do quick_login_as_admin visit spree.admin_reports_path @@ -179,7 +170,7 @@ feature ' let(:user1) { create_enterprise_user enterprises: [distributor1] } let(:user2) { create_enterprise_user enterprises: [distributor2] } let!(:shipping_method) { create(:shipping_method_with, :expensive_name, distributors: [distributor1]) } - let(:enterprise_fee) { create(:enterprise_fee, enterprise: user1.enterprises.first, tax_category: product2.tax_category, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 120.0)) } + let(:enterprise_fee) { create(:enterprise_fee, enterprise: user1.enterprises.first, tax_category: product2.tax_category, calculator: Calculator::FlatRate.new(preferred_amount: 120.0)) } let(:order_cycle) { create(:simple_order_cycle, coordinator: distributor1, coordinator_fees: [enterprise_fee], distributors: [distributor1], variants: [product1.master]) } let!(:zone) { create(:zone_with_member) } @@ -393,8 +384,8 @@ feature ' let(:shipping_method) { create(:shipping_method_with, :expensive_name) } let(:shipment) { create(:shipment_with, :shipping_method, shipping_method: shipping_method) } - let(:enterprise_fee1) { create(:enterprise_fee, enterprise: user1.enterprises.first, tax_category: product2.tax_category, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 10)) } - let(:enterprise_fee2) { create(:enterprise_fee, enterprise: user1.enterprises.first, tax_category: product2.tax_category, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 20)) } + let(:enterprise_fee1) { create(:enterprise_fee, enterprise: user1.enterprises.first, tax_category: product2.tax_category, calculator: Calculator::FlatRate.new(preferred_amount: 10)) } + let(:enterprise_fee2) { create(:enterprise_fee, enterprise: user1.enterprises.first, tax_category: product2.tax_category, calculator: Calculator::FlatRate.new(preferred_amount: 20)) } let(:order_cycle) { create(:simple_order_cycle, coordinator: distributor1, coordinator_fees: [enterprise_fee1, enterprise_fee2], distributors: [distributor1], variants: [product1.master]) } let!(:zone) { create(:zone_with_member) } diff --git a/spec/features/consumer/authentication_spec.rb b/spec/features/consumer/authentication_spec.rb index 201cd45621..c172f40f38 100644 --- a/spec/features/consumer/authentication_spec.rb +++ b/spec/features/consumer/authentication_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' feature "Authentication", js: true do + include AuthenticationWorkflow include UIComponentHelper include OpenFoodNetwork::EmailHelper @@ -135,5 +136,57 @@ feature "Authentication", js: true do uri = URI.parse(current_url) expect(uri.path + "#" + uri.fragment).to eq('/#/login') end + + describe "with user locales" do + before do + visit root_path + open_login_modal + end + + context "when the user has a valid locale saved" do + before do + user.update!(locale: "es") + end + + it "logs in successfully, applying the saved locale" do + fill_in_and_submit_login_form(user) + expect_logged_in + + expect(page).to have_content I18n.t(:home_shop, locale: :es).upcase + end + end + + context "when the user has an unavailable locale saved" do + before do + user.update!(locale: "xx") + end + + it "logs in successfully and resets the user's locale to the default" do + fill_in_and_submit_login_form(user) + expect_logged_in + + expect(page).to have_content I18n.t(:home_shop, locale: :en).upcase + expect(user.reload.locale).to eq "en" + end + end + + context "when the user has never selected a locale, but one has been selected before login" do + before do + user.update!(locale: nil) + end + + it "logs in successfully and uses the locale from cookies" do + page.driver.browser.manage.add_cookie(name: 'locale', value: 'es') + + fill_in_and_submit_login_form(user) + expect_logged_in + + expect(page).to have_content I18n.t(:home_shop, locale: :es).upcase + expect(user.reload.locale).to eq "es" + + page.driver.browser.manage.delete_cookie('locale') + end + end + end end end diff --git a/spec/features/consumer/caching/darkwarm_caching_spec.rb b/spec/features/consumer/caching/darkswarm_caching_spec.rb similarity index 95% rename from spec/features/consumer/caching/darkwarm_caching_spec.rb rename to spec/features/consumer/caching/darkswarm_caching_spec.rb index 55f41aeee1..14ccbd2ab9 100644 --- a/spec/features/consumer/caching/darkwarm_caching_spec.rb +++ b/spec/features/consumer/caching/darkswarm_caching_spec.rb @@ -45,6 +45,9 @@ feature "Darkswarm data caching", js: true, caching: true do expect(page).to have_content property.presentation end + # Update rows which should also update the timestamp. + # The timestamp represents seconds, so waiting one second is enough. + sleep 1 taxon.update!(name: "Changed Taxon") property.update!(presentation: "Changed Property") diff --git a/spec/features/consumer/shopping/cart_spec.rb b/spec/features/consumer/shopping/cart_spec.rb index 194b202d26..0bc4523a24 100644 --- a/spec/features/consumer/shopping/cart_spec.rb +++ b/spec/features/consumer/shopping/cart_spec.rb @@ -80,7 +80,7 @@ feature "full-page cart", js: true do describe "admin and handling flat fees" do context "when there are fees" do let(:handling_fee) { - create(:enterprise_fee, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 1), + create(:enterprise_fee, calculator: Calculator::FlatRate.new(preferred_amount: 1), enterprise: order_cycle.coordinator, fee_type: 'admin') } diff --git a/spec/features/consumer/shopping/checkout_spec.rb b/spec/features/consumer/shopping/checkout_spec.rb index a6135bf88c..342c699a43 100644 --- a/spec/features/consumer/shopping/checkout_spec.rb +++ b/spec/features/consumer/shopping/checkout_spec.rb @@ -26,11 +26,11 @@ feature "As a consumer I want to check out my cart", js: true do end describe "with shipping and payment methods" do - let(:free_shipping) { create(:shipping_method, require_ship_address: true, name: "Frogs", description: "yellow", calculator: Spree::Calculator::FlatRate.new(preferred_amount: 0.00)) } - let(:shipping_with_fee) { create(:shipping_method, require_ship_address: false, name: "Donkeys", description: "blue", calculator: Spree::Calculator::FlatRate.new(preferred_amount: 4.56)) } + let(:free_shipping) { create(:shipping_method, require_ship_address: true, name: "Frogs", description: "yellow", calculator: Calculator::FlatRate.new(preferred_amount: 0.00)) } + let(:shipping_with_fee) { create(:shipping_method, require_ship_address: false, name: "Donkeys", description: "blue", calculator: Calculator::FlatRate.new(preferred_amount: 4.56)) } let(:tagged_shipping) { create(:shipping_method, require_ship_address: false, name: "Local", tag_list: "local") } let!(:check_without_fee) { create(:payment_method, distributors: [distributor], name: "Roger rabbit", type: "Spree::PaymentMethod::Check") } - let!(:check_with_fee) { create(:payment_method, distributors: [distributor], calculator: Spree::Calculator::FlatRate.new(preferred_amount: 5.67)) } + let!(:check_with_fee) { create(:payment_method, distributors: [distributor], calculator: Calculator::FlatRate.new(preferred_amount: 5.67)) } let!(:paypal) do Spree::Gateway::PayPalExpress.create!(name: "Paypal", environment: 'test', distributor_ids: [distributor.id]).tap do |pm| pm.preferred_login = 'devnull-facilitator_api1.rohanmitchell.com' @@ -74,7 +74,7 @@ feature "As a consumer I want to check out my cart", js: true do fill_out_form end - it "allows user to save default billing address and shipping address" do + it "creates a new default billing address and shipping address" do expect(user.bill_address).to be_nil expect(user.ship_address).to be_nil @@ -94,6 +94,32 @@ feature "As a consumer I want to check out my cart", js: true do expect(user.reload.ship_address.address1).to eq '123 Your Head' end + context "when the user and customer have existing default addresses" do + let(:existing_address) { create(:address) } + + before do + user.bill_address = existing_address + user.ship_address = existing_address + end + + it "updates billing address and shipping address" do + expect(order.bill_address).to be_nil + expect(order.ship_address).to be_nil + + place_order + expect(page).to have_content "Your order has been processed successfully" + + expect(order.reload.bill_address.address1).to eq '123 Your Head' + expect(order.reload.ship_address.address1).to eq '123 Your Head' + + expect(order.customer.bill_address.address1).to eq '123 Your Head' + expect(order.customer.ship_address.address1).to eq '123 Your Head' + + expect(user.reload.bill_address.address1).to eq '123 Your Head' + expect(user.reload.ship_address.address1).to eq '123 Your Head' + end + end + it "it doesn't tell about previous orders" do expect(page).to have_no_content("You have an order for this order cycle already.") end diff --git a/spec/features/consumer/shopping/embedded_shopfronts_spec.rb b/spec/features/consumer/shopping/embedded_shopfronts_spec.rb index 20c7bfccb8..445a92186f 100644 --- a/spec/features/consumer/shopping/embedded_shopfronts_spec.rb +++ b/spec/features/consumer/shopping/embedded_shopfronts_spec.rb @@ -45,7 +45,7 @@ feature "Using embedded shopfront functionality", js: true do it "allows shopping and checkout" do on_embedded_page do - fill_in "variants[#{variant.id}]", with: 1 + click_add_to_cart edit_cart diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index b947b23d3d..b28dcbefec 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -104,9 +104,8 @@ feature "As a consumer I want to shop with a distributor", js: true do expect(page).to have_content with_currency(1020.99) # -- Cart shows correct price - fill_in "variants[#{variant.id}]", with: 1 - toggle_cart - within(".cart-sidebar") { expect(page).to have_content with_currency(1020.99) } + click_add_to_cart variant + expect(page).to have_in_cart with_currency(1020.99) toggle_cart # -- Changing order cycle @@ -119,10 +118,8 @@ feature "As a consumer I want to shop with a distributor", js: true do # ng-animate means that the old product row is likely to be present, so we ensure # that we are not filling in the quantity on the outgoing row expect(page).not_to have_selector "tr.product-cart" - within('product:not(.ng-leave)') { fill_in "variants[#{variant.id}]", with: 1 } - - wait_for_cart - within(".cart-sidebar") { expect(page).to have_content with_currency(19.99) } + within('product:not(.ng-leave)') { click_add_to_cart variant } + expect(page).to have_in_cart with_currency(19.99) end describe "declining to clear the cart" do @@ -132,15 +129,14 @@ feature "As a consumer I want to shop with a distributor", js: true do visit shop_path select "turtles", from: "order_cycle_id" - fill_in "variants[#{variant.id}]", with: 1 + click_add_to_cart variant end it "leaves the cart untouched when the user declines" do handle_js_confirm(false) do select "frogs", from: "order_cycle_id" - toggle_cart + expect(page).to have_in_cart "1" expect(page).to have_selector "tr.product-cart" - expect(page).to have_selector '.cart-sidebar', text: '1' # The order cycle choice should not have changed expect(page).to have_select 'order_cycle_id', selected: 'turtles' @@ -148,17 +144,6 @@ feature "As a consumer I want to shop with a distributor", js: true do end end end - - context "when logged in" do - let!(:prev_order) { create(:completed_order_with_totals, order_cycle: oc1, distributor: distributor, user: order.user) } - - before do - distributor.allow_order_changes = true - distributor.save - quick_login_as order.user - visit shop_path - end - end end end @@ -209,14 +194,16 @@ feature "As a consumer I want to shop with a distributor", js: true do end it "returns search results for products where the search term matches one of the product's variant names" do + pending "has been broken for a while" + visit shop_path fill_in "search", with: "Badg" # For variant with display_name "Badgers" within('div.pad-top') do - expect(page).to have_content product.name - expect(page).to have_content variant2.display_name expect(page).not_to have_content product2.name expect(page).not_to have_content variant3.display_name + expect(page).to have_content product.name + expect(page).to have_content variant2.display_name end end @@ -230,11 +217,8 @@ feature "As a consumer I want to shop with a distributor", js: true do visit shop_path expect(page).to have_content product.name - # Add product to cart - fill_in "variants[#{variant.id}]", with: '1' - wait_for_debounce + click_add_to_cart variant expect(page).to have_in_cart product.name - wait_until { !cart_dirty } # Try to go to cart visit main_app.cart_path @@ -260,17 +244,14 @@ feature "As a consumer I want to shop with a distributor", js: true do it "should save group buy data to the cart and display it on shopfront reload" do # -- Quantity - fill_in "variants[#{variant.id}]", with: 6 - wait_for_debounce + click_add_bulk_to_cart variant, 6 expect(page).to have_in_cart product.name - wait_until { !cart_dirty } + toggle_cart expect(order.reload.line_items.first.quantity).to eq(6) # -- Max quantity - fill_in "variant_attributes[#{variant.id}][max_quantity]", with: 7 - wait_for_debounce - wait_until { !cart_dirty } + click_add_bulk_max_to_cart variant, 7 expect(order.reload.line_items.first.max_quantity).to eq(7) @@ -296,15 +277,15 @@ feature "As a consumer I want to shop with a distributor", js: true do end it "lets us add and remove products from our cart" do - fill_in "variants[#{variant.id}]", with: '1' + click_add_to_cart variant expect(page).to have_in_cart product.name - wait_until { !cart_dirty } li = Spree::Order.order(:created_at).last.line_items.order(:created_at).last expect(li.quantity).to eq(1) - fill_in "variants[#{variant.id}]", with: '0' + toggle_cart + click_remove_from_cart variant + toggle_cart within('.cart-sidebar') { expect(page).not_to have_content product.name } - wait_until { !cart_dirty } expect(Spree::LineItem.where(id: li)).to be_empty end @@ -313,7 +294,7 @@ feature "As a consumer I want to shop with a distributor", js: true do variant.update on_hand: 5, on_demand: true visit shop_path - fill_in "variants[#{variant.id}]", with: '10' + click_add_to_cart variant, 10 expect(page).to have_field "variants[#{variant.id}]", with: '10' end @@ -322,6 +303,7 @@ feature "As a consumer I want to shop with a distributor", js: true do variant.update on_hand: 5 visit shop_path + accept_alert 'Insufficient stock available, only 5 remaining' do fill_in "variants[#{variant.id}]", with: '10' end @@ -336,9 +318,7 @@ feature "As a consumer I want to shop with a distributor", js: true do variant.update! on_hand: 0 # -- Messaging - expect(page).to have_input "variants[#{variant.id}]" - fill_in "variants[#{variant.id}]", with: '1' - wait_until { !cart_dirty } + click_add_to_cart variant within(".out-of-stock-modal") do expect(page).to have_content "stock levels for one or more of the products in your cart have reduced" @@ -353,8 +333,6 @@ feature "As a consumer I want to shop with a distributor", js: true do # Update amount available in product list # If amount falls to zero, variant should be greyed out and input disabled expect(page).to have_selector "#variant-#{variant.id}.out-of-stock" - expect(page).to have_selector "#variants_#{variant.id}[ofn-on-hand='0']" - expect(page).to have_selector "#variants_#{variant.id}[disabled='disabled']" end it 'does not show out of stock modal if product is on_demand' do @@ -362,9 +340,7 @@ feature "As a consumer I want to shop with a distributor", js: true do variant.update! on_hand: 0, on_demand: true - expect(page).to have_input "variants[#{variant.id}]" - fill_in "variants[#{variant.id}]", with: '1' - wait_until { !cart_dirty } + click_add_to_cart variant expect(page).to_not have_selector '.out-of-stock-modal' end @@ -374,13 +350,11 @@ feature "As a consumer I want to shop with a distributor", js: true do it "does the same" do # -- Place in cart so we can set max_quantity, then make out of stock - fill_in "variants[#{variant.id}]", with: '1' - wait_until { !cart_dirty } + click_add_bulk_to_cart variant variant.update! on_hand: 0 # -- Messaging - fill_in "variant_attributes[#{variant.id}][max_quantity]", with: '1' - wait_until { !cart_dirty } + click_add_bulk_max_to_cart variant within(".out-of-stock-modal") do expect(page).to have_content "stock levels for one or more of the products in your cart have reduced" @@ -400,13 +374,11 @@ feature "As a consumer I want to shop with a distributor", js: true do context "when the update is for another product" do it "updates quantity" do - fill_in "variants[#{variant.id}]", with: '2' - wait_until { !cart_dirty } + click_add_to_cart variant, 2 variant.update! on_hand: 1 - fill_in "variants[#{variant2.id}]", with: '1' - wait_until { !cart_dirty } + click_add_to_cart variant2 within(".out-of-stock-modal") do expect(page).to have_content "stock levels for one or more of the products in your cart have reduced" @@ -418,13 +390,12 @@ feature "As a consumer I want to shop with a distributor", js: true do let(:product) { create(:simple_product, group_buy: true) } it "does not update max_quantity" do - fill_in "variants[#{variant.id}]", with: '2' - fill_in "variant_attributes[#{variant.id}][max_quantity]", with: '3' - wait_until { !cart_dirty } + click_add_bulk_to_cart variant, 2 + click_add_bulk_max_to_cart variant, 3 + variant.update! on_hand: 1 - fill_in "variants[#{variant2.id}]", with: '1' - wait_until { !cart_dirty } + click_add_bulk_to_cart variant2 within(".out-of-stock-modal") do expect(page).to have_content "stock levels for one or more of the products in your cart have reduced" @@ -443,7 +414,7 @@ feature "As a consumer I want to shop with a distributor", js: true do it "handles it as if the variant has gone out of stock" do variant.delete - fill_in "variants[#{variant.id}]", with: '1' + click_add_to_cart variant expect_out_of_stock_behavior end @@ -458,7 +429,7 @@ feature "As a consumer I want to shop with a distributor", js: true do it "handles it as if the variant has gone out of stock" do variant.delete - fill_in "variants[#{variant.id}]", with: '1' + click_add_to_cart variant expect_out_of_stock_behavior end @@ -578,16 +549,7 @@ feature "As a consumer I want to shop with a distributor", js: true do expect(page).to have_content product.name end - def wait_for_debounce - # The auto-submit on these specific form elements (add to cart) now has a small built-in - # waiting period before submitting the data... - sleep 0.6 - end - def expect_out_of_stock_behavior - wait_for_debounce - wait_until { !cart_dirty } - # Shows an "out of stock" modal, with helpful user feedback within(".out-of-stock-modal") do expect(page).to have_content I18n.t('js.out_of_stock.out_of_stock_text') @@ -598,9 +560,5 @@ feature "As a consumer I want to shop with a distributor", js: true do expect(page).to have_selector "#variant-#{variant.id}.out-of-stock" expect(page).to have_selector "#variants_#{variant.id}[ofn-on-hand='0']" expect(page).to have_selector "#variants_#{variant.id}[disabled='disabled']" - - # We need to wait again for the cart to finish updating in Angular or the test can fail - # as the session cannot be reset properly (after the test) while it's still loading - wait_until { !cart_dirty } end end diff --git a/spec/features/consumer/shopping/variant_overrides_spec.rb b/spec/features/consumer/shopping/variant_overrides_spec.rb index 6dfed074a7..9c7ca0a5d1 100644 --- a/spec/features/consumer/shopping/variant_overrides_spec.rb +++ b/spec/features/consumer/shopping/variant_overrides_spec.rb @@ -37,8 +37,7 @@ feature "shopping with variant overrides defined", js: true do outgoing_exchange.variants = [product1_variant1, product1_variant2, product2_variant1, product1_variant3, product3_variant1, product3_variant2, product4_variant1] outgoing_exchange.enterprise_fees << enterprise_fee sm.calculator.preferred_amount = 0 - visit shops_path - click_link hub.name + visit enterprise_shop_path(hub) end describe "viewing products" do @@ -66,9 +65,7 @@ feature "shopping with variant overrides defined", js: true do end it "shows the correct prices when products are in the cart" do - fill_in "variants[#{product1_variant1.id}]", with: "2" - toggle_cart - wait_until_enabled '.cart-sidebar a.edit-cart' + click_add_to_cart product1_variant1, 2 visit shop_path expect(page).to have_price with_currency(61.11) end @@ -77,14 +74,14 @@ feature "shopping with variant overrides defined", js: true do # https://github.com/openfoodfoundation/openfoodnetwork/issues/312 it "shows the overridden price with fees in the quick cart" do - fill_in "variants[#{product1_variant1.id}]", with: "2" + click_add_to_cart product1_variant1, 2 toggle_cart expect(page).to have_selector "#cart-variant-#{product1_variant1.id} .quantity", text: '2' expect(page).to have_selector "#cart-variant-#{product1_variant1.id} .total-price", text: with_currency(122.22) end it "shows the correct prices in the shopping cart" do - fill_in "variants[#{product1_variant1.id}]", with: "2" + click_add_to_cart product1_variant1, 2 edit_cart expect(page).to have_selector "tr.line-item.variant-#{product1_variant1.id} .cart-item-price", text: with_currency(61.11) @@ -96,7 +93,7 @@ feature "shopping with variant overrides defined", js: true do end it "shows the correct prices in the checkout" do - fill_in "variants[#{product1_variant1.id}]", with: "2" + click_add_to_cart product1_variant1, 2 click_checkout expect(page).to have_selector 'form.edit_order .cart-total', text: with_currency(122.22) @@ -107,7 +104,7 @@ feature "shopping with variant overrides defined", js: true do describe "creating orders" do it "creates the order with the correct prices" do - fill_in "variants[#{product1_variant1.id}]", with: "2" + click_add_to_cart product1_variant1, 2 click_checkout complete_checkout @@ -118,7 +115,7 @@ feature "shopping with variant overrides defined", js: true do end it "subtracts stock from the override" do - fill_in "variants[#{product1_variant3.id}]", with: "2" + click_add_to_cart product1_variant3, 2 click_checkout expect do @@ -129,7 +126,7 @@ feature "shopping with variant overrides defined", js: true do end it "subtracts stock from stock-overridden on_demand variants" do - fill_in "variants[#{product3_variant2.id}]", with: "2" + click_add_to_cart product3_variant2, 2 click_checkout expect do @@ -140,7 +137,7 @@ feature "shopping with variant overrides defined", js: true do end it "does not subtract stock from overrides that do not override count_on_hand" do - fill_in "variants[#{product1_variant1.id}]", with: "2" + click_add_to_cart product1_variant1, 2 click_checkout expect do complete_checkout @@ -149,7 +146,7 @@ feature "shopping with variant overrides defined", js: true do end it "does not subtract stock from variants where the override has on_demand: true" do - fill_in "variants[#{product4_variant1.id}]", with: "2" + click_add_to_cart product4_variant1, 2 click_checkout expect do complete_checkout @@ -159,7 +156,7 @@ feature "shopping with variant overrides defined", js: true do it "does not show out of stock flags on order confirmation page" do product1_variant3.on_hand = 0 - fill_in "variants[#{product1_variant3.id}]", with: "2" + click_add_to_cart product1_variant3, 2 click_checkout complete_checkout @@ -202,7 +199,7 @@ feature "shopping with variant overrides defined", js: true do def click_checkout toggle_cart - wait_until_enabled '.cart-sidebar a.edit-cart' - first(:link, I18n.t('shared.menu.cart_sidebar.checkout')).click + wait_for_cart + click_link I18n.t('shared.menu.cart_sidebar.checkout') end end diff --git a/spec/javascripts/unit/admin/orders/controllers/orders_controller_spec.js.coffee b/spec/javascripts/unit/admin/orders/controllers/orders_controller_spec.js.coffee index 2fa1efbac3..0ccbbe3b9a 100644 --- a/spec/javascripts/unit/admin/orders/controllers/orders_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/orders/controllers/orders_controller_spec.js.coffee @@ -63,3 +63,14 @@ describe "ordersCtrl", -> expect(Orders.index).toHaveBeenCalledWith(jasmine.objectContaining({ 'q[order_cycle_id_in][]': ['4', '5'] })) + + it "filters orders on inclusive dates", -> + $scope['q']['completed_at_gteq'] = '2020-06-08' + $scope['q']['completed_at_lteq'] = '2020-06-09' + + $scope.fetchResults() + + expect(Orders.index).toHaveBeenCalledWith(jasmine.objectContaining({ + 'q[completed_at_gteq]': '2020-06-08 00:00:00' + 'q[completed_at_lteq]': '2020-06-09 23:59:59' + })) diff --git a/spec/javascripts/unit/darkswarm/services/products_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/products_spec.js.coffee index 88ff585f00..db9ed1e628 100644 --- a/spec/javascripts/unit/darkswarm/services/products_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/products_spec.js.coffee @@ -107,7 +107,7 @@ describe 'Products service', -> $httpBackend.expectGET(endpoint).respond([product]) $httpBackend.flush() expect(Products.products[0].primaryImage).toBeUndefined() - expect(Products.products[0].primaryImageOrMissing).toEqual "/assets/noimage/small.png" + expect(Products.products[0].primaryImageOrMissing).toEqual "/noimage/small.png" it "sets largeImage", -> $httpBackend.expectGET(endpoint).respond([productWithImage]) diff --git a/spec/lib/open_food_network/bulk_coop_report_spec.rb b/spec/lib/open_food_network/bulk_coop_report_spec.rb deleted file mode 100644 index 5b43ba0283..0000000000 --- a/spec/lib/open_food_network/bulk_coop_report_spec.rb +++ /dev/null @@ -1,98 +0,0 @@ -require 'spec_helper' -require 'open_food_network/bulk_coop_report' - -include AuthenticationWorkflow - -module OpenFoodNetwork - describe BulkCoopReport do - describe "fetching orders" do - let(:d1) { create(:distributor_enterprise) } - let(:oc1) { create(:simple_order_cycle) } - let(:o1) { create(:order, completed_at: 1.day.ago, order_cycle: oc1, distributor: d1) } - let(:li1) { build(:line_item_with_shipment) } - - before { o1.line_items << li1 } - - context "as a site admin" do - let(:user) { create(:admin_user) } - subject { BulkCoopReport.new user, {}, true } - - it "fetches completed orders" do - o2 = create(:order) - o2.line_items << build(:line_item) - expect(subject.table_items).to eq([li1]) - end - - it "does not show cancelled orders" do - o2 = create(:order, state: "canceled", completed_at: 1.day.ago) - o2.line_items << build(:line_item_with_shipment) - expect(subject.table_items).to eq([li1]) - end - end - - context "as a manager of a supplier" do - let!(:user) { create(:user) } - subject { BulkCoopReport.new user, {}, true } - - let(:s1) { create(:supplier_enterprise) } - - before do - s1.enterprise_roles.create!(user: user) - end - - context "that has granted P-OC to the distributor" do - let(:o2) { create(:order, distributor: d1, completed_at: 1.day.ago, bill_address: create(:address), ship_address: create(:address)) } - let(:li2) { build(:line_item_with_shipment, product: create(:simple_product, supplier: s1)) } - - before do - o2.line_items << li2 - create(:enterprise_relationship, parent: s1, child: d1, permissions_list: [:add_to_order_cycle]) - end - - it "shows line items supplied by my producers, with names hidden" do - expect(subject.table_items).to eq([li2]) - expect(subject.table_items.first.order.bill_address.firstname).to eq("HIDDEN") - end - end - - context "that has not granted P-OC to the distributor" do - let(:o2) { create(:order, distributor: d1, completed_at: 1.day.ago, bill_address: create(:address), ship_address: create(:address)) } - let(:li2) { build(:line_item_with_shipment, product: create(:simple_product, supplier: s1)) } - - before do - o2.line_items << li2 - end - - it "does not show line items supplied by my producers" do - expect(subject.table_items).to eq([]) - end - end - end - - context "as a manager of a distributor" do - let!(:user) { create(:user) } - subject { PackingReport.new user, {}, true } - - before do - d1.enterprise_roles.create!(user: user) - end - - it "only shows line items distributed by enterprises managed by the current user" do - d2 = create(:distributor_enterprise) - d2.enterprise_roles.create!(user: create(:user)) - o2 = create(:order, distributor: d2, completed_at: 1.day.ago) - o2.line_items << build(:line_item_with_shipment) - expect(subject.table_items).to eq([li1]) - end - - it "only shows the selected order cycle" do - oc2 = create(:simple_order_cycle) - o2 = create(:order, distributor: d1, order_cycle: oc2) - o2.line_items << build(:line_item) - allow(subject).to receive(:params).and_return(order_cycle_id_in: oc1.id) - expect(subject.table_items).to eq([li1]) - end - end - end - end -end diff --git a/spec/lib/open_food_network/enterprise_fee_calculator_spec.rb b/spec/lib/open_food_network/enterprise_fee_calculator_spec.rb index c1e361503e..2f613393f9 100644 --- a/spec/lib/open_food_network/enterprise_fee_calculator_spec.rb +++ b/spec/lib/open_food_network/enterprise_fee_calculator_spec.rb @@ -16,7 +16,7 @@ module OpenFoodNetwork describe "summing all the per-item fees for the variant in the specified hub + order cycle" do let(:enterprise_fee1) { create(:enterprise_fee, amount: 20) } let(:enterprise_fee2) { create(:enterprise_fee, amount: 3) } - let(:enterprise_fee3) { create(:enterprise_fee, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 2)) } + let(:enterprise_fee3) { create(:enterprise_fee, calculator: Calculator::FlatRate.new(preferred_amount: 2)) } describe "supplier fees" do let!(:exchange1) { @@ -131,7 +131,7 @@ module OpenFoodNetwork let(:order) { create(:order, distributor: distributor, order_cycle: order_cycle) } let!(:line_item) { create(:line_item, order: order, variant: product1.master) } let(:enterprise_fee_line_item) { create(:enterprise_fee) } - let(:enterprise_fee_order) { create(:enterprise_fee, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 2)) } + let(:enterprise_fee_order) { create(:enterprise_fee, calculator: Calculator::FlatRate.new(preferred_amount: 2)) } let!(:exchange) { create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor, incoming: false, variants: [product1.master]) } before { order.reload } diff --git a/spec/lib/open_food_network/products_and_inventory_report_spec.rb b/spec/lib/open_food_network/products_and_inventory_report_spec.rb index e4d4d27142..f0844dfffb 100644 --- a/spec/lib/open_food_network/products_and_inventory_report_spec.rb +++ b/spec/lib/open_food_network/products_and_inventory_report_spec.rb @@ -97,18 +97,7 @@ module OpenFoodNetwork describe "Filtering variants" do let(:variants) { Spree::Variant.where(nil).joins(:product).where(is_master: false) } - it "should return unfiltered variants sans-params" do - product1 = create(:simple_product, supplier: supplier) - product2 = create(:simple_product, supplier: supplier) - expect(subject.filter(Spree::Variant.where(nil))).to match_array [product1.master, product1.variants.first, product2.master, product2.variants.first] - end - it "should filter deleted products" do - product1 = create(:simple_product, supplier: supplier) - product2 = create(:simple_product, supplier: supplier) - product2.destroy - expect(subject.filter(Spree::Variant.where(nil))).to match_array [product1.master, product1.variants.first] - end describe "based on report type" do it "returns only variants on hand" do product1 = create(:simple_product, supplier: supplier, on_hand: 99) diff --git a/spec/lib/open_food_network/reports/report_spec.rb b/spec/lib/open_food_network/reports/report_spec.rb index 0bd6bb9c3c..86e76bcec5 100644 --- a/spec/lib/open_food_network/reports/report_spec.rb +++ b/spec/lib/open_food_network/reports/report_spec.rb @@ -1,98 +1,15 @@ require 'open_food_network/reports/report' module OpenFoodNetwork::Reports - P1 = proc { |o| o[:one] } - P2 = proc { |o| o[:two] } - P3 = proc { |o| o[:three] } - P4 = proc { |o| o[:four] } - class TestReport < Report header 'One', 'Two', 'Three', 'Four' - - columns do - column(&P1) - column(&P2) - column(&P3) - column(&P4) - end - - organise do - group(&P1) - sort(&P2) - - organise do - group(&P3) - sort(&P4) - - summary_row do - column(&P1) - column(&P4) - end - end - end - end - - class HelperReport < Report - columns do - column { |obj| my_helper(obj) } - end - - private - - def self.my_helper(obj) - obj[:one] - end end describe Report do let(:report) { TestReport.new } - let(:helper_report) { HelperReport.new } - let(:rules_head) { TestReport._rules_head } - let(:data) { { one: 1, two: 2, three: 3, four: 4 } } it "returns the header" do expect(report.header).to eq(%w(One Two Three Four)) end - - it "returns columns as an array of procs" do - expect(report.columns[0].call(data)).to eq(1) - expect(report.columns[1].call(data)).to eq(2) - expect(report.columns[2].call(data)).to eq(3) - expect(report.columns[3].call(data)).to eq(4) - end - - it "supports helpers when outputting columns" do - expect(helper_report.columns[0].call(data)).to eq(1) - end - - describe "rules" do - let(:group_by) { rules_head.to_h[:group_by] } - let(:sort_by) { rules_head.to_h[:sort_by] } - let(:next_group_by) { rules_head.next.to_h[:group_by] } - let(:next_sort_by) { rules_head.next.to_h[:sort_by] } - let(:next_summary_columns) { rules_head.next.to_h[:summary_columns] } - - it "constructs the head of the rules list" do - expect(group_by.call(data)).to eq(1) - expect(sort_by.call(data)).to eq(2) - end - - it "constructs nested rules" do - expect(next_group_by.call(data)).to eq(3) - expect(next_sort_by.call(data)).to eq(4) - end - - it "constructs summary columns for rules" do - expect(next_summary_columns[0].call(data)).to eq(1) - expect(next_summary_columns[1].call(data)).to eq(4) - end - end - - describe "outputting rules" do - it "outputs the rules" do - expect(report.rules).to eq([{ group_by: P1, sort_by: P2 }, - { group_by: P3, sort_by: P4, summary_columns: [P1, P4] }]) - end - end end end diff --git a/spec/lib/open_food_network/reports/rule_spec.rb b/spec/lib/open_food_network/reports/rule_spec.rb index d800f6e818..eae5b26045 100644 --- a/spec/lib/open_food_network/reports/rule_spec.rb +++ b/spec/lib/open_food_network/reports/rule_spec.rb @@ -17,23 +17,5 @@ module OpenFoodNetwork::Reports rule.sort(&proc) expect(rule.to_h).to eq(group_by: nil, sort_by: proc) end - - it "can define a nested rule" do - rule.organise(&proc) - expect(rule.next).to be_a Rule - end - - it "can define a summary row and return it in a hash" do - rule.summary_row do - column {} - column {} - column {} - end - - expect(rule.to_h[:summary_columns].count).to eq(3) - expect(rule.to_h[:summary_columns][0]).to be_a Proc - expect(rule.to_h[:summary_columns][1]).to be_a Proc - expect(rule.to_h[:summary_columns][2]).to be_a Proc - end end end diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb index 66dd425f95..57c548f73e 100644 --- a/spec/mailers/producer_mailer_spec.rb +++ b/spec/mailers/producer_mailer_spec.rb @@ -7,7 +7,7 @@ describe ProducerMailer, type: :mailer do before { setup_email } let!(:zone) { create(:zone_with_member) } - let!(:tax_rate) { create(:tax_rate, included_in_price: true, calculator: Spree::Calculator::DefaultTax.new, zone: zone, amount: 0.1) } + let!(:tax_rate) { create(:tax_rate, included_in_price: true, calculator: Calculator::DefaultTax.new, zone: zone, amount: 0.1) } let!(:tax_category) { create(:tax_category, tax_rates: [tax_rate]) } let(:s1) { create(:supplier_enterprise) } let(:s2) { create(:supplier_enterprise) } diff --git a/spec/models/spree/calculator/flat_percent_item_total_spec.rb b/spec/models/calculator/flat_percent_item_total_spec.rb similarity index 75% rename from spec/models/spree/calculator/flat_percent_item_total_spec.rb rename to spec/models/calculator/flat_percent_item_total_spec.rb index 529c2b11d9..b0ee8f1aa0 100644 --- a/spec/models/spree/calculator/flat_percent_item_total_spec.rb +++ b/spec/models/calculator/flat_percent_item_total_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' -describe Spree::Calculator::FlatPercentItemTotal do - let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new } +describe Calculator::FlatPercentItemTotal do + let(:calculator) { Calculator::FlatPercentItemTotal.new } let(:line_item) { build(:line_item, price: 10, quantity: 1) } before { allow(calculator).to receive_messages preferred_flat_percent: 10 } @@ -14,7 +14,7 @@ describe Spree::Calculator::FlatPercentItemTotal do it_behaves_like "a model using the LocalizedNumber module", [:preferred_flat_percent] end - it "computes amount correctly for a given Stock::Package" do + it "computes amount correctly for a given OrderManagement::Stock::Package" do order = double(:order, line_items: [line_item] ) package = double(:package, order: order) diff --git a/spec/models/spree/calculator/flat_rate_spec.rb b/spec/models/calculator/flat_rate_spec.rb similarity index 70% rename from spec/models/spree/calculator/flat_rate_spec.rb rename to spec/models/calculator/flat_rate_spec.rb index 3e3bba064b..5036d6b180 100644 --- a/spec/models/spree/calculator/flat_rate_spec.rb +++ b/spec/models/calculator/flat_rate_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' -describe Spree::Calculator::FlatRate do - let(:calculator) { Spree::Calculator::FlatRate.new } +describe Calculator::FlatRate do + let(:calculator) { Calculator::FlatRate.new } before { allow(calculator).to receive_messages preferred_amount: 10 } diff --git a/spec/models/spree/calculator/flexi_rate_spec.rb b/spec/models/calculator/flexi_rate_spec.rb similarity index 81% rename from spec/models/spree/calculator/flexi_rate_spec.rb rename to spec/models/calculator/flexi_rate_spec.rb index 0440e87ca8..9192aecdb5 100644 --- a/spec/models/spree/calculator/flexi_rate_spec.rb +++ b/spec/models/calculator/flexi_rate_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' -describe Spree::Calculator::FlexiRate do +describe Calculator::FlexiRate do let(:line_item) { build(:line_item, quantity: quantity) } let(:calculator) do - Spree::Calculator::FlexiRate.new( + Calculator::FlexiRate.new( preferred_first_item: 2, preferred_additional_item: 1, preferred_max_items: 3 @@ -27,7 +27,7 @@ describe Spree::Calculator::FlexiRate do end it "allows creation of new object with all the attributes" do - Spree::Calculator::FlexiRate.new(preferred_first_item: 1, preferred_additional_item: 1, preferred_max_items: 1) + Calculator::FlexiRate.new(preferred_first_item: 1, preferred_additional_item: 1, preferred_max_items: 1) end context "extends LocalizedNumber" do diff --git a/spec/models/spree/calculator/per_item_spec.rb b/spec/models/calculator/per_item_spec.rb similarity index 80% rename from spec/models/spree/calculator/per_item_spec.rb rename to spec/models/calculator/per_item_spec.rb index 640c4b32f6..7ce41251f4 100644 --- a/spec/models/spree/calculator/per_item_spec.rb +++ b/spec/models/calculator/per_item_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' -describe Spree::Calculator::PerItem do - let(:calculator) { Spree::Calculator::PerItem.new(preferred_amount: 10) } +describe Calculator::PerItem do + let(:calculator) { Calculator::PerItem.new(preferred_amount: 10) } let(:shipping_calculable) { double(:calculable) } let(:line_item) { build(:line_item, quantity: 5) } diff --git a/spec/models/spree/calculator/price_sack_spec.rb b/spec/models/calculator/price_sack_spec.rb similarity index 95% rename from spec/models/spree/calculator/price_sack_spec.rb rename to spec/models/calculator/price_sack_spec.rb index 703bea1531..83a6ae84fb 100644 --- a/spec/models/spree/calculator/price_sack_spec.rb +++ b/spec/models/calculator/price_sack_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' -describe Spree::Calculator::PriceSack do +describe Calculator::PriceSack do let(:calculator) do - calculator = Spree::Calculator::PriceSack.new + calculator = Calculator::PriceSack.new calculator.preferred_minimal_amount = 5 calculator.preferred_normal_amount = 10 calculator.preferred_discount_amount = 1 diff --git a/spec/models/content_configuration_spec.rb b/spec/models/content_configuration_spec.rb index 7ac8877b83..d9eaf960d4 100644 --- a/spec/models/content_configuration_spec.rb +++ b/spec/models/content_configuration_spec.rb @@ -10,8 +10,7 @@ describe ContentConfiguration do end def image_exist?(default_url) - image_path = default_url.gsub(%r{/assets/}, '/assets/images/') - File.exist?(File.join(Rails.root, 'app', image_path)) + File.exist?(File.join(Rails.root, 'public', default_url)) end end end diff --git a/spec/models/enterprise_fee_spec.rb b/spec/models/enterprise_fee_spec.rb index a422358bb8..903d22f757 100644 --- a/spec/models/enterprise_fee_spec.rb +++ b/spec/models/enterprise_fee_spec.rb @@ -60,17 +60,17 @@ describe EnterpriseFee do describe "scopes" do describe "finding per-item enterprise fees" do it "does not return fees with FlatRate, FlexiRate and PriceSack calculators" do - create(:enterprise_fee, calculator: Spree::Calculator::FlatRate.new) - create(:enterprise_fee, calculator: Spree::Calculator::FlexiRate.new) - create(:enterprise_fee, calculator: Spree::Calculator::PriceSack.new) + create(:enterprise_fee, calculator: Calculator::FlatRate.new) + create(:enterprise_fee, calculator: Calculator::FlexiRate.new) + create(:enterprise_fee, calculator: Calculator::PriceSack.new) expect(EnterpriseFee.per_item).to be_empty end it "returns fees with any other calculator" do - ef1 = create(:enterprise_fee, calculator: Spree::Calculator::DefaultTax.new) + ef1 = create(:enterprise_fee, calculator: Calculator::DefaultTax.new) ef2 = create(:enterprise_fee, calculator: Calculator::FlatPercentPerItem.new) - ef3 = create(:enterprise_fee, calculator: Spree::Calculator::PerItem.new) + ef3 = create(:enterprise_fee, calculator: Calculator::PerItem.new) expect(EnterpriseFee.per_item).to match_array [ef1, ef2, ef3] end @@ -78,17 +78,17 @@ describe EnterpriseFee do describe "finding per-order enterprise fees" do it "returns fees with FlatRate, FlexiRate and PriceSack calculators" do - ef1 = create(:enterprise_fee, calculator: Spree::Calculator::FlatRate.new) - ef2 = create(:enterprise_fee, calculator: Spree::Calculator::FlexiRate.new) - ef3 = create(:enterprise_fee, calculator: Spree::Calculator::PriceSack.new) + ef1 = create(:enterprise_fee, calculator: Calculator::FlatRate.new) + ef2 = create(:enterprise_fee, calculator: Calculator::FlexiRate.new) + ef3 = create(:enterprise_fee, calculator: Calculator::PriceSack.new) expect(EnterpriseFee.per_order).to match_array [ef1, ef2, ef3] end it "does not return fees with any other calculator" do - ef1 = create(:enterprise_fee, calculator: Spree::Calculator::DefaultTax.new) + ef1 = create(:enterprise_fee, calculator: Calculator::DefaultTax.new) ef2 = create(:enterprise_fee, calculator: Calculator::FlatPercentPerItem.new) - ef3 = create(:enterprise_fee, calculator: Spree::Calculator::PerItem.new) + ef3 = create(:enterprise_fee, calculator: Calculator::PerItem.new) expect(EnterpriseFee.per_order).to be_empty end diff --git a/spec/models/spree/adjustment_spec.rb b/spec/models/spree/adjustment_spec.rb index 57047457ac..0ea3eea273 100644 --- a/spec/models/spree/adjustment_spec.rb +++ b/spec/models/spree/adjustment_spec.rb @@ -127,7 +127,7 @@ module Spree describe "EnterpriseFee adjustments" do let(:zone) { create(:zone_with_member) } - let(:fee_tax_rate) { create(:tax_rate, included_in_price: true, calculator: Calculator::DefaultTax.new, zone: zone, amount: 0.1) } + let(:fee_tax_rate) { create(:tax_rate, included_in_price: true, calculator: ::Calculator::DefaultTax.new, zone: zone, amount: 0.1) } let(:fee_tax_category) { create(:tax_category, tax_rates: [fee_tax_rate]) } let(:coordinator) { create(:distributor_enterprise, charges_sales_tax: true) } @@ -143,7 +143,7 @@ module Spree end context "when enterprise fees are taxed per-order" do - let(:enterprise_fee) { create(:enterprise_fee, enterprise: coordinator, tax_category: fee_tax_category, calculator: Calculator::FlatRate.new(preferred_amount: 50.0)) } + let(:enterprise_fee) { create(:enterprise_fee, enterprise: coordinator, tax_category: fee_tax_category, calculator: ::Calculator::FlatRate.new(preferred_amount: 50.0)) } describe "when the tax rate includes the tax in the price" do it "records the tax on the enterprise fee adjustments" do @@ -181,7 +181,7 @@ module Spree end context "when enterprise fees are taxed per-item" do - let(:enterprise_fee) { create(:enterprise_fee, enterprise: coordinator, tax_category: fee_tax_category, calculator: Calculator::PerItem.new(preferred_amount: 50.0)) } + let(:enterprise_fee) { create(:enterprise_fee, enterprise: coordinator, tax_category: fee_tax_category, calculator: ::Calculator::PerItem.new(preferred_amount: 50.0)) } describe "when the tax rate includes the tax in the price" do it "records the tax on the enterprise fee adjustments" do @@ -205,7 +205,7 @@ module Spree end context "when enterprise fees inherit their tax_category from the product they are applied to" do - let(:product_tax_rate) { create(:tax_rate, included_in_price: true, calculator: Calculator::DefaultTax.new, zone: zone, amount: 0.2) } + let(:product_tax_rate) { create(:tax_rate, included_in_price: true, calculator: ::Calculator::DefaultTax.new, zone: zone, amount: 0.2) } let(:product_tax_category) { create(:tax_category, tax_rates: [product_tax_rate]) } before do @@ -216,7 +216,7 @@ module Spree end context "when enterprise fees are taxed per-order" do - let(:enterprise_fee) { create(:enterprise_fee, enterprise: coordinator, inherits_tax_category: true, calculator: Calculator::FlatRate.new(preferred_amount: 50.0)) } + let(:enterprise_fee) { create(:enterprise_fee, enterprise: coordinator, inherits_tax_category: true, calculator: ::Calculator::FlatRate.new(preferred_amount: 50.0)) } describe "when the tax rate includes the tax in the price" do it "records no tax on the enterprise fee adjustments" do @@ -246,7 +246,7 @@ module Spree end context "when enterprise fees are taxed per-item" do - let(:enterprise_fee) { create(:enterprise_fee, enterprise: coordinator, inherits_tax_category: true, calculator: Calculator::PerItem.new(preferred_amount: 50.0)) } + let(:enterprise_fee) { create(:enterprise_fee, enterprise: coordinator, inherits_tax_category: true, calculator: ::Calculator::PerItem.new(preferred_amount: 50.0)) } describe "when the tax rate includes the tax in the price" do it "records the tax on the enterprise fee adjustments" do diff --git a/spec/models/spree/line_item_spec.rb b/spec/models/spree/line_item_spec.rb index ca4a5353d9..2836c2c51c 100644 --- a/spec/models/spree/line_item_spec.rb +++ b/spec/models/spree/line_item_spec.rb @@ -35,7 +35,7 @@ module Spree end describe "finding line items with and without tax" do - let(:tax_rate) { create(:tax_rate, calculator: Spree::Calculator::DefaultTax.new) } + let(:tax_rate) { create(:tax_rate, calculator: Calculator::DefaultTax.new) } let!(:adjustment1) { create(:adjustment, originator: tax_rate, label: "TR", amount: 123, included_tax: 10.00) } before do @@ -97,7 +97,7 @@ module Spree end it "caps at zero when stock is negative" do - v.update! on_hand: -2 + v.__send__(:stock_item).update_column(:count_on_hand, -2) li.cap_quantity_at_stock! expect(li.reload.quantity).to eq 0 end @@ -123,7 +123,7 @@ module Spree before { vo.update(count_on_hand: -3) } it "caps at zero" do - v.update(on_hand: -2) + v.__send__(:stock_item).update_column(:count_on_hand, -2) li.cap_quantity_at_stock! expect(li.reload.quantity).to eq 0 end @@ -311,7 +311,7 @@ module Spree describe "tax" do let(:li_no_tax) { create(:line_item) } let(:li_tax) { create(:line_item) } - let(:tax_rate) { create(:tax_rate, calculator: Spree::Calculator::DefaultTax.new) } + let(:tax_rate) { create(:tax_rate, calculator: Calculator::DefaultTax.new) } let!(:adjustment) { create(:adjustment, adjustable: li_tax, originator: tax_rate, label: "TR", amount: 123, included_tax: 10.00) } context "checking if a line item has tax included" do diff --git a/spec/models/spree/order/checkout_spec.rb b/spec/models/spree/order/checkout_spec.rb index e2a133ebbc..8eed383c6a 100644 --- a/spec/models/spree/order/checkout_spec.rb +++ b/spec/models/spree/order/checkout_spec.rb @@ -42,9 +42,6 @@ describe Spree::Order do it "can progress to delivery" do shipping_method.shipping_categories << other_shipping_category - # If the shipping category package splitter is enabled, - # an order with products with two shipping categories will be split into two shipments - # and the spec will fail with a unique constraint error on index_spree_shipments_on_order_id order.next order.next expect(order.state).to eq "delivery" diff --git a/spec/models/spree/order_spec.rb b/spec/models/spree/order_spec.rb index 11ac15fa27..0c2c3d33e5 100644 --- a/spec/models/spree/order_spec.rb +++ b/spec/models/spree/order_spec.rb @@ -137,7 +137,7 @@ describe Spree::Order do let(:li) { create(:line_item, order: o) } it "returns the sum of eligible enterprise fee adjustments" do - ef = create(:enterprise_fee, calculator: Spree::Calculator::FlatRate.new ) + ef = create(:enterprise_fee, calculator: Calculator::FlatRate.new ) ef.calculator.set_preference :amount, 123.45 a = ef.create_adjustment("adjustment", o, o, true) @@ -145,7 +145,7 @@ describe Spree::Order do end it "does not include ineligible adjustments" do - ef = create(:enterprise_fee, calculator: Spree::Calculator::FlatRate.new ) + ef = create(:enterprise_fee, calculator: Calculator::FlatRate.new ) ef.calculator.set_preference :amount, 123.45 a = ef.create_adjustment("adjustment", o, o, true) @@ -155,7 +155,7 @@ describe Spree::Order do end it "does not include adjustments that do not originate from enterprise fees" do - sm = create(:shipping_method, calculator: Spree::Calculator::FlatRate.new ) + sm = create(:shipping_method, calculator: Calculator::FlatRate.new ) sm.calculator.set_preference :amount, 123.45 sm.create_adjustment("adjustment", o, o, true) @@ -163,7 +163,7 @@ describe Spree::Order do end it "does not include adjustments whose source is a line item" do - ef = create(:enterprise_fee, calculator: Spree::Calculator::PerItem.new ) + ef = create(:enterprise_fee, calculator: Calculator::PerItem.new ) ef.calculator.set_preference :amount, 123.45 ef.create_adjustment("adjustment", li.order, li, true) @@ -669,7 +669,7 @@ describe Spree::Order do end context "changing the shipping method to one without fees" do - let(:shipping_method) { create(:shipping_method, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 0)) } + let(:shipping_method) { create(:shipping_method, calculator: Calculator::FlatRate.new(preferred_amount: 0)) } it "updates shipping fees" do order.shipments = [create(:shipment_with, :shipping_method, shipping_method: shipping_method)] @@ -681,7 +681,7 @@ describe Spree::Order do end context "changing the payment method to one without fees" do - let(:payment_method) { create(:payment_method, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 0)) } + let(:payment_method) { create(:payment_method, calculator: Calculator::FlatRate.new(preferred_amount: 0)) } it "removes transaction fees" do # Change the payment method diff --git a/spec/models/spree/payment_spec.rb b/spec/models/spree/payment_spec.rb index 46a493fa96..1ca723566c 100644 --- a/spec/models/spree/payment_spec.rb +++ b/spec/models/spree/payment_spec.rb @@ -136,12 +136,11 @@ module Spree context "when order-based calculator" do let!(:shop) { create(:enterprise) } let!(:payment_method) { create(:payment_method, calculator: calculator) } - let!(:calculator) do - Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) + ::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) end - context "when order complete and inventory tracking enabled" do + context "when order complete" do let!(:order) { create(:completed_order_with_totals, distributor: shop) } let!(:variant) { order.line_items.first.variant } let!(:inventory_item) { create(:inventory_item, enterprise: shop, variant: variant) } @@ -159,7 +158,7 @@ module Spree let(:shop) { create(:enterprise) } let(:payment_method) { create(:stripe_payment_method, distributor_ids: [create(:distributor_enterprise).id], preferred_enterprise_id: shop.id) } let(:payment) { create(:payment, order: order, payment_method: payment_method, amount: order.total) } - let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) } + let(:calculator) { ::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) } before do payment_method.calculator = calculator diff --git a/spec/models/spree/shipment_spec.rb b/spec/models/spree/shipment_spec.rb index b37dd498ff..e24cebb801 100644 --- a/spec/models/spree/shipment_spec.rb +++ b/spec/models/spree/shipment_spec.rb @@ -1,33 +1,485 @@ -require "spec_helper" +# frozen_string_literal: true + +require 'spec_helper' +require 'benchmark' describe Spree::Shipment do - describe "manifest" do - let!(:product) { create(:product) } - let!(:order) { create(:order, distributor: product.supplier) } - let!(:deleted_variant) { create(:variant, product: product) } - let!(:other_variant) { create(:variant, product: product) } - let!(:line_item_for_deleted) { create(:line_item, order: order, variant: deleted_variant) } - let!(:line_item_for_other) { create(:line_item, order: order, variant: other_variant) } - let!(:shipment) { create(:shipment_with, :shipping_method, order: order) } + let(:order) { build(:order) } + let(:shipping_method) { build(:shipping_method, name: "UPS") } + let(:shipment) do + shipment = Spree::Shipment.new order: order + allow(shipment).to receive_messages(shipping_method: shipping_method) + shipment.state = 'pending' + shipment + end - context "when the variant is soft-deleted" do - before { deleted_variant.delete } + let(:charge) { build(:adjustment) } + let(:variant) { build(:variant) } - it "can still access the variant" do - shipment.reload - variants = shipment.manifest.map(&:variant).uniq - expect(variants.sort_by(&:id)).to eq([deleted_variant, other_variant].sort_by(&:id)) + it 'is backordered if one of its inventory_units is backordered' do + unit1 = create(:inventory_unit) + unit2 = create(:inventory_unit) + allow(unit1).to receive(:backordered?) { false } + allow(unit2).to receive(:backordered?) { true } + allow(shipment).to receive_messages(inventory_units: [unit1, unit2]) + expect(shipment).to be_backordered + end + + context "#cost" do + it "should return the amount of any shipping charges that it originated" do + allow(shipment).to receive_message_chain :adjustment, amount: 10 + expect(shipment.cost).to eq 10 + end + + it "should return 0 if there are no relevant shipping adjustments" do + expect(shipment.cost).to eq 0 + end + end + + context "display_cost" do + it "retuns a Spree::Money" do + allow(shipment).to receive(:cost) { 21.22 } + expect(shipment.display_cost).to eq Spree::Money.new(21.22) + end + end + + context "display_item_cost" do + it "retuns a Spree::Money" do + allow(shipment).to receive(:item_cost) { 21.22 } + expect(shipment.display_item_cost).to eq Spree::Money.new(21.22) + end + end + + context "display_total_cost" do + it "retuns a Spree::Money" do + allow(shipment).to receive(:total_cost) { 21.22 } + expect(shipment.display_total_cost).to eq Spree::Money.new(21.22) + end + end + + it "#item_cost" do + shipment = create(:shipment, order: create(:order_with_totals)) + expect(shipment.item_cost).to eql(10.0) + end + + context "manifest" do + let(:order) { Spree::Order.create } + let(:variant) { create(:variant) } + let!(:line_item) { order.contents.add variant } + let!(:shipment) { order.create_proposed_shipments.first } + + it "returns variant expected" do + expect(shipment.manifest.first.variant).to eq variant + end + + context "variant was removed" do + before { variant.product.destroy } + + it "still returns variant expected" do + expect(shipment.manifest.first.variant).to eq variant end end - context "when the product is soft-deleted" do - before { deleted_variant.product.delete } + describe "with soft-deleted products or variants" do + let!(:product) { create(:product) } + let!(:order) { create(:order, distributor: product.supplier) } - it "can still access the variant" do - shipment.reload - variants = shipment.manifest.map(&:variant) - expect(variants.sort_by(&:id)).to eq([deleted_variant, other_variant].sort_by(&:id)) + context "when the variant is soft-deleted" do + it "can still access the variant" do + order.line_items.first.variant.delete + + variants = shipment.reload.manifest.map(&:variant).uniq + expect(variants).to eq [order.line_items.first.variant] + end + end + + context "when the product is soft-deleted" do + it "can still access the variant" do + order.line_items.first.variant.delete + + variants = shipment.reload.manifest.map(&:variant) + expect(variants).to eq [order.line_items.first.variant] + end end end end + + context 'shipping_rates' do + let(:shipment) { create(:shipment) } + let(:shipping_method1) { create(:shipping_method) } + let(:shipping_method2) { create(:shipping_method) } + let(:shipping_rates) { + [ + Spree::ShippingRate.new(shipping_method: shipping_method1, cost: 10.00, selected: true), + Spree::ShippingRate.new(shipping_method: shipping_method2, cost: 20.00) + ] + } + + it 'returns shipping_method from selected shipping_rate' do + shipment.shipping_rates.delete_all + shipment.shipping_rates.create shipping_method: shipping_method1, cost: 10.00, selected: true + expect(shipment.shipping_method).to eq shipping_method1 + end + + context 'refresh_rates' do + let(:mock_estimator) { double('estimator', shipping_rates: shipping_rates) } + + it 'should request new rates, and maintain shipping_method selection' do + expect(OrderManagement::Stock::Estimator). + to receive(:new).with(shipment.order).and_return(mock_estimator) + # The first call is for the original shippping method, + # the second call is for the shippping method after the Estimator was executed + allow(shipment).to receive(:shipping_method).and_return(shipping_method2, shipping_method1) + + expect(shipment.refresh_rates).to eq shipping_rates + expect(shipment.reload.selected_shipping_rate.shipping_method_id).to eq shipping_method2.id + end + + it 'should handle no shipping_method selection' do + expect(OrderManagement::Stock::Estimator). + to receive(:new).with(shipment.order).and_return(mock_estimator) + allow(shipment).to receive_messages(shipping_method: nil) + expect(shipment.refresh_rates).to eq shipping_rates + expect(shipment.reload.selected_shipping_rate).to_not be_nil + end + + it 'should not refresh if shipment is shipped' do + expect(OrderManagement::Stock::Estimator).not_to receive(:new) + shipment.shipping_rates.delete_all + allow(shipment).to receive_messages(shipped?: true) + expect(shipment.refresh_rates).to eq [] + end + + context 'to_package' do + it 'should use symbols for states when adding contents to package' do + allow(shipment). + to receive_message_chain(:inventory_units, + includes: [build(:inventory_unit, variant: variant, + state: 'on_hand'), + build(:inventory_unit, variant: variant, + state: 'backordered')] ) + package = shipment.to_package + expect(package.on_hand.count).to eq 1 + expect(package.backordered.count).to eq 1 + end + end + end + end + + it '#total_cost' do + allow(shipment).to receive_messages cost: 5.0 + allow(shipment).to receive_messages item_cost: 50.0 + expect(shipment.total_cost).to eql(55.0) + end + + context "#update!" do + shared_examples_for "immutable once shipped" do + it "should remain in shipped state once shipped" do + shipment.state = 'shipped' + expect(shipment).to receive(:update_column).with(:state, 'shipped') + shipment.update!(order) + end + end + + shared_examples_for "pending if backordered" do + it "should have a state of pending if backordered" do + unit = create(:inventory_unit) + allow(unit).to receive(:backordered?) { true } + allow(shipment).to receive_messages(inventory_units: [unit]) + expect(shipment).to receive(:update_column).with(:state, 'pending') + shipment.update!(order) + end + end + + context "when order is canceled" do + it "should result in a 'pending' state" do + allow(order).to receive(:canceled?) { true } + + expect(shipment).to receive(:update_column).with(:state, 'canceled') + shipment.update!(order) + end + end + + context "when order cannot ship" do + it "should result in a 'pending' state" do + allow(order).to receive(:can_ship?) { false } + + expect(shipment).to receive(:update_column).with(:state, 'pending') + shipment.update!(order) + end + end + + context "when order can ship" do + before { allow(order).to receive(:can_ship?) { true } } + + context "when order is paid" do + before { allow(order).to receive(:paid?) { true } } + + it "should result in a 'ready' state" do + expect(shipment).to receive(:update_column).with(:state, 'ready') + shipment.update!(order) + end + + it_should_behave_like 'immutable once shipped' + + it_should_behave_like 'pending if backordered' + + context "when order has a credit owed" do + before { allow(order).to receive(:payment_state) { 'credit_owed' } } + + it "should result in a 'ready' state" do + shipment.state = 'pending' + expect(shipment).to receive(:update_column).with(:state, 'ready') + shipment.update!(order) + end + + it_should_behave_like 'immutable once shipped' + + it_should_behave_like 'pending if backordered' + end + end + + context "when order has balance due" do + before { allow(order).to receive(:paid?) { false } } + + it "should result in a 'pending' state" do + shipment.state = 'ready' + expect(shipment).to receive(:update_column).with(:state, 'pending') + shipment.update!(order) + end + + it_should_behave_like 'immutable once shipped' + + it_should_behave_like 'pending if backordered' + end + end + + context "when shipment state changes to shipped" do + it "should call after_ship" do + shipment.state = 'pending' + expect(shipment).to receive :after_ship + allow(shipment).to receive_messages determine_state: 'shipped' + expect(shipment).to receive(:update_column).with(:state, 'shipped') + shipment.update!(order) + end + end + end + + context "when order is completed" do + before do + allow(order).to receive_messages completed?: true + allow(order).to receive_messages canceled?: false + end + + it "should validate with inventory" do + shipment.inventory_units = [create(:inventory_unit)] + expect(shipment.valid?).to be_truthy + end + end + + context "#cancel" do + it 'cancels the shipment' do + allow(shipment).to receive(:ensure_correct_adjustment) + allow(shipment.order).to receive(:update!) + + shipment.state = 'pending' + expect(shipment).to receive(:after_cancel) + shipment.cancel! + expect(shipment.state).to eq 'canceled' + end + + it 'restocks the items' do + unit = double(:inventory_unit, variant: variant) + allow(unit).to receive(:quantity) { 1 } + allow(shipment).to receive_message_chain(:inventory_units, + :group_by, + map: [unit]) + shipment.stock_location = build(:stock_location) + expect(shipment.stock_location).to receive(:restock).with(variant, 1, shipment) + shipment.after_cancel + end + end + + context "#resume" do + it 'will determine new state based on order' do + allow(shipment).to receive(:ensure_correct_adjustment) + allow(shipment.order).to receive(:update!) + + shipment.state = 'canceled' + expect(shipment).to receive(:determine_state).and_return(:ready) + expect(shipment).to receive(:after_resume) + shipment.resume! + expect(shipment.state).to eq 'ready' + end + + it 'unstocks the items' do + unit = create(:inventory_unit, variant: variant) + allow(unit).to receive(:quantity) { 1 } + allow(shipment).to receive_message_chain(:inventory_units, + :group_by, + map: [unit]) + shipment.stock_location = create(:stock_location) + expect(shipment.stock_location).to receive(:unstock).with(variant, 1, shipment) + shipment.after_resume + end + + it 'will determine new state based on order' do + allow(shipment).to receive(:ensure_correct_adjustment) + allow(shipment.order).to receive(:update!) + + shipment.state = 'canceled' + expect(shipment).to receive(:determine_state).twice.and_return('ready') + expect(shipment).to receive(:after_resume) + shipment.resume! + # Shipment is pending because order is already paid + expect(shipment.state).to eq 'pending' + end + end + + context "#ship" do + before do + allow(order).to receive(:update!) + allow(shipment).to receive_messages(update_order: true, state: 'ready') + allow(shipment).to receive_messages(adjustment: charge) + allow(shipping_method).to receive(:create_adjustment) + allow(shipment).to receive(:ensure_correct_adjustment) + end + + it "should update shipped_at timestamp" do + allow(shipment).to receive(:send_shipped_email) + shipment.ship! + expect(shipment.shipped_at).to_not be_nil + # Ensure value is persisted + shipment.reload + expect(shipment.shipped_at).to_not be_nil + end + + it "should send a shipment email" do + mail_message = double 'Mail::Message' + shipment_id = nil + expect(Spree::ShipmentMailer).to receive(:shipped_email) { |*args| + shipment_id = args[0] + mail_message + } + expect(mail_message).to receive :deliver + shipment.ship! + expect(shipment_id).to eq shipment.id + end + + it "should finalize the shipment's adjustment" do + allow(shipment).to receive(:send_shipped_email) + shipment.ship! + expect(shipment.adjustment.state).to eq 'finalized' + expect(shipment.adjustment).to be_immutable + end + end + + context "#ready" do + # Regression test for #2040 + it "cannot ready a shipment for an order if the order is unpaid" do + allow(order).to receive_messages(paid?: false) + assert !shipment.can_ready? + end + end + + context "ensure_correct_adjustment" do + before { allow(shipment).to receive(:reload) } + + it "should create adjustment when not present" do + allow(shipment).to receive_messages(selected_shipping_rate_id: 1) + expect(shipping_method).to receive(:create_adjustment).with(shipping_method.adjustment_label, + order, shipment, true, "open") + shipment.__send__(:ensure_correct_adjustment) + end + + # Regression test for #3138 + it "should use the shipping method's adjustment label" do + allow(shipment).to receive_messages(selected_shipping_rate_id: 1) + allow(shipping_method).to receive_messages(adjustment_label: "Foobar") + expect(shipping_method).to receive(:create_adjustment).with("Foobar", order, + shipment, true, "open") + shipment.__send__(:ensure_correct_adjustment) + end + + it "should update originator when adjustment is present" do + allow(shipment). + to receive_messages(selected_shipping_rate: Spree::ShippingRate.new(cost: 10.00)) + adjustment = build(:adjustment) + allow(shipment).to receive_messages(adjustment: adjustment) + allow(adjustment).to receive(:open?) { true } + expect(shipment.adjustment).to receive(:originator=).with(shipping_method) + expect(shipment.adjustment).to receive(:label=).with(shipping_method.adjustment_label) + expect(shipment.adjustment).to receive(:amount=).with(10.00) + allow(shipment.adjustment).to receive(:save!) + expect(shipment.adjustment).to receive(:reload) + shipment.__send__(:ensure_correct_adjustment) + end + + it 'should not update amount if adjustment is not open?' do + allow(shipment). + to receive_messages(selected_shipping_rate: Spree::ShippingRate.new(cost: 10.00)) + adjustment = build(:adjustment) + allow(shipment).to receive_messages(adjustment: adjustment) + allow(adjustment).to receive(:open?) { false } + expect(shipment.adjustment).to receive(:originator=).with(shipping_method) + expect(shipment.adjustment).to receive(:label=).with(shipping_method.adjustment_label) + expect(shipment.adjustment).not_to receive(:amount=).with(10.00) + allow(shipment.adjustment).to receive(:save!) + expect(shipment.adjustment).to receive(:reload) + shipment.__send__(:ensure_correct_adjustment) + end + end + + context "update_order" do + it "should update order" do + expect(order).to receive(:update!) + shipment.__send__(:update_order) + end + end + + context "after_save" do + it "should run correct callbacks" do + expect(shipment).to receive(:ensure_correct_adjustment) + expect(shipment).to receive(:update_order) + shipment.run_callbacks(:save) + end + end + + context "currency" do + it "returns the order currency" do + expect(shipment.currency).to eq order.currency + end + end + + context "#tracking_url" do + it "uses shipping method to determine url" do + expect(shipping_method).to receive(:build_tracking_url).with('1Z12345').and_return(:some_url) + shipment.tracking = '1Z12345' + + expect(shipment.tracking_url).to eq :some_url + end + end + + context "set up new inventory units" do + let(:variant) { double("Variant", id: 9) } + let(:inventory_units) { double } + let(:params) do + { variant_id: variant.id, state: 'on_hand', order_id: order.id } + end + + before { allow(shipment).to receive_messages inventory_units: inventory_units } + + it "associates variant and order" do + expect(inventory_units).to receive(:create).with(params) + unit = shipment.set_up_inventory('on_hand', variant, order) + end + end + + # Regression test for #3349 + context "#destroy" do + it "destroys linked shipping_rates" do + reflection = Spree::Shipment.reflect_on_association(:shipping_rates) + reflection.options[:dependent] = :destroy + end + end end diff --git a/spec/models/spree/stock_item_spec.rb b/spec/models/spree/stock_item_spec.rb new file mode 100644 index 0000000000..9c9b233986 --- /dev/null +++ b/spec/models/spree/stock_item_spec.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Spree::StockItem do + let(:stock_location) { create(:stock_location_with_items) } + + subject { stock_location.stock_items.order(:id).first } + + describe "validation" do + let(:stock_item) { stock_location.stock_items.first } + + it "requires count_on_hand to be positive if not backorderable" do + stock_item.backorderable = false + + stock_item.__send__(:count_on_hand=, 1) + expect(stock_item.valid?).to eq(true) + + stock_item.__send__(:count_on_hand=, 0) + expect(stock_item.valid?).to eq(true) + + stock_item.__send__(:count_on_hand=, -1) + expect(stock_item.valid?).to eq(false) + end + + it "allows count_on_hand to be negative if backorderable" do + stock_item.backorderable = true + + stock_item.__send__(:count_on_hand=, 1) + expect(stock_item.valid?).to eq(true) + + stock_item.__send__(:count_on_hand=, -1) + expect(stock_item.valid?).to eq(true) + end + end + + it 'maintains the count on hand for a variant' do + expect(subject.count_on_hand).to eq 15 + end + + it "can return the stock item's variant's name" do + expect(subject.variant_name).to eq(subject.variant.name) + end + + context "available to be included in shipment" do + context "has stock" do + it { expect(subject).to be_available } + end + + context "backorderable" do + before { subject.backorderable = true } + it { expect(subject).to be_available } + end + + context "no stock and not backorderable" do + before do + subject.backorderable = false + allow(subject).to receive_messages(count_on_hand: 0) + end + + it { expect(subject).not_to be_available } + end + end + + context "adjust count_on_hand" do + let!(:current_on_hand) { subject.count_on_hand } + + it 'is updated pessimistically' do + copy = Spree::StockItem.find(subject.id) + + subject.adjust_count_on_hand(5) + expect(subject.count_on_hand).to eq(current_on_hand + 5) + + expect(copy.count_on_hand).to eq(current_on_hand) + copy.adjust_count_on_hand(5) + expect(copy.count_on_hand).to eq(current_on_hand + 10) + end + + context "item out of stock (by two items)" do + let(:inventory_unit) { double('InventoryUnit') } + let(:inventory_unit_2) { double('InventoryUnit2') } + + before do + allow(subject).to receive(:backorderable?).and_return(true) + subject.adjust_count_on_hand(- (current_on_hand + 2)) + end + + it "doesn't process backorders" do + expect(subject).not_to receive(:backordered_inventory_units) + subject.adjust_count_on_hand(1) + end + + context "adds new items" do + before { allow(subject).to receive_messages(backordered_inventory_units: [inventory_unit, inventory_unit_2]) } + + it "fills existing backorders" do + expect(inventory_unit).to receive(:fill_backorder) + expect(inventory_unit_2).to receive(:fill_backorder) + + subject.adjust_count_on_hand(3) + expect(subject.count_on_hand).to eq(1) + end + end + end + end +end diff --git a/spec/models/spree/tax_rate_spec.rb b/spec/models/spree/tax_rate_spec.rb index 888b25c890..61938c5d0f 100644 --- a/spec/models/spree/tax_rate_spec.rb +++ b/spec/models/spree/tax_rate_spec.rb @@ -33,7 +33,7 @@ module Spree end describe "ensuring that tax rate is marked as tax included_in_price" do - let(:tax_rate) { create(:tax_rate, included_in_price: false, calculator: Spree::Calculator::DefaultTax.new) } + let(:tax_rate) { create(:tax_rate, included_in_price: false, calculator: Calculator::DefaultTax.new) } it "sets included_in_price to true" do tax_rate.send(:with_tax_included_in_price) do diff --git a/spec/models/spree/user_spec.rb b/spec/models/spree/user_spec.rb index 0e48621452..ba5b4cd488 100644 --- a/spec/models/spree/user_spec.rb +++ b/spec/models/spree/user_spec.rb @@ -9,23 +9,25 @@ describe Spree.user_class do describe "addresses" do let(:user) { create(:user, bill_address: create(:address)) } - it 'updates billing address with new address' do - old_bill_address = user.bill_address - new_bill_address = create(:address, firstname: 'abc') + context "updating addresses via nested attributes" do + it 'updates billing address with new address' do + old_bill_address = user.bill_address + new_bill_address = create(:address, firstname: 'abc') - user.update(bill_address_attributes: new_bill_address.clone.attributes.merge('id' => old_bill_address.id)) + user.update(bill_address_attributes: new_bill_address.dup.attributes.merge('id' => old_bill_address.id).except!('created_at', 'updated_at')) - expect(user.bill_address.id).to eq old_bill_address.id - expect(user.bill_address.firstname).to eq new_bill_address.firstname - end + expect(user.bill_address.id).to eq old_bill_address.id + expect(user.bill_address.firstname).to eq new_bill_address.firstname + end - it 'creates new shipping address' do - new_ship_address = create(:address, firstname: 'abc') + it 'creates new shipping address' do + new_ship_address = create(:address, firstname: 'abc') - user.update(ship_address_attributes: new_ship_address.clone.attributes) + user.update(ship_address_attributes: new_ship_address.dup.attributes.except!('created_at', 'updated_at')) - expect(user.ship_address.id).not_to eq new_ship_address.id - expect(user.ship_address.firstname).to eq new_ship_address.firstname + expect(user.ship_address.id).not_to eq new_ship_address.id + expect(user.ship_address.firstname).to eq new_ship_address.firstname + end end end diff --git a/spec/models/stock/package_spec.rb b/spec/models/stock/package_spec.rb deleted file mode 100644 index 6a3ac355cc..0000000000 --- a/spec/models/stock/package_spec.rb +++ /dev/null @@ -1,58 +0,0 @@ -require 'spec_helper' - -module Stock - describe Package do - let(:stock_location) { double(:stock_location) } - - subject(:package) { Package.new(stock_location, order, contents) } - - let(:enterprise) { create(:enterprise) } - let(:other_enterprise) { create(:enterprise) } - - let(:order) { build(:order, distributor: enterprise) } - - let(:variant1) do - instance_double( - Spree::Variant, - shipping_category: shipping_method1.shipping_categories.first - ) - end - let(:variant2) do - instance_double( - Spree::Variant, - shipping_category: shipping_method2.shipping_categories.first - ) - end - let(:variant3) do - instance_double(Spree::Variant, shipping_category: nil) - end - - let(:contents) do - [ - Package::ContentItem.new(variant1, 1), - Package::ContentItem.new(variant1, 1), - Package::ContentItem.new(variant2, 1), - Package::ContentItem.new(variant3, 1) - ] - end - - let(:shipping_method1) { create(:shipping_method, distributors: [enterprise]) } - let(:shipping_method2) { create(:shipping_method, distributors: [other_enterprise]) } - - describe '#shipping_methods' do - it 'does not return shipping methods not used by the package\'s order distributor' do - expect(package.shipping_methods).to eq [shipping_method1] - end - end - - describe '#shipping_categories' do - it "returns shipping categories that are not shipping categories of the order's products" do - package - other_shipping_category = Spree::ShippingCategory.create(name: "Custom") - - expect(package.shipping_categories).to eq [shipping_method1.shipping_categories.first, - other_shipping_category] - end - end - end -end diff --git a/spec/models/tag_rule/discount_order_spec.rb b/spec/models/tag_rule/discount_order_spec.rb index 2a1260f7e3..831f2faf01 100644 --- a/spec/models/tag_rule/discount_order_spec.rb +++ b/spec/models/tag_rule/discount_order_spec.rb @@ -73,7 +73,7 @@ describe TagRule::DiscountOrder, type: :model do end context "when shipping charges apply" do - let!(:shipping_method) { create(:shipping_method, calculator: Spree::Calculator::FlatRate.new( preferred_amount: 25.00 ) ) } + let!(:shipping_method) { create(:shipping_method, calculator: Calculator::FlatRate.new( preferred_amount: 25.00 ) ) } before do shipping_method.create_adjustment("Shipping", order, order, true) end diff --git a/spec/models/variant_override_spec.rb b/spec/models/variant_override_spec.rb index 22b93a06d4..c6620351a2 100644 --- a/spec/models/variant_override_spec.rb +++ b/spec/models/variant_override_spec.rb @@ -33,7 +33,7 @@ describe VariantOverride do expect(VariantOverride.indexed(hub2)).to eq( variant => vo2 ) end - it "does not include overrides for soft-deleted variants" do + xit "does not include overrides for soft-deleted variants" do variant.delete expect(VariantOverride.indexed(hub1)).to eq( nil => vo1 ) end diff --git a/spec/requests/api/orders_spec.rb b/spec/requests/api/orders_spec.rb new file mode 100644 index 0000000000..b282b4045a --- /dev/null +++ b/spec/requests/api/orders_spec.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +require 'swagger_helper' + +describe 'api/orders', type: :request do + path '/api/orders' do + get('list orders') do + tags 'Orders' + # type should be replaced with swagger 3.01 valid schema: {type: string} when rswag #317 is resolved: + # https://github.com/rswag/rswag/pull/319 + parameter name: 'X-Spree-Token', in: :header, type: :string + parameter name: 'q[distributor_id_eq]', in: :query, type: :string, required: false, description: "Query orders for a specific distributor id." + parameter name: 'q[completed_at_gt]', in: :query, type: :string, required: false, description: "Query orders completed after a date." + parameter name: 'q[completed_at_lt]', in: :query, type: :string, required: false, description: "Query orders completed before a date." + parameter name: 'q[state_eq]', in: :query, type: :string, required: false, description: "Query orders by order state, eg 'cart', 'complete'." + parameter name: 'q[payment_state_eq]', in: :query, type: :string, required: false, description: "Query orders by order payment_state, eg 'balance_due', 'paid', 'failed'." + parameter name: 'q[email_cont]', in: :query, type: :string, required: false, description: "Query orders where the order email contains a string." + parameter name: 'q[order_cycle_id_eq]', in: :query, type: :string, required: false, description: "Query orders for a specific order_cycle id." + + response(200, 'get orders') do + # Adds model metadata for Swagger UI. Ideally we'd be able to just add: + # schema '$ref' => '#/components/schemas/Order_Concise' + # Which would also validate the response in the test, this is an open + # issue with rswag: https://github.com/rswag/rswag/issues/268 + metadata[:response][:content] = { + "application/json": { + schema: {'$ref' => '#/components/schemas/Order_Concise'} + } + } + context "when there are four orders with different properties set" do + let!(:order_dist_1) { create(:order_with_distributor, email: "specific_name@example.com") } + let!(:order_dist_2) { create(:order_with_totals_and_distribution) } + let!(:order_dist_1_complete) { create(:order, distributor: order_dist_1.distributor, state: 'complete', completed_at: Time.zone.today - 7.days) } + let!(:order_dist_1_credit_owed) { create(:order, distributor: order_dist_1.distributor, payment_state: 'credit_owed', completed_at: Time.zone.today) } + + let(:user) { order_dist_1.distributor.owner } + let(:'X-Spree-Token') do + user.generate_spree_api_key! + user.spree_api_key + end + + context "and there are no query parameters" do + run_test! do |response| + expect(response).to have_http_status(200) + + data = JSON.parse(response.body) + orders = data["orders"] + expect(orders.size).to eq 4 + end + end + + context "and queried by distributor id" do + let(:'q[distributor_id_eq]') { order_dist_2.distributor.id } + + before { order_dist_2.distributor.update_attributes owner: user } + + run_test! do |response| + expect(response).to have_http_status(200) + + data = JSON.parse(response.body) + orders = data["orders"] + expect(orders.size).to eq 1 + expect(orders.first["id"]).to eq order_dist_2.id + end + end + + context "and queried within a date range" do + let(:'q[completed_at_gt]') { Time.zone.today - 7.days - 1.second } + let(:'q[completed_at_lt]') { Time.zone.today - 6.days } + + run_test! do |response| + expect(response).to have_http_status(200) + + data = JSON.parse(response.body) + orders = data["orders"] + expect(orders.size).to eq 1 + expect(orders.first["id"]).to eq order_dist_1_complete.id + end + end + + context "and queried by complete state" do + let(:'q[state_eq]') { "complete" } + run_test! do |response| + expect(response).to have_http_status(200) + + data = JSON.parse(response.body) + orders = data["orders"] + expect(orders.size).to eq 1 + expect(orders.first["id"]).to eq order_dist_1_complete.id + end + end + + context "and queried by credit_owed payment_state" do + let(:'q[payment_state_eq]') { "credit_owed" } + run_test! do |response| + expect(response).to have_http_status(200) + + data = JSON.parse(response.body) + orders = data["orders"] + expect(orders.size).to eq 1 + expect(orders.first["id"]).to eq order_dist_1_credit_owed.id + end + end + + context "and queried by buyer email contains a specific string" do + let(:'q[email_cont]') { order_dist_1.email.split("@").first } + run_test! do |response| + expect(response).to have_http_status(200) + + data = JSON.parse(response.body) + orders = data["orders"] + expect(orders.size).to eq 1 + expect(orders.first["id"]).to eq order_dist_1.id + end + end + + context "and queried by a specific order_cycle" do + let(:'q[order_cycle_id_eq]') { + order_dist_2.order_cycle.id + } + + before { order_dist_2.distributor.update_attributes owner: user } + + run_test! do |response| + expect(response).to have_http_status(200) + + data = JSON.parse(response.body) + orders = data["orders"] + expect(orders.size).to eq 1 + expect(orders.first["id"]).to eq order_dist_2.id + end + end + end + end + end + end +end diff --git a/spec/requests/checkout/failed_checkout_spec.rb b/spec/requests/checkout/failed_checkout_spec.rb index f45efe3951..41062d97d0 100644 --- a/spec/requests/checkout/failed_checkout_spec.rb +++ b/spec/requests/checkout/failed_checkout_spec.rb @@ -32,7 +32,7 @@ describe "checking out an order that initially fails", type: :request do end context "when shipping and payment fees apply" do - let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) } + let(:calculator) { Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) } before do payment_method.calculator = calculator.dup diff --git a/spec/requests/checkout/paypal_spec.rb b/spec/requests/checkout/paypal_spec.rb index 96c1baf4e9..e309d0ea5d 100644 --- a/spec/requests/checkout/paypal_spec.rb +++ b/spec/requests/checkout/paypal_spec.rb @@ -50,7 +50,7 @@ describe "checking out an order with a paypal express payment method", type: :re end context "with a flat percent calculator" do - let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) } + let(:calculator) { Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) } before do payment_method.calculator = calculator diff --git a/spec/requests/checkout/stripe_connect_spec.rb b/spec/requests/checkout/stripe_connect_spec.rb index 5c1561c13c..000b2250a8 100644 --- a/spec/requests/checkout/stripe_connect_spec.rb +++ b/spec/requests/checkout/stripe_connect_spec.rb @@ -10,7 +10,7 @@ describe "checking out an order with a Stripe Connect payment method", type: :re let!(:shipping_method) do create( :shipping_method, - calculator: Spree::Calculator::FlatRate.new(preferred_amount: 0), + calculator: Calculator::FlatRate.new(preferred_amount: 0), distributors: [enterprise] ) end diff --git a/spec/requests/checkout/stripe_sca_spec.rb b/spec/requests/checkout/stripe_sca_spec.rb index e590b3f601..d2734f66d4 100644 --- a/spec/requests/checkout/stripe_sca_spec.rb +++ b/spec/requests/checkout/stripe_sca_spec.rb @@ -12,7 +12,7 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques let!(:shipping_method) do create( :shipping_method, - calculator: Spree::Calculator::FlatRate.new(preferred_amount: 0), + calculator: Calculator::FlatRate.new(preferred_amount: 0), distributors: [enterprise] ) end @@ -64,12 +64,20 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques } end let(:payment_intent_response_mock) do - { status: 200, body: JSON.generate(object: "payment_intent", amount: 2000, charges: { data: [{ id: "ch_1234", amount: 2000 }] }) } + { + status: 200, body: JSON.generate(object: "payment_intent", + amount: 2000, + charges: { data: [{ id: "ch_1234", amount: 2000 }] }) + } end let(:payment_intent_authorize_response_mock) do - { status: 200, body: JSON.generate(id: payment_intent_id, object: "payment_intent", amount: 2000, + { + status: 200, body: JSON.generate(id: payment_intent_id, + object: "payment_intent", + amount: 2000, status: "requires_capture", last_payment_error: nil, - charges: { data: [{ id: "ch_1234", amount: 2000 }] }) } + charges: { data: [{ id: "ch_1234", amount: 2000 }] }) + } end before do @@ -166,13 +174,15 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques headers: { 'Stripe-Account' => 'abc123' }) .to_return(hubs_payment_method_response_mock) - # Creates a customer (this stubs the customers call to the main stripe account and also the call to the connected account) + # Creates a customer + # This stubs the customers call to both the main stripe account and the connected account stub_request(:post, "https://api.stripe.com/v1/customers") .with(body: { email: order.email }) .to_return(customer_response_mock) # Attaches the payment method to the customer in the hub's stripe account - stub_request(:post, "https://api.stripe.com/v1/payment_methods/#{hubs_stripe_payment_method}/attach") + stub_request(:post, + "https://api.stripe.com/v1/payment_methods/#{hubs_stripe_payment_method}/attach") .with(body: { customer: customer_id }, headers: { 'Stripe-Account' => 'abc123' }) .to_return(hubs_payment_method_response_mock) @@ -191,7 +201,8 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques source_attributes[:save_requested_by_customer] = '1' # Attaches the payment method to the customer - stub_request(:post, "https://api.stripe.com/v1/payment_methods/#{stripe_payment_method}/attach") + stub_request(:post, + "https://api.stripe.com/v1/payment_methods/#{stripe_payment_method}/attach") .with(body: { customer: customer_id }) .to_return(payment_method_attach_response_mock) end @@ -316,9 +327,13 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques context "when the stripe API sends a url for the authorization of the transaction" do let(:payment_intent_authorize_response_mock) do - { status: 200, body: JSON.generate(id: payment_intent_id, object: "payment_intent", - next_source_action: { type: "authorize_with_url", authorize_with_url: { url: stripe_redirect_url } }, - status: "requires_source_action" ) } + { status: 200, body: JSON.generate(id: payment_intent_id, + object: "payment_intent", + next_source_action: { + type: "authorize_with_url", + authorize_with_url: { url: stripe_redirect_url } + }, + status: "requires_source_action") } end it "redirects the user to the authorization stripe url" do diff --git a/spec/requests/home_controller_spec.rb b/spec/requests/home_controller_spec.rb new file mode 100644 index 0000000000..9400fedcd9 --- /dev/null +++ b/spec/requests/home_controller_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe HomeController, type: :request do + context "#unauthorized" do + it "renders the unauthorized template" do + get "/unauthorized" + + expect(response.status).to eq 401 + expect(response).to render_template("shared/unauthorized", layout: 'darkswarm') + end + end +end diff --git a/spec/services/checkout/form_data_adapter_spec.rb b/spec/services/checkout/form_data_adapter_spec.rb index be248a2af0..e2b3c38a94 100644 --- a/spec/services/checkout/form_data_adapter_spec.rb +++ b/spec/services/checkout/form_data_adapter_spec.rb @@ -36,6 +36,26 @@ describe Checkout::FormDataAdapter do end end + describe "and a credit card is provided" do + before do + params[:order][:payments_attributes].first[:source_attributes] = {number: "4444333322221111"} + end + + it "fills in missing credit card brand" do + expect(adapter.params[:order][:payments_attributes].first[:source_attributes][:cc_type]).to eq "visa" + end + + it "leaves an existing credit card brand" do + params[:order][:payments_attributes].first[:source_attributes][:cc_type] = "test" + expect(adapter.params[:order][:payments_attributes].first[:source_attributes][:cc_type]).to eq "test" + end + + it "doesn't touch the credit card brand without a number" do + params[:order][:payments_attributes].first[:source_attributes][:number] = "" + expect(adapter.params[:order][:payments_attributes].first[:source_attributes].key?(:cc_type)).to eq false + end + end + describe "and existing credit card is provided" do before { params[:order][:existing_card_id] = credit_card.id } diff --git a/spec/services/exchange_products_renderer_spec.rb b/spec/services/exchange_products_renderer_spec.rb index 9e93611f7c..b2eba1c6c3 100644 --- a/spec/services/exchange_products_renderer_spec.rb +++ b/spec/services/exchange_products_renderer_spec.rb @@ -7,8 +7,9 @@ describe ExchangeProductsRenderer do describe "#exchange_products" do describe "for an incoming exchange" do + let(:exchange) { order_cycle.exchanges.incoming.first } + it "loads products" do - exchange = order_cycle.exchanges.incoming.first products = renderer.exchange_products(true, exchange.sender) expect(products.first.supplier.name).to eq exchange.variants.first.product.supplier.name @@ -16,14 +17,34 @@ describe ExchangeProductsRenderer do end describe "for an outgoing exchange" do + let(:exchange) { order_cycle.exchanges.outgoing.first } + it "loads products" do - exchange = order_cycle.exchanges.outgoing.first products = renderer.exchange_products(false, exchange.receiver) suppliers = [exchange.variants[0].product.supplier.name, exchange.variants[1].product.supplier.name] expect(suppliers).to include products.first.supplier.name expect(suppliers).to include products.second.supplier.name end + + context "showing products from coordinator inventory only" do + before { order_cycle.update prefers_product_selection_from_coordinator_inventory_only: true } + + it "loads no products if there are no products from the coordinator inventory" do + products = renderer.exchange_products(false, exchange.receiver) + + expect(products).to be_empty + end + + it "loads products from the coordinator inventory" do + # Add variant already in the exchange to the coordinator's inventory + exchange.variants.first.inventory_items = [create(:inventory_item, enterprise: order_cycle.coordinator)] + + products = renderer.exchange_products(false, exchange.receiver) + + expect(products).to eq [exchange.variants.first.product] + end + end end end @@ -36,8 +57,8 @@ describe ExchangeProductsRenderer do expect(variants.first.product.supplier.name).to eq exchange.variants.first.product.supplier.name end - xdescribe "when OC is showing only the coordinators inventory" do - let(:exchange_with_visible_variant) { order_cycle.exchanges.incoming.second } + describe "when OC is showing only the coordinators inventory" do + let(:exchange_with_visible_variant) { order_cycle.exchanges.incoming.last } let(:exchange_with_hidden_variant) { order_cycle.exchanges.incoming.first } let!(:visible_inventory_item) { create(:inventory_item, enterprise: order_cycle.coordinator, variant: exchange_with_visible_variant.variants.first, visible: true) } let!(:hidden_inventory_item) { create(:inventory_item, enterprise: order_cycle.coordinator, variant: exchange_with_hidden_variant.variants.first, visible: false) } diff --git a/spec/services/order_tax_adjustments_fetcher_spec.rb b/spec/services/order_tax_adjustments_fetcher_spec.rb index 2b06e5c4a2..6707aa1abf 100644 --- a/spec/services/order_tax_adjustments_fetcher_spec.rb +++ b/spec/services/order_tax_adjustments_fetcher_spec.rb @@ -4,24 +4,57 @@ require "spec_helper" describe OrderTaxAdjustmentsFetcher do describe "#totals" do - let(:zone) { create(:zone_with_member) } - let(:coordinator) { create(:distributor_enterprise, charges_sales_tax: true) } + let(:zone) { create(:zone_with_member) } + let(:coordinator) { create(:distributor_enterprise, charges_sales_tax: true) } - let(:tax_rate10) { create(:tax_rate, included_in_price: true, calculator: Spree::Calculator::DefaultTax.new, amount: 0.1, zone: zone) } - let(:tax_rate15) { create(:tax_rate, included_in_price: true, calculator: Spree::Calculator::DefaultTax.new, amount: 0.15, zone: zone) } - let(:tax_rate20) { create(:tax_rate, included_in_price: true, calculator: Spree::Calculator::DefaultTax.new, amount: 0.2, zone: zone) } - let(:tax_rate25) { create(:tax_rate, included_in_price: true, calculator: Spree::Calculator::DefaultTax.new, amount: 0.25, zone: zone) } - let(:tax_category10) { create(:tax_category, tax_rates: [tax_rate10]) } - let(:tax_category15) { create(:tax_category, tax_rates: [tax_rate15]) } - let(:tax_category20) { create(:tax_category, tax_rates: [tax_rate20]) } - let(:tax_category25) { create(:tax_category, tax_rates: [tax_rate25]) } + let(:tax_rate10) do + create(:tax_rate, included_in_price: true, + calculator: Calculator::DefaultTax.new, + amount: 0.1, + zone: zone) + end + let(:tax_rate15) do + create(:tax_rate, included_in_price: true, + calculator: Calculator::DefaultTax.new, + amount: 0.15, + zone: zone) + end + let(:tax_rate20) do + create(:tax_rate, included_in_price: true, + calculator: Calculator::DefaultTax.new, + amount: 0.2, + zone: zone) + end + let(:tax_rate25) do + create(:tax_rate, included_in_price: true, + calculator: Calculator::DefaultTax.new, + amount: 0.25, + zone: zone) + end + let(:tax_category10) { create(:tax_category, tax_rates: [tax_rate10]) } + let(:tax_category15) { create(:tax_category, tax_rates: [tax_rate15]) } + let(:tax_category20) { create(:tax_category, tax_rates: [tax_rate20]) } + let(:tax_category25) { create(:tax_category, tax_rates: [tax_rate25]) } - let(:variant) { create(:variant, product: create(:product, tax_category: tax_category10)) } - let(:enterprise_fee) { create(:enterprise_fee, enterprise: coordinator, tax_category: tax_category20, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 48.0)) } - let(:additional_adjustment) { create(:adjustment, amount: 50.0, included_tax: tax_rate25.compute_tax(50.0)) } + let(:variant) do + create(:variant, product: create(:product, tax_category: tax_category10)) + end + let(:enterprise_fee) do + create(:enterprise_fee, enterprise: coordinator, + tax_category: tax_category20, + calculator: Calculator::FlatRate.new(preferred_amount: 48.0)) + end + let(:additional_adjustment) do + create(:adjustment, amount: 50.0, included_tax: tax_rate25.compute_tax(50.0)) + end - let(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator, coordinator_fees: [enterprise_fee], distributors: [coordinator], variants: [variant]) } - let(:line_item) { create(:line_item, variant: variant, price: 44.0) } + let(:order_cycle) do + create(:simple_order_cycle, coordinator: coordinator, + coordinator_fees: [enterprise_fee], + distributors: [coordinator], + variants: [variant]) + end + let(:line_item) { create(:line_item, variant: variant, price: 44.0) } let(:order) do create( :order, @@ -38,8 +71,12 @@ describe OrderTaxAdjustmentsFetcher do allow(Spree::Config).to receive(:shipping_tax_rate).and_return(tax_rate15.amount) end - let(:shipping_method) { create(:shipping_method, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 46.0)) } - let!(:shipment) { create(:shipment_with, :shipping_method, shipping_method: shipping_method, order: order) } + let(:shipping_method) do + create(:shipping_method, calculator: Calculator::FlatRate.new(preferred_amount: 46.0)) + end + let!(:shipment) do + create(:shipment_with, :shipping_method, shipping_method: shipping_method, order: order) + end before do order.create_tax_charge! diff --git a/spec/services/tax_rate_finder_spec.rb b/spec/services/tax_rate_finder_spec.rb index 3f5daadfcf..6881d66ebf 100644 --- a/spec/services/tax_rate_finder_spec.rb +++ b/spec/services/tax_rate_finder_spec.rb @@ -66,7 +66,7 @@ describe TaxRateFinder do create( :tax_rate, amount: amount, - calculator: Spree::Calculator::DefaultTax.new, + calculator: Calculator::DefaultTax.new, zone: zone ) end diff --git a/spec/services/user_locale_setter_spec.rb b/spec/services/user_locale_setter_spec.rb new file mode 100644 index 0000000000..1c2979a286 --- /dev/null +++ b/spec/services/user_locale_setter_spec.rb @@ -0,0 +1,147 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe UserLocaleSetter do + let(:user) { create(:user) } + let(:default_locale) { I18n.default_locale } + let(:locale_params) { {} } + let(:cookies) { {} } + let(:service) { UserLocaleSetter.new(user, locale_params, cookies) } + + describe "#set_locale" do + describe "persists selected locale from params" do + let(:locale_params) { "es" } + + context "when the user is logged in" do + it "saves selected locale to user.locale and cookies[:locale]" do + service.set_locale + + expect(user.reload.locale).to eq "es" + expect(cookies).to eq({ locale: "es" }) + end + end + + context "when the user is not logged in" do + it "saves selected locale to cookies[:locale]" do + service.set_locale + + expect(cookies).to eq({ locale: "es" }) + end + end + end + + describe "sets the current locale" do + context "when the user is logged in" do + context "and has a valid locale saved" do + before { user.update(locale: "es") } + + it "applies the user's locale" do + service.set_locale + + expect(I18n.locale).to eq :es + end + end + + context "and has an invalid locale saved" do + before { user.update(locale: "xx") } + + it "applies the default locale" do + service.set_locale + + expect(I18n.locale).to eq I18n.default_locale + end + end + + context "and has no locale saved" do + before { user.update(locale: nil) } + + it "applies the locale from cookies if present" do + cookies[:locale] = "es" + service.set_locale + + expect(I18n.locale).to eq :es + end + + it "applies the default locale otherwise " do + service.set_locale + + expect(I18n.locale).to eq I18n.default_locale + end + end + end + + context "when the user is not logged in" do + let(:user) { nil } + + context "with a locale set in cookies" do + it "applies the value from cookies" do + cookies[:locale] = "es" + service.set_locale + + expect(I18n.locale).to eq :es + end + end + + context "with no locale set in cookies" do + it "applies the default locale" do + service.set_locale + + expect(I18n.locale).to eq I18n.default_locale + end + end + end + end + end + + describe "#ensure_valid_locale_persisted" do + context "when a user is present" do + context "and has an unavailable locale saved" do + before { user.update(locale: "xx") } + + context "with no locale set in cookies" do + it "set the user's locale to the default" do + service.ensure_valid_locale_persisted + + expect(user.reload.locale).to eq default_locale.to_s + end + end + + context "with a locale set in cookies" do + let(:cookies) { {locale: "es"} } + + it "set the user's locale to the cookie value" do + service.ensure_valid_locale_persisted + + expect(user.reload.locale).to eq "es" + end + end + end + end + end + + describe "#valid_current_locale" do + let(:service) { UserLocaleSetter.new(user) } + + context "when the user has a locale set" do + it "returns the user's locale" do + user.update(locale: "es") + expect(service.valid_current_locale).to eq "es" + end + end + + context "when the user has no locale set" do + it "returns the default locale" do + expect(service.valid_current_locale).to eq default_locale + end + end + + context "when the given user argument is nil" do + let(:user) { nil } + + it "returns the default locale" do + expect(service.valid_current_locale).to eq default_locale + end + end + end +end diff --git a/spec/support/request/authentication_workflow.rb b/spec/support/request/authentication_workflow.rb index e07327d3e7..8968c84da5 100644 --- a/spec/support/request/authentication_workflow.rb +++ b/spec/support/request/authentication_workflow.rb @@ -46,28 +46,16 @@ module AuthenticationWorkflow # click_button 'Login' end - def login_to_consumer_section - user_role = Spree::Role.find_or_create_by!(name: 'user') - user = create_enterprise_user( - email: 'someone@ofn.org', - password: 'passw0rd', - password_confirmation: 'passw0rd', - remember_me: false, - persistence_token: 'pass', - login: 'someone@ofn.org' - ) - - user.spree_roles << user_role - - visit spree.login_path - fill_in_and_submit_login_form user - end - def fill_in_and_submit_login_form(user) fill_in "email", with: user.email fill_in "password", with: user.password click_button "Login" end + + def expect_logged_in + # Ensure page has been reloaded after submitting login form + expect(page).to_not have_selector ".menu #login-link" + end end RSpec.configure do |config| diff --git a/spec/support/request/checkout_workflow.rb b/spec/support/request/checkout_workflow.rb index 8a4ea7974d..c6863a1bf5 100644 --- a/spec/support/request/checkout_workflow.rb +++ b/spec/support/request/checkout_workflow.rb @@ -4,7 +4,7 @@ module CheckoutWorkflow end def checkout_as_guest - find("button", text: "Checkout as guest").click + click_button "Checkout as guest" end def place_order diff --git a/spec/support/request/shop_workflow.rb b/spec/support/request/shop_workflow.rb index e5123715e0..9e64ea19cc 100644 --- a/spec/support/request/shop_workflow.rb +++ b/spec/support/request/shop_workflow.rb @@ -1,13 +1,28 @@ module ShopWorkflow + # If a spec uses `within` but we want to check something outside of that + # scope, we can search from the body element instead. + def find_body + page.all("body").first || page.find(:xpath, "ancestor::body") + end + def wait_for_cart - first("#cart").click - within '.cart-sidebar' do - expect(page).to_not have_link "Updating cart..." + # Wait for debounce + # + # The auto-submit on these specific form elements (add to cart) now has a small built-in + # waiting period before submitting the data... + sleep 0.6 + + within find_body do + # We ignore visibility in case the cart dropdown is not open. + within '.cart-sidebar', visible: false do + expect(page).to_not have_link "Updating cart...", visible: false + end end end def edit_cart wait_for_cart + toggle_cart within '.cart-sidebar' do expect(page).to have_link I18n.t('shared.menu.cart_sidebar.edit_cart') end @@ -34,6 +49,59 @@ module ShopWorkflow order.update_distribution_charge! end + # Add an item to the cart + # + # At the moment, the user enters the quantity into an input field. + # But with the coming mobile-friendly UX, the user will click a button to + # add an item, hence the naming. + # + # This is temporary code. The duplication will be removed by the mobile + # product listings feature. This has been backported to avoid merge + # conflicts and to make the current build more stable. + def click_add_to_cart(variant = nil, quantity = 1) + within_variant(variant) do + input = page.find("input") + new_quantity = input.value.to_i + quantity + fill_in input[:name], with: new_quantity + end + wait_for_cart + end + + def click_remove_from_cart(variant = nil, quantity = 1) + within_variant(variant) do + input = page.find("input") + new_quantity = input.value.to_i - quantity + fill_in input[:name], with: new_quantity + end + wait_for_cart + end + + def click_add_bulk_to_cart(variant = nil, quantity = 1) + within_variant(variant) do + input = page.find("input") + new_quantity = input.value.to_i + quantity + fill_in input[:name], with: new_quantity + end + wait_for_cart + end + + def click_add_bulk_max_to_cart(variant = nil, quantity = 1) + within_variant(variant) do + input = page.find(:field, "variant_attributes[#{variant.id}][max_quantity]") + new_quantity = input.value.to_i + quantity + fill_in input[:name], with: new_quantity + end + wait_for_cart + end + + def within_variant(variant = nil) + selector = variant ? "#variant-#{variant.id}" : ".variants" + expect(page).to have_selector selector + within(selector) do + yield + end + end + def toggle_accordion(name) find("dd a", text: name).click end diff --git a/spec/support/request/ui_component_helper.rb b/spec/support/request/ui_component_helper.rb index b23002e3d7..1230517d38 100644 --- a/spec/support/request/ui_component_helper.rb +++ b/spec/support/request/ui_component_helper.rb @@ -67,10 +67,7 @@ module UIComponentHelper def toggle_cart page.find("#cart").click - end - - def cart_dirty - page.find("span.cart-span")[:class].include? 'pure-dirty' + sleep 0.3 # Allow 300ms for sidebar animation to finish end def wait_for_ajax diff --git a/spec/support/request/web_helper.rb b/spec/support/request/web_helper.rb index 732e306a29..25ae07bbc1 100644 --- a/spec/support/request/web_helper.rb +++ b/spec/support/request/web_helper.rb @@ -95,10 +95,6 @@ module WebHelper end end - def wait_until_enabled(selector) - wait_until(10) { first("#{selector}:not([disabled='disabled'])") } - end - def select2_select(value, options) id = options[:from] options[:from] = "#s2id_#{id}" diff --git a/spec/swagger_helper.rb b/spec/swagger_helper.rb new file mode 100644 index 0000000000..0bf64f9ced --- /dev/null +++ b/spec/swagger_helper.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.configure do |config| + config.swagger_root = Rails.root.join('swagger').to_s + config.swagger_docs = { + 'v1/swagger.yaml' => { + openapi: '3.0.1', + info: { + title: 'The Open Food Network', + description: 'This spec is auto generated using the rswag gem. It is incomplete and not yet valid for openapi 3.0.1. Do not publish this. \ +Some endpoints are public and require no authorization; others require authorization. Talk to us to get your credentials set up. \ +Check out our repo! https://github.com/openfoodfoundation/openfoodnetwork', + version: '0.1', + }, + components: { + securitySchemes: { + api_key: { + type: :apiKey, + name: 'X-Spree-Token', + in: :header + } + }, + schemas: { + Order_Concise: { + type: 'object', + properties: { + id: { type: 'integer' }, + number: { type: 'string' }, + full_name: { type: 'string' }, + email: { type: 'string' }, + phone: { type: 'string' }, + completed_at: { type: 'string' }, + display_total: { type: 'string' }, + show_path: { type: 'string' }, + edit_path: { type: 'string' }, + state: { type: 'string' }, + payment_state: { type: 'string' }, + shipment_state: { type: 'string' }, + payments_path: { type: 'string' }, + shipments_path: { type: 'string' }, + ship_path: { type: 'string' }, + ready_to_ship: { type: 'string' }, + created_at: { type: 'string' }, + distributor_name: { type: 'string' }, + special_instructions: { type: 'string' }, + payment_capture_path: { type: 'string' }, + distributor: { + type: 'object', + properties: { + id: { type: 'integer' } + } + }, + order_cycle: { + type: 'object', + properties: { + id: { type: 'integer' } + } + } + } + } + } + }, + paths: {}, + servers: [ + { + url: 'https://staging.katuma.org/api' + } + ] + } + } + config.swagger_format = :yaml +end diff --git a/swagger.yaml b/swagger.yaml index 838f09f2d4..9f4accc980 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -342,9 +342,63 @@ paths: /orders: get: - description: Gets all Orders. + description: > + Gets all Orders. Use combinations of parameters to filter your query. + For example /api/orders?q[completed_at_gt]=2020_02_02&q[completed_at_lt]=2020_02_10 returns orders between 2nd and 10th February 2020. + Query parameters are generated for the '#/components/schemas/Order_Concise' model with [Ransack](https://github.com/activerecord-hackery/ransack#search-matchers) search matchers tags: - orders + parameters: + - in: query + name: q[distributor_id_eq] + schema: + type: string + style: deepObject + description: Query orders for a specific distributor id. + required: false + - in: query + name: q[completed_at_gt] + schema: + type: string + style: deepObject + description: Query orders completed after a date. + required: false + - in: query + name: q[completed_at_lt] + schema: + type: string + style: deepObject + description: Query orders completed before a date. + required: false + - in: query + name: q[state_eq] + schema: + type: string + style: deepObject + description: Query orders by order state, eg 'cart', 'complete'. + required: false + - in: query + name: q[payment_state_eq] + schema: + type: string + style: deepObject + description: Query orders by order payment_state, eg 'balance_due', 'paid', 'failed'. + required: false + - in: query + name: q[email_cont] + schema: + type: string + style: deepObject + description: Query orders where the order email contains a string. + required: false + - in: query + name: q[order_cycle_id_eq] + schema: + type: string + style: deepObject + description: Query orders for a specific order_cycle id. + required: false + responses: '200': description: successful operation diff --git a/swagger/v1/swagger.yaml b/swagger/v1/swagger.yaml new file mode 100644 index 0000000000..88d54ed960 --- /dev/null +++ b/swagger/v1/swagger.yaml @@ -0,0 +1,124 @@ +--- +openapi: 3.0.1 +info: + title: The Open Food Network + description: |- + This spec is auto generated using the rswag gem. It is incomplete and not yet valid for openapi 3.0.1. Do not publish this. \ + Some endpoints are public and require no authorization; others require authorization. Talk to us to get your credentials set up. \ + Check out our repo! https://github.com/openfoodfoundation/openfoodnetwork + version: '0.1' +components: + securitySchemes: + api_key: + type: apiKey + name: X-Spree-Token + in: header + schemas: + Order_Concise: + type: object + properties: + id: + type: integer + number: + type: string + full_name: + type: string + email: + type: string + phone: + type: string + completed_at: + type: string + display_total: + type: string + show_path: + type: string + edit_path: + type: string + state: + type: string + payment_state: + type: string + shipment_state: + type: string + payments_path: + type: string + shipments_path: + type: string + ship_path: + type: string + ready_to_ship: + type: string + created_at: + type: string + distributor_name: + type: string + special_instructions: + type: string + payment_capture_path: + type: string + distributor: + type: object + properties: + id: + type: integer + order_cycle: + type: object + properties: + id: + type: integer +paths: + "/api/orders": + get: + summary: list orders + tags: + - Orders + parameters: + - name: X-Spree-Token + in: header + type: string + - name: q[distributor_id_eq] + in: query + type: string + required: false + description: Query orders for a specific distributor id. + - name: q[completed_at_gt] + in: query + type: string + required: false + description: Query orders completed after a date. + - name: q[completed_at_lt] + in: query + type: string + required: false + description: Query orders completed before a date. + - name: q[state_eq] + in: query + type: string + required: false + description: Query orders by order state, eg 'cart', 'complete'. + - name: q[payment_state_eq] + in: query + type: string + required: false + description: Query orders by order payment_state, eg 'balance_due', 'paid', + 'failed'. + - name: q[email_cont] + in: query + type: string + required: false + description: Query orders where the order email contains a string. + - name: q[order_cycle_id_eq] + in: query + type: string + required: false + description: Query orders for a specific order_cycle id. + responses: + '200': + description: get orders + content: + application/json: + schema: + "$ref": "#/components/schemas/Order_Concise" +servers: +- url: https://staging.katuma.org/api