Compare commits

..

5 Commits

Author SHA1 Message Date
Matt-Yorkley
49de11567b Report environment correctly in Bugsnag JS 2020-03-31 17:16:13 +01:00
Matt-Yorkley
3af0365c6b Rename partial to bugsnag_js for clarity 2020-03-31 17:16:13 +01:00
Matt-Yorkley
be92b0049b Move conditional inside partial and use default key as fallback 2020-03-31 17:16:13 +01:00
Matt-Yorkley
a1c94d0d9f Add bugsnag js script to admin layout above all.js 2020-03-31 17:16:13 +01:00
Maikel Linke
f6bb8a9a04 Add Bugsnag JS logging 2020-03-31 17:16:13 +01:00
188 changed files with 3206 additions and 20134 deletions

View File

@@ -33,6 +33,7 @@ Layout/LineLength:
- app/controllers/admin/inventory_items_controller.rb
- app/controllers/admin/manager_invitations_controller.rb
- app/controllers/admin/product_import_controller.rb
- app/controllers/admin/proxy_orders_controller.rb
- app/controllers/admin/schedules_controller.rb
- app/controllers/admin/subscriptions_controller.rb
- app/controllers/admin/variant_overrides_controller.rb
@@ -97,6 +98,7 @@ Layout/LineLength:
- app/services/embedded_page_service.rb
- app/services/order_cycle_form.rb
- app/services/order_factory.rb
- app/services/subscriptions_count.rb
- app/services/variants_stock_levels.rb
- engines/web/app/helpers/web/cookies_policy_helper.rb
- lib/discourse/single_sign_on.rb
@@ -237,7 +239,10 @@ Layout/LineLength:
- spec/lib/open_food_network/packing_report_spec.rb
- spec/lib/open_food_network/permissions_spec.rb
- spec/lib/open_food_network/products_and_inventory_report_spec.rb
- spec/lib/open_food_network/proxy_order_syncer_spec.rb
- spec/lib/open_food_network/scope_variant_to_hub_spec.rb
- spec/lib/open_food_network/subscription_payment_updater_spec.rb
- spec/lib/open_food_network/subscription_summarizer_spec.rb
- spec/lib/open_food_network/tag_rule_applicator_spec.rb
- spec/lib/open_food_network/users_and_enterprises_report_spec.rb
- spec/lib/open_food_network/xero_invoices_report_spec.rb
@@ -312,6 +317,10 @@ Layout/LineLength:
- spec/services/permissions/order_spec.rb
- spec/services/product_tag_rules_filterer_spec.rb
- spec/services/products_renderer_spec.rb
- spec/services/subscription_estimator_spec.rb
- spec/services/subscription_form_spec.rb
- spec/services/subscription_validator_spec.rb
- spec/services/subscription_variants_service_spec.rb
- spec/spec_helper.rb
- spec/support/cancan_helper.rb
- spec/support/delayed_job_helper.rb
@@ -402,7 +411,7 @@ Metrics/AbcSize:
- app/services/cart_service.rb
- app/services/create_order_cycle.rb
- app/services/order_syncer.rb
- engines/order_management/app/services/order_management/subscriptions/validator.rb
- app/services/subscription_validator.rb
- lib/active_merchant/billing/gateways/stripe_decorator.rb
- lib/active_merchant/billing/gateways/stripe_payment_intents.rb
- lib/discourse/single_sign_on.rb
@@ -677,12 +686,6 @@ Metrics/ModuleLength:
- app/helpers/injection_helper.rb
- app/helpers/spree/admin/navigation_helper.rb
- app/helpers/spree/admin/base_helper.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
- engines/order_management/spec/services/order_management/subscriptions/summarizer_spec.rb
- engines/order_management/spec/services/order_management/subscriptions/validator_spec.rb
- engines/order_management/spec/services/order_management/subscriptions/variants_list_spec.rb
- lib/open_food_network/column_preference_defaults.rb
- spec/controllers/admin/enterprises_controller_spec.rb
- spec/controllers/admin/order_cycles_controller_spec.rb
@@ -698,7 +701,9 @@ Metrics/ModuleLength:
- spec/lib/open_food_network/order_grouper_spec.rb
- spec/lib/open_food_network/permissions_spec.rb
- spec/lib/open_food_network/products_and_inventory_report_spec.rb
- spec/lib/open_food_network/proxy_order_syncer_spec.rb
- spec/lib/open_food_network/scope_variant_to_hub_spec.rb
- spec/lib/open_food_network/subscription_payment_updater_spec.rb
- spec/lib/open_food_network/tag_rule_applicator_spec.rb
- spec/lib/open_food_network/users_and_enterprises_report_spec.rb
- spec/models/spree/ability_spec.rb

View File

@@ -71,6 +71,7 @@ Lint/DuplicateHashKey:
Lint/DuplicateMethods:
Exclude:
- 'lib/discourse/single_sign_on.rb'
- 'lib/open_food_network/subscription_summary.rb'
# Offense count: 10
Lint/IneffectiveAccessModifier:
@@ -158,7 +159,7 @@ Naming/MethodParameterName:
Exclude:
- 'app/helpers/spree/admin/base_helper_decorator.rb'
- 'app/helpers/spree/base_helper_decorator.rb'
- 'engines/order_management/app/services/order_management/subscriptions/validator.rb'
- 'app/services/subscription_validator.rb'
- 'lib/open_food_network/reports/bulk_coop_report.rb'
- 'lib/open_food_network/xero_invoices_report.rb'
- 'spec/lib/open_food_network/reports/report_spec.rb'
@@ -848,6 +849,11 @@ Style/FrozenStringLiteralComment:
- 'app/services/reset_order_service.rb'
- 'app/services/restart_checkout.rb'
- 'app/services/search_orders.rb'
- 'app/services/subscription_estimator.rb'
- 'app/services/subscription_form.rb'
- 'app/services/subscription_validator.rb'
- 'app/services/subscription_variants_service.rb'
- 'app/services/subscriptions_count.rb'
- 'app/services/tax_rate_finder.rb'
- 'app/services/upload_sanitizer.rb'
- 'app/services/variant_deleter.rb'
@@ -886,7 +892,6 @@ Style/FrozenStringLiteralComment:
- 'engines/order_management/lib/order_management/engine.rb'
- 'engines/order_management/lib/order_management/version.rb'
- 'engines/order_management/order_management.gemspec'
- 'engines/order_management/spec/performance/order_management/subscriptions/proxy_order_syncer_spec.rb'
- 'engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/authorizer_spec.rb'
- 'engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/parameters_spec.rb'
- 'engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/permissions_spec.rb'
@@ -944,6 +949,7 @@ Style/FrozenStringLiteralComment:
- 'lib/open_food_network/products_and_inventory_report.rb'
- 'lib/open_food_network/products_and_inventory_report_base.rb'
- 'lib/open_food_network/property_merge.rb'
- 'lib/open_food_network/proxy_order_syncer.rb'
- 'lib/open_food_network/rack_request_blocker.rb'
- 'lib/open_food_network/referer_parser.rb'
- 'lib/open_food_network/reports/bulk_coop_allocation_report.rb'
@@ -960,6 +966,8 @@ Style/FrozenStringLiteralComment:
- 'lib/open_food_network/scope_variants_for_search.rb'
- 'lib/open_food_network/spree_api_key_loader.rb'
- 'lib/open_food_network/subscription_payment_updater.rb'
- 'lib/open_food_network/subscription_summarizer.rb'
- 'lib/open_food_network/subscription_summary.rb'
- 'lib/open_food_network/tag_rule_applicator.rb'
- 'lib/open_food_network/user_balance_calculator.rb'
- 'lib/open_food_network/users_and_enterprises_report.rb'
@@ -1201,6 +1209,7 @@ Style/FrozenStringLiteralComment:
- 'spec/lib/open_food_network/permissions_spec.rb'
- 'spec/lib/open_food_network/products_and_inventory_report_spec.rb'
- 'spec/lib/open_food_network/property_merge_spec.rb'
- 'spec/lib/open_food_network/proxy_order_syncer_spec.rb'
- 'spec/lib/open_food_network/referer_parser_spec.rb'
- 'spec/lib/open_food_network/reports/report_spec.rb'
- 'spec/lib/open_food_network/reports/row_spec.rb'
@@ -1209,6 +1218,8 @@ Style/FrozenStringLiteralComment:
- 'spec/lib/open_food_network/scope_variant_to_hub_spec.rb'
- 'spec/lib/open_food_network/scope_variants_to_search_spec.rb'
- 'spec/lib/open_food_network/subscription_payment_updater_spec.rb'
- 'spec/lib/open_food_network/subscription_summarizer_spec.rb'
- 'spec/lib/open_food_network/subscription_summary_spec.rb'
- 'spec/lib/open_food_network/tag_rule_applicator_spec.rb'
- 'spec/lib/open_food_network/user_balance_calculator_spec.rb'
- 'spec/lib/open_food_network/users_and_enterprises_report_spec.rb'
@@ -1293,6 +1304,7 @@ Style/FrozenStringLiteralComment:
- 'spec/models/variant_override_spec.rb'
- 'spec/performance/injection_helper_spec.rb'
- 'spec/performance/orders_controller_spec.rb'
- 'spec/performance/proxy_order_syncer_spec.rb'
- 'spec/performance/shop_controller_spec.rb'
- 'spec/requests/checkout/failed_checkout_spec.rb'
- 'spec/requests/checkout/paypal_spec.rb'
@@ -1343,6 +1355,11 @@ Style/FrozenStringLiteralComment:
- 'spec/services/reset_order_service_spec.rb'
- 'spec/services/restart_checkout_spec.rb'
- 'spec/services/search_orders_spec.rb'
- 'spec/services/subscription_estimator_spec.rb'
- 'spec/services/subscription_form_spec.rb'
- 'spec/services/subscription_validator_spec.rb'
- 'spec/services/subscription_variants_service_spec.rb'
- 'spec/services/subscriptions_count_spec.rb'
- 'spec/services/tax_rate_finder_spec.rb'
- 'spec/services/upload_sanitizer_spec.rb'
- 'spec/services/variants_stock_levels_spec.rb'
@@ -1540,6 +1557,7 @@ Style/Send:
- 'spec/lib/open_food_network/products_and_inventory_report_spec.rb'
- 'spec/lib/open_food_network/sales_tax_report_spec.rb'
- 'spec/lib/open_food_network/subscription_payment_updater_spec.rb'
- 'spec/lib/open_food_network/subscription_summarizer_spec.rb'
- 'spec/lib/open_food_network/tag_rule_applicator_spec.rb'
- 'spec/lib/open_food_network/xero_invoices_report_spec.rb'
- 'spec/lib/stripe/webhook_handler_spec.rb'

View File

@@ -35,20 +35,13 @@ Download the Docker images and build the containers:
$ docker-compose build
```
Setup the database and seed it with sample data:
```sh
$ docker-compose run web bundle exec rake db:reset
$ docker-compose run web bundle exec rake db:test:prepare
$ docker-compose run web bundle exec rake ofn:sample_data
```
Finally, run the app with all the required containers:
Run the app with all the required containers:
```sh
$ docker-compose up
```
The default admin user is 'ofn@example.com' with 'ofn123' password.
This command will setup the database and seed it with sample data. The default admin user is 'ofn@example.com' with 'ofn123' password.
Check the app in the browser at `http://localhost:3000`.
You will then get the trace of the containers in the terminal. You can stop the containers using Ctrl-C in the terminal.

View File

@@ -12,7 +12,7 @@ ENV BUNDLE_PATH /bundles
WORKDIR /usr/src/app
COPY .ruby-version .
# Install Rbenv & Ruby
# Rbenv & Ruby part
RUN git clone https://github.com/rbenv/rbenv.git ${RBENV_ROOT} && \
git clone https://github.com/rbenv/ruby-build.git ${RBENV_ROOT}/plugins/ruby-build && \
${RBENV_ROOT}/plugins/ruby-build/install.sh && \
@@ -21,7 +21,7 @@ RUN git clone https://github.com/rbenv/rbenv.git ${RBENV_ROOT} && \
rbenv global $(cat .ruby-version) && \
gem install bundler --version=1.17.2
# Install Postgres
# Postgres
RUN sh -c "echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' > /etc/apt/sources.list.d/pgdg.list" && \
wget --quiet -O - https://apt.postgresql.org/pub/repos/apt/ACCC4CF8.asc | apt-key add - && \
apt-get update && \
@@ -38,6 +38,4 @@ RUN wget https://chromedriver.storage.googleapis.com/2.41/chromedriver_linux64.z
unzip chromedriver_linux64.zip -d /usr/bin && \
chmod u+x /usr/bin/chromedriver
# Copy code and install app dependencies
COPY . /usr/src/app/
RUN bundle install

View File

@@ -45,6 +45,10 @@ gem 'daemons'
gem 'delayed_job_active_record'
gem 'delayed_job_web'
# Fix bug in simple_form preventing collection_check_boxes usage within form_for block
# When merged, revert to upstream gem
gem 'simple_form', github: 'RohanM/simple_form'
# Spree's default pagination gem (locked to the current version used by Spree)
# We use it's methods in OFN code as well, so this is a direct dependency
gem 'kaminari', '~> 0.14.1'
@@ -108,6 +112,7 @@ gem 'momentjs-rails'
gem 'turbo-sprockets-rails3'
gem "foundation-rails"
gem 'foundation_rails_helper', github: 'willrjmarshall/foundation_rails_helper', branch: "rails3"
gem 'jquery-migrate-rails'
gem 'jquery-rails', '3.1.5'
@@ -118,7 +123,6 @@ gem 'ofn-qz', github: 'openfoodfoundation/ofn-qz', ref: '60da2ae4c44cbb4c8d602f5
group :production, :staging do
gem 'ddtrace'
gem 'unicorn-worker-killer'
end
group :test, :development do

View File

@@ -1,3 +1,11 @@
GIT
remote: https://github.com/RohanM/simple_form.git
revision: 45f08a213b40f3d4bda5f5398db841137587160a
specs:
simple_form (2.0.2)
actionpack (~> 3.0)
activemodel (~> 3.0)
GIT
remote: https://github.com/jeremydurham/custom-err-msg.git
revision: 3a8ec9dddc7a5b0aab7c69a6060596de300c68f4
@@ -57,6 +65,16 @@ GIT
rails-i18n
spree_core (>= 1.1)
GIT
remote: https://github.com/willrjmarshall/foundation_rails_helper.git
revision: 4d5d53fdc4b1fb71e66524d298c5c635de82cfbb
branch: rails3
specs:
foundation_rails_helper (0.4)
actionpack (>= 3.0)
activemodel (>= 3.0)
railties (>= 3.0)
PATH
remote: engines/catalog
specs:
@@ -155,7 +173,7 @@ GEM
xpath (>= 2.0, < 4.0)
childprocess (3.0.0)
chronic (0.10.2)
chunky_png (1.3.11)
chunky_png (1.3.10)
climate_control (0.2.0)
cocaine (0.5.8)
climate_control (>= 0.0.3, < 1.0)
@@ -182,7 +200,7 @@ GEM
sass (>= 3.3.0, < 3.5)
compass-import-once (1.0.5)
sass (>= 3.2, < 3.5)
compass-rails (4.0.0)
compass-rails (3.1.0)
compass (~> 1.0.0)
sass-rails (< 5.1)
sprockets (< 4.0)
@@ -198,7 +216,7 @@ GEM
activerecord (>= 3.2.0, < 5.0)
fog (~> 1.0)
rails (>= 3.2.0, < 5.0)
ddtrace (0.34.2)
ddtrace (0.33.1)
msgpack
debugger-linecache (1.2.0)
deface (1.0.2)
@@ -239,7 +257,7 @@ GEM
faraday (1.0.0)
multipart-post (>= 1.2, < 3)
ffaker (1.22.1)
ffi (1.12.2)
ffi (1.11.3)
figaro (1.1.1)
thor (~> 0.14)
fission (0.5.0)
@@ -403,8 +421,6 @@ GEM
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
geocoder (1.1.8)
get_process_mem (0.2.5)
ffi (~> 1.0)
gmaps4rails (1.5.6)
haml (4.0.7)
tilt
@@ -469,7 +485,7 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
oj (3.10.6)
oj (3.10.5)
orm_adapter (0.5.0)
paper_trail (5.2.3)
activerecord (>= 3.0, < 6.0)
@@ -483,7 +499,7 @@ GEM
parallel (1.19.1)
paranoia (1.3.4)
activerecord (~> 3.1)
parser (2.7.1.0)
parser (2.7.0.5)
ast (~> 2.4.0)
paypal-sdk-core (0.2.10)
multi_json (~> 1.0)
@@ -543,8 +559,8 @@ GEM
activerecord (~> 3.0)
polyamorous (~> 0.5.0)
rb-fsevent (0.10.3)
rb-inotify (0.10.1)
ffi (~> 1.0)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
rbvmomi (1.13.0)
builder (~> 3.0)
json (>= 1.8)
@@ -588,15 +604,15 @@ GEM
rspec-retry (0.6.2)
rspec-core (> 3.3)
rspec-support (3.9.2)
rubocop (0.81.0)
rubocop (0.80.1)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
parser (>= 2.7.0.1)
rainbow (>= 2.2.2, < 4.0)
rexml
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 2.0)
rubocop-rails (2.5.2)
unicode-display_width (>= 1.4.0, < 1.7)
rubocop-rails (2.5.0)
activesupport
rack (>= 1.1)
rubocop (>= 0.72.0)
@@ -655,16 +671,13 @@ GEM
tzinfo (0.3.56)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
unicode-display_width (1.7.0)
unicode-display_width (1.6.1)
unicorn (5.5.4)
kgio (~> 2.6)
raindrops (~> 0.7)
unicorn-rails (2.2.1)
rack
unicorn
unicorn-worker-killer (0.4.4)
get_process_mem (~> 0)
unicorn (>= 4, < 6)
uuidtools (2.1.5)
warden (1.2.7)
rack (>= 1.0)
@@ -727,6 +740,7 @@ DEPENDENCIES
foreigner
foundation-icons-sass-rails
foundation-rails
foundation_rails_helper!
fuubar (~> 2.5.0)
geocoder
gmaps4rails
@@ -773,6 +787,7 @@ DEPENDENCIES
select2-rails (~> 3.4.7)
selenium-webdriver
shoulda-matchers
simple_form!
simplecov
spinjs-rails
spree_core!
@@ -788,7 +803,6 @@ DEPENDENCIES
uglifier (>= 1.0.3)
unicorn
unicorn-rails
unicorn-worker-killer
web!
webdrivers
webmock

View File

@@ -2,24 +2,19 @@ angular.module("admin.indexUtils").factory "PagedFetcher", (dataFetcher) ->
new class PagedFetcher
# Given a URL like http://example.com/foo?page=::page::&per_page=20
# And the response includes an attribute pages with the number of pages to fetch
# Fetch each page async, and call the pageCallback callback with the resulting data
# Developer note: this class should not be re-used!
page: 1
last_page: 1
# Fetch each page async, and call the processData callback with the resulting data
fetch: (url, processData, onLastPageComplete) ->
dataFetcher(@urlForPage(url, 1)).then (data) =>
processData data
fetch: (url, pageCallback) ->
@fetchPages(url, @page, pageCallback)
if data.pages > 1
for page in [2..data.pages]
lastPromise = dataFetcher(@urlForPage(url, page)).then (data) ->
processData data
onLastPageComplete && lastPromise.then onLastPageComplete
return
else
onLastPageComplete && onLastPageComplete()
urlForPage: (url, page) ->
url.replace("::page::", page)
fetchPages: (url, page, pageCallback) ->
dataFetcher(@urlForPage(url, page)).then (data) =>
@page++
@last_page = data.pages
pageCallback(data) if pageCallback
if @page <= @last_page
@fetchPages(url, @page, pageCallback)

View File

@@ -1,7 +1,7 @@
angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $http, $q, StatusMessage, Columns, SortOptions, Dereferencer, Orders, LineItems, Enterprises, OrderCycles, VariantUnitManager, RequestMonitor) ->
$scope.initialized = false
$scope.RequestMonitor = RequestMonitor
$scope.line_items = LineItems.all
$scope.filteredLineItems = []
$scope.confirmDelete = true
$scope.startDate = moment().startOf('day').subtract(7, 'days').format('YYYY-MM-DD')
$scope.endDate = moment().startOf('day').format('YYYY-MM-DD')
@@ -15,77 +15,50 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.confirmRefresh = ->
LineItems.allSaved() || confirm(t("unsaved_changes_warning"))
$scope.resetFilters = ->
$scope.distributorFilter = ''
$scope.supplierFilter = ''
$scope.orderCycleFilter = ''
$scope.quickSearch = ''
$scope.resetSelectFilters = ->
$scope.resetFilters()
$scope.refreshData()
$scope.distributorFilter = 0
$scope.supplierFilter = 0
$scope.orderCycleFilter = 0
$scope.quickSearch = ""
$scope.refreshData = ->
unless !$scope.orderCycleFilter? || $scope.orderCycleFilter == ''
$scope.setOrderCycleDateRange()
unless !$scope.orderCycleFilter? || $scope.orderCycleFilter == 0
$scope.startDate = moment(OrderCycles.byID[$scope.orderCycleFilter].orders_open_at).format('YYYY-MM-DD')
$scope.endDate = moment(OrderCycles.byID[$scope.orderCycleFilter].orders_close_at).startOf('day').format('YYYY-MM-DD')
$scope.formattedStartDate = moment($scope.startDate).format()
$scope.formattedEndDate = moment($scope.endDate).add(1,'day').format()
formatted_start_date = moment($scope.startDate).format()
formatted_end_date = moment($scope.endDate).add(1,'day').format()
return unless moment($scope.formattedStartDate).isValid() and moment($scope.formattedEndDate).isValid()
$scope.loadOrders()
$scope.loadLineItems()
unless $scope.initialized
$scope.loadAssociatedData()
$scope.dereferenceLoadedData()
$scope.setOrderCycleDateRange = ->
start_date = OrderCycles.byID[$scope.orderCycleFilter].orders_open_at
end_date = OrderCycles.byID[$scope.orderCycleFilter].orders_close_at
format = "YYYY-MM-DD HH:mm:ss Z"
$scope.startDate = moment(start_date, format).format('YYYY-MM-DD')
$scope.endDate = moment(end_date, format).startOf('day').format('YYYY-MM-DD')
$scope.loadOrders = ->
RequestMonitor.load $scope.orders = Orders.index(
"q[state_not_eq]": "canceled",
"q[completed_at_not_null]": "true",
"q[distributor_id_eq]": $scope.distributorFilter,
"q[order_cycle_id_eq]": $scope.orderCycleFilter,
"q[completed_at_gteq]": $scope.formattedStartDate,
"q[completed_at_lt]": $scope.formattedEndDate
"q[completed_at_gteq]": formatted_start_date,
"q[completed_at_lt]": formatted_end_date
)
$scope.loadLineItems = ->
RequestMonitor.load LineItems.index(
"q[order_state_not_eq]": "canceled",
"q[order_completed_at_not_null]": "true",
"q[order_distributor_id_eq]": $scope.distributorFilter,
"q[variant_product_supplier_id_eq]": $scope.supplierFilter,
"q[order_order_cycle_id_eq]": $scope.orderCycleFilter,
"q[order_completed_at_gteq]": $scope.formattedStartDate,
"q[order_completed_at_lt]": $scope.formattedEndDate
RequestMonitor.load $scope.lineItems = LineItems.index(
"q[order][state_not_eq]": "canceled",
"q[order][completed_at_not_null]": "true",
"q[order][completed_at_gteq]": formatted_start_date,
"q[order][completed_at_lt]": formatted_end_date
)
$scope.loadAssociatedData = ->
RequestMonitor.load $scope.distributors = Enterprises.index(action: "visible", ams_prefix: "basic", "q[sells_in][]": ["own", "any"])
RequestMonitor.load $scope.orderCycles = OrderCycles.index(ams_prefix: "basic", as: "distributor", "q[orders_close_at_gt]": "#{moment().subtract(90,'days').format()}")
RequestMonitor.load $scope.suppliers = Enterprises.index(action: "visible", ams_prefix: "basic", "q[is_primary_producer_eq]": "true")
unless $scope.initialized
RequestMonitor.load $scope.distributors = Enterprises.index(action: "visible", ams_prefix: "basic", "q[sells_in][]": ["own", "any"])
RequestMonitor.load $scope.orderCycles = OrderCycles.index(ams_prefix: "basic", as: "distributor", "q[orders_close_at_gt]": "#{moment().subtract(90,'days').format()}")
RequestMonitor.load $scope.suppliers = Enterprises.index(action: "visible", ams_prefix: "basic", "q[is_primary_producer_eq]": "true")
$scope.dereferenceLoadedData = ->
RequestMonitor.load $q.all([$scope.orders.$promise, $scope.distributors.$promise, $scope.orderCycles.$promise, $scope.suppliers.$promise, $scope.line_items.$promise]).then ->
RequestMonitor.load $q.all([$scope.orders.$promise, $scope.distributors.$promise, $scope.orderCycles.$promise, $scope.suppliers.$promise, $scope.lineItems.$promise]).then ->
Dereferencer.dereferenceAttr $scope.orders, "distributor", Enterprises.byID
Dereferencer.dereferenceAttr $scope.orders, "order_cycle", OrderCycles.byID
Dereferencer.dereferenceAttr $scope.line_items, "supplier", Enterprises.byID
Dereferencer.dereferenceAttr $scope.line_items, "order", Orders.byID
Dereferencer.dereferenceAttr $scope.lineItems, "supplier", Enterprises.byID
Dereferencer.dereferenceAttr $scope.lineItems, "order", Orders.byID
$scope.bulk_order_form.$setPristine()
StatusMessage.clear()
unless $scope.initialized
$scope.initialized = true
$timeout ->
$scope.resetSelectFilters()
$scope.$watch 'bulk_order_form.$dirty', (newVal, oldVal) ->
if newVal == true
@@ -104,12 +77,13 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.deleteLineItem = (lineItem) ->
if ($scope.confirmDelete && confirm(t "are_you_sure")) || !$scope.confirmDelete
LineItems.delete lineItem
LineItems.delete lineItem, =>
$scope.lineItems.splice $scope.lineItems.indexOf(lineItem), 1
$scope.deleteLineItems = (lineItemsToDelete) ->
$scope.deleteLineItems = (lineItems) ->
existingState = $scope.confirmDelete
$scope.confirmDelete = false
$scope.deleteLineItem lineItem for lineItem in lineItemsToDelete when lineItem.checked
$scope.deleteLineItem lineItem for lineItem in lineItems when lineItem.checked
$scope.confirmDelete = existingState
$scope.allBoxesChecked = ->
@@ -180,5 +154,4 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
lineItem.final_weight_volume = LineItems.pristineByID[lineItem.id].final_weight_volume * lineItem.quantity / LineItems.pristineByID[lineItem.id].quantity
$scope.weightAdjustedPrice(lineItem)
$scope.resetFilters()
$scope.refreshData()

View File

@@ -14,10 +14,9 @@ angular.module("admin.payments").factory 'AdminStripeElements', ($rootScope, Sta
@stripe.createToken(@card, cardData).then (response) =>
if(response.error)
StatusMessage.display 'error', response.error.message
console.error(JSON.stringify(response.error))
else
secrets.token = response.token.id
secrets.cc_type = @mapTokenApiCardBrand(response.token.card.brand)
secrets.cc_type = @mapCC(response.token.card.brand)
secrets.card = response.token.card
submit()
@@ -30,16 +29,15 @@ angular.module("admin.payments").factory 'AdminStripeElements', ($rootScope, Sta
@stripe.createPaymentMethod({ type: 'card', card: @card }, @card, cardData).then (response) =>
if(response.error)
StatusMessage.display 'error', response.error.message
console.error(JSON.stringify(response.error))
else
secrets.token = response.paymentMethod.id
secrets.cc_type = @mapPaymentMethodsApiCardBrand(response.paymentMethod.card.brand)
secrets.cc_type = response.paymentMethod.card.brand
secrets.card = response.paymentMethod.card
submit()
# Maps the brand returned by Stripe's tokenAPI to that required by activemerchant
mapTokenApiCardBrand: (cardBrand) ->
switch cardBrand
# Maps the brand returned by Stripe to that required by activemerchant
mapCC: (ccType) ->
switch ccType
when 'MasterCard' then return 'master'
when 'Visa' then return 'visa'
when 'American Express' then return 'american_express'
@@ -47,14 +45,6 @@ angular.module("admin.payments").factory 'AdminStripeElements', ($rootScope, Sta
when 'JCB' then return 'jcb'
when 'Diners Club' then return 'diners_club'
# Maps the brand returned by Stripe's paymentMethodsAPI to that required by activemerchant
mapPaymentMethodsApiCardBrand: (cardBrand) ->
switch cardBrand
when 'mastercard' then return 'master'
when 'amex' then return 'american_express'
when 'diners' then return 'diners_club'
else return cardBrand # a few brands are equal, for example, visa
# It doesn't matter if any of these are nil, all are optional.
makeCardData: (secrets) ->
{'name': secrets.name,

View File

@@ -2,6 +2,7 @@ angular.module("admin.resources").factory 'LineItemResource', ($resource) ->
$resource('/admin/bulk_line_items/:id.json', {}, {
'index':
method: 'GET'
isArray: true
'update':
method: 'PUT'
transformRequest: (data, headersGetter) =>

View File

@@ -1,27 +1,20 @@
angular.module("admin.resources").factory 'LineItems', ($q, LineItemResource) ->
new class LineItems
all: []
byID: {}
pristineByID: {}
pagination: {}
index: (params={}, callback=null) ->
request = LineItemResource.index params, (data) =>
LineItemResource.index params, (data) =>
@load(data)
(callback || angular.noop)(data)
@all.$promise = request.$promise
@all
resetData: ->
@all.length = 0
@byID = {}
@pristineByID = {}
load: (data) ->
angular.extend(@pagination, data.pagination)
load: (lineItems) ->
@resetData()
for lineItem in data.line_items
@all.push lineItem
for lineItem in lineItems
@byID[lineItem.id] = lineItem
@pristineByID[lineItem.id] = angular.copy(lineItem)
@@ -32,9 +25,8 @@ angular.module("admin.resources").factory 'LineItems', ($q, LineItemResource) ->
save: (lineItem) ->
deferred = $q.defer()
lineItemResource = new LineItemResource(lineItem)
lineItem.errors = {}
lineItemResource.$update({id: lineItem.id})
lineItem.$update({id: lineItem.id})
.then( (data) =>
@pristineByID[lineItem.id] = angular.copy(lineItem)
deferred.resolve(data)
@@ -62,10 +54,8 @@ angular.module("admin.resources").factory 'LineItems', ($q, LineItemResource) ->
delete: (lineItem, callback=null) ->
deferred = $q.defer()
lineItemResource = new LineItemResource(lineItem)
lineItemResource.$delete({id: lineItem.id})
lineItem.$delete({id: lineItem.id})
.then( (data) =>
@all.splice(@all.indexOf(lineItem),1)
delete @byID[lineItem.id]
delete @pristineByID[lineItem.id]
(callback || angular.noop)(data)

View File

@@ -43,11 +43,12 @@ angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl",
$scope.fetchProducts = ->
url = "/api/products/overridable?page=::page::;per_page=100"
PagedFetcher.fetch url, $scope.addProducts
PagedFetcher.fetch url, (data) => $scope.addProducts data.products
$scope.addProducts = (data) ->
$scope.products = $scope.products.concat data.products
VariantOverrides.ensureDataFor hubs, data.products
$scope.addProducts = (products) ->
$scope.products = $scope.products.concat products
VariantOverrides.ensureDataFor hubs, products
$scope.displayDirty = ->
if DirtyVariantOverrides.count() > 0

View File

@@ -9,8 +9,6 @@
#= require angular-animate
#= require angular-resource
#= require lodash.underscore.js
# bluebird.js is a dependency of angular-google-maps.js 2.0.0
#= require bluebird.js
#= require angular-scroll.min.js
#= require angular-google-maps.min.js
#= require ../shared/mm-foundation-tpls-0.9.0-20180826174721.min.js

View File

@@ -9,13 +9,8 @@ Darkswarm.controller "EnterprisesCtrl", ($scope, $rootScope, $timeout, $location
$scope.show_closed = false
$scope.filtersActive = false
$scope.distanceMatchesShown = false
$scope.closed_shops_loading = false
$scope.closed_shops_loaded = false
$scope.$watch "query", (query)->
$scope.resetSearch(query)
$scope.resetSearch = (query) ->
Enterprises.flagMatching query
Search.search query
$rootScope.$broadcast 'enterprisesChanged'
@@ -24,7 +19,6 @@ Darkswarm.controller "EnterprisesCtrl", ($scope, $rootScope, $timeout, $location
$timeout ->
Enterprises.calculateDistance query, $scope.firstNameMatch()
$rootScope.$broadcast 'enterprisesChanged'
$scope.closed_shops_loading = false
$timeout ->
if $location.search()['show_closed']?
@@ -79,12 +73,6 @@ Darkswarm.controller "EnterprisesCtrl", ($scope, $rootScope, $timeout, $location
undefined
$scope.showClosedShops = ->
unless $scope.closed_shops_loaded
$scope.closed_shops_loading = true
$scope.closed_shops_loaded = true
Enterprises.loadClosedEnterprises().then ->
$scope.resetSearch($scope.query)
$scope.show_closed = true
$location.search('show_closed', '1')

View File

@@ -24,7 +24,7 @@ Darkswarm.controller "HubNodeCtrl", ($scope, HashNavigation, CurrentHub, $http,
$scope.shopfront_loading = true
$scope.toggle_tab(event)
$http.get("/api/shops/" + $scope.hub.id)
$http.get("/api/enterprises/" + $scope.hub.id + "/shopfront")
.success (data) ->
$scope.shopfront_loading = false
$scope.hub = data

View File

@@ -24,7 +24,7 @@ Darkswarm.controller "ProducerNodeCtrl", ($scope, HashNavigation, $anchorScroll,
$scope.shopfront_loading = true
$scope.toggle_tab(event)
$http.get("/api/shops/" + $scope.producer.id)
$http.get("/api/enterprises/" + $scope.producer.id + "/shopfront")
.success (data) ->
$scope.shopfront_loading = false
$scope.producer = data

View File

@@ -7,7 +7,7 @@ window.Darkswarm = angular.module("Darkswarm", [
'templates',
'ngSanitize',
'ngAnimate',
'uiGmapgoogle-maps',
'google-maps',
'duScroll',
'angularFileUpload',
'angularSlideables'

View File

@@ -1,6 +1,6 @@
Darkswarm.directive 'mapOsmTiles', ($timeout) ->
restrict: 'E'
require: '^uiGmapGoogleMap'
require: '^googleMap'
scope: {}
link: (scope, elem, attrs, ctrl) ->
$timeout =>

View File

@@ -1,7 +1,7 @@
Darkswarm.directive 'mapSearch', ($timeout, Search) ->
# Install a basic search field in a map
restrict: 'E'
require: ['^uiGmapGoogleMap', 'ngModel']
require: ['^googleMap', 'ngModel']
replace: true
template: '<input id="pac-input" ng-model="query" placeholder="' + t('location_placeholder') + '"></input>'
scope: {}

View File

@@ -14,28 +14,15 @@ Darkswarm.factory 'Checkout', ($injector, CurrentOrder, ShippingMethods, StripeE
submit: =>
Loading.message = t 'submitting_order'
$http.put('/checkout.json', {order: @preprocess()})
.then (response) =>
Navigation.go response.data.path
.catch (response) =>
try
@handle_checkout_error_response(response)
catch error
@loadFlash(error: t("checkout.failed")) # inform the user about the unexpected error
throw error # generate a BugsnagJS alert
handle_checkout_error_response: (response) =>
if response.data.path
Navigation.go response.data.path
else
throw response unless response.data.flash
@errors = response.data.errors
@loadFlash(response.data.flash)
loadFlash: (flash) =>
Loading.clear()
RailsFlashLoader.loadFlash(flash)
$http.put('/checkout.json', {order: @preprocess()}).success (data, status)=>
Navigation.go data.path
.error (response, status)=>
if response.path
Navigation.go response.path
else
Loading.clear()
@errors = response.errors
RailsFlashLoader.loadFlash(response.flash)
# Rails wants our Spree::Address data to be provided with _attributes
preprocess: ->

View File

@@ -5,7 +5,7 @@ Darkswarm.factory "EnterpriseModal", ($modal, $rootScope, $http)->
scope = $rootScope.$new(true) # Spawn an isolate to contain the enterprise
scope.embedded_layout = window.location.search.indexOf("embedded_shopfront=true") != -1
$http.get("/api/shops/" + enterprise.id).success (data) ->
$http.get("/api/enterprises/" + enterprise.id + "/shopfront").success (data) ->
scope.enterprise = data
$modal.open(templateUrl: "enterprise_modal.html", scope: scope)
.error (data) ->

View File

@@ -1,30 +1,27 @@
Darkswarm.factory 'Enterprises', (enterprises, ShopsResource, CurrentHub, Taxons, Dereferencer, Matcher, Geo, $rootScope) ->
Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Taxons, Dereferencer, Matcher, Geo, $rootScope) ->
new class Enterprises
enterprises: []
enterprises_by_id: {}
constructor: ->
# Populate Enterprises.enterprises from json in page.
@initEnterprises(enterprises)
@enterprises = enterprises
initEnterprises: (enterprises) ->
# Map enterprises to id/object pairs for lookup.
for enterprise in enterprises
@enterprises.push enterprise
@enterprises_by_id[enterprise.id] = enterprise
# Replace enterprise and taxons ids with actual objects.
@dereferenceEnterprises(enterprises)
@dereferenceEnterprises()
@producers = @enterprises.filter (enterprise)->
enterprise.category in ["producer_hub", "producer_shop", "producer"]
@hubs = @enterprises.filter (enterprise)->
enterprise.category in ["hub", "hub_profile", "producer_hub", "producer_shop"]
dereferenceEnterprises: (enteprises) ->
dereferenceEnterprises: ->
if CurrentHub.hub?.id
CurrentHub.hub = @enterprises_by_id[CurrentHub.hub.id]
for enterprise in enterprises
for enterprise in @enterprises
@dereferenceEnterprise enterprise
dereferenceEnterprise: (enterprise) ->
@@ -45,12 +42,6 @@ Darkswarm.factory 'Enterprises', (enterprises, ShopsResource, CurrentHub, Taxons
for enterprise in new_enterprises
@enterprises_by_id[enterprise.id] = enterprise
loadClosedEnterprises: ->
request = ShopsResource.closed_shops {}, (data) =>
@initEnterprises(data)
request.$promise
flagMatching: (query) ->
for enterprise in @enterprises
enterprise.matches_name_query = if query? && query.length > 0
@@ -59,7 +50,7 @@ Darkswarm.factory 'Enterprises', (enterprises, ShopsResource, CurrentHub, Taxons
false
calculateDistance: (query, firstMatching) ->
if query?.length > 0 and Geo.OK
if query?.length > 0
if firstMatching?
@setDistanceFrom firstMatching
else

View File

@@ -1,6 +1,6 @@
Darkswarm.service "Geo", ->
new class Geo
OK: google?.maps?.GeocoderStatus?.OK
OK: google.maps.GeocoderStatus.OK
# Usage:
# Geo.geocode address, (results, status) ->

View File

@@ -1,7 +0,0 @@
Darkswarm.factory 'ShopsResource', ($resource) ->
$resource('/api/shops/:id.json', {}, {
'closed_shops':
method: 'GET'
isArray: true
url: '/api/shops/closed_shops.json'
})

View File

@@ -15,11 +15,9 @@ Darkswarm.factory 'StripeElements', ($rootScope, Loading, RailsFlashLoader) ->
if(response.error)
Loading.clear()
RailsFlashLoader.loadFlash({error: t("error") + ": #{response.error.message}"})
@triggerAngularDigest()
console.error(JSON.stringify(response.error))
else
secrets.token = response.token.id
secrets.cc_type = @mapTokenApiCardBrand(response.token.card.brand)
secrets.cc_type = @mapCC(response.token.card.brand)
secrets.card = response.token.card
submit()
@@ -34,21 +32,15 @@ Darkswarm.factory 'StripeElements', ($rootScope, Loading, RailsFlashLoader) ->
if(response.error)
Loading.clear()
RailsFlashLoader.loadFlash({error: t("error") + ": #{response.error.message}"})
@triggerAngularDigest()
console.error(JSON.stringify(response.error))
else
secrets.token = response.paymentMethod.id
secrets.cc_type = @mapPaymentMethodsApiCardBrand(response.paymentMethod.card.brand)
secrets.cc_type = response.paymentMethod.card.brand
secrets.card = response.paymentMethod.card
submit()
triggerAngularDigest: ->
# $evalAsync is improved way of triggering a digest without calling $apply
$rootScope.$evalAsync()
# Maps the brand returned by Stripe's tokenAPI to that required by activemerchant
mapTokenApiCardBrand: (cardBrand) ->
switch cardBrand
# Maps the brand returned by Stripe to that required by activemerchant
mapCC: (ccType) ->
switch ccType
when 'MasterCard' then return 'master'
when 'Visa' then return 'visa'
when 'American Express' then return 'american_express'
@@ -56,14 +48,6 @@ Darkswarm.factory 'StripeElements', ($rootScope, Loading, RailsFlashLoader) ->
when 'JCB' then return 'jcb'
when 'Diners Club' then return 'diners_club'
# Maps the brand returned by Stripe's paymentMethodsAPI to that required by activemerchant
mapPaymentMethodsApiCardBrand: (cardBrand) ->
switch cardBrand
when 'mastercard' then return 'master'
when 'amex' then return 'american_express'
when 'diners' then return 'diners_club'
else return cardBrand # a few brands are equal, for example, visa
# It doesn't matter if any of these are nil, all are optional.
makeCardData: (secrets) ->
{'name': secrets.name,

View File

@@ -109,4 +109,9 @@ checkout {
}
}
}
.error {
color: #c82020;
}
}

View File

@@ -14,10 +14,5 @@
.more-controls {
text-align: center;
.spinner {
height: 2.25em;
margin-right: 0.5em;
}
}
}

View File

@@ -4,17 +4,18 @@ module Admin
#
def index
order_params = params[:q].andand.delete :order
orders = order_permissions.editable_orders.ransack(order_params).result
@line_items = order_permissions.
order_permissions = ::Permissions::Order.new(spree_current_user)
orders = order_permissions.
editable_orders.ransack(order_params).result
line_items = order_permissions.
editable_line_items.where(order_id: orders).
includes(variant: { option_values: :option_type }).
ransack(params[:q]).result.
reorder('spree_line_items.order_id ASC, spree_line_items.id ASC')
@line_items = @line_items.page(page).per(params[:per_page]) if using_pagination?
render json: { line_items: serialized_line_items, pagination: pagination_data }
render_as_json line_items
end
# PUT /admin/bulk_line_items/:id.json
@@ -64,12 +65,6 @@ module Admin
Api::Admin::LineItemSerializer
end
def serialized_line_items
ActiveModel::ArraySerializer.new(
@line_items, each_serializer: serializer(nil)
)
end
def authorize_update!
authorize! :update, order
authorize! :read, order
@@ -78,28 +73,5 @@ module Admin
def order
@line_item.order
end
def order_permissions
::Permissions::Order.new(spree_current_user)
end
def using_pagination?
params[:per_page]
end
def pagination_data
return unless using_pagination?
{
results: @line_items.total_count,
pages: @line_items.num_pages,
page: page.to_i,
per_page: params[:per_page].to_i
}
end
def page
params[:page] || 1
end
end
end

View File

@@ -17,9 +17,8 @@ module Admin
respond_to do |format|
format.html
format.json do
render_as_json @collection,
tag_rule_mapping: tag_rule_mapping,
customer_tags: customer_tags_by_id
tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: params[:enterprise_id]))
render_as_json @collection, tag_rule_mapping: tag_rule_mapping
end
end
end
@@ -65,13 +64,8 @@ module Admin
def collection
return Customer.where("1=0") unless json_request? && params[:enterprise_id].present?
Customer.of(managed_enterprise_id).
includes(:bill_address, :ship_address, user: :credit_cards)
end
def managed_enterprise_id
@managed_enterprise_id ||= Enterprise.managed_by(spree_current_user).
select('enterprises.id').find_by_id(params[:enterprise_id])
enterprise = Enterprise.managed_by(spree_current_user).find_by_id(params[:enterprise_id])
Customer.of(enterprise)
end
def load_managed_shops
@@ -86,26 +80,5 @@ module Admin
def ams_prefix_whitelist
[:subscription]
end
def tag_rule_mapping
TagRule.mapping_for(Enterprise.where(id: managed_enterprise_id))
end
# Fetches tags for all customers of the enterprise and returns a hash indexed by customer_id
def customer_tags_by_id
customer_tags = ::ActsAsTaggableOn::Tag.
joins(:taggings).
includes(:taggings).
where(taggings:
{ taggable_type: 'Customer',
taggable_id: Customer.of(managed_enterprise_id),
context: 'tags' })
customer_tags.each_with_object({}) do |tag, indexed_hash|
customer_id = tag.taggings.first.taggable_id
indexed_hash[customer_id] ||= []
indexed_hash[customer_id] << tag.name
end
end
end
end

View File

@@ -16,7 +16,7 @@ module Admin
render_as_json @collection,
ams_prefix: params[:ams_prefix],
current_user: spree_current_user,
subscriptions_count: OrderManagement::Subscriptions::Count.new(@collection)
subscriptions_count: SubscriptionsCount.new(@collection)
end
end
end
@@ -74,7 +74,7 @@ module Admin
render_as_json @order_cycles,
ams_prefix: 'index',
current_user: spree_current_user,
subscriptions_count: OrderManagement::Subscriptions::Count.new(@collection)
subscriptions_count: SubscriptionsCount.new(@collection)
else
order_cycle = order_cycle_set.collection.find{ |oc| oc.errors.present? }
render json: { errors: order_cycle.errors.full_messages }, status: :unprocessable_entity

View File

@@ -9,19 +9,25 @@ module Admin
def cancel
if @proxy_order.cancel
render_as_json @proxy_order
respond_with(@proxy_order) do |format|
format.json { render_as_json @proxy_order }
end
else
render json: { errors: [t('admin.proxy_orders.cancel.could_not_cancel_the_order')] },
status: :unprocessable_entity
respond_with(@proxy_order) do |format|
format.json { render json: { errors: [t('admin.proxy_orders.cancel.could_not_cancel_the_order')] }, status: :unprocessable_entity }
end
end
end
def resume
if @proxy_order.resume
render_as_json @proxy_order
respond_with(@proxy_order) do |format|
format.json { render_as_json @proxy_order }
end
else
render json: { errors: [t('admin.proxy_orders.resume.could_not_resume_the_order')] },
status: :unprocessable_entity
respond_with(@proxy_order) do |format|
format.json { render json: { errors: [t('admin.proxy_orders.resume.could_not_resume_the_order')] }, status: :unprocessable_entity }
end
end
end
end

View File

@@ -1,5 +1,5 @@
require 'open_food_network/permissions'
require 'order_management/subscriptions/proxy_order_syncer'
require 'open_food_network/proxy_order_syncer'
module Admin
class SchedulesController < ResourceController
@@ -81,7 +81,7 @@ module Admin
return unless removed_ids.any? || new_ids.any?
subscriptions = Subscription.where(schedule_id: @schedule)
syncer = OrderManagement::Subscriptions::ProxyOrderSyncer.new(subscriptions)
syncer = OpenFoodNetwork::ProxyOrderSyncer.new(subscriptions)
syncer.sync!
end
end

View File

@@ -56,7 +56,7 @@ module Admin
end
def variant_if_eligible(variant_id)
OrderManagement::Subscriptions::VariantsList.eligible_variants(@shop).find_by_id(variant_id)
SubscriptionVariantsService.eligible_variants(@shop).find_by_id(variant_id)
end
end
end

View File

@@ -1,4 +1,5 @@
require 'open_food_network/permissions'
require 'open_food_network/proxy_order_syncer'
module Admin
class SubscriptionsController < ResourceController
@@ -64,7 +65,7 @@ module Admin
private
def save_form_and_render(render_issues = true)
form = OrderManagement::Subscriptions::Form.new(@subscription, params[:subscription])
form = SubscriptionForm.new(@subscription, params[:subscription])
unless form.save
render json: { errors: form.json_errors }, status: :unprocessable_entity
return

View File

@@ -73,10 +73,8 @@ module Admin
end
def collection
@variant_overrides = VariantOverride.
includes(variant: :product).
for_hubs(params[:hub_id] || @hubs).
select { |vo| vo.variant.present? }
@variant_overrides = VariantOverride.includes(:variant).for_hubs(params[:hub_id] || @hubs)
@variant_overrides.select { |vo| vo.variant.present? }
end
def collection_actions

View File

@@ -5,6 +5,7 @@ module Api
before_filter :override_sells, only: [:create, :update]
before_filter :override_visible, only: [:create, :update]
respond_to :json
skip_authorization_check only: [:shopfront]
def create
authorize! :create, Enterprise
@@ -41,6 +42,12 @@ module Api
end
end
def shopfront
enterprise = Enterprise.find_by_id(params[:id])
render text: Api::EnterpriseShopfrontSerializer.new(enterprise).to_json, status: :ok
end
private
def override_owner

View File

@@ -69,12 +69,12 @@ module Api
end
def overridable
producer_ids = OpenFoodNetwork::Permissions.new(current_api_user).
variant_override_producers.by_name.select('enterprises.id')
producers = OpenFoodNetwork::Permissions.new(current_api_user).
variant_override_producers.by_name
@products = paged_products_for_producers producer_ids
@products = paged_products_for_producers producers
render_paged_products @products, ::Api::Admin::ProductSimpleSerializer
render_paged_products @products
end
# POST /api/products/:product_id/clone
@@ -118,20 +118,19 @@ module Api
]
end
def paged_products_for_producers(producer_ids)
def paged_products_for_producers(producers)
Spree::Product.scoped.
merge(product_scope).
includes(variants: [:product, :default_price, :stock_items]).
where(supplier_id: producer_ids).
where(supplier_id: producers).
by_producer.by_name.
ransack(params[:q]).result.
page(params[:page]).per(params[:per_page])
end
def render_paged_products(products, product_serializer = ::Api::Admin::ProductSerializer)
def render_paged_products(products)
serializer = ActiveModel::ArraySerializer.new(
products,
each_serializer: product_serializer
each_serializer: ::Api::Admin::ProductSerializer
)
render text: {

View File

@@ -1,27 +0,0 @@
# frozen_string_literal: true
module Api
class ShopsController < BaseController
respond_to :json
skip_authorization_check only: [:show, :closed_shops]
def show
enterprise = Enterprise.find_by_id(params[:id])
render text: Api::EnterpriseShopfrontSerializer.new(enterprise).to_json, status: :ok
end
def closed_shops
@active_distributor_ids = []
@earliest_closing_times = []
serialized_closed_shops = ActiveModel::ArraySerializer.new(
ShopsListService.new.closed_shops,
each_serializer: Api::EnterpriseSerializer,
data: OpenFoodNetwork::EnterpriseInjectionData.new
)
render json: serialized_closed_shops
end
end
end

View File

@@ -29,19 +29,17 @@ class BaseController < ApplicationController
return
end
@order_cycles = Shop::OrderCyclesList.new(@distributor, current_customer).call
@order_cycles = OrderCycle.with_distributor(@distributor).active
.order(@distributor.preferred_shopfront_order_cycle_order)
set_order_cycle
end
applicator = OpenFoodNetwork::TagRuleApplicator.new(@distributor,
"FilterOrderCycles",
current_customer.andand.tag_list)
applicator.filter!(@order_cycles)
# Default to the only order cycle if there's only one
#
# Here we need to use @order_cycles.size not @order_cycles.count
# because OrderCyclesList returns a modified ActiveRecord::Relation
# and these modifications are not seen if it is reloaded with count
def set_order_cycle
return if @order_cycles.size != 1
current_order(true).set_order_cycle! @order_cycles.first
# And default to the only order cycle if there's only the one
if @order_cycles.count == 1
current_order(true).set_order_cycle! @order_cycles.first
end
end
end

View File

@@ -53,8 +53,9 @@ class CheckoutController < Spree::StoreController
rescue Spree::Core::GatewayError => e
rescue_from_spree_gateway_error(e)
rescue StandardError => e
Bugsnag.notify(e)
flash[:error] = I18n.t("checkout.failed")
update_failed(e)
update_failed
end
# Clears the cached order. Required for #current_order to return a new order
@@ -164,7 +165,7 @@ class CheckoutController < Spree::StoreController
checkout_succeeded
redirect_to(order_path(@order)) && return
else
flash[:error] = order_error
flash[:error] = order_workflow_error
checkout_failed
end
end
@@ -179,6 +180,7 @@ class CheckoutController < Spree::StoreController
next if advance_order_state(@order)
flash[:error] = order_workflow_error
return update_failed
end
@@ -203,7 +205,7 @@ class CheckoutController < Spree::StoreController
false
end
def order_error
def order_workflow_error
if @order.errors.present?
@order.errors.full_messages.to_sentence
else
@@ -216,7 +218,7 @@ class CheckoutController < Spree::StoreController
checkout_succeeded
update_succeeded_response
else
update_failed(RuntimeError.new("Order not complete after the checkout workflow"))
update_failed
end
end
@@ -242,10 +244,7 @@ class CheckoutController < Spree::StoreController
end
end
def update_failed(error = RuntimeError.new(order_error))
Bugsnag.notify(error)
flash[:error] = order_error if flash.empty?
def update_failed
checkout_failed
update_failed_response
end

View File

@@ -96,8 +96,8 @@ class EnterprisesController < BaseController
end
def reset_order_cycle(order, distributor)
order_cycles = Shop::OrderCyclesList.new(distributor, current_customer).call
order.order_cycle = order_cycles.first if order_cycles.size == 1
order_cycle_options = OrderCycle.active.with_distributor(distributor)
order.order_cycle = order_cycle_options.first if order_cycle_options.count == 1
end
def shop_order_cycles

View File

@@ -5,23 +5,12 @@ class HomeController < BaseController
def index
if ContentConfig.home_show_stats
@num_distributors = cached_count('distributors', Enterprise.is_distributor.activated.visible)
@num_producers = cached_count('producers', Enterprise.is_primary_producer.activated.visible)
@num_orders = cached_count('orders', Spree::Order.complete)
@num_users = cached_count(
'users', Spree::Order.complete.select('DISTINCT spree_orders.user_id')
)
@num_distributors = Enterprise.is_distributor.activated.visible.count
@num_producers = Enterprise.is_primary_producer.activated.visible.count
@num_users = Spree::Order.complete.count('DISTINCT user_id')
@num_orders = Spree::Order.complete.count
end
end
def sell; end
private
# Cache the value of the query count for 24 hours
def cached_count(key, query)
Rails.cache.fetch("home_stats_count_#{key}", expires_in: 1.day, race_condition_ttl: 10) do
query.count
end
end
end

View File

@@ -4,6 +4,13 @@ class ShopsController < BaseController
before_filter :enable_embedded_shopfront
def index
@enterprises = ShopsListService.new.open_shops
@enterprises = Enterprise
.activated
.visible
.is_distributor
.includes(address: [:state, :country])
.includes(:properties)
.includes(supplied_products: :properties)
.all
end
end

View File

@@ -1,4 +1,6 @@
module ApplicationHelper
include FoundationRailsHelper::FlashHelper
def feature?(feature)
OpenFoodNetwork::FeatureToggle.enabled? feature
end

View File

@@ -47,6 +47,17 @@ module OrderCyclesHelper
end
end
def order_cycle_options
@order_cycles.
with_distributor(current_distributor).
map { |oc| [order_cycle_close_to_s(oc.orders_close_at), oc.id] }
end
def order_cycle_close_to_s(orders_close_at)
"%s (%s)" % [orders_close_at.strftime("#{orders_close_at.day.ordinalize} %b"),
distance_of_time_in_words_to_now(orders_close_at)]
end
def active_order_cycle_for_distributor?(_distributor)
OrderCycle.active.with_distributor(@distributor).present?
end

View File

@@ -1,9 +1,17 @@
require 'order_management/subscriptions/summarizer'
require 'open_food_network/subscription_payment_updater'
require 'open_food_network/subscription_summarizer'
# Confirms orders of unconfirmed proxy orders in recently closed Order Cycles
class SubscriptionConfirmJob
def perform
confirm_proxy_orders!
ids = proxy_orders.pluck(:id)
proxy_orders.update_all(confirmed_at: Time.zone.now)
ProxyOrder.where(id: ids).each do |proxy_order|
Rails.logger.info "Confirming Order for Proxy Order #{proxy_order.id}"
@order = proxy_order.order
process!
end
send_confirmation_summary_emails
end
private
@@ -12,26 +20,10 @@ class SubscriptionConfirmJob
delegate :record_and_log_error, :send_confirmation_summary_emails, to: :summarizer
def summarizer
@summarizer ||= OrderManagement::Subscriptions::Summarizer.new
@summarizer ||= OpenFoodNetwork::SubscriptionSummarizer.new
end
def confirm_proxy_orders!
# Fetch all unconfirmed proxy orders
unconfirmed_proxy_orders_ids = unconfirmed_proxy_orders.pluck(:id)
# Mark these proxy orders as confirmed
unconfirmed_proxy_orders.update_all(confirmed_at: Time.zone.now)
# Confirm these proxy orders
ProxyOrder.where(id: unconfirmed_proxy_orders_ids).each do |proxy_order|
Rails.logger.info "Confirming Order for Proxy Order #{proxy_order.id}"
confirm_order!(proxy_order.order)
end
send_confirmation_summary_emails
end
def unconfirmed_proxy_orders
def proxy_orders
ProxyOrder.not_canceled.where('confirmed_at IS NULL AND placed_at IS NOT NULL')
.joins(:order_cycle).merge(recently_closed_order_cycles)
.joins(:order).merge(Spree::Order.complete.not_state('canceled'))
@@ -41,55 +33,30 @@ class SubscriptionConfirmJob
OrderCycle.closed.where('order_cycles.orders_close_at BETWEEN (?) AND (?) OR order_cycles.updated_at BETWEEN (?) AND (?)', 1.hour.ago, Time.zone.now, 1.hour.ago, Time.zone.now)
end
# It sets up payments, processes payments and sends confirmation emails
def confirm_order!(order)
record_order(order)
def process!
record_order(@order)
update_payment! if @order.payment_required?
return send_failed_payment_email if @order.errors.present?
if process_payment!(order)
send_confirmation_email(order)
else
send_failed_payment_email(order)
end
@order.process_payments! if @order.payment_required?
return send_failed_payment_email if @order.errors.present?
send_confirm_email
end
def process_payment!(order)
return false if order.errors.present?
return true unless order.payment_required?
setup_payment!(order)
return false if order.errors.any?
authorize_payment!(order)
return false if order.errors.any?
order.process_payments!
return false if order.errors.any?
true
def update_payment!
OpenFoodNetwork::SubscriptionPaymentUpdater.new(@order).update!
end
def setup_payment!(order)
OrderManagement::Subscriptions::PaymentSetup.new(order).call!
return if order.errors.any?
OrderManagement::Subscriptions::StripePaymentSetup.new(order).call!
def send_confirm_email
@order.update!
record_success(@order)
SubscriptionMailer.confirmation_email(@order).deliver
end
def authorize_payment!(order)
return if order.subscription.payment_method.class != Spree::Gateway::StripeSCA
OrderManagement::Subscriptions::StripeScaPaymentAuthorize.new(order).call!
end
def send_confirmation_email(order)
order.update!
record_success(order)
SubscriptionMailer.confirmation_email(order).deliver
end
def send_failed_payment_email(order)
order.update!
record_and_log_error(:failed_payment, order)
SubscriptionMailer.failed_payment_email(order).deliver
def send_failed_payment_email
@order.update!
record_and_log_error(:failed_payment, @order)
SubscriptionMailer.failed_payment_email(@order).deliver
end
end

View File

@@ -1,4 +1,4 @@
require 'order_management/subscriptions/summarizer'
require 'open_food_network/subscription_summarizer'
class SubscriptionPlacementJob
def perform
@@ -17,7 +17,7 @@ class SubscriptionPlacementJob
delegate :record_and_log_error, :send_placement_summary_emails, to: :summarizer
def summarizer
@summarizer ||= OrderManagement::Subscriptions::Summarizer.new
@summarizer ||= OpenFoodNetwork::SubscriptionSummarizer.new
end
def proxy_orders

View File

@@ -17,7 +17,7 @@ class OrderCycle < ActiveRecord::Base
has_many :distributors, source: :receiver, through: :cached_outgoing_exchanges, uniq: true
has_and_belongs_to_many :schedules, join_table: 'order_cycle_schedules'
has_paper_trail meta: { custom_data: proc { |order_cycle| order_cycle.schedule_ids.to_s } }
has_paper_trail meta: { custom_data: :schedule_ids }
attr_accessor :incoming_exchanges, :outgoing_exchanges

View File

@@ -1,6 +1,6 @@
class Schedule < ActiveRecord::Base
has_and_belongs_to_many :order_cycles, join_table: 'order_cycle_schedules'
has_paper_trail meta: { custom_data: proc { |schedule| schedule.order_cycle_ids.to_s } }
has_paper_trail meta: { custom_data: :order_cycle_ids }
has_many :coordinators, uniq: true, through: :order_cycles

View File

@@ -1,36 +0,0 @@
# frozen_string_literal: true
module Spree
module Stock
class Quantifier
attr_reader :stock_items
def initialize(variant)
@variant = variant
@stock_items = fetch_stock_items
end
def total_on_hand
stock_items.sum(&:count_on_hand)
end
def backorderable?
stock_items.any?(&:backorderable)
end
def can_supply?(required)
total_on_hand >= required || backorderable?
end
private
def fetch_stock_items
# Don't re-fetch associated stock items from the DB if we've already eager-loaded them
return @variant.stock_items.to_a if @variant.stock_items.loaded?
Spree::StockItem.joins(:stock_location).
where(:variant_id => @variant, Spree::StockLocation.table_name => { active: true })
end
end
end
end

View File

@@ -125,12 +125,7 @@ module Spree
end
def default_card
# Don't re-fetch associated cards from the DB if they're already eager-loaded
if credit_cards.loaded?
credit_cards.to_a.find(&:is_default)
else
credit_cards.where(is_default: true).first
end
credit_cards.where(is_default: true).first
end
# Checks whether the specified user is a superadmin, with full control of the

View File

@@ -15,10 +15,8 @@ class VariantOverrideSet < ModelSet
tag_list.empty?
end
# Override of ModelSet method to allow us to check presence of a tag_list (which is not an attribute)
# This method will delete VariantOverrides that have no values (see deletable? above)
# If the user sets all values to nil in the UI the VO will be deleted from the DB
def collection_to_delete
# Override of ModelSet method to allow us to check presence of a tag_list (which is not an attribute)
deleted = []
collection.delete_if { |e| deleted << e if @delete_if.andand.call(e.attributes, e.tag_list) }
deleted

View File

@@ -6,7 +6,7 @@ class Api::Admin::CustomerSerializer < ActiveModel::Serializer
has_one :bill_address, serializer: Api::AddressSerializer
def tag_list
customer_tag_list.join(",")
object.tag_list.join(",")
end
def name
@@ -14,7 +14,7 @@ class Api::Admin::CustomerSerializer < ActiveModel::Serializer
end
def tags
customer_tag_list.map do |tag|
object.tag_list.map do |tag|
tag_rule_map = options[:tag_rule_mapping].andand[tag]
tag_rule_map || { text: tag, rules: nil }
end
@@ -25,12 +25,4 @@ class Api::Admin::CustomerSerializer < ActiveModel::Serializer
object.user.default_card.present?
end
private
def customer_tag_list
return object.tag_list unless options[:customer_tags]
options[:customer_tags].andand[object.id] || []
end
end

View File

@@ -1,15 +0,0 @@
# frozen_string_literal: true
module Api
module Admin
class ProductSimpleSerializer < ActiveModel::Serializer
attributes :id, :name, :producer_id
has_many :variants, key: :variants, serializer: Api::Admin::VariantSimpleSerializer
def producer_id
object.supplier_id
end
end
end
end

View File

@@ -13,9 +13,9 @@ module Api
end
def in_open_and_upcoming_order_cycles
OrderManagement::Subscriptions::VariantsList.in_open_and_upcoming_order_cycles?(option_or_assigned_shop,
option_or_assigned_schedule,
object.variant)
SubscriptionVariantsService.in_open_and_upcoming_order_cycles?(option_or_assigned_shop,
option_or_assigned_schedule,
object.variant)
end
private

View File

@@ -1,32 +0,0 @@
# frozen_string_literal: true
module Api
module Admin
class VariantSimpleSerializer < ActiveModel::Serializer
attributes :id, :name, :import_date,
:options_text, :unit_value, :unit_description, :unit_to_display,
:display_as, :display_name, :name_to_display,
:price, :on_demand, :on_hand
has_many :variant_overrides
def name
if object.full_name.present?
"#{object.name} - #{object.full_name}"
else
object.name
end
end
def on_hand
return 0 if object.on_hand.nil?
object.on_hand
end
def price
object.price.nil? ? 0.to_f : object.price
end
end
end
end

View File

@@ -73,16 +73,12 @@ module Api
# This results in 3 queries per enterprise
def distributed_properties
return [] unless active
(distributed_product_properties + distributed_producer_properties).uniq do |property_object|
property_object.property.presentation
end
end
def distributed_product_properties
return [] unless active
properties = Spree::Property
.joins(products: { variants: { exchanges: :order_cycle } })
.merge(Exchange.outgoing)
@@ -95,8 +91,6 @@ module Api
end
def distributed_producer_properties
return [] unless active
properties = Spree::Property
.joins(
producer_properties: {

View File

@@ -12,8 +12,11 @@ module Checkout
def path
return unless stripe_payment_method?
payment = OrderManagement::Subscriptions::StripeScaPaymentAuthorize.new(@order).call!
raise if @order.errors.any?
payment = @order.pending_payments.last
return unless payment&.checkout?
payment.authorize!
raise unless payment.pending?
field_with_url(payment) if url?(field_with_url(payment))
end

View File

@@ -1,6 +1,6 @@
require 'open_food_network/permissions'
require 'open_food_network/proxy_order_syncer'
require 'open_food_network/order_cycle_form_applicator'
require 'order_management/subscriptions/proxy_order_syncer'
class OrderCycleForm
def initialize(order_cycle, params, user)
@@ -58,7 +58,7 @@ class OrderCycleForm
return unless schedule_ids?
return unless schedule_sync_required?
OrderManagement::Subscriptions::ProxyOrderSyncer.new(subscriptions_to_sync).sync!
OpenFoodNetwork::ProxyOrderSyncer.new(subscriptions_to_sync).sync!
end
def schedule_sync_required?

View File

@@ -40,7 +40,7 @@ class ProductsRenderer
end
def product_scoper
@product_scoper ||= OpenFoodNetwork::ScopeProductToHub.new(distributor)
OpenFoodNetwork::ScopeProductToHub.new(distributor)
end
def enterprise_fee_calculator

View File

@@ -1,31 +0,0 @@
# frozen_string_literal: true
# Lists available order cycles for a given customer in a given distributor
module Shop
class OrderCyclesList
def initialize(distributor, customer)
@distributor = distributor
@customer = customer
end
def call
order_cycles = OrderCycle.with_distributor(@distributor).active
.order(@distributor.preferred_shopfront_order_cycle_order)
apply_tag_rules!(order_cycles)
end
private
# order_cycles is a ActiveRecord::Relation that is modified with reject in the TagRuleApplicator
# If this relation is reloaded (for example by calling count on it), the modifications are lost
def apply_tag_rules!(order_cycles)
applicator = OpenFoodNetwork::TagRuleApplicator.new(@distributor,
"FilterOrderCycles",
@customer.andand.tag_list)
applicator.filter!(order_cycles)
order_cycles
end
end
end

View File

@@ -1,23 +0,0 @@
# frozen_string_literal: true
class ShopsListService
def open_shops
shops_list.ready_for_checkout.all
end
def closed_shops
shops_list.not_ready_for_checkout.all
end
private
def shops_list
Enterprise
.activated
.visible
.is_distributor
.includes(address: [:state, :country])
.includes(:properties)
.includes(supplied_products: :properties)
end
end

View File

@@ -0,0 +1,63 @@
require 'open_food_network/scope_variant_to_hub'
# Responsible for estimating prices and fees for subscriptions
# Used by SubscriptionForm as part of the create/update process
# The values calculated here are intended to be persisted in the db
class SubscriptionEstimator
def initialize(subscription)
@subscription = subscription
end
def estimate!
assign_price_estimates
assign_fee_estimates
end
private
attr_accessor :subscription
delegate :subscription_line_items, :shipping_method, :payment_method, :shop, to: :subscription
def assign_price_estimates
subscription_line_items.each do |item|
item.price_estimate =
price_estimate_for(item.variant, item.price_estimate_was)
end
end
def price_estimate_for(variant, fallback)
return fallback unless fee_calculator && variant
scoper.scope(variant)
fees = fee_calculator.indexed_fees_for(variant)
(variant.price + fees).to_d
end
def fee_calculator
return @fee_calculator unless @fee_calculator.nil?
next_oc = subscription.schedule.andand.current_or_next_order_cycle
return nil unless shop && next_oc
@fee_calculator = OpenFoodNetwork::EnterpriseFeeCalculator.new(shop, next_oc)
end
def scoper
OpenFoodNetwork::ScopeVariantToHub.new(shop)
end
def assign_fee_estimates
subscription.shipping_fee_estimate = shipping_fee_estimate
subscription.payment_fee_estimate = payment_fee_estimate
end
def shipping_fee_estimate
shipping_method.calculator.compute(subscription)
end
def payment_fee_estimate
payment_method.calculator.compute(subscription)
end
end

View File

@@ -0,0 +1,34 @@
require 'open_food_network/proxy_order_syncer'
class SubscriptionForm
attr_accessor :subscription, :params, :order_update_issues, :validator, :order_syncer, :estimator
delegate :json_errors, :valid?, to: :validator
delegate :order_update_issues, to: :order_syncer
def initialize(subscription, params = {})
@subscription = subscription
@params = params
@estimator = SubscriptionEstimator.new(subscription)
@validator = SubscriptionValidator.new(subscription)
@order_syncer = OrderSyncer.new(subscription)
end
def save
subscription.assign_attributes(params)
return false unless valid?
subscription.transaction do
estimator.estimate!
proxy_order_syncer.sync!
order_syncer.sync!
subscription.save!
end
end
private
def proxy_order_syncer
OpenFoodNetwork::ProxyOrderSyncer.new(subscription)
end
end

View File

@@ -0,0 +1,127 @@
# Encapsulation of all of the validation logic required for subscriptions
# Public interface consists of #valid? method provided by ActiveModel::Validations
# and #json_errors which compiles a serializable hash of errors
class SubscriptionValidator
include ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
attr_reader :subscription
validates :shop, :customer, :schedule, :shipping_method, :payment_method, presence: true
validates :bill_address, :ship_address, :begins_at, presence: true
validate :shipping_method_allowed?
validate :payment_method_allowed?
validate :payment_method_type_allowed?
validate :ends_at_after_begins_at?
validate :customer_allowed?
validate :schedule_allowed?
validate :credit_card_ok?
validate :subscription_line_items_present?
validate :requested_variants_available?
delegate :shop, :customer, :schedule, :shipping_method, :payment_method, to: :subscription
delegate :bill_address, :ship_address, :begins_at, :ends_at, to: :subscription
delegate :subscription_line_items, to: :subscription
def initialize(subscription)
@subscription = subscription
end
def json_errors
errors.messages.each_with_object({}) do |(k, v), errors|
errors[k] = v.map { |msg| build_msg_from(k, msg) }
end
end
private
def shipping_method_allowed?
return unless shipping_method
return if shipping_method.distributors.include?(shop)
errors.add(:shipping_method, :not_available_to_shop, shop: shop.name)
end
def payment_method_allowed?
return unless payment_method
return if payment_method.distributors.include?(shop)
errors.add(:payment_method, :not_available_to_shop, shop: shop.name)
end
def payment_method_type_allowed?
return unless payment_method
return if Subscription::ALLOWED_PAYMENT_METHOD_TYPES.include? payment_method.type
errors.add(:payment_method, :invalid_type)
end
def ends_at_after_begins_at?
# Only validates ends_at if it is present
return if begins_at.blank? || ends_at.blank?
return if ends_at > begins_at
errors.add(:ends_at, :after_begins_at)
end
def customer_allowed?
return unless customer
return if customer.enterprise == shop
errors.add(:customer, :does_not_belong_to_shop, shop: shop.name)
end
def schedule_allowed?
return unless schedule
return if schedule.coordinators.include?(shop)
errors.add(:schedule, :not_coordinated_by_shop, shop: shop.name)
end
def credit_card_ok?
return unless customer && payment_method
return unless stripe_payment_method?(payment_method)
return errors.add(:payment_method, :charges_not_allowed) unless customer.allow_charges
return if customer.user.andand.default_card.present?
errors.add(:payment_method, :no_default_card)
end
def stripe_payment_method?(payment_method)
payment_method.type == "Spree::Gateway::StripeConnect" ||
payment_method.type == "Spree::Gateway::StripeSCA"
end
def subscription_line_items_present?
return if subscription_line_items.reject(&:marked_for_destruction?).any?
errors.add(:subscription_line_items, :at_least_one_product)
end
def requested_variants_available?
subscription_line_items.each { |sli| verify_availability_of(sli.variant) }
end
def verify_availability_of(variant)
return if available_variant_ids.include? variant.id
name = "#{variant.product.name} - #{variant.full_name}"
errors.add(:subscription_line_items, :not_available, name: name)
end
def available_variant_ids
return @available_variant_ids if @available_variant_ids.present?
subscription_variant_ids = subscription_line_items.map(&:variant_id)
@available_variant_ids = SubscriptionVariantsService.eligible_variants(shop)
.where(id: subscription_variant_ids).pluck(:id)
end
def build_msg_from(k, msg)
return msg[1..-1] if msg.starts_with?("^")
errors.full_message(k, msg)
end
end

View File

@@ -0,0 +1,39 @@
class SubscriptionVariantsService
# Includes the following variants:
# - Variants of permitted producers
# - Variants of hub
# - Variants that are in outgoing exchanges where the hub is receiver
def self.eligible_variants(distributor)
variant_conditions = ["spree_products.supplier_id IN (?)", permitted_producer_ids(distributor)]
exchange_variant_ids = outgoing_exchange_variant_ids(distributor)
if exchange_variant_ids.present?
variant_conditions[0] << " OR spree_variants.id IN (?)"
variant_conditions << exchange_variant_ids
end
Spree::Variant.joins(:product).where(is_master: false).where(*variant_conditions)
end
def self.in_open_and_upcoming_order_cycles?(distributor, schedule, variant)
scope = ExchangeVariant.joins(exchange: { order_cycle: :schedules })
.where(variant_id: variant, exchanges: { incoming: false, receiver_id: distributor })
.merge(OrderCycle.not_closed)
scope = scope.where(schedules: { id: schedule })
scope.any?
end
def self.permitted_producer_ids(distributor)
other_permitted_producer_ids = EnterpriseRelationship.joins(:parent)
.permitting(distributor.id).with_permission(:add_to_order_cycle)
.merge(Enterprise.is_primary_producer)
.pluck(:parent_id)
other_permitted_producer_ids | [distributor.id]
end
def self.outgoing_exchange_variant_ids(distributor)
ExchangeVariant.select("DISTINCT exchange_variants.variant_id").joins(:exchange)
.where(exchanges: { incoming: false, receiver_id: distributor.id })
.pluck(:variant_id)
end
end

View File

@@ -0,0 +1,20 @@
class SubscriptionsCount
def initialize(order_cycles)
@order_cycles = order_cycles
end
def for(order_cycle_id)
active[order_cycle_id] || 0
end
private
attr_accessor :order_cycles
def active
return @active unless @active.nil?
return @active = [] if order_cycles.blank?
@active ||= ProxyOrder.not_canceled.group(:order_cycle_id).where(order_cycle_id: order_cycles).count
end
end

View File

@@ -1,36 +0,0 @@
# frozen_string_literal: true
# Produces mappings of variant overrides by distributor id and variant id
# The primary use case for data structured in this way is for injection into
# the initializer of the OpenFoodNetwork::ScopeVariantToHub class
class VariantOverridesIndexed
def initialize(variant_ids, distributor_ids)
@variant_ids = variant_ids
@distributor_ids = distributor_ids
end
def indexed
scoped_variant_overrides.each_with_object(hash_of_hashes) do |variant_override, indexed|
indexed[variant_override.hub_id][variant_override.variant] = variant_override
end
end
private
attr_reader :variant_ids, :distributor_ids
def scoped_variant_overrides
VariantOverride
.joins(:variant)
.preload(:variant)
.where(
hub_id: distributor_ids,
variant_id: variant_ids,
)
end
def hash_of_hashes
Hash.new { |hash, key| hash[key] = {} }
end
end

View File

@@ -1,49 +1,45 @@
# Report the stock levels of:
# - all variants in the order
# - all requested variant ids
require 'open_food_network/scope_variant_to_hub'
class VariantsStockLevels
def call(order, requested_variant_ids)
variant_stock_levels = variant_stock_levels(order.line_items)
# Variants are not scoped here and so the stock levels reported are incorrect
# See cart_controller_spec for more details and #3222
order_variant_ids = variant_stock_levels.keys
missing_variants = Spree::Variant.includes(:stock_items).
where(id: (requested_variant_ids - order_variant_ids))
missing_variants.each do |missing_variant|
variant = scoped_variant(order.distributor, missing_variant)
variant_stock_levels[variant.id] =
{ quantity: 0, max_quantity: 0, on_hand: variant.on_hand, on_demand: variant.on_demand }
missing_variant_ids = requested_variant_ids - order_variant_ids
missing_variant_ids.each do |variant_id|
variant = Spree::Variant.find(variant_id)
variant_stock_levels[variant_id] = { quantity: 0, max_quantity: 0, on_hand: variant.on_hand, on_demand: variant.on_demand }
end
# The code above is most probably dead code, this bugsnag notification will confirm it
notify_bugsnag(order, requested_variant_ids, order_variant_ids) if missing_variant_ids.present?
variant_stock_levels
end
private
def notify_bugsnag(order, requested_variant_ids, order_variant_ids)
error_msg = "VariantsStockLevels.call with variants in the request that are not in the order"
Bugsnag.notify(RuntimeError.new(error_msg),
requested_variant_ids: requested_variant_ids.as_json,
order_variant_ids: order_variant_ids.as_json,
order: order.as_json,
line_items: order.line_items.as_json)
end
def variant_stock_levels(line_items)
Hash[
line_items.map do |line_item|
variant = scoped_variant(line_item.order.distributor, line_item.variant)
[variant.id,
[line_item.variant.id,
{ quantity: line_item.quantity,
max_quantity: line_item.max_quantity,
on_hand: variant.on_hand,
on_demand: variant.on_demand }]
on_hand: line_item.variant.on_hand,
on_demand: line_item.variant.on_demand }]
end
]
end
def scoped_variant(distributor, variant)
return variant if distributor.blank?
scoper(distributor).scope(variant)
variant
end
def scoper(distributor)
@scoper ||= OpenFoodNetwork::ScopeVariantToHub.new(distributor)
end
end

View File

@@ -3,7 +3,7 @@
= inject_available_payment_methods
= inject_saved_credit_cards
= form_for current_order,
= f_form_for current_order,
html: {name: "checkout",
id: "checkout_form",
novalidate: true,

View File

@@ -52,8 +52,7 @@
.row
.small-12.columns
%label{ for: 'order_special_instructions'}= t(:checkout_instructions)
= f.text_area :special_instructions, size: "60x4", "ng-model" => "order.special_instructions"
= f.text_area :special_instructions, label: t(:checkout_instructions), size: "60x4", "ng-model" => "order.special_instructions"
.row
.small-12.columns.text-right

View File

@@ -34,10 +34,10 @@
select: "select(\'map\')"}
.map-container
%map{"ng-if" => "(isActive(\'/map\') && (mapShowed = true)) || mapShowed"}
%ui-gmap-google-map{options: "map.additional_options", center: "map.center", zoom: "map.zoom", styles: "map.styles", draggable: "true"}
%google-map{options: "map.additional_options", center: "map.center", zoom: "map.zoom", styles: "map.styles", draggable: "true"}
%map-osm-tiles
%map-search
%ui-gmap-markers{models: "mapMarkers", fit: "true",
%markers{models: "mapMarkers", fit: "true",
coords: "'self'", icon: "'icon'", click: "'reveal'"}
%tab{heading: t(:groups_about),

View File

@@ -6,10 +6,10 @@
.map-container{"fill-vertical" => true}
%map{"ng-controller" => "MapCtrl"}
%ui-gmap-google-map{options: "map.additional_options", center: "map.center", zoom: "map.zoom", styles: "map.styles", draggable: "true"}
%google-map{options: "map.additional_options", center: "map.center", zoom: "map.zoom", styles: "map.styles", draggable: "true"}
%map-osm-tiles
%map-search
%ui-gmap-markers{models: "OfnMap.enterprises", fit: "true",
%markers{models: "OfnMap.enterprises", fit: "true",
coords: "'self'", icon: "'icon'", click: "'reveal'"}
.map-footer

View File

@@ -26,11 +26,8 @@
%a{href: "", "ng-click" => "showDistanceMatches()"}
= t :hubs_distance_filter, location: "{{ nameMatchesFiltered[0].name }}"
.more-controls
%img.spinner.text-center{ng: {show: "closed_shops_loading"}, src: "/assets/spinning-circles.svg" }
%span{ng: {if: "!show_closed", cloak: true}}
%a.button{href: "", ng: {click: "showClosedShops()"}}
= t '.show_closed_shops'
%span{ng: {if: "show_closed", cloak: true}}
%a.button{href: "", ng: {click: "hideClosedShops()"}}
= t '.hide_closed_shops'
%a.button{href: "", ng: {click: "showClosedShops()", show: "!show_closed"}}
= t '.show_closed_shops'
%a.button{href: "", ng: {click: "hideClosedShops()", show: "show_closed"}}
= t '.hide_closed_shops'
%a.button{href: main_app.map_path}= t '.show_on_map'

View File

@@ -20,28 +20,28 @@
%label{ :for => 'start_date_filter' }
= t("admin.start_date")
%br
%input{ :class => "two columns alpha", :type => "text", :id => 'start_date_filter', 'ng-model' => 'startDate', 'datepicker' => "startDate", 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()', 'ng-model-options' => '{ debounce: 1000 }' }
%input{ :class => "two columns alpha", :type => "text", :id => 'start_date_filter', 'ng-model' => 'startDate', 'datepicker' => "startDate", 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()' }
.date_filter{ :class => "two columns" }
%label{ :for => 'end_date_filter' }
= t("admin.end_date")
%br
%input{ :class => "two columns alpha", :type => "text", :id => 'end_date_filter', 'ng-model' => 'endDate', 'datepicker' => "endDate", 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()', 'ng-model-options' => '{ debounce: 1000 }' }
%input{ :class => "two columns alpha", :type => "text", :id => 'end_date_filter', 'ng-model' => 'endDate', 'datepicker' => "endDate", 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()' }
.one.column &nbsp;
.filter_select{ :class => "three columns" }
%label{ :for => 'supplier_filter' }
= t("admin.producer")
%br
%input#supplier_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'suppliers', placeholder: "#{t(:all)}", blank: "{ id: '', name: '#{t(:all)}' }", on: { selecting: "confirmRefresh" }, ng: { model: 'supplierFilter', change: 'refreshData()' } }
%input#supplier_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'suppliers', blank: "{ id: 0, name: '#{t(:all)}' }", ng: { model: 'supplierFilter' } }
.filter_select{ :class => "three columns" }
%label{ :for => 'distributor_filter' }
= t("admin.shop")
%br
%input#distributor_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'distributors', placeholder: "#{t(:all)}", blank: "{ id: '', name: '#{t(:all)}' }", on: { selecting: "confirmRefresh" }, ng: { model: 'distributorFilter', change: 'refreshData()' } }
%input#distributor_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'distributors', blank: "{ id: 0, name: '#{t(:all)}' }", ng: { model: 'distributorFilter' } }
.filter_select{ :class => "three columns" }
%label{ :for => 'order_cycle_filter' }
= t("admin.order_cycle")
%br
%input#order_cycle_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'orderCycles', placeholder: "#{t(:all)}", blank: "{ id: '', name: '#{t(:all)}' }", on: { selecting: "confirmRefresh" }, ng: { model: 'orderCycleFilter', change: 'refreshData()' } }
%input#order_cycle_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'orderCycles', blank: "{ id: 0, name: '#{t(:all)}' }", on: { selecting: "confirmRefresh" }, ng: { model: 'orderCycleFilter', change: 'refreshData()' } }
.filter_clear{ :class => "two columns omega" }
%label{ :for => 'clear_all_filters' }
%br
@@ -94,7 +94,7 @@
%hr.divider.sixteen.columns.alpha.omega
.controls.sixteen.columns.alpha.omega{ ng: { hide: 'RequestMonitor.loading || line_items.length == 0' } }
.controls.sixteen.columns.alpha.omega{ ng: { hide: 'RequestMonitor.loading || lineItems.length == 0' } }
%div.three.columns.alpha
%input.fullwidth{ :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' }
= render 'admin/shared/bulk_actions_dropdown'
@@ -157,7 +157,7 @@
= t("admin.orders.bulk_management.ask")
%input{ :type => 'checkbox', 'ng-model' => "confirmDelete" }
%tr.line_item{ ng: { repeat: "line_item in filteredLineItems = ( line_items | filter:quickSearch | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:sorting.predicate:sorting.reverse )", 'class-even' => "'even'", 'class-odd' => "'odd'", attr: { id: "li_{{line_item.id}}" } } }
%tr.line_item{ ng: { repeat: "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:sorting.predicate:sorting.reverse )", 'class-even' => "'even'", 'class-odd' => "'odd'", attr: { id: "li_{{line_item.id}}" } } }
%td.bulk
%input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked', 'ignore-dirty' => true }
%td.order_no{ 'ng-show' => 'columns.order_no.visible' } {{ line_item.order.number }}
@@ -175,7 +175,7 @@
%span.error{ ng: { bind: 'line_item.errors.quantity' } }
%td.max{ 'ng-show' => 'columns.max.visible' } {{ line_item.max_quantity }}
%td.final_weight_volume{ 'ng-show' => 'columns.final_weight_volume.visible' }
%input.show-dirty{ type: 'number', step: 'any', :name => 'final_weight_volume', :id => 'final_weight_volume', ng: { model: "line_item.final_weight_volume", readonly: "unitValueLessThanZero(line_item)", change: "weightAdjustedPrice(line_item)", required: "true", class: '{"update-error": line_item.errors.final_weight_volume}' }, min: 0, 'ng-pattern' => '/[0-9]*[.]?[0-9]+/' }
%input.show-dirty{ :type => 'number', :name => 'final_weight_volume', :id => 'final_weight_volume', ng: { model: "line_item.final_weight_volume", readonly: "unitValueLessThanZero(line_item)", change: "weightAdjustedPrice(line_item)", required: "true", class: '{"update-error": line_item.errors.final_weight_volume}' }, min: 0, 'ng-pattern' => '/[1-9]+/' }
%span.error{ ng: { bind: 'line_item.errors.final_weight_volume' } }
%td.price{ 'ng-show' => 'columns.price.visible' }
%input.show-dirty{ :type => 'text', :name => 'price', :id => 'price', :ng => { value: 'line_item.price * line_item.quantity | currency:""', readonly: "true", class: '{"update-error": line_item.errors.price}' } }

View File

@@ -1,3 +1,4 @@
= @payment_method
- case @payment_method
- when Spree::Gateway::StripeConnect
= render 'stripe_connect'

View File

@@ -1,5 +1,4 @@
.three.columns.omega{ "ng-if" => "product.variant_unit_with_scale != 'items'" }
= f.field_container :display_as do
= f.label :product_display_as, t('.display_as')
%span.required *
%input#product_display_as.fullwidth{name: "product[display_as]", placeholder: "{{ placeholder_text }}", type: "text"}

View File

@@ -38,7 +38,6 @@
.three.columns.omega{ 'ng-show' => "product.variant_unit_with_scale == 'items'" }
= f.field_container :unit_name do
= f.label :product_variant_unit_name, t(".unit_name")
%span.required *
%input.fullwidth{ id: 'product_variant_unit_name','ng-model' => 'product.variant_unit_name', :name => 'product[variant_unit_name]', :placeholder => t('admin.products.unit_name_placeholder'), :type => 'text' }
.twelve.columns.alpha
.six.columns.alpha

View File

@@ -13,8 +13,7 @@
%tr.order-row
%td.order1
%a{"ng-href" => "{{::order.path}}", "ng-bind" => "::order.number"}
%td.order2
%a{"ng-href" => "{{::Orders.shopsByID[order.shop_id].hash}}#{main_app.shop_path}", "ng-bind" => "::Orders.shopsByID[order.shop_id].name"}
%td.order2{"ng-bind" => "::Orders.shopsByID[order.shop_id].name"}
%td.order3.show-for-large-up{"ng-bind" => "::order.changes_allowed_until"}
%td.order4.show-for-large-up{"ng-bind" => "::order.item_count"}
%td.order5.text-right{"ng-class" => "{'credit' : order.total < 0, 'debit' : order.total > 0, 'paid' : order.total == 0}","ng-bind" => "::order.total | localizeCurrency"}

View File

@@ -13,8 +13,7 @@
%tr.order-row
%td.order1
%a{"ng-href" => "{{::order.path}}", "ng-bind" => "::order.number"}
%td.order2
%a{"ng-href" => "{{::Orders.shopsByID[order.shop_id].hash}}#{main_app.shop_path}", "ng-bind" => "::Orders.shopsByID[order.shop_id].name"}
%td.order2{"ng-bind" => "::Orders.shopsByID[order.shop_id].name"}
%td.order3.show-for-large-up{"ng-bind" => "::order.completed_at"}
%td.order4.show-for-large-up{"ng-bind" => "::order.item_count"}
%td.order5.text-right{"ng-class" => "{'debit': order.payment_state != 'paid', 'credit': order.payment_state == 'paid'}","ng-bind" => "::order.total | localizeCurrency"}

View File

@@ -1,4 +1,4 @@
= form_for @spree_user, :as => :spree_user, :url => spree.spree_user_password_path, :method => :put do |f|
= f_form_for @spree_user, :as => :spree_user, :url => spree.spree_user_password_path, :method => :put do |f|
= render :partial => 'spree/shared/error_messages', :locals => { :target => @spree_user }
%fieldset
.row
@@ -6,11 +6,9 @@
%legend= t(:change_my_password)
.row
.small-12.medium-6.large-4.columns.medium-centered.large-centered
%label{ for: 'spree_user_password'}= t(:password)
= f.password_field :password
.row
.small-12.medium-6.large-4.columns.medium-centered.large-centered
%label{ for: 'spree_user_password_confirmation'}= t(:password_confirmation)
= f.password_field :password_confirmation
= f.hidden_field :reset_password_token
.row

View File

@@ -1,17 +1,4 @@
# This file is used by Rack-based servers to start the application.
if ENV.fetch('KILL_UNICORNS', false) && ['production', 'staging'].include?(ENV['RAILS_ENV'])
# Gracefully restart individual unicorn workers if they have:
# - performed between 25000 and 30000 requests
# - grown in memory usage to between 700 and 850 MB
require 'unicorn/worker_killer'
use Unicorn::WorkerKiller::MaxRequests,
ENV.fetch('UWK_REQS_MIN', 25_000).to_i,
ENV.fetch('UWK_REQS_MAX', 30_000).to_i
use Unicorn::WorkerKiller::Oom,
( ENV.fetch('UWK_MEM_MIN', 700).to_i * (1024**2) ),
( ENV.fetch('UWK_MEM_MAX', 850).to_i * (1024**2) )
end
require ::File.expand_path('../config/environment', __FILE__)
run Openfoodnetwork::Application

View File

@@ -1,7 +1,7 @@
defaults: &defaults
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch('OFN_DB_POOL', 5) %>
pool: 5
host: <%= ENV.fetch('OFN_DB_HOST', 'localhost') %>
username: <%= ENV.fetch('OFN_DB_USERNAME', 'ofn') %>
password: <%= ENV.fetch('OFN_DB_PASSWORD', 'f00d') %>

View File

@@ -4,6 +4,5 @@ if ENV['DATADOG_RAILS_APM']
c.use :delayed_job, service_name: 'delayed_job'
c.use :dalli, service_name: 'memcached'
c.analytics_enabled = true
c.runtime_metrics_enabled = true
end
end

View File

@@ -0,0 +1,138 @@
# Use this setup block to configure all options available in SimpleForm.
SimpleForm.setup do |config|
# Wrappers are used by the form builder to generate a
# complete input. You can remove any component from the
# wrapper, change the order or even add your own to the
# stack. The options given below are used to wrap the
# whole input.
config.wrappers :default, :class => :input,
:hint_class => :field_with_hint, :error_class => :field_with_errors do |b|
## Extensions enabled by default
# Any of these extensions can be disabled for a
# given input by passing: `f.input EXTENSION_NAME => false`.
# You can make any of these extensions optional by
# renaming `b.use` to `b.optional`.
# Determines whether to use HTML5 (:email, :url, ...)
# and required attributes
b.use :html5
# Calculates placeholders automatically from I18n
# You can also pass a string as f.input :placeholder => "Placeholder"
b.use :placeholder
## Optional extensions
# They are disabled unless you pass `f.input EXTENSION_NAME => :lookup`
# to the input. If so, they will retrieve the values from the model
# if any exists. If you want to enable the lookup for any of those
# extensions by default, you can change `b.optional` to `b.use`.
# Calculates maxlength from length validations for string inputs
b.optional :maxlength
# Calculates pattern from format validations for string inputs
b.optional :pattern
# Calculates min and max from length validations for numeric inputs
b.optional :min_max
# Calculates readonly automatically from readonly attributes
b.optional :readonly
## Inputs
b.use :label_input
b.use :hint, :wrap_with => { :tag => :span, :class => :hint }
b.use :error, :wrap_with => { :tag => :span, :class => :error }
end
# The default wrapper to be used by the FormBuilder.
config.default_wrapper = :default
# Define the way to render check boxes / radio buttons with labels.
# Defaults to :nested for bootstrap config.
# :inline => input + label
# :nested => label > input
config.boolean_style = :nested
# Default class for buttons
config.button_class = 'btn'
# Method used to tidy up errors. Specify any Rails Array method.
# :first lists the first message for each field.
# Use :to_sentence to list all errors for each field.
# config.error_method = :first
# Default tag used for error notification helper.
config.error_notification_tag = :div
# CSS class to add for error notification helper.
config.error_notification_class = 'alert alert-error'
# ID to add for error notification helper.
# config.error_notification_id = nil
# Series of attempts to detect a default label method for collection.
# config.collection_label_methods = [ :to_label, :name, :title, :to_s ]
# Series of attempts to detect a default value method for collection.
# config.collection_value_methods = [ :id, :to_s ]
# You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none.
# config.collection_wrapper_tag = nil
# You can define the class to use on all collection wrappers. Defaulting to none.
# config.collection_wrapper_class = nil
# You can wrap each item in a collection of radio/check boxes with a tag,
# defaulting to :span. Please note that when using :boolean_style = :nested,
# SimpleForm will force this option to be a label.
# config.item_wrapper_tag = :span
# You can define a class to use in all item wrappers. Defaulting to none.
# config.item_wrapper_class = nil
# How the label text should be generated altogether with the required text.
# config.label_text = lambda { |label, required| "#{required} #{label}" }
# You can define the class to use on all labels. Default is nil.
config.label_class = 'control-label'
# You can define the class to use on all forms. Default is simple_form.
# config.form_class = :simple_form
# You can define which elements should obtain additional classes
# config.generate_additional_classes_for = [:wrapper, :label, :input]
# Whether attributes are required by default (or not). Default is true.
# config.required_by_default = true
# Tell browsers whether to use default HTML5 validations (novalidate option).
# Default is enabled.
config.browser_validations = false
# Collection of methods to detect if a file type was given.
# config.file_methods = [ :mounted_as, :file?, :public_filename ]
# Custom mappings for input types. This should be a hash containing a regexp
# to match as key, and the input type that will be used when the field name
# matches the regexp as value.
# config.input_mappings = { /count/ => :integer }
# Default priority for time_zone inputs.
# config.time_zone_priority = nil
# Default priority for country inputs.
# config.country_priority = nil
# Default size for text inputs.
# config.default_input_size = 50
# When false, do not use translations for labels.
# config.translate_labels = true
# Automatically discover new inputs in Rails' autoload path.
# config.inputs_discovery = true
# Cache SimpleForm inputs discovery
# config.cache_discovery = !Rails.env.development?
end

View File

@@ -19,8 +19,6 @@ ar:
shipping_category_id: "نوع الشحن"
variant_unit: "وحدة النوع"
variant_unit_name: "اسم وحدة النوع"
spree/credit_card:
base: "بطاقة ائتمان"
order_cycle:
orders_close_at: تاريخ الاغلاق
errors:
@@ -55,7 +53,7 @@ ar:
messages:
inclusion: "غير مدرجة في القائمة"
models:
order_management/subscriptions/validator:
subscription_validator:
attributes:
subscription_line_items:
at_least_one_product: "^ الرجاء إضافة منتج واحد على الأقل"
@@ -862,11 +860,6 @@ ar:
cancel: "إلغاء"
back_to_list: "العودة للقائمة"
outgoing:
outgoing: "الصادر"
distributor: "الموزع"
products: "منتجات"
tags: "الاوسمة"
fees: "رسوم"
previous: "السابق"
save: "حفظ"
save_and_back_to_list: "حفظ والعودة إلى القائمة"

View File

@@ -19,8 +19,6 @@ ca:
shipping_category_id: "Categoria d'enviament"
variant_unit: "Unitat de la variant"
variant_unit_name: "Nom de la unitat de la variant"
spree/credit_card:
base: "Targeta de crèdit"
order_cycle:
orders_close_at: Data de tancament
errors:
@@ -31,10 +29,6 @@ ca:
taken: "Ja hi ha un compte per a aquest correu electrònic. Si us plau, inicia sessió o restableix la contrasenya."
spree/order:
no_card: No hi ha targetes de crèdit autoritzades disponibles per carregar
spree/credit_card:
attributes:
base:
card_expired: "Ha expirat"
order_cycle:
attributes:
orders_close_at:
@@ -59,7 +53,7 @@ ca:
messages:
inclusion: "no està inclòs a la llista"
models:
order_management/subscriptions/validator:
subscription_validator:
attributes:
subscription_line_items:
at_least_one_product: "^Afegiu com a mínim un producte"
@@ -252,8 +246,6 @@ ca:
notes: Notes
error: Error
processing_payment: "S'està processant el pagament..."
no_pending_payments: "No hi ha pagaments pendents"
invalid_payment_state: "Estat de pagament no vàlid"
filter_results: Aplicar filtre
quantity: Quantitat
pick_up: Recollida
@@ -870,12 +862,6 @@ ca:
cancel: "Cancel·lar"
back_to_list: "Tornar a la llista"
outgoing:
outgoing: "Sortint"
distributor: "Distribuïdora"
products: "Productes "
tags: "Etiquetes"
delivery_details: "Detalls d'enviament"
fees: "Comissions"
previous: "Anterior"
save: "Desa"
save_and_back_to_list: "Desa i torna a la llista"
@@ -1456,13 +1442,13 @@ ca:
email_payment_summary: Resum del pagament
email_payment_method: "Pagament a través de:"
email_so_placement_intro_html: "Tens una nova comanda amb <strong> %{distributor} </ strong>"
email_so_placement_details_html: "Aquests són els detalls de la comanda de <strong>%{distributor}</strong> :"
email_so_placement_details_html: "Aquests són els detalls de la comanda de <strong> %{distributor} </ strong>:"
email_so_placement_changes: "Malauradament, no tots els productes que has demanat estaven disponibles. Les quantitats originals que has sol·licitat apareixen ratllades a sota."
email_so_payment_success_intro_html: "S'ha processat un pagament automàtic per a la vostra comanda des de <strong> %{distributor} </ strong>."
email_so_placement_explainer_html: "Aquesta comanda s'ha creat automàticament per tu."
email_so_edit_true_html: "Pots <a href='%{order_url}'>fer canvis</a> fins que les comandes es tanquin el %{orders_close_at}."
email_so_edit_true_html: "Potd <a href='%{order_url}'> fer canvis </ a> fins que les comandes es tanquin el %{orders_close_at}."
email_so_edit_false_html: "Pots <a href='%{order_url}'> veure detalls d'aquesta comanda </a> en qualsevol moment."
email_so_contact_distributor_html: "Si tens alguna pregunta pots contactar amb <strong>%{distributor}</strong> a través d'%{email}."
email_so_contact_distributor_html: "Si tens alguna pregunta pots contactar amb <strong> %{distributor} </ strong> a través d'%{email}."
email_so_contact_distributor_to_change_order_html: "Aquesta comanda s'ha creat automàticament per a vostè. Podeu fer canvis fins que les comandes es tanquin a %{orders_close_at} contactant a <strong> %{distributor} </ strong> a través d'%{email}."
email_so_confirmation_intro_html: "La teva comanda amb <strong> %{distributor} </ strong> ja està confirmada"
email_so_confirmation_explainer_html: "Vas realitzar aquesta comanda automàticament i ara s'ha finalitzat."
@@ -1501,7 +1487,6 @@ ca:
shopping_oc_closed_description: "Si us plau espera fins que s'obri el pròxim cicle (o posa't en contacte amb nosaltres directament per veure si podem acceptar alguna comanda fora de temps)"
shopping_oc_last_closed: "L'últim cicle va tancar fa %{distance_of_time} "
shopping_oc_next_open: "El següent cicle s'obre en %{distance_of_time}"
shopping_oc_select: "Selecciona ..."
shopping_tabs_home: "Inici"
shopping_tabs_shop: "Botiga"
shopping_tabs_about: "Sobre"
@@ -1875,7 +1860,6 @@ ca:
headline: "Acabat!"
thanks: "Gràcies per omplir els detalls de%{enterprise}."
login: "Pots canviar o actualitzar la teva organització en qualsevol moment accedint a Katuma i anant a Admin."
action: "Vés al panell de control de l'organització"
back: "Enrere"
continue: "Continua"
action_or: "O"
@@ -2645,12 +2629,6 @@ ca:
tub:
one: "tina"
other: "cubells"
punnet:
one: "puntet"
other: "capses"
packet:
one: "paquet"
other: "paquets"
item:
one: "article"
other: "articles"
@@ -3037,8 +3015,6 @@ ca:
tax_invoice: "FACTURA D'IMPOSTOS"
code: "Codi"
from: "De"
to: "Facturar a"
shipping: "Enviament"
form:
distribution_fields:
title: "Distribució"

View File

@@ -19,8 +19,6 @@ de_DE:
shipping_category_id: "Versandkategorie"
variant_unit: "Varianteneinheit"
variant_unit_name: "Name der Varianteneinheit"
spree/credit_card:
base: "Kreditkarte"
order_cycle:
orders_close_at: Schlussdatum
errors:
@@ -31,10 +29,6 @@ de_DE:
taken: "Es gibt bereits ein Konto für diese E-Mail-Adresse. Bitte versuchen Sie sich einzuloggen oder setzen Sie Ihr Passwort zurück."
spree/order:
no_card: Es sind keine belastbaren Karten verfügbar.
spree/credit_card:
attributes:
base:
card_expired: "abgelaufen"
order_cycle:
attributes:
orders_close_at:
@@ -42,7 +36,7 @@ de_DE:
variant_override:
count_on_hand:
using_producer_stock_settings_but_count_on_hand_set: "muss leer sein, da die Einstellungen des Produzentenbestands verwendet werden"
on_demand_but_count_on_hand_set: "muss leer sein falls Produktion auf Nachfrage"
on_demand_but_count_on_hand_set: "muss bei Bedarf leer sein"
limited_stock_but_no_count_on_hand: "muss angegeben werden, da nur begrenzte Lagerbestände erforderlich sind"
activemodel:
attributes:
@@ -52,14 +46,12 @@ de_DE:
distributor_ids: "Hubs"
producer_ids: "Erzeuger"
order_cycle_ids: "Bestellrunden"
enterprise_fee_ids: "Gebührennamen"
enterprise_fee_ids: "Gebühren Namen"
shipping_method_ids: "Lieferart"
payment_method_ids: "Zahlungsarten"
errors:
messages:
inclusion: "ist in der Liste nicht enthalten"
models:
order_management/subscriptions/validator:
subscription_validator:
attributes:
subscription_line_items:
at_least_one_product: "^ Bitte fügen Sie mindestens ein Produkt hinzu"
@@ -252,8 +244,6 @@ de_DE:
notes: Anmerkungen
error: Fehler
processing_payment: "Bezahlung wird verarbeitet..."
no_pending_payments: "Keine ausstehenden Zahlungen"
invalid_payment_state: "Ungültiger Zahlungsstatus"
filter_results: Ergebnisse filtern
quantity: Menge
pick_up: Abholen
@@ -710,11 +700,6 @@ de_DE:
enable_subscriptions_false: "deaktiviert"
enable_subscriptions_true: "aktiviert"
shopfront_message: "Laden-Nachricht"
shopfront_message_placeholder: >
Eine optionale Nachricht, um Kunden willkommen zu heißen und zu erklären,
wie Sie bei Ihnen einkaufen können. Wenn hier Text eingegeben wird,
wird dieser in einem Home-Tab in Ihrem Shop angezeigt, wenn Kunden zum
ersten Mal ihren Shop besuchen.
shopfront_message_link_tooltip: "Link einfügen / bearbeiten"
shopfront_message_link_prompt: "Bitte geben Sie eine einzufügende URL ein"
shopfront_closed_message: "Laden Geschlossen Nachricht"
@@ -869,12 +854,6 @@ de_DE:
cancel: "Abbrechen"
back_to_list: "Zurück zur Liste"
outgoing:
outgoing: "Ausgehend"
distributor: "Verteiler"
products: "Produkte"
tags: "Stichwörter"
delivery_details: "Lieferdetails"
fees: "Gebühren"
previous: "Bisherige"
save: "Speichern"
save_and_back_to_list: "Speichern und zurück zur Liste"
@@ -1121,13 +1100,10 @@ de_DE:
destroy_attachment_does_not_exist: "Logo existiert nicht"
enterprise_promo_image:
destroy_attachment_does_not_exist: "Webebild existiert nicht"
orders:
failed_to_update: "Bestellung konnte nicht aktualisiert werden"
checkout:
already_ordered:
cart: "Warenkorb"
message_html: "Sie haben bereits eine Bestellung für diesen Bestellzyklus. Überprüfen Sie den %{cart}, um die Artikel zu sehen, die Sie zuvor bestellt haben. Sie können Artikel auch stornieren, solange der Bestellzyklus geöffnet ist."
failed: "Die Bestellung ist fehlgeschlagen. Bitte geben Sie uns Bescheid, damit wir Ihre Bestellung trotzdem bearbeiten können."
shops:
hubs:
show_closed_shops: "Geschlossene Läden anzeigen"
@@ -1145,7 +1121,7 @@ de_DE:
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?"
selling_on_ofn: "Interesse am Open Food Network?"
register: "Hier anmelden"
footer:
footer_secure: "Sicher und vertrauenswürdig."
@@ -1298,7 +1274,6 @@ de_DE:
saving_credit_card: Kreditkarte speichern ...
card_has_been_removed: "Ihre Karte wurde entfernt (Nummer: %{number})"
card_could_not_be_removed: Die Karte konnte nicht entfernt werden
invalid_credit_card: "Ungültige Kreditkarte"
ie_warning_headline: "Ihr Browser ist veraltet :-("
ie_warning_text: "Für das beste Open-Food-Network-Erlebnis empfehlen wir dringend, Ihren Browser zu aktualisieren:"
ie_warning_chrome: Chrome herunterladen
@@ -1386,7 +1361,7 @@ de_DE:
checkout_default_bill_address: "Als Standard-Rechnungsadresse speichern"
checkout_shipping: Versandinformation
checkout_default_ship_address: "Als Standardversandadresse speichern"
checkout_method_free: kostenlos
checkout_method_free: ??
checkout_address_same: Lieferadresse wie Rechnungsadresse?
checkout_ready_for: "Bereit am:"
checkout_instructions: "Kommentare oder spezielle Anweisungen?"
@@ -1500,7 +1475,6 @@ de_DE:
shopping_oc_closed_description: "Bitte warten Sie, bis der nächste Zyklus beginnt (oder kontaktieren Sie uns direkt, um zu sehen, ob wir verspätete Bestellungen annehmen können)"
shopping_oc_last_closed: "Der letzte Zyklus wurde vor %{distance_of_time} geschlossen"
shopping_oc_next_open: "Der nächste Zyklus wird in %{distance_of_time} geöffnet"
shopping_oc_select: "Wählen..."
shopping_tabs_home: "Startseite"
shopping_tabs_shop: "Laden"
shopping_tabs_about: "Über Uns"
@@ -1784,13 +1758,13 @@ de_DE:
address1_field_placeholder: "z.B. 123 Cranberry-Laufwerk"
address1_field_error: "Bitte geben Sie eine Adresse an"
address2_field: "Anschrift Zeile 2:"
suburb_field: "Ort:"
suburb_field: "Vorort:"
suburb_field_placeholder: "z.B. Northcote"
suburb_field_error: "Bitte geben Sie einen Vorort ein"
postcode_field: "Postleitzahl:"
postcode_field_placeholder: "z.B. 3070"
postcode_field_error: "Postleitzahl erforderlich"
state_field: "Bundesland:"
state_field: "Zustand:"
state_field_error: "Staat erforderlich"
country_field: "Land:"
country_field_error: "Bitte wähle ein Land"
@@ -1809,8 +1783,8 @@ de_DE:
yes_producer: "Ja, ich bin ein Produzent."
no_producer: "Nein, ich bin kein Produzent"
producer_field_error: "Bitte wählen Sie: Sind Sie ein Produzent?"
yes_producer_help: "Die Produzenten machen leckere Sachen zum Essen und / oder Trinken. Sie sind ein Produzent, wenn Sie anbauen, brauen, backen, fermentieren, melken oder sonst wie Lebenmittel produzieren."
no_producer_help: "Wenn Sie kein Produzent sind, sind Sie wahrscheinlich jemand, der Lebensmittel verkauft und verteilt. Sie könnten ein Foodhub, eine Coop, eine Einkaufsgruppe, Einzelhändler, ein Hofladen, Großhändler oder vergleichbares sein."
yes_producer_help: "Hersteller machen leckere Dinge zu essen und / oder zu trinken. Du bist ein Produzent, wenn du ihn anbaust, erziehst ihn, braue ihn, backe ihn, gähre ihn, melke ihn oder forme ihn."
no_producer_help: "Wenn Sie kein Produzent sind, sind Sie wahrscheinlich jemand, der Lebensmittel verkauft und verteilt. Sie könnten ein Hub, Coop, Einkaufsgruppe, Einzelhändler, Großhändler oder andere sein."
create_profile: "Profil erstellen"
about:
title: "Über Uns"
@@ -1874,7 +1848,6 @@ de_DE:
headline: "Fertig!"
thanks: "Vielen Dank, dass Sie die Details für %{enterprise} ausgefüllt haben."
login: "Sie können Ihr Unternehmen jederzeit ändern oder aktualisieren, indem Sie sich bei Open Food Network anmelden und zum Administrator wechseln."
action: "Gehen Sie zum Enterprise Dashboard"
back: "Zurück"
continue: "Fortsetzen"
action_or: "ODER"
@@ -1960,7 +1933,6 @@ de_DE:
tax_category: "Steuerkategorie"
calculator: "Rechner"
calculator_values: "Rechnerwerte"
calculator_settings_warning: "Wenn Sie den Gebühren-Typ ändern, müssen Sie zuerst speichern, bevor Sie die Gebühren-Einstellungen bearbeiten können"
flat_percent_per_item: "Flache Prozent (pro Artikel)"
flat_rate_per_item: "Pauschale (pro Stück)"
flat_rate_per_order: "Pauschalpreis pro Bestellung)"
@@ -2291,7 +2263,6 @@ de_DE:
enterprise_register_success_notice: "Herzliche Glückwünsche! Registrierung für %{enterprise} ist abgeschlossen!"
enterprise_bulk_update_success_notice: "Unternehmen wurden erfolgreich aktualisiert"
enterprise_bulk_update_error: 'Update fehlgeschlagen'
enterprise_shop_show_error: "Der gesuchte Shop existiert nicht oder ist auf OFN inaktiv. Bitte schauen Sie nach anderen Shops!"
order_cycles_create_notice: 'Ihr Bestellzyklus wurde erstellt.'
order_cycles_update_notice: 'Ihr Bestellzyklus wurde aktualisiert.'
order_cycles_bulk_update_notice: 'Bestellzyklen wurden aktualisiert.'
@@ -2446,12 +2417,6 @@ de_DE:
severity: Schwere
description: Beschreibung
resolve: Entschlossenheit
exchange_products:
load_more_variants: "Weitere Varianten laden"
load_all_variants: "Alle Varianten laden"
select_all_variants: "Wählen Sie alle %{total_number_of_variants}-Varianten aus"
variants_loaded: "%{num_of_variants_loaded} von %{total_number_of_variants} Varianten geladen"
loading_variants: "lade Varianten"
tag_rules:
shipping_method_tagged_top: "Versandarten markiert"
shipping_method_tagged_bottom: "sind:"
@@ -2534,7 +2499,6 @@ de_DE:
customer_placeholder: "Kunde@beispiel.org"
valid_email_error: "Bitte geben Sie eine gültige E-Mail-Adresse ein"
subscriptions:
error_saving: "Fehler beim Speichern des Abonnements"
new:
please_select_a_shop: "Bitte wählen Sie einen Laden"
insufficient_stock: "Nicht genügend Lagerbestand verfügbar, nur noch %{on_hand} verfügbar"
@@ -2611,78 +2575,9 @@ de_DE:
have_an_account: "Hast du schon ein Konto?"
action_login: "Jetzt einloggen."
inflections:
each:
one: "jeder"
other: "pro Stück"
bunch:
one: "Bündel"
other: "Bündel"
pack:
one: "Pack"
other: "Packungen"
box:
one: "Box"
other: "Kisten"
bottle:
one: "Flasche"
other: "Flaschen"
jar:
one: "Krug"
other: "Gläser"
head:
one: "Kopf"
other: "Köpfe"
bag:
one: "Tasche"
other: "Beutel"
loaf:
one: "Laib"
other: "Laibe"
single:
one: "Einzeln"
other: "Einzel"
tub:
one: "Wanne"
other: "Wannen"
punnet:
one: "Körbchen"
other: "Körbchen"
packet:
one: "Paket"
other: "Pakete"
item:
one: "Artikel"
other: "Artikel"
dozen:
one: "Dutzend"
other: "Dutzende"
unit:
one: "Einheit"
other: "Einheiten"
serve:
one: "Portion"
other: "Portionen"
tray:
one: "Schale"
other: "Schalen"
piece:
one: "Stück"
other: "Stücke"
pot:
one: "Topf"
other: "Töpfe"
bundle:
one: "bündeln"
other: "Bündel"
flask:
one: "Flasche"
other: "Flaschen"
basket:
one: "Korb"
other: "Körbe"
sack:
one: "Sack"
other: "Säcke"
producers:
signup:
start_free_profile: "Beginnen Sie mit einem kostenlosen Profil und erweitern Sie es, wenn Sie fertig sind!"
@@ -2748,7 +2643,6 @@ de_DE:
status: "Status"
new: "Neu"
start: "Start"
end: "Ende"
stop: "Halt"
first: "Zuerst"
previous: "Bisherige"
@@ -2925,8 +2819,6 @@ de_DE:
zipcode: Postleitzahl
weight: Gewicht (pro kg)
error_user_destroy_with_orders: "Benutzer mit abgeschlossenen Bestellungen dürfen nicht gelöscht werden"
cannot_create_payment_without_payment_methods: "Sie können keine Zahlung für eine Bestellung erstellen, ohne dass Zahlungsmethoden definiert sind."
please_define_payment_methods: "Bitte definieren Sie zunächst die Zahlungsmethoden."
options: "Optionen"
actions:
update: "Aktualisieren"
@@ -3013,7 +2905,6 @@ de_DE:
capture: "Erfassung"
ship: "Liefern"
edit: "Bearbeiten"
order_not_updated: "Die Bestellung konnte nicht aktualisiert werden"
note: "Hinweis"
first: "Zuerst"
last: "Letzte"
@@ -3036,8 +2927,6 @@ de_DE:
tax_invoice: "Steuerrechnung"
code: "Code"
from: "Von"
to: "Rechnungsempfänger"
shipping: "Versand"
form:
distribution_fields:
title: "Verteilung"
@@ -3235,12 +3124,6 @@ de_DE:
used_saved_card: "Verwende eine gespeicherte Karte:"
or_enter_new_card: "Oder geben Sie Details für eine neue Karte ein:"
remember_this_card: Erinnerst du dich an diese Karte?
stripe_sca:
choose_one: Wähle Sie eine Option
enter_new_card: Geben Sie Details für eine neue Karte ein
used_saved_card: "Verwenden Sie eine gespeicherte Karte:"
or_enter_new_card: "Oder geben Sie Details für eine neue Karte ein:"
remember_this_card: Diese Karte speichern?
date_picker:
format: '% Y-% m-%d'
js_format: 'JJ-MM-TT'

View File

@@ -40,8 +40,6 @@ en:
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
errors:
@@ -52,10 +50,6 @@ en:
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:
@@ -80,7 +74,7 @@ en:
messages:
inclusion: "is not included in the list"
models:
order_management/subscriptions/validator:
subscription_validator:
attributes:
subscription_line_items:
at_least_one_product: "^Please add at least one product"
@@ -920,12 +914,6 @@ en:
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"

View File

@@ -19,8 +19,6 @@ en_AU:
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
errors:
@@ -53,7 +51,7 @@ en_AU:
payment_method_ids: "Payment Methods"
errors:
models:
order_management/subscriptions/validator:
subscription_validator:
attributes:
subscription_line_items:
at_least_one_product: "^Please add at least one product"
@@ -701,16 +699,6 @@ en_AU:
enable_subscriptions_false: "Disabled"
enable_subscriptions_true: "Enabled"
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.
shopfront_message_link_tooltip: "Insert / edit link"
shopfront_message_link_prompt: "Please enter a URL to insert"
shopfront_closed_message: "Shopfront Closed Message"
@@ -872,11 +860,6 @@ en_AU:
cancel: "Cancel"
back_to_list: "Back To List"
outgoing:
outgoing: "Outgoing"
distributor: "Distributor"
products: "Products"
tags: "Tags"
fees: "Fees"
previous: "Previous"
save: "Save"
save_and_back_to_list: "Save and Back to List"

View File

@@ -19,8 +19,6 @@ en_BE:
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
errors:
@@ -53,7 +51,7 @@ en_BE:
payment_method_ids: "Payment Methods"
errors:
models:
order_management/subscriptions/validator:
subscription_validator:
attributes:
subscription_line_items:
at_least_one_product: "^Please add at least one product"
@@ -838,11 +836,6 @@ en_BE:
next: "Next"
cancel: "Cancel"
outgoing:
outgoing: "Outgoing"
distributor: "Distributor"
products: "Products"
tags: "Tags"
fees: "Fees"
previous: "Previous"
save: "Save"
cancel: "Cancel"

View File

@@ -19,8 +19,6 @@ en_CA:
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
errors:
@@ -55,7 +53,7 @@ en_CA:
messages:
inclusion: "is not included in the list"
models:
order_management/subscriptions/validator:
subscription_validator:
attributes:
subscription_line_items:
at_least_one_product: "^Please add at least one product"
@@ -863,11 +861,6 @@ en_CA:
cancel: "Cancel"
back_to_list: "Back to List"
outgoing:
outgoing: "Outgoing"
distributor: "Distributor"
products: "Products"
tags: "Tags"
fees: "Fees"
previous: "Previous"
save: "Save"
save_and_back_to_list: "Save and Back to List"

View File

@@ -19,8 +19,6 @@ en_DE:
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
errors:
@@ -53,7 +51,7 @@ en_DE:
payment_method_ids: "Payment Methods"
errors:
models:
order_management/subscriptions/validator:
subscription_validator:
attributes:
subscription_line_items:
at_least_one_product: "^Please add at least one product"
@@ -846,11 +844,6 @@ en_DE:
next: "Next"
cancel: "Cancel"
outgoing:
outgoing: "Outgoing"
distributor: "Distributor"
products: "Products"
tags: "Tags"
fees: "Fees"
previous: "Previous"
save: "Save"
cancel: "Cancel"

View File

@@ -19,8 +19,6 @@ en_FR:
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
errors:
@@ -31,10 +29,6 @@ en_FR:
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:
@@ -59,7 +53,7 @@ en_FR:
messages:
inclusion: "is not included in the list"
models:
order_management/subscriptions/validator:
subscription_validator:
attributes:
subscription_line_items:
at_least_one_product: "^Please add at least one product"
@@ -867,12 +861,6 @@ en_FR:
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"
@@ -3031,8 +3019,6 @@ en_FR:
tax_invoice: "TAX INVOICE"
code: "Code"
from: "From"
to: "Bill to"
shipping: "Shipping"
form:
distribution_fields:
title: "Distribution"

View File

@@ -19,8 +19,6 @@ en_GB:
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
errors:
@@ -31,10 +29,6 @@ en_GB:
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:
@@ -59,7 +53,7 @@ en_GB:
messages:
inclusion: "is not included in the list"
models:
order_management/subscriptions/validator:
subscription_validator:
attributes:
subscription_line_items:
at_least_one_product: "^Please add at least one product"
@@ -867,12 +861,6 @@ en_GB:
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"
@@ -3037,8 +3025,6 @@ en_GB:
tax_invoice: "TAX INVOICE"
code: "Code"
from: "From"
to: "Bill to"
shipping: "Shipping"
form:
distribution_fields:
title: "Distribution"

View File

@@ -19,8 +19,6 @@ en_NZ:
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
errors:
@@ -31,10 +29,6 @@ en_NZ:
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:
@@ -59,7 +53,7 @@ en_NZ:
messages:
inclusion: "is not included in the list"
models:
order_management/subscriptions/validator:
subscription_validator:
attributes:
subscription_line_items:
at_least_one_product: "^Please add at least one product"
@@ -867,12 +861,6 @@ en_NZ:
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"

File diff suppressed because it is too large Load Diff

View File

@@ -19,8 +19,6 @@ en_US:
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
errors:
@@ -55,7 +53,7 @@ en_US:
messages:
inclusion: "is not included in the list"
models:
order_management/subscriptions/validator:
subscription_validator:
attributes:
subscription_line_items:
at_least_one_product: "^Please add at least one product"
@@ -610,7 +608,7 @@ en_US:
desc_long: About Us
desc_long_placeholder: Tell customers about yourself. This information appears on your public profile.
business_details:
abn: Tax ID, DUNS Number, or other business ID
abn: Tax ID Number or EIN (optional)
abn_placeholder: eg. 123456789
acn: Legal Business Name
acn_placeholder: eg. Martin's Produce LLC
@@ -863,12 +861,6 @@ en_US:
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"
@@ -1180,11 +1172,11 @@ en_US:
invoice_column_price_without_taxes: "Total price (Excl. tax)"
invoice_column_tax_rate: "Tax rate"
invoice_tax_total: "Tax total:"
tax_invoice: "INVOICE"
tax_invoice: "TAX INVOICE"
tax_total: "Total tax (%{rate}):"
total_excl_tax: "Total (Excl. tax):"
total_incl_tax: "Total (Incl. tax):"
abn: "Tax ID, DUNS Number, or other business ID"
abn: "Tax ID Number or EIN (optional)"
acn: "Legal Business Name"
invoice_issued_on: "Invoice issued on:"
order_number: "Invoice number:"
@@ -1817,7 +1809,7 @@ en_US:
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: "Tax ID, DUNS Number, or other business ID"
enterprise_abn: "Tax ID Number or EIN (optional)"
enterprise_abn_placeholder: "eg. 123456789"
enterprise_acn: "Legal Business Name"
enterprise_acn_placeholder: "eg. Justins Produce LLC"
@@ -2896,12 +2888,6 @@ en_US:
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."
@@ -2913,9 +2899,6 @@ en_US:
zipcode: Zipcode
weight: Weight (per lb)
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"
errors:
@@ -2947,53 +2930,27 @@ en_US:
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 Authorization"
return_authorizations: "Return Authorizations"
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 Authorization"
back_to_return_authorizations_list: "Back To Return Authorization List"
continue: "Continue"
edit:
receive: "receive"
are_you_sure: "Are you sure?"
return_authorization: "Return Authorization"
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: "Authorized"
received: "Received"
canceled: "Canceled"
orders:
index:
listing_orders: "Listing Orders"
@@ -3001,7 +2958,6 @@ en_US:
capture: "Capture"
ship: "Ship"
edit: "Edit"
order_not_updated: "The order could not be updated"
note: "Note"
first: "First"
last: "Last"
@@ -3021,11 +2977,9 @@ en_US:
email: "Customer E-mail"
invoice:
issued_on: "Issued on"
tax_invoice: "INVOICE"
tax_invoice: "TAX INVOICE"
code: "Code"
from: "From"
to: "Bill to"
shipping: "Shipping"
form:
distribution_fields:
title: "Distribution"
@@ -3186,22 +3140,12 @@ en_US:
index:
sku: "SKU"
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:
sku: "SKU"
price: "Price"
display_as: "Display As"
display_name: "Display Name"
autocomplete:
producer_name: "Producer"
unit: "Unit"
@@ -3347,19 +3291,3 @@ en_US:
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 authorized 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."

View File

@@ -19,8 +19,6 @@ en_ZA:
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
errors:
@@ -53,7 +51,7 @@ en_ZA:
payment_method_ids: "Payment Methods"
errors:
models:
order_management/subscriptions/validator:
subscription_validator:
attributes:
subscription_line_items:
at_least_one_product: "^Please add at least one product"
@@ -842,11 +840,6 @@ en_ZA:
next: "Next"
cancel: "Cancel"
outgoing:
outgoing: "Outgoing"
distributor: "Distributor"
products: "Products"
tags: "Tags"
fees: "Fees"
previous: "Previous"
save: "Save"
cancel: "Cancel"

View File

@@ -19,8 +19,6 @@ es:
shipping_category_id: "Categoría de envío"
variant_unit: "Unidad Variante"
variant_unit_name: "Nombre de la unidad de la variante"
spree/credit_card:
base: "Tarjeta de crédito"
order_cycle:
orders_close_at: Fecha de cierre
errors:
@@ -31,10 +29,6 @@ es:
taken: "Ya existe una cuenta con este email. Inicie sesión o restablezca tu contraseña."
spree/order:
no_card: No hay tarjetas de crédito autorizadas disponibles para cargar
spree/credit_card:
attributes:
base:
card_expired: "ha expirado"
order_cycle:
attributes:
orders_close_at:
@@ -56,10 +50,8 @@ es:
shipping_method_ids: "Métodos de envío"
payment_method_ids: "Métodos de Pago"
errors:
messages:
inclusion: "no está incluido en la lista"
models:
order_management/subscriptions/validator:
subscription_validator:
attributes:
subscription_line_items:
at_least_one_product: "^Por favor agrega al menos un producto"
@@ -252,8 +244,6 @@ es:
notes: Notas
error: Error
processing_payment: "Procesando el pago..."
no_pending_payments: "No tiene pagos pendientes"
invalid_payment_state: "Estado de pago no válido"
filter_results: Filtrar resultados
quantity: Cantidad
pick_up: Recogida
@@ -446,12 +436,9 @@ es:
infinity: "infinito"
to_order_tip: "Los artículos hechos según demanda no tienen un nivel de stock, como por ejemplo panes hechos según demanda."
back_to_products_list: "Volver a la lista de productos"
editing_product: "Editando producto"
tabs:
product_details: "Detalles del Producto"
group_buy_options: "Opciones de compra grupales"
images: "Imágenes"
variants: "variaciones"
product_properties: "Propiedades del producto"
product_import:
title: Importación de productos
@@ -710,11 +697,6 @@ es:
enable_subscriptions_false: "Deshabilitado"
enable_subscriptions_true: "Habilitado"
shopfront_message: "Mensaje de la Tienda"
shopfront_message_placeholder: >
Mensaje de bienvenida opcional para compradores, explica como comprar
en el sitio. si el texto se agrega en este campo, va a ser mostrado
en la pestaña de inicio cuando los clientes ingresen por primera vez
a la tienda.
shopfront_message_link_tooltip: "Insertar / editar enlace"
shopfront_message_link_prompt: "Por favor introduzca una URL para insertar"
shopfront_closed_message: "Mensaje de tienda cerrada"
@@ -852,38 +834,20 @@ es:
new:
create: "Crear"
cancel: "Cancelar"
back_to_list: "Regresar a la lista"
edit:
advanced_settings: "Configuración Avanzada"
save: "Guardar"
save_and_next: "Salvar y continuar"
next: "Siguiente"
cancel: "Cancelar"
back_to_list: "Regresar a la lista"
save_and_back_to_list: "Salvar y volver a lista"
choose_products_from: "Escoger Productos desde:"
incoming:
save: "Guardar"
save_and_next: "Salvar y continuar"
next: "Siguiente"
cancel: "Cancelar"
back_to_list: "Regresar a la lista"
outgoing:
outgoing: "Saliente"
distributor: "Distribuidora"
products: "Productos"
tags: "Tags"
delivery_details: "Detalles de entrega"
fees: "Comisiones"
previous: "Anterior"
save: "Guardar"
save_and_back_to_list: "Salvar y volver a lista"
cancel: "Cancelar"
back_to_list: "Regresar a la lista"
wizard_progress:
edit: "1. Configuración general"
incoming: "2. Productos entrantes"
outgoing: "3. Productos salientes"
exchange_form:
pickup_time_tip: Cuando los pedidos de este ciclo de pedido estarán listos para la consumidora
pickup_instructions_placeholder: "Instrucciones de recogida"
@@ -1121,13 +1085,10 @@ es:
destroy_attachment_does_not_exist: "El logotipo no existe"
enterprise_promo_image:
destroy_attachment_does_not_exist: "La imagen promocional no existe"
orders:
failed_to_update: "Error al actualizar pedido"
checkout:
already_ordered:
cart: "carrito"
message_html: "Ya realizó un pedido para este ciclo de pedido. Compruebe el %{cart}para ver los artículos que pidió. También puede cancelar artículos mientras el ciclo de pedido siga abierto."
failed: "La finalización de compra falló, por favor comunicate con nosotros para procesar la orden."
shops:
hubs:
show_closed_shops: "Mostrar tiendas cerradas"
@@ -1298,7 +1259,6 @@ es:
saving_credit_card: Guardando tarjeta de crédito...
card_has_been_removed: "Su tarjeta ha sido eliminada (número: %{number})"
card_could_not_be_removed: Lo sentimos, la tarjeta no se pudo quitar
invalid_credit_card: "Tarjeta de crédito inválida"
ie_warning_headline: "Su navegador está desactualizado :-("
ie_warning_text: "Para la mejor esperiencia de Open Food Network, recomendamos actualizar su navegador:"
ie_warning_chrome: Descargar Chrome
@@ -1500,7 +1460,6 @@ es:
shopping_oc_closed_description: "Por favor espere hasta que el próximo ciclo abra (o contactanos de forma directa para ver si podemos aceptar algunos pedidos tardíos)"
shopping_oc_last_closed: "El último ciclo cerró hace %{distance_of_time}"
shopping_oc_next_open: "El próximo ciclo abrirá en %{distance_of_time}"
shopping_oc_select: "Seleccionar"
shopping_tabs_home: "Inicio"
shopping_tabs_shop: "Tienda"
shopping_tabs_about: "Acerca de"
@@ -1874,7 +1833,6 @@ es:
headline: "¡Terminado!"
thanks: "Gracias por llenar los detalles de %{enterprise}."
login: "Puede cambiar o actualizar su negocio en cualquier etapa iniciando sesión en Open Food Network y yendo a Admin."
action: "Ir al Panel de Organización"
back: "Atrás"
continue: "Continuar"
action_or: "Ó"
@@ -1960,7 +1918,6 @@ es:
tax_category: "Categoría del impuesto"
calculator: "Calculadora"
calculator_values: "Calculadora de valores"
calculator_settings_warning: "Si está cambiando el tipo de calculadora, debe de salvar primero antes de editar las configuraciones de la calculadora"
flat_percent_per_item: "Porcentaje fijo (por artículo)"
flat_rate_per_item: "Tarifa plana (por artículo)"
flat_rate_per_order: "Tarifa plana (por pedido)"
@@ -2291,7 +2248,6 @@ es:
enterprise_register_success_notice: "¡Felicidades! ¡Se ha completado el registro de %{enterprise}!"
enterprise_bulk_update_success_notice: "Organizaciones actualizadas con éxito"
enterprise_bulk_update_error: 'Error en la actualización'
enterprise_shop_show_error: "La tienda que busca no existe o esta inactiva en OFN. por favor visita otras tiendas."
order_cycles_create_notice: 'Se ha creado el ciclo de pedido.'
order_cycles_update_notice: 'Se ha actualizado su ciclo de pedido.'
order_cycles_bulk_update_notice: 'Los ciclos de pedido han sido actualizados.'
@@ -2447,12 +2403,6 @@ es:
severity: Gravedad
description: Descripción
resolve: Resolver
exchange_products:
load_more_variants: "Cargar mas variantes"
load_all_variants: "cargar todas las variantes"
select_all_variants: "Seleccionar todo"
variants_loaded: "%{num_of_variants_loaded} de %{total_number_of_variants} variantes cargadas"
loading_variants: "Cargando variantes"
tag_rules:
shipping_method_tagged_top: "Métodos de envío etiquetados"
shipping_method_tagged_bottom: "son:"
@@ -2535,7 +2485,6 @@ es:
customer_placeholder: "customer@example.org"
valid_email_error: "Introduce un email válido"
subscriptions:
error_saving: "Error al salvar suscripción "
new:
please_select_a_shop: "Por favor seleccione una tienda"
insufficient_stock: "Stock insuficiente disponible, solo quedan %{on_hand}"
@@ -2611,79 +2560,6 @@ es:
signup_or_login: "Empieza registrándose (o iniciando sesión)"
have_an_account: "¿Ya tiene una cuenta?"
action_login: "Inicie sesión ahora."
inflections:
each:
one: "each"
other: "cada"
bunch:
one: "manojo"
other: "manojos"
pack:
one: "paquete"
other: "paquetes"
box:
one: "caja"
other: "cajas"
bottle:
one: "botella"
other: "botellas"
jar:
one: "frasco"
other: "frascos"
head:
one: "cabeza"
other: "cabezas"
bag:
one: "bolsa"
other: "bolsas"
loaf:
one: "hogaza"
other: "hogazas"
single:
one: "single"
other: "individuales"
tub:
one: "tub"
other: "recipientes"
punnet:
one: "canastilla"
other: "canastillas"
packet:
one: "paquete"
other: "paquetes"
item:
one: "elemento"
other: "elementos"
dozen:
one: "docena"
other: "docenas"
unit:
one: "unidad"
other: "unidades"
serve:
one: "serve"
other: "porción"
tray:
one: "bandeja"
other: "bandejas"
piece:
one: "pieza"
other: "piezas"
pot:
one: "maceta"
other: "contenedores"
bundle:
one: "haz"
other: "paquetes"
flask:
one: "flask"
other: "frascos"
basket:
one: "canasta"
other: "canastas"
sack:
one: "sacos"
other: "sacos"
producers:
signup:
start_free_profile: "Empieze con un perfil gratuito, y amplíelo cuando esté preparado!"
@@ -2909,12 +2785,6 @@ es:
minimal_amount: "Cantidad mínima"
normal_amount: "Cantidad normal"
discount_amount: "Importe de descuento"
no_images_found: "No se encontraron imágenes "
new_image: "Nueva Imagen"
filename: "Nombre de archivo"
alt_text: "Texto Alternativo"
thumbnail: "Miniatura"
back_to_images_list: "Volver a lista de imágenes "
email: Email
account_updated: "Cuenta actualizada!"
email_updated: "La cuenta se actualizará una vez que se confirme el nuevo correo electrónico."
@@ -2926,9 +2796,6 @@ es:
zipcode: Código Postal
weight: Peso (en kg)
error_user_destroy_with_orders: "Los usuarios con pedidos completados no pueden ser eliminados"
cannot_create_payment_without_payment_methods: "No se puede crear un pago para una orden sin un medio de pago definido"
please_define_payment_methods: "por favor definir métodos de pago"
options: "Opciones"
actions:
update: "Actualizar"
errors:
@@ -2960,53 +2827,27 @@ es:
product_properties:
index:
inherits_properties_checkbox_hint: "¿Heredar propiedades desde %{supplier}? (a menos que sea anulado arriba)"
add_product_properties: "Agregar Propiedades del producto"
select_from_prototype: "seleccionar de prototipo"
properties:
index:
properties: "Propiedades"
new_property: "Nueva propiedad"
name: "Nombre"
presentation: "presentación"
new:
new_property: "Nueva propiedad"
edit:
editing_property: "Editar Propiedad"
back_to_properties_list: "volver a lista de propiedades"
form:
name: "Nombre"
presentation: "presentación"
return_authorizations:
index:
new_return_authorization: "Nueva autorización de devolución"
return_authorizations: "Autorizaciones de devolución"
back_to_orders_list: "Volver a la lista de pedidos"
rma_number: "número RMA"
status: "Estado"
amount: "Cantidad"
cannot_create_returns: "No se pueden crear devoluciones ya que este pedido no tiene unidades enviadas."
continue: "Continuar"
new:
new_return_authorization: "Nueva autorización de devolución"
back_to_return_authorizations_list: "Back To Return Authorization List"
continue: "Continuar"
edit:
receive: "recibir"
are_you_sure: "¿Está seguro?"
return_authorization: "volver a autorización"
form:
product: "Producto"
quantity_shipped: "cantidad enviada"
quantity_returned: "Cantidad devuelta"
return_quantity: "cantidad a devolver"
amount: "Cantidad"
rma_value: "Valor RMA"
reason: "razón"
stock_location: "localización de inventario"
states:
authorized: "autorizado"
received: "recibido"
canceled: "cancelado"
orders:
index:
listing_orders: "Pedidos de listado"
@@ -3014,7 +2855,6 @@ es:
capture: "Captura"
ship: "Envío"
edit: "Editar"
order_not_updated: "El pedido no se pudo actualizar"
note: "Nota"
first: "primero"
last: "Último"
@@ -3037,8 +2877,6 @@ es:
tax_invoice: "FACTURA DE IMPUESTOS"
code: "Código"
from: "De"
to: "Facturar a"
shipping: "envío"
form:
distribution_fields:
title: "Distribución"
@@ -3199,23 +3037,12 @@ es:
index:
sku: "SKU"
price: "Precio"
options: "Opciones"
no_results: "No hay resultados"
to_add_variants_you_must_first_define: "para agregar variantes, se debe primero definir"
option_types: "Tipos de opciones"
option_values: "valores de opción"
and: "y"
new_variant: "Nueva Variante"
show_active: "mostrar activo"
show_deleted: "Mostrar eliminados"
new:
new_variant: "Nueva Variante"
form:
cost_price: "Precio de costo"
sku: "SKU"
price: "Precio"
display_as: "Mostrar como"
display_name: "Nombre para mostrar"
autocomplete:
producer_name: "Productora"
unit: "Unidad"
@@ -3361,19 +3188,3 @@ es:
allow_charges?: "¿Permitir cargos?"
localized_number:
invalid_format: tiene un formato invalido. Por favor introduzca un numero.
api:
invalid_api_key: "La llave de API especificada (%{key}) es inválida."
unauthorized: "No tiene autorización para realizar esta acción."
invalid_resource: "Recurso inválido. Por favor corrija los errores e intente nuevamente."
resource_not_found: "El recurso que buscaba no puede ser encontrado."
access: "acceso al API"
key: "Llave"
clear_key: "valor vacío"
regenerate_key: "Regenerar llave"
no_key: "sin valor"
generate_key: "Generar llave de API"
key_generated: "Llave generada"
key_cleared: "valor borrado"
shipment:
cannot_ready: "No se puede completar envío"
invalid_taxonomy_id: "El identificador de taxonomía es inválido."

Some files were not shown because too many files have changed in this diff Show More