mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-12 18:36:49 +00:00
Compare commits
153 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6ba956355 | ||
|
|
76cffe4c7f | ||
|
|
26477a8d4b | ||
|
|
52ab6c52bf | ||
|
|
413e93fe40 | ||
|
|
9e1b2eb4ca | ||
|
|
1ceae6cf8d | ||
|
|
5183d93601 | ||
|
|
247854b7fe | ||
|
|
85bd803785 | ||
|
|
c6543edc85 | ||
|
|
a91d0973d4 | ||
|
|
61c581ae52 | ||
|
|
db8e2891d4 | ||
|
|
a947d8df6c | ||
|
|
818d41ffac | ||
|
|
14f67053c8 | ||
|
|
76e32b126f | ||
|
|
c234cfb890 | ||
|
|
38bda1697d | ||
|
|
5fb9ebc594 | ||
|
|
acbd4b076d | ||
|
|
123f7aa68e | ||
|
|
2c4af84a81 | ||
|
|
34ed16ff30 | ||
|
|
0b20b80219 | ||
|
|
bef745378e | ||
|
|
a6cb5903d6 | ||
|
|
d1b36aded0 | ||
|
|
9c3c74aa93 | ||
|
|
67a5a1cdc2 | ||
|
|
9723e2cd49 | ||
|
|
4af014df6b | ||
|
|
be6c64db75 | ||
|
|
1aebc30128 | ||
|
|
f8209ac7d5 | ||
|
|
6d50176e6b | ||
|
|
d6d2c19dc7 | ||
|
|
f897478736 | ||
|
|
064b86da5d | ||
|
|
f7b58300f9 | ||
|
|
0fe4edfbf5 | ||
|
|
f45eb35eb1 | ||
|
|
1a0e99dce2 | ||
|
|
d7caf91de1 | ||
|
|
1e2ab27cda | ||
|
|
3e7bd7dc19 | ||
|
|
c60261a847 | ||
|
|
11fea650d6 | ||
|
|
92f1fa3b52 | ||
|
|
ad52022927 | ||
|
|
9cffe48c70 | ||
|
|
d006ded439 | ||
|
|
b2e5ff46a8 | ||
|
|
f46e0a2a31 | ||
|
|
dd66df6379 | ||
|
|
d7b9dc1190 | ||
|
|
5b6efaf687 | ||
|
|
8aab9bacbe | ||
|
|
66fdbe4379 | ||
|
|
abcc22c34b | ||
|
|
0d34b607c3 | ||
|
|
f840179573 | ||
|
|
b5a521476b | ||
|
|
7c64777a50 | ||
|
|
c98b4b276b | ||
|
|
f1138709aa | ||
|
|
b29983ac60 | ||
|
|
91188c5724 | ||
|
|
bf291ec318 | ||
|
|
5846593637 | ||
|
|
688dad2334 | ||
|
|
6d419d60ae | ||
|
|
0dc8ae1561 | ||
|
|
f396f6bebd | ||
|
|
07fcc8f361 | ||
|
|
237cd5438b | ||
|
|
4e366d0f2e | ||
|
|
fb3af77d0b | ||
|
|
1fa6e4bea8 | ||
|
|
498a2b7462 | ||
|
|
80edfe469c | ||
|
|
b2616d317f | ||
|
|
0ac0bdc381 | ||
|
|
1a756cbc6b | ||
|
|
34466c8218 | ||
|
|
00e869f40c | ||
|
|
42cd9a5152 | ||
|
|
cc342387cc | ||
|
|
6715f872e3 | ||
|
|
e849c4867b | ||
|
|
ada34d27ca | ||
|
|
40c329ba68 | ||
|
|
3c7af90dfa | ||
|
|
241e581779 | ||
|
|
85dede84cc | ||
|
|
0288dfc992 | ||
|
|
3372339907 | ||
|
|
f7bb609546 | ||
|
|
02c0b89fa0 | ||
|
|
f57c9d4a25 | ||
|
|
783c3c9e90 | ||
|
|
44753d0320 | ||
|
|
dd7d5803ba | ||
|
|
c9e23154d8 | ||
|
|
279b633513 | ||
|
|
9d0ac79983 | ||
|
|
deb17f47a7 | ||
|
|
61ee0f04a6 | ||
|
|
353804a3fa | ||
|
|
598426a5e9 | ||
|
|
7b0c55e15a | ||
|
|
cbe2477d04 | ||
|
|
c730958fe4 | ||
|
|
37e5e1923c | ||
|
|
542c1bf684 | ||
|
|
06c896b93b | ||
|
|
6433d69d02 | ||
|
|
bf8c632fce | ||
|
|
b3c89a9d6c | ||
|
|
d45403f1d4 | ||
|
|
f2affe80cd | ||
|
|
573a69477f | ||
|
|
c6ce516129 | ||
|
|
2539b84b33 | ||
|
|
dd6d0d25da | ||
|
|
c54cff10d4 | ||
|
|
ab330e882e | ||
|
|
a50ae3f8ce | ||
|
|
9f3b4100c3 | ||
|
|
b625ea0c61 | ||
|
|
8857404ddf | ||
|
|
49f98422fd | ||
|
|
4d49dc3689 | ||
|
|
8d30dc997f | ||
|
|
cf40bfa58e | ||
|
|
8e03f402b1 | ||
|
|
5a84a3688b | ||
|
|
614dc5d255 | ||
|
|
da7456e6e0 | ||
|
|
f134cd9473 | ||
|
|
e96252f2ed | ||
|
|
06e1f56ae9 | ||
|
|
fe0de98821 | ||
|
|
d997b8f5ee | ||
|
|
cf3f321632 | ||
|
|
797a3ad091 | ||
|
|
01d1e8243c | ||
|
|
a1a5c3b7fe | ||
|
|
bc826f73a1 | ||
|
|
7c264af0c2 | ||
|
|
4c4bdd78e7 | ||
|
|
bb56e9a5b9 |
@@ -139,7 +139,6 @@ Metrics/LineLength:
|
||||
- lib/open_food_network/payments_report.rb
|
||||
- lib/open_food_network/permalink_generator.rb
|
||||
- lib/open_food_network/products_cache.rb
|
||||
- lib/open_food_network/products_renderer.rb
|
||||
- lib/open_food_network/reports/bulk_coop_allocation_report.rb
|
||||
- lib/open_food_network/reports/line_items.rb
|
||||
- lib/open_food_network/sales_tax_report.rb
|
||||
@@ -249,13 +248,10 @@ Metrics/LineLength:
|
||||
- spec/helpers/order_cycles_helper_spec.rb
|
||||
- spec/helpers/spree/admin/base_helper_spec.rb
|
||||
- spec/jobs/confirm_order_job_spec.rb
|
||||
- spec/jobs/products_cache_integrity_checker_job_spec.rb
|
||||
- spec/jobs/refresh_products_cache_job_spec.rb
|
||||
- spec/jobs/subscription_confirm_job_spec.rb
|
||||
- spec/jobs/subscription_placement_job_spec.rb
|
||||
- spec/lib/open_food_network/address_finder_spec.rb
|
||||
- spec/lib/open_food_network/bulk_coop_report_spec.rb
|
||||
- spec/lib/open_food_network/cached_products_renderer_spec.rb
|
||||
- spec/lib/open_food_network/customers_report_spec.rb
|
||||
- spec/lib/open_food_network/enterprise_fee_applicator_spec.rb
|
||||
- spec/lib/open_food_network/enterprise_fee_calculator_spec.rb
|
||||
@@ -272,7 +268,6 @@ Metrics/LineLength:
|
||||
- spec/lib/open_food_network/permissions_spec.rb
|
||||
- spec/lib/open_food_network/products_and_inventory_report_spec.rb
|
||||
- spec/lib/open_food_network/products_cache_spec.rb
|
||||
- spec/lib/open_food_network/products_renderer_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
|
||||
@@ -615,7 +610,6 @@ Metrics/MethodLength:
|
||||
- lib/open_food_network/payments_report.rb
|
||||
- lib/open_food_network/permissions.rb
|
||||
- lib/open_food_network/products_and_inventory_report.rb
|
||||
- lib/open_food_network/products_renderer.rb
|
||||
- lib/open_food_network/rack_request_blocker.rb
|
||||
- lib/open_food_network/reports/bulk_coop_allocation_report.rb
|
||||
- lib/open_food_network/reports/bulk_coop_supplier_report.rb
|
||||
@@ -670,7 +664,6 @@ Metrics/ModuleLength:
|
||||
- spec/controllers/api/orders_controller_spec.rb
|
||||
- spec/controllers/spree/api/products_controller_spec.rb
|
||||
- spec/lib/open_food_network/address_finder_spec.rb
|
||||
- spec/lib/open_food_network/cached_products_renderer_spec.rb
|
||||
- spec/lib/open_food_network/customers_report_spec.rb
|
||||
- spec/lib/open_food_network/enterprise_fee_calculator_spec.rb
|
||||
- spec/lib/open_food_network/option_value_namer_spec.rb
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.1.9
|
||||
2.2.10
|
||||
|
||||
@@ -19,6 +19,10 @@ If you want to run the whole test suite, we recommend using a free CI service to
|
||||
|
||||
bundle exec rspec spec
|
||||
|
||||
## Which issue to pick first?
|
||||
|
||||
We have curated all issues interesting for new members of the community within the [Welcome New Developers project board][welcome-dev]. Have a look and pick the one you would prefer working on!
|
||||
|
||||
## Internationalisation (i18n)
|
||||
|
||||
The locale `en` is maintained in the source code, but other locales are managed at [Transifex][ofn-transifex]. Read more about [internationalisation][i18n] in the developer wiki.
|
||||
@@ -62,3 +66,4 @@ From here, your pull request will progress through the [Review, Test, Merge & De
|
||||
[slack-dev]: https://openfoodnetwork.slack.com/messages/C2GQ45KNU
|
||||
[ofn-transifex]: https://www.transifex.com/open-food-foundation/open-food-network/
|
||||
[i18n]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/i18n
|
||||
[welcome-dev]: https://github.com/openfoodfoundation/openfoodnetwork/projects/27
|
||||
|
||||
12
Gemfile
12
Gemfile
@@ -1,9 +1,9 @@
|
||||
source 'https://rubygems.org'
|
||||
ruby "2.1.9"
|
||||
ruby "2.2.10"
|
||||
git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" }
|
||||
|
||||
gem 'i18n', '~> 0.6.11'
|
||||
gem 'i18n-js', '~> 3.3.0'
|
||||
gem 'i18n-js', '~> 3.4.1'
|
||||
gem 'rails', '~> 3.2.22'
|
||||
gem 'rails-i18n', '~> 3.0.0'
|
||||
gem 'rails_safe_tasks', '~> 1.0'
|
||||
@@ -15,12 +15,12 @@ gem 'nokogiri', '>= 1.6.7.1'
|
||||
gem "order_management", path: "./engines/order_management"
|
||||
gem 'web', path: './engines/web'
|
||||
|
||||
gem 'pg'
|
||||
gem 'activerecord-postgresql-adapter'
|
||||
gem 'pg', '~> 0.21.0'
|
||||
|
||||
# OFN-maintained and patched version of Spree v2.0.4. See
|
||||
# https://github.com/openfoodfoundation/openfoodnetwork/wiki/Spree-2.0-upgrade
|
||||
# for details.
|
||||
gem 'spree_api', github: 'openfoodfoundation/spree', branch: '2-0-4-stable'
|
||||
gem 'spree_backend', github: 'openfoodfoundation/spree', branch: '2-0-4-stable'
|
||||
gem 'spree_core', github: 'openfoodfoundation/spree', branch: '2-0-4-stable'
|
||||
|
||||
@@ -98,6 +98,8 @@ gem 'roo-xls', '~> 1.1.0'
|
||||
|
||||
gem 'whenever', require: false
|
||||
|
||||
gem 'test-unit', '~> 3.0'
|
||||
|
||||
# Gems used only for assets and not required
|
||||
# in production environments by default.
|
||||
group :assets do
|
||||
@@ -136,7 +138,7 @@ group :test, :development do
|
||||
gem 'capybara', '>= 2.15.4'
|
||||
gem 'database_cleaner', '0.7.1', require: false
|
||||
gem "factory_bot_rails", require: false
|
||||
gem 'fuubar', '~> 2.4.1'
|
||||
gem 'fuubar', '~> 2.5.0'
|
||||
gem 'json_spec', '~> 1.1.4'
|
||||
gem 'knapsack'
|
||||
gem 'letter_opener', '>= 1.4.1'
|
||||
|
||||
24
Gemfile.lock
24
Gemfile.lock
@@ -129,8 +129,10 @@ GEM
|
||||
activesupport (= 3.2.22.5)
|
||||
arel (~> 3.0.2)
|
||||
tzinfo (~> 0.3.29)
|
||||
activerecord-import (1.0.2)
|
||||
activerecord-import (1.0.3)
|
||||
activerecord (>= 3.2)
|
||||
activerecord-postgresql-adapter (0.0.1)
|
||||
pg
|
||||
activeresource (3.2.22.5)
|
||||
activemodel (= 3.2.22.5)
|
||||
activesupport (= 3.2.22.5)
|
||||
@@ -164,7 +166,7 @@ GEM
|
||||
bcrypt-ruby (3.1.5)
|
||||
bcrypt (>= 3.1.3)
|
||||
blockenspiel (0.5.0)
|
||||
bugsnag (6.12.1)
|
||||
bugsnag (6.12.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
builder (3.0.4)
|
||||
byebug (9.0.6)
|
||||
@@ -424,7 +426,7 @@ GEM
|
||||
foundation-rails (5.5.2.1)
|
||||
railties (>= 3.1.0)
|
||||
sass (>= 3.3.0, < 3.5)
|
||||
fuubar (2.4.1)
|
||||
fuubar (2.5.0)
|
||||
rspec-core (~> 3.0)
|
||||
ruby-progressbar (~> 1.4)
|
||||
geocoder (1.1.8)
|
||||
@@ -437,7 +439,7 @@ GEM
|
||||
httparty (0.16.2)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (0.6.11)
|
||||
i18n-js (3.3.0)
|
||||
i18n-js (3.4.1)
|
||||
i18n (>= 0.6.6)
|
||||
immigrant (0.3.6)
|
||||
activerecord (>= 3.0)
|
||||
@@ -522,6 +524,7 @@ GEM
|
||||
polyamorous (0.5.0)
|
||||
activerecord (~> 3.0)
|
||||
polyglot (0.3.5)
|
||||
power_assert (1.1.5)
|
||||
powerpack (0.1.1)
|
||||
pry (0.12.2)
|
||||
coderay (~> 1.1.0)
|
||||
@@ -671,6 +674,8 @@ GEM
|
||||
stripe (4.24.0)
|
||||
faraday (~> 0.13)
|
||||
net-http-persistent (~> 3.0)
|
||||
test-unit (3.3.3)
|
||||
power_assert
|
||||
thor (0.20.3)
|
||||
tilt (1.4.1)
|
||||
timecop (0.9.1)
|
||||
@@ -722,6 +727,7 @@ DEPENDENCIES
|
||||
active_model_serializers (= 0.8.4)
|
||||
activemerchant (~> 1.78)
|
||||
activerecord-import
|
||||
activerecord-postgresql-adapter
|
||||
acts-as-taggable-on (~> 3.4)
|
||||
andand
|
||||
angular-rails-templates (~> 0.3.0)
|
||||
@@ -757,12 +763,12 @@ DEPENDENCIES
|
||||
foundation-icons-sass-rails
|
||||
foundation-rails
|
||||
foundation_rails_helper!
|
||||
fuubar (~> 2.4.1)
|
||||
fuubar (~> 2.5.0)
|
||||
geocoder
|
||||
gmaps4rails
|
||||
haml
|
||||
i18n (~> 0.6.11)
|
||||
i18n-js (~> 3.3.0)
|
||||
i18n-js (~> 3.4.1)
|
||||
immigrant
|
||||
jquery-migrate-rails
|
||||
jquery-rails (= 3.0.4)
|
||||
@@ -783,7 +789,7 @@ DEPENDENCIES
|
||||
order_management!
|
||||
paper_trail (~> 5.2.3)
|
||||
paperclip (~> 3.4.1)
|
||||
pg
|
||||
pg (~> 0.21.0)
|
||||
pry-byebug (>= 3.4.3)
|
||||
rabl
|
||||
rack-mini-profiler (< 1.0.0)
|
||||
@@ -806,7 +812,6 @@ DEPENDENCIES
|
||||
simple_form!
|
||||
simplecov
|
||||
spinjs-rails
|
||||
spree_api!
|
||||
spree_backend!
|
||||
spree_core!
|
||||
spree_i18n!
|
||||
@@ -814,6 +819,7 @@ DEPENDENCIES
|
||||
spring (= 1.7.2)
|
||||
spring-commands-rspec
|
||||
stripe
|
||||
test-unit (~> 3.0)
|
||||
timecop
|
||||
truncate_html
|
||||
turbo-sprockets-rails3
|
||||
@@ -828,7 +834,7 @@ DEPENDENCIES
|
||||
wkhtmltopdf-binary
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.1.9p490
|
||||
ruby 2.2.10p489
|
||||
|
||||
BUNDLED WITH
|
||||
1.17.2
|
||||
|
||||
@@ -3,20 +3,20 @@
|
||||
|
||||
# Open Food Network
|
||||
|
||||
The Open Food Network is an online marketplace for local food. It enables a network of independent online food stores that connect farmers and food hubs (including coops, online farmers' markets, independent food businesses etc); with individuals and local businesses. It gives farmers and food hubs an easier and fairer way to distribute their food.
|
||||
The Open Food Network is an online marketplace for local food. It enables a network of independent online food stores that connects farmers and food hubs (including co-ops, online farmers markets, independent food businesses, etc) with individuals and local businesses. It gives farmers and food hubs an easier and fairer way to distribute their food.
|
||||
|
||||
Supported by the Open Food Foundation and a network of global affiliates, we are proudly open source and not-for-profit - we're trying to seriously disrupt the concentration of power in global agri-food systems, and we need as many smart people working together on this as possible.
|
||||
|
||||
We're part of global movement - get involved!
|
||||
|
||||
* Join the conversation [on Slack][slack-invite]. Make sure you introduce yourself in the #general channel
|
||||
* Join the conversation [on Slack][slack-invite]. Make sure you introduce yourself in the #general channel.
|
||||
* Head to [https://openfoodnetwork.org](https://openfoodnetwork.org) for more information about the global OFN project.
|
||||
* Check out the [User Guide](https://guide.openfoodnetwork.org/) for a list of features and tutorials.
|
||||
* Join our [discussion forum](https://community.openfoodnetwork.org).
|
||||
|
||||
## Contributing
|
||||
|
||||
If you are interested in contributing to the OFN in any capacity, please introducing yourself [on Slack][slack-invite], and have a look through our [Contributor Guide][contributor-guide]
|
||||
If you are interested in contributing to the OFN in any capacity, please introduce yourself [on Slack][slack-invite], and have a look through our [Contributor Guide][contributor-guide].
|
||||
|
||||
Our [GETTING_STARTED](GETTING_STARTED.md) and [CONTRIBUTING](CONTRIBUTING.md) guides are the best place to start for developers looking to set up a development environment and make contributions to the codebase.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $filter, $http, $window, BulkProducts, DisplayProperties, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth, Columns, tax_categories, RequestMonitor) ->
|
||||
angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $filter, $http, $window, BulkProducts, DisplayProperties, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, Columns, tax_categories, RequestMonitor) ->
|
||||
$scope.StatusMessage = StatusMessage
|
||||
|
||||
$scope.columns = Columns.columns
|
||||
@@ -39,12 +39,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
$scope.DisplayProperties = DisplayProperties
|
||||
|
||||
$scope.initialise = ->
|
||||
SpreeApiAuth.authorise()
|
||||
.then ->
|
||||
$scope.spree_api_key_ok = true
|
||||
$scope.fetchProducts()
|
||||
.catch (message) ->
|
||||
$scope.api_error_msg = message
|
||||
$scope.fetchProducts()
|
||||
|
||||
$scope.$watchCollection '[query, producerFilter, categoryFilter, importDateFilter, per_page]', ->
|
||||
$scope.page = 1 # Reset page when changing filters for new search
|
||||
@@ -108,9 +103,15 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
$scope.categoryFilter = "0"
|
||||
$scope.importDateFilter = "0"
|
||||
|
||||
confirm_unsaved_changes = () ->
|
||||
(DirtyProducts.count() > 0 and confirm(t("unsaved_changes_confirmation"))) or (DirtyProducts.count() == 0)
|
||||
|
||||
editProductUrl = (product, variant) ->
|
||||
"/admin/products/" + product.permalink_live + ((if variant then "/variants/" + variant.id else "")) + "/edit"
|
||||
|
||||
$scope.editWarn = (product, variant) ->
|
||||
if (DirtyProducts.count() > 0 and confirm(t("unsaved_changes_confirmation"))) or (DirtyProducts.count() == 0)
|
||||
window.location = "/admin/products/" + product.permalink_live + ((if variant then "/variants/" + variant.id else "")) + "/edit"
|
||||
if confirm_unsaved_changes()
|
||||
window.open(editProductUrl(product, variant), "_blank")
|
||||
|
||||
|
||||
$scope.toggleShowAllVariants = ->
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
angular.module("admin.indexUtils").factory "SpreeApiAuth", ($q, $http, SpreeApiKey) ->
|
||||
new class SpreeApiAuth
|
||||
authorise: ->
|
||||
deferred = $q.defer()
|
||||
|
||||
$http.get("/api/users/authorise_api?token=" + SpreeApiKey)
|
||||
.success (response) ->
|
||||
if response?.success == "Use of API Authorised"
|
||||
$http.defaults.headers.common["X-Spree-Token"] = SpreeApiKey
|
||||
deferred.resolve()
|
||||
|
||||
.error (response) ->
|
||||
error = response?.error || t('js.unauthorized')
|
||||
deferred.reject(error)
|
||||
|
||||
deferred.promise
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl", ($scope, $http, $timeout, Indexer, Columns, Views, SpreeApiAuth, PagedFetcher, StatusMessage, RequestMonitor, hubs, producers, hubPermissions, InventoryItems, VariantOverrides, DirtyVariantOverrides) ->
|
||||
angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl", ($scope, $http, $timeout, Indexer, Columns, Views, PagedFetcher, StatusMessage, RequestMonitor, hubs, producers, hubPermissions, InventoryItems, VariantOverrides, DirtyVariantOverrides) ->
|
||||
$scope.hubs = Indexer.index hubs
|
||||
$scope.hub_id = if hubs.length == 1 then hubs[0].id else null
|
||||
$scope.products = []
|
||||
@@ -39,13 +39,7 @@ angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl",
|
||||
$scope.producerFilter != 0 || $scope.query != ''
|
||||
|
||||
$scope.initialise = ->
|
||||
SpreeApiAuth.authorise()
|
||||
.then ->
|
||||
$scope.spree_api_key_ok = true
|
||||
$scope.fetchProducts()
|
||||
.catch (message) ->
|
||||
$scope.api_error_msg = message
|
||||
|
||||
$scope.fetchProducts()
|
||||
|
||||
$scope.fetchProducts = ->
|
||||
url = "/api/products/overridable?page=::page::;per_page=100"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Darkswarm.controller "OrderCycleCtrl", ($scope, $timeout, OrderCycle) ->
|
||||
Darkswarm.controller "OrderCycleCtrl", ($scope, $rootScope, $timeout, OrderCycle) ->
|
||||
$scope.order_cycle = OrderCycle.order_cycle
|
||||
$scope.OrderCycle = OrderCycle
|
||||
|
||||
@@ -6,11 +6,12 @@ Darkswarm.controller "OrderCycleCtrl", ($scope, $timeout, OrderCycle) ->
|
||||
# This is a hack. We should probably write our own "popover" directive
|
||||
# That takes an expression instead of a trigger, and binds to that
|
||||
$timeout =>
|
||||
$rootScope.$broadcast 'orderCycleSelected'
|
||||
if !$scope.OrderCycle.selected()
|
||||
$("#order_cycle_id").trigger("openTrigger")
|
||||
|
||||
|
||||
Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $timeout, OrderCycle, Products, Variants, Cart, ChangeableOrdersAlert) ->
|
||||
Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $rootScope, $timeout, OrderCycle, Products, Variants, Cart, ChangeableOrdersAlert) ->
|
||||
# Track previous order cycle id for use with revertOrderCycle()
|
||||
$scope.previous_order_cycle_id = OrderCycle.order_cycle.order_cycle_id
|
||||
$scope.$watch 'order_cycle.order_cycle_id', (newValue, oldValue)->
|
||||
@@ -32,3 +33,4 @@ Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $timeout, OrderCycle, Prod
|
||||
Products.update()
|
||||
Cart.reloadFinalisedLineItems()
|
||||
ChangeableOrdersAlert.reload()
|
||||
$rootScope.$broadcast 'orderCycleSelected'
|
||||
|
||||
@@ -1,41 +1,65 @@
|
||||
Darkswarm.controller "ProductsCtrl", ($scope, $filter, $rootScope, Products, OrderCycle, FilterSelectorsService, Cart, Taxons, Properties) ->
|
||||
Darkswarm.controller "ProductsCtrl", ($scope, $filter, $rootScope, Products, OrderCycle, OrderCycleResource, FilterSelectorsService, Cart, Dereferencer, Taxons, Properties, currentHub, $timeout) ->
|
||||
$scope.Products = Products
|
||||
$scope.Cart = Cart
|
||||
$scope.query = ""
|
||||
$scope.taxonSelectors = FilterSelectorsService.createSelectors()
|
||||
$scope.propertySelectors = FilterSelectorsService.createSelectors()
|
||||
$scope.filtersActive = true
|
||||
$scope.limit = 10
|
||||
$scope.page = 1
|
||||
$scope.per_page = 10
|
||||
$scope.order_cycle = OrderCycle.order_cycle
|
||||
# $scope.infiniteDisabled = true
|
||||
$scope.supplied_taxons = null
|
||||
$scope.supplied_properties = null
|
||||
|
||||
# All of this logic basically just replicates the functionality filtering an ng-repeat
|
||||
# except that it allows us to filter a separate list before rendering, meaning that
|
||||
# we can get much better performance when applying filters by resetting the limit on the
|
||||
# number of products being rendered each time a filter is changed.
|
||||
$rootScope.$on "orderCycleSelected", ->
|
||||
$scope.update_filters()
|
||||
$scope.clearAll()
|
||||
|
||||
$scope.$watch "Products.loading", (newValue, oldValue) ->
|
||||
$scope.updateFilteredProducts()
|
||||
$scope.$broadcast("loadFilterSelectors") if !newValue
|
||||
$scope.update_filters = ->
|
||||
order_cycle_id = OrderCycle.order_cycle.order_cycle_id
|
||||
|
||||
$scope.incrementLimit = ->
|
||||
if $scope.limit < Products.products.length
|
||||
$scope.limit += 10
|
||||
$scope.updateVisibleProducts()
|
||||
return unless order_cycle_id
|
||||
|
||||
$scope.$watch 'query', -> $scope.updateFilteredProducts()
|
||||
$scope.$watchCollection 'activeTaxons', -> $scope.updateFilteredProducts()
|
||||
$scope.$watchCollection 'activeProperties', -> $scope.updateFilteredProducts()
|
||||
params = {
|
||||
id: order_cycle_id,
|
||||
distributor: currentHub.id
|
||||
}
|
||||
OrderCycleResource.taxons params, (data)=>
|
||||
$scope.supplied_taxons = {}
|
||||
data.map( (taxon) ->
|
||||
$scope.supplied_taxons[taxon.id] = Taxons.taxons_by_id[taxon.id]
|
||||
)
|
||||
OrderCycleResource.properties params, (data)=>
|
||||
$scope.supplied_properties = {}
|
||||
data.map( (property) ->
|
||||
$scope.supplied_properties[property.id] = Properties.properties_by_id[property.id]
|
||||
)
|
||||
|
||||
$scope.updateFilteredProducts = ->
|
||||
$scope.limit = 10
|
||||
f1 = $filter('products')(Products.products, $scope.query)
|
||||
f2 = $filter('taxons')(f1, $scope.activeTaxons)
|
||||
$scope.filteredProducts = $filter('properties')(f2, $scope.activeProperties)
|
||||
$scope.updateVisibleProducts()
|
||||
$scope.loadMore = ->
|
||||
if ($scope.page * $scope.per_page) <= Products.products.length
|
||||
$scope.loadMoreProducts()
|
||||
|
||||
$scope.updateVisibleProducts = ->
|
||||
$scope.visibleProducts = $filter('limitTo')($scope.filteredProducts, $scope.limit)
|
||||
$scope.$watch 'query', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
|
||||
$scope.$watchCollection 'activeTaxons', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
|
||||
$scope.$watchCollection 'activeProperties', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
|
||||
|
||||
$scope.loadProducts = ->
|
||||
$scope.page = 1
|
||||
Products.update($scope.queryParams())
|
||||
|
||||
$scope.loadMoreProducts = ->
|
||||
Products.update($scope.queryParams($scope.page + 1), true)
|
||||
$scope.page += 1
|
||||
|
||||
$scope.queryParams = (page = null) ->
|
||||
{
|
||||
id: $scope.order_cycle.order_cycle_id,
|
||||
page: page || $scope.page,
|
||||
per_page: $scope.per_page,
|
||||
'q[name_or_meta_keywords_or_supplier_name_cont]': $scope.query,
|
||||
'q[properties_id_or_supplier_properties_id_in_any][]': $scope.activeProperties,
|
||||
'q[primary_taxon_id_in_any][]': $scope.activeTaxons
|
||||
}
|
||||
|
||||
$scope.searchKeypress = (e)->
|
||||
code = e.keyCode || e.which
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
Darkswarm.factory 'OrderCycleResource', ($resource) ->
|
||||
$resource('/api/order_cycles/:id', {}, {
|
||||
'products':
|
||||
method: 'GET'
|
||||
isArray: true
|
||||
url: '/api/order_cycles/:id/products'
|
||||
params:
|
||||
id: '@id'
|
||||
'taxons':
|
||||
method: 'GET'
|
||||
isArray: true
|
||||
url: '/api/order_cycles/:id/taxons'
|
||||
params:
|
||||
id: '@id'
|
||||
'properties':
|
||||
method: 'GET'
|
||||
isArray: true
|
||||
url: '/api/order_cycles/:id/properties'
|
||||
params:
|
||||
id: '@id'
|
||||
})
|
||||
@@ -1,26 +1,34 @@
|
||||
Darkswarm.factory 'Products', ($resource, Shopfront, Dereferencer, Taxons, Properties, Cart, Variants) ->
|
||||
Darkswarm.factory 'Products', (OrderCycleResource, OrderCycle, Shopfront, currentHub, Dereferencer, Taxons, Properties, Cart, Variants) ->
|
||||
new class Products
|
||||
constructor: ->
|
||||
@update()
|
||||
|
||||
# TODO: don't need to scope this into object
|
||||
# Already on object as far as controller scope is concerned
|
||||
products: null
|
||||
products: []
|
||||
fetched_products: []
|
||||
loading: true
|
||||
|
||||
update: =>
|
||||
update: (params = {}, load_more = false) =>
|
||||
@loading = true
|
||||
@products = []
|
||||
$resource("/shop/products").query (products)=>
|
||||
@products = products
|
||||
order_cycle_id = OrderCycle.order_cycle.order_cycle_id
|
||||
|
||||
if order_cycle_id == undefined
|
||||
@loading = false
|
||||
return
|
||||
|
||||
params['id'] = order_cycle_id
|
||||
params['distributor'] = currentHub.id
|
||||
|
||||
OrderCycleResource.products params, (data)=>
|
||||
@products = [] unless load_more
|
||||
@fetched_products = data
|
||||
@extend()
|
||||
@dereference()
|
||||
@registerVariants()
|
||||
@products = @products.concat(@fetched_products)
|
||||
@loading = false
|
||||
|
||||
extend: ->
|
||||
for product in @products
|
||||
for product in @fetched_products
|
||||
if product.variants?.length > 0
|
||||
prices = (v.price for v in product.variants)
|
||||
product.price = Math.min.apply(null, prices)
|
||||
@@ -30,7 +38,7 @@ Darkswarm.factory 'Products', ($resource, Shopfront, Dereferencer, Taxons, Prope
|
||||
product.largeImage = product.images[0]?.large_url if product.images
|
||||
|
||||
dereference: ->
|
||||
for product in @products
|
||||
for product in @fetched_products
|
||||
product.supplier = Shopfront.producers_by_id[product.supplier.id]
|
||||
Dereferencer.dereference product.taxons, Taxons.taxons_by_id
|
||||
|
||||
@@ -40,7 +48,7 @@ Darkswarm.factory 'Products', ($resource, Shopfront, Dereferencer, Taxons, Prope
|
||||
# May return different objects! If the variant has already been registered
|
||||
# by another service, we fetch those
|
||||
registerVariants: ->
|
||||
for product in @products
|
||||
for product in @fetched_products
|
||||
if product.variants
|
||||
product.variant_names = ""
|
||||
product.variants = for variant in product.variants
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
.small-4.medium-2.large-2.columns.variant-price
|
||||
.table-cell.price
|
||||
%i.ofn-i_009-close
|
||||
{{ ::variant.price_with_fees | localizeCurrency }}
|
||||
{{ variant.price_with_fees | localizeCurrency }}
|
||||
|
||||
-# Now in a template in app/assets/javascripts/templates !
|
||||
%price-breakdown{"price-breakdown" => "_", variant: "variant",
|
||||
|
||||
@@ -255,6 +255,9 @@ text-angular {
|
||||
background-color: #4583bf;
|
||||
}
|
||||
}
|
||||
a {
|
||||
color: $spree-green
|
||||
}
|
||||
}
|
||||
|
||||
span.required {
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
require 'open_food_network/products_cache_integrity_checker'
|
||||
|
||||
module Admin
|
||||
class CacheSettingsController < Spree::Admin::BaseController
|
||||
def edit
|
||||
@results = Exchange.cachable.map do |exchange|
|
||||
checker = OpenFoodNetwork::ProductsCacheIntegrityChecker
|
||||
.new(exchange.receiver, exchange.order_cycle)
|
||||
|
||||
{
|
||||
distributor: exchange.receiver,
|
||||
order_cycle: exchange.order_cycle,
|
||||
status: checker.ok?,
|
||||
diff: checker.diff
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
Spree::Config.set(params[:preferences])
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to main_app.edit_admin_cache_settings_path }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,16 +1,44 @@
|
||||
# Base controller for OFN's API
|
||||
# Includes the minimum machinery required by ActiveModelSerializers
|
||||
require_dependency 'spree/api/controller_setup'
|
||||
|
||||
module Api
|
||||
class BaseController < Spree::Api::BaseController
|
||||
# Need to include these because Spree::Api::BaseContoller inherits
|
||||
# from ActionController::Metal rather than ActionController::Base
|
||||
# and they are required by ActiveModelSerializers
|
||||
class BaseController < ActionController::Metal
|
||||
include Spree::Api::ControllerSetup
|
||||
include Spree::Core::ControllerHelpers::SSL
|
||||
include ::ActionController::Head
|
||||
|
||||
respond_to :json
|
||||
|
||||
attr_accessor :current_api_user
|
||||
|
||||
before_filter :set_content_type
|
||||
before_filter :authenticate_user
|
||||
after_filter :set_jsonp_format
|
||||
|
||||
rescue_from Exception, with: :error_during_processing
|
||||
rescue_from CanCan::AccessDenied, with: :unauthorized
|
||||
rescue_from ActiveRecord::RecordNotFound, with: :not_found
|
||||
|
||||
helper Spree::Api::ApiHelpers
|
||||
|
||||
ssl_allowed
|
||||
|
||||
# Include these because we inherit from ActionController::Metal
|
||||
# rather than ActionController::Base and these are required for AMS
|
||||
include ActionController::Serialization
|
||||
include ActionController::UrlFor
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
use_renderers :json
|
||||
check_authorization
|
||||
|
||||
def set_jsonp_format
|
||||
return unless params[:callback] && request.get?
|
||||
|
||||
self.response_body = "#{params[:callback]}(#{response_body})"
|
||||
headers["Content-Type"] = 'application/javascript'
|
||||
end
|
||||
|
||||
def respond_with_conflict(json_hash)
|
||||
render json: json_hash, status: :conflict
|
||||
end
|
||||
@@ -19,16 +47,62 @@ module Api
|
||||
|
||||
# Use logged in user (spree_current_user) for API authentication (current_api_user)
|
||||
def authenticate_user
|
||||
@current_api_user = try_spree_current_user
|
||||
super
|
||||
return if @current_api_user = try_spree_current_user
|
||||
if api_key.blank?
|
||||
# An anonymous user
|
||||
@current_api_user = Spree.user_class.new
|
||||
return
|
||||
end
|
||||
|
||||
return if @current_api_user = Spree.user_class.find_by_spree_api_key(api_key.to_s)
|
||||
|
||||
invalid_api_key
|
||||
end
|
||||
|
||||
# Allows API access without authentication, but only for OFN controllers which inherit
|
||||
# from Api::BaseController. @current_api_user will now initialize an empty Spree::User
|
||||
# unless one is present. We now also apply devise's `check_authorization`. See here for
|
||||
# details: https://github.com/CanCanCommunity/cancancan/wiki/Ensure-Authorization
|
||||
def requires_authentication?
|
||||
false
|
||||
def set_content_type
|
||||
content_type = case params[:format]
|
||||
when "json"
|
||||
"application/json"
|
||||
when "xml"
|
||||
"text/xml"
|
||||
end
|
||||
headers["Content-Type"] = content_type
|
||||
end
|
||||
|
||||
def error_during_processing(exception)
|
||||
render(text: { exception: exception.message }.to_json,
|
||||
status: :unprocessable_entity) && return
|
||||
end
|
||||
|
||||
def current_ability
|
||||
Spree::Ability.new(current_api_user)
|
||||
end
|
||||
|
||||
def api_key
|
||||
request.headers["X-Spree-Token"] || params[:token]
|
||||
end
|
||||
helper_method :api_key
|
||||
|
||||
def invalid_resource!(resource)
|
||||
@resource = resource
|
||||
render(json: { error: I18n.t(:invalid_resource, scope: "spree.api"),
|
||||
errors: @resource.errors },
|
||||
status: :unprocessable_entity)
|
||||
end
|
||||
|
||||
def invalid_api_key
|
||||
render(json: { error: I18n.t(:invalid_api_key, key: api_key, scope: "spree.api") },
|
||||
status: :unauthorized) && return
|
||||
end
|
||||
|
||||
def unauthorized
|
||||
render(json: { error: I18n.t(:unauthorized, scope: "spree.api") },
|
||||
status: :unauthorized) && return
|
||||
end
|
||||
|
||||
def not_found
|
||||
render(json: { error: I18n.t(:resource_not_found, scope: "spree.api") },
|
||||
status: :not_found) && return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
88
app/controllers/api/order_cycles_controller.rb
Normal file
88
app/controllers/api/order_cycles_controller.rb
Normal file
@@ -0,0 +1,88 @@
|
||||
module Api
|
||||
class OrderCyclesController < BaseController
|
||||
include EnterprisesHelper
|
||||
respond_to :json
|
||||
|
||||
skip_authorization_check
|
||||
|
||||
def products
|
||||
products = ProductsRenderer.new(
|
||||
distributor,
|
||||
order_cycle,
|
||||
customer,
|
||||
search_params
|
||||
).products_json
|
||||
|
||||
render json: products
|
||||
rescue ProductsRenderer::NoProducts
|
||||
render status: :not_found, json: ''
|
||||
end
|
||||
|
||||
def taxons
|
||||
taxons = Spree::Taxon.
|
||||
joins(:products).
|
||||
where(spree_products: { id: distributed_products }).
|
||||
select('DISTINCT spree_taxons.*')
|
||||
|
||||
render json: ActiveModel::ArraySerializer.new(taxons, each_serializer: Api::TaxonSerializer)
|
||||
end
|
||||
|
||||
def properties
|
||||
render json: ActiveModel::ArraySerializer.new(
|
||||
product_properties | producer_properties, each_serializer: Api::PropertySerializer
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def product_properties
|
||||
Spree::Property.
|
||||
joins(:products).
|
||||
where(spree_products: { id: distributed_products }).
|
||||
select('DISTINCT spree_properties.*')
|
||||
end
|
||||
|
||||
def producer_properties
|
||||
producers = Enterprise.
|
||||
joins(:supplied_products).
|
||||
where(spree_products: { id: distributed_products })
|
||||
|
||||
Spree::Property.
|
||||
joins(:producer_properties).
|
||||
where(producer_properties: { producer_id: producers }).
|
||||
select('DISTINCT spree_properties.*')
|
||||
end
|
||||
|
||||
def search_params
|
||||
permitted_search_params = params.slice :q, :page, :per_page
|
||||
|
||||
if permitted_search_params.key? :q
|
||||
permitted_search_params[:q].slice!(*permitted_ransack_params)
|
||||
end
|
||||
|
||||
permitted_search_params
|
||||
end
|
||||
|
||||
def permitted_ransack_params
|
||||
[:name_or_meta_keywords_or_supplier_name_cont,
|
||||
:properties_id_or_supplier_properties_id_in_any,
|
||||
:primary_taxon_id_in_any]
|
||||
end
|
||||
|
||||
def distributor
|
||||
Enterprise.find_by_id(params[:distributor])
|
||||
end
|
||||
|
||||
def order_cycle
|
||||
OrderCycle.find_by_id(params[:id])
|
||||
end
|
||||
|
||||
def customer
|
||||
@current_api_user.andand.customer_of(distributor) || nil
|
||||
end
|
||||
|
||||
def distributed_products
|
||||
OrderCycleDistributedProducts.new(distributor, order_cycle, customer).products_relation
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -47,7 +47,6 @@ module Api
|
||||
render json: @product, serializer: Api::Admin::ProductSerializer, status: 204
|
||||
end
|
||||
|
||||
# TODO: This should be named 'managed'. Is the action above used? Maybe we should remove it.
|
||||
def bulk_products
|
||||
product_query = OpenFoodNetwork::Permissions.new(current_api_user).
|
||||
editable_products.merge(product_scope)
|
||||
@@ -94,10 +93,13 @@ module Api
|
||||
|
||||
private
|
||||
|
||||
# Copied and modified from SpreeApi::BaseController to allow
|
||||
# enterprise users to access inactive products
|
||||
def find_product(id)
|
||||
product_scope.find_by_permalink!(id.to_s)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
product_scope.find(id)
|
||||
end
|
||||
|
||||
def product_scope
|
||||
# This line modified
|
||||
if current_api_user.has_spree_role?("admin") || current_api_user.enterprises.present?
|
||||
scope = Spree::Product
|
||||
if params[:show_deleted]
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
require 'open_food_network/cached_products_renderer'
|
||||
|
||||
class ShopController < BaseController
|
||||
layout "darkswarm"
|
||||
before_filter :require_distributor_chosen, :set_order_cycles, except: :changeable_orders_alert
|
||||
@@ -9,19 +7,6 @@ class ShopController < BaseController
|
||||
redirect_to main_app.enterprise_shop_path(current_distributor)
|
||||
end
|
||||
|
||||
def products
|
||||
renderer = OpenFoodNetwork::CachedProductsRenderer.new(current_distributor,
|
||||
current_order_cycle)
|
||||
|
||||
# If we add any more filtering logic, we should probably
|
||||
# move it all to a lib class like 'CachedProductsFilterer'
|
||||
products_json = filter(renderer.products_json)
|
||||
|
||||
render json: products_json
|
||||
rescue OpenFoodNetwork::CachedProductsRenderer::NoProducts
|
||||
render status: :not_found, json: ''
|
||||
end
|
||||
|
||||
def order_cycle
|
||||
if request.post?
|
||||
if oc = OrderCycle.with_distributor(@distributor).active.find_by_id(params[:order_cycle_id])
|
||||
@@ -39,27 +24,4 @@ class ShopController < BaseController
|
||||
def changeable_orders_alert
|
||||
render layout: false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filtered_json(products_json)
|
||||
if applicator.rules.any?
|
||||
filter(products_json)
|
||||
else
|
||||
products_json
|
||||
end
|
||||
end
|
||||
|
||||
def filter(products_json)
|
||||
products_hash = JSON.parse(products_json)
|
||||
applicator.filter!(products_hash)
|
||||
JSON.unparse(products_hash)
|
||||
end
|
||||
|
||||
def applicator
|
||||
return @applicator unless @applicator.nil?
|
||||
@applicator = OpenFoodNetwork::TagRuleApplicator.new(current_distributor,
|
||||
"FilterProducts",
|
||||
current_customer.andand.tag_list)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -46,8 +46,11 @@ Spree::Admin::ProductsController.class_eval do
|
||||
end
|
||||
|
||||
def update
|
||||
original_supplier_id = @product.supplier_id
|
||||
|
||||
delete_stock_params_and_set_after do
|
||||
super
|
||||
ExchangeVariantDeleter.new.delete(@product) if original_supplier_id != @product.supplier_id
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ Spree::Admin::ReportsController.class_eval do
|
||||
end
|
||||
|
||||
def orders_and_fulfillment
|
||||
params[:q] ||= {}
|
||||
params[:q] ||= orders_and_fulfillment_default_filters
|
||||
|
||||
# -- Prepare Form Options
|
||||
permissions = OpenFoodNetwork::Permissions.new(spree_current_user)
|
||||
@@ -278,4 +278,10 @@ Spree::Admin::ReportsController.class_eval do
|
||||
def timestamp
|
||||
Time.zone.now.strftime("%Y%m%d")
|
||||
end
|
||||
|
||||
def orders_and_fulfillment_default_filters
|
||||
now = Time.zone.now
|
||||
{ completed_at_gt: (now - 1.month).beginning_of_day,
|
||||
completed_at_lt: (now + 1.day).beginning_of_day }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -58,14 +58,14 @@ module Spree
|
||||
|
||||
def generate_api_key
|
||||
if @user.generate_spree_api_key!
|
||||
flash[:success] = Spree.t('api.key_generated')
|
||||
flash[:success] = t('spree.api.key_generated')
|
||||
end
|
||||
redirect_to edit_admin_user_path(@user)
|
||||
end
|
||||
|
||||
def clear_api_key
|
||||
if @user.clear_spree_api_key!
|
||||
flash[:success] = Spree.t('api.key_cleared')
|
||||
flash[:success] = t('spree.api.key_cleared')
|
||||
end
|
||||
redirect_to edit_admin_user_path(@user)
|
||||
end
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
require_dependency 'spree/api/controller_setup'
|
||||
|
||||
module Spree
|
||||
module Api
|
||||
class BaseController < ActionController::Metal
|
||||
include Spree::Api::ControllerSetup
|
||||
include Spree::Core::ControllerHelpers::SSL
|
||||
include ::ActionController::Head
|
||||
|
||||
self.responder = Spree::Api::Responders::AppResponder
|
||||
|
||||
respond_to :json
|
||||
|
||||
attr_accessor :current_api_user
|
||||
|
||||
before_filter :set_content_type
|
||||
before_filter :check_for_user_or_api_key, :if => :requires_authentication?
|
||||
before_filter :authenticate_user
|
||||
after_filter :set_jsonp_format
|
||||
|
||||
rescue_from Exception, :with => :error_during_processing
|
||||
rescue_from CanCan::AccessDenied, :with => :unauthorized
|
||||
rescue_from ActiveRecord::RecordNotFound, :with => :not_found
|
||||
|
||||
helper Spree::Api::ApiHelpers
|
||||
|
||||
ssl_allowed
|
||||
|
||||
def set_jsonp_format
|
||||
if params[:callback] && request.get?
|
||||
self.response_body = "#{params[:callback]}(#{response_body})"
|
||||
headers["Content-Type"] = 'application/javascript'
|
||||
end
|
||||
end
|
||||
|
||||
def map_nested_attributes_keys(klass, attributes)
|
||||
nested_keys = klass.nested_attributes_options.keys
|
||||
attributes.inject({}) do |h, (k, v)|
|
||||
key = nested_keys.include?(k.to_sym) ? "#{k}_attributes" : k
|
||||
h[key] = v
|
||||
h
|
||||
end.with_indifferent_access
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_content_type
|
||||
content_type = case params[:format]
|
||||
when "json"
|
||||
"application/json"
|
||||
when "xml"
|
||||
"text/xml"
|
||||
end
|
||||
headers["Content-Type"] = content_type
|
||||
end
|
||||
|
||||
def check_for_user_or_api_key
|
||||
# User is already authenticated with Spree, make request this way instead.
|
||||
return true if @current_api_user = try_spree_current_user ||
|
||||
!requires_authentication?
|
||||
|
||||
return if api_key.present?
|
||||
render("spree/api/errors/must_specify_api_key", status: :unauthorized) && return
|
||||
end
|
||||
|
||||
def authenticate_user
|
||||
return if @current_api_user
|
||||
|
||||
if requires_authentication? || api_key.present?
|
||||
unless @current_api_user = Spree.user_class.find_by_spree_api_key(api_key.to_s)
|
||||
render("spree/api/errors/invalid_api_key", status: :unauthorized) && return
|
||||
end
|
||||
else
|
||||
# An anonymous user
|
||||
@current_api_user = Spree.user_class.new
|
||||
end
|
||||
end
|
||||
|
||||
def unauthorized
|
||||
render("spree/api/errors/unauthorized", status: :unauthorized) && return
|
||||
end
|
||||
|
||||
def error_during_processing(exception)
|
||||
render(text: { exception: exception.message }.to_json,
|
||||
status: :unprocessable_entity) && return
|
||||
end
|
||||
|
||||
def requires_authentication?
|
||||
true
|
||||
end
|
||||
|
||||
def not_found
|
||||
render("spree/api/errors/not_found", status: :not_found) && return
|
||||
end
|
||||
|
||||
def current_ability
|
||||
Spree::Ability.new(current_api_user)
|
||||
end
|
||||
|
||||
def invalid_resource!(resource)
|
||||
@resource = resource
|
||||
render "spree/api/errors/invalid_resource", status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def api_key
|
||||
request.headers["X-Spree-Token"] || params[:token]
|
||||
end
|
||||
helper_method :api_key
|
||||
|
||||
def find_product(id)
|
||||
product_scope.find_by_permalink!(id.to_s)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
product_scope.find(id)
|
||||
end
|
||||
|
||||
def product_scope
|
||||
if current_api_user.has_spree_role?("admin")
|
||||
scope = Product
|
||||
if params[:show_deleted]
|
||||
scope = scope.with_deleted
|
||||
end
|
||||
else
|
||||
scope = Product.active
|
||||
end
|
||||
|
||||
scope.includes(:master)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
module Spree
|
||||
module Api
|
||||
class UsersController < Spree::Api::BaseController
|
||||
respond_to :json
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,30 +0,0 @@
|
||||
require 'open_food_network/products_cache_integrity_checker'
|
||||
|
||||
ProductsCacheIntegrityCheckerJob = Struct.new(:distributor_id, :order_cycle_id) do
|
||||
def perform
|
||||
unless checker.ok?
|
||||
exception = RuntimeError.new(
|
||||
"Products JSON differs from cached version for distributor: #{distributor_id}, " \
|
||||
"order cycle: #{order_cycle_id}"
|
||||
)
|
||||
|
||||
Bugsnag.notify(exception) do |report|
|
||||
report.add_tab(:products_cache, diff: checker.diff.to_s(:text))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def checker
|
||||
OpenFoodNetwork::ProductsCacheIntegrityChecker.new(distributor, order_cycle)
|
||||
end
|
||||
|
||||
def distributor
|
||||
Enterprise.find distributor_id
|
||||
end
|
||||
|
||||
def order_cycle
|
||||
OrderCycle.find order_cycle_id
|
||||
end
|
||||
end
|
||||
@@ -1,21 +0,0 @@
|
||||
require 'open_food_network/products_renderer'
|
||||
|
||||
RefreshProductsCacheJob = Struct.new(:distributor_id, :order_cycle_id) do
|
||||
def perform
|
||||
Rails.cache.write(key, products_json)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def key
|
||||
"products-json-#{distributor_id}-#{order_cycle_id}"
|
||||
end
|
||||
|
||||
def products_json
|
||||
distributor = Enterprise.find distributor_id
|
||||
order_cycle = OrderCycle.find order_cycle_id
|
||||
OpenFoodNetwork::ProductsRenderer.new(distributor, order_cycle).products_json
|
||||
end
|
||||
end
|
||||
@@ -1,13 +1,4 @@
|
||||
class CoordinatorFee < ActiveRecord::Base
|
||||
belongs_to :order_cycle
|
||||
belongs_to :enterprise_fee
|
||||
|
||||
after_save :refresh_products_cache
|
||||
after_destroy :refresh_products_cache
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
order_cycle.refresh_products_cache
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,10 +10,6 @@ class EnterpriseFee < ActiveRecord::Base
|
||||
has_many :exchange_fees, dependent: :destroy
|
||||
has_many :exchanges, through: :exchange_fees
|
||||
|
||||
after_save :refresh_products_cache
|
||||
# After destroy, the products cache is refreshed via the after_destroy hook for
|
||||
# coordinator_fees and exchange_fees
|
||||
|
||||
attr_accessible :enterprise_id, :fee_type, :name, :tax_category_id, :calculator_type, :inherits_tax_category
|
||||
|
||||
FEE_TYPES = %w(packing transport admin sales fundraising).freeze
|
||||
@@ -59,8 +55,4 @@ class EnterpriseFee < ActiveRecord::Base
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def refresh_products_cache
|
||||
OpenFoodNetwork::ProductsCache.enterprise_fee_changed self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,9 +23,6 @@ class Exchange < ActiveRecord::Base
|
||||
validates :order_cycle, :sender, :receiver, presence: true
|
||||
validates :sender_id, uniqueness: { scope: [:order_cycle_id, :receiver_id, :incoming] }
|
||||
|
||||
after_save :refresh_products_cache
|
||||
after_destroy :refresh_products_cache_from_destroy
|
||||
|
||||
accepts_nested_attributes_for :variants
|
||||
|
||||
scope :in_order_cycle, lambda { |order_cycle| where(order_cycle_id: order_cycle) }
|
||||
@@ -98,12 +95,4 @@ class Exchange < ActiveRecord::Base
|
||||
def participant
|
||||
incoming? ? sender : receiver
|
||||
end
|
||||
|
||||
def refresh_products_cache
|
||||
OpenFoodNetwork::ProductsCache.exchange_changed self
|
||||
end
|
||||
|
||||
def refresh_products_cache_from_destroy
|
||||
OpenFoodNetwork::ProductsCache.exchange_destroyed self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
class ExchangeFee < ActiveRecord::Base
|
||||
belongs_to :exchange
|
||||
belongs_to :enterprise_fee
|
||||
|
||||
after_save :refresh_products_cache
|
||||
after_destroy :refresh_products_cache
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
exchange.refresh_products_cache
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
require 'open_food_network/products_cache'
|
||||
|
||||
class InventoryItem < ActiveRecord::Base
|
||||
attr_accessible :enterprise, :enterprise_id, :variant, :variant_id, :visible
|
||||
|
||||
@@ -13,12 +11,4 @@ class InventoryItem < ActiveRecord::Base
|
||||
|
||||
scope :visible, -> { where(visible: true) }
|
||||
scope :hidden, -> { where(visible: false) }
|
||||
|
||||
after_save :refresh_products_cache
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
OpenFoodNetwork::ProductsCache.inventory_item_changed self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,8 +23,6 @@ class OrderCycle < ActiveRecord::Base
|
||||
validates :name, :coordinator_id, presence: true
|
||||
validate :orders_close_at_after_orders_open_at?
|
||||
|
||||
after_save :refresh_products_cache
|
||||
|
||||
preference :product_selection_from_coordinator_inventory_only, :boolean, default: false
|
||||
|
||||
scope :active, lambda {
|
||||
@@ -247,10 +245,6 @@ class OrderCycle < ActiveRecord::Base
|
||||
coordinator.users.include? user
|
||||
end
|
||||
|
||||
def refresh_products_cache
|
||||
OpenFoodNetwork::ProductsCache.order_cycle_changed self
|
||||
end
|
||||
|
||||
def items_bought_by_user(user, distributor)
|
||||
# The Spree::Order.complete scope only checks for completed_at date
|
||||
# it does not ensure state is "complete"
|
||||
|
||||
@@ -4,9 +4,6 @@ class ProducerProperty < ActiveRecord::Base
|
||||
|
||||
default_scope order("#{table_name}.position")
|
||||
|
||||
after_save :refresh_products_cache
|
||||
after_destroy :refresh_products_cache_from_destroy
|
||||
|
||||
scope :ever_sold_by, ->(shop) {
|
||||
joins(producer: { supplied_products: { variants: { exchanges: :order_cycle } } }).
|
||||
merge(Exchange.outgoing).
|
||||
@@ -29,14 +26,4 @@ class ProducerProperty < ActiveRecord::Base
|
||||
Spree::Property.create(name: name, presentation: name)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
OpenFoodNetwork::ProductsCache.producer_property_changed self
|
||||
end
|
||||
|
||||
def refresh_products_cache_from_destroy
|
||||
OpenFoodNetwork::ProductsCache.producer_property_destroyed self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,16 +2,9 @@ Spree::Classification.class_eval do
|
||||
belongs_to :product, class_name: "Spree::Product", touch: true
|
||||
|
||||
before_destroy :dont_destroy_if_primary_taxon
|
||||
after_destroy :refresh_products_cache
|
||||
after_save :refresh_products_cache
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
product = Spree::Product.with_deleted.find(product_id) if product.blank?
|
||||
product.refresh_products_cache
|
||||
end
|
||||
|
||||
def dont_destroy_if_primary_taxon
|
||||
if product.primary_taxon == taxon
|
||||
errors.add :base, I18n.t(:spree_classification_primary_taxon_error, taxon: taxon.name, product: product.name)
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
Spree::Image.class_eval do
|
||||
after_save :refresh_products_cache
|
||||
after_destroy :refresh_products_cache
|
||||
|
||||
# Spree stores attachent definitions in JSON. This converts the style name and format to
|
||||
# strings. However, when paperclip encounters these, it doesn't recognise the format.
|
||||
# Here we solve that problem by converting format and style name to symbols.
|
||||
@@ -23,10 +20,4 @@ Spree::Image.class_eval do
|
||||
end
|
||||
|
||||
reformat_styles
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
viewable.try :refresh_products_cache
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
module Spree
|
||||
OptionType.class_eval do
|
||||
has_many :products, through: :product_option_types
|
||||
after_save :refresh_products_cache
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
products(:reload).each(&:refresh_products_cache)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
module Spree
|
||||
OptionValue.class_eval do
|
||||
after_save :refresh_products_cache
|
||||
around_destroy :refresh_products_cache_from_destroy
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
variants(:reload).each(&:refresh_products_cache)
|
||||
end
|
||||
|
||||
def refresh_products_cache_from_destroy
|
||||
vs = variants(:reload).to_a
|
||||
yield
|
||||
vs.each(&:refresh_products_cache)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,30 +0,0 @@
|
||||
require 'open_food_network/products_cache'
|
||||
|
||||
module Spree
|
||||
Preference.class_eval do
|
||||
after_save :refresh_products_cache
|
||||
|
||||
# When the setting preferred_product_selection_from_inventory_only has changed, we want to
|
||||
# refresh all active exchanges for this enterprise.
|
||||
def refresh_products_cache
|
||||
if product_selection_from_inventory_only_changed?
|
||||
OpenFoodNetwork::ProductsCache.distributor_changed(enterprise)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def product_selection_from_inventory_only_changed?
|
||||
!!(key =~ product_selection_from_inventory_only_regex)
|
||||
end
|
||||
|
||||
def enterprise
|
||||
enterprise_id = key.match(product_selection_from_inventory_only_regex)[1]
|
||||
Enterprise.find(enterprise_id)
|
||||
end
|
||||
|
||||
def product_selection_from_inventory_only_regex
|
||||
/^enterprise\/product_selection_from_inventory_only\/(\d+)$/
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2,8 +2,6 @@ module Spree
|
||||
Price.class_eval do
|
||||
acts_as_paranoid without_default_scope: true
|
||||
|
||||
after_save :refresh_products_cache
|
||||
|
||||
# Allow prices to access associated soft-deleted variants.
|
||||
def variant
|
||||
Spree::Variant.unscoped { super }
|
||||
@@ -16,9 +14,5 @@ module Spree
|
||||
self.currency = Spree::Config[:currency]
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_products_cache
|
||||
variant.andand.refresh_products_cache
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -38,7 +38,6 @@ Spree::Product.class_eval do
|
||||
after_save :remove_previous_primary_taxon_from_taxons
|
||||
after_save :ensure_standard_variant
|
||||
after_save :update_units
|
||||
after_save :refresh_products_cache
|
||||
|
||||
# -- Joins
|
||||
scope :with_order_cycles_outer, -> {
|
||||
@@ -192,23 +191,17 @@ Spree::Product.class_eval do
|
||||
|
||||
def destroy_with_delete_from_order_cycles
|
||||
transaction do
|
||||
OpenFoodNetwork::ProductsCache.product_deleted(self) do
|
||||
touch_distributors
|
||||
touch_distributors
|
||||
|
||||
ExchangeVariant.
|
||||
where('exchange_variants.variant_id IN (?)', variants_including_master.with_deleted.
|
||||
select(:id)).destroy_all
|
||||
ExchangeVariant.
|
||||
where('exchange_variants.variant_id IN (?)', variants_including_master.with_deleted.
|
||||
select(:id)).destroy_all
|
||||
|
||||
destroy_without_delete_from_order_cycles
|
||||
end
|
||||
destroy_without_delete_from_order_cycles
|
||||
end
|
||||
end
|
||||
alias_method_chain :destroy, :delete_from_order_cycles
|
||||
|
||||
def refresh_products_cache
|
||||
OpenFoodNetwork::ProductsCache.product_changed self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_available_on_to_now
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
module Spree
|
||||
ProductProperty.class_eval do
|
||||
belongs_to :product, class_name: "Spree::Product", touch: true
|
||||
|
||||
after_save :refresh_products_cache
|
||||
after_destroy :refresh_products_cache
|
||||
|
||||
delegate :refresh_products_cache, to: :product
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,6 +3,20 @@ class Spree::ProductSet < ModelSet
|
||||
super(Spree::Product, [], attributes, proc { |attrs| attrs[:product_id].blank? })
|
||||
end
|
||||
|
||||
def save
|
||||
@collection_hash.each_value.all? do |product_attributes|
|
||||
update_attributes(product_attributes)
|
||||
end
|
||||
end
|
||||
|
||||
def collection_attributes=(attributes)
|
||||
@collection = Spree::Product
|
||||
.where(id: attributes.each_value.map { |product| product[:id] })
|
||||
@collection_hash = attributes
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# A separate method of updating products was required due to an issue with
|
||||
# the way Rails' assign_attributes and updates_attributes behave when
|
||||
# delegated attributes of a nested object are updated via the parent object
|
||||
@@ -19,14 +33,11 @@ class Spree::ProductSet < ModelSet
|
||||
def update_attributes(attributes)
|
||||
split_taxon_ids!(attributes)
|
||||
|
||||
found_model = @collection.find do |model|
|
||||
model.id.to_s == attributes[:id].to_s && model.persisted?
|
||||
end
|
||||
|
||||
if found_model.nil?
|
||||
product = find_model(@collection, attributes[:id])
|
||||
if product.nil?
|
||||
@klass.new(attributes).save unless @reject_if.andand.call(attributes)
|
||||
else
|
||||
update_product(found_model, attributes)
|
||||
update_product(product, attributes)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -34,28 +45,34 @@ class Spree::ProductSet < ModelSet
|
||||
attributes[:taxon_ids] = attributes[:taxon_ids].split(',') if attributes[:taxon_ids].present?
|
||||
end
|
||||
|
||||
def update_product(found_model, attributes)
|
||||
update_product_only_attributes(found_model, attributes) &&
|
||||
update_product_variants(found_model, attributes) &&
|
||||
update_product_master(found_model, attributes)
|
||||
def update_product(product, attributes)
|
||||
original_supplier = product.supplier_id
|
||||
return false unless update_product_only_attributes(product, attributes)
|
||||
ExchangeVariantDeleter.new.delete(product) if original_supplier != product.supplier_id
|
||||
|
||||
update_product_variants(product, attributes) &&
|
||||
update_product_master(product, attributes)
|
||||
end
|
||||
|
||||
def update_product_only_attributes(product, attributes)
|
||||
variant_related_attrs = [:id, :variants_attributes, :master_attributes]
|
||||
product_related_attrs = attributes.except(*variant_related_attrs)
|
||||
|
||||
return true if product_related_attrs.blank?
|
||||
|
||||
product.assign_attributes(product_related_attrs)
|
||||
|
||||
product.variants.each do |variant|
|
||||
validate_presence_of_unit_value(product, variant)
|
||||
end
|
||||
validate_presence_of_unit_value_in_product(product)
|
||||
|
||||
product.save if errors.empty?
|
||||
product.errors.empty? && product.save
|
||||
end
|
||||
|
||||
def validate_presence_of_unit_value(product, variant)
|
||||
def validate_presence_of_unit_value_in_product(product)
|
||||
product.variants.each do |variant|
|
||||
validate_presence_of_unit_value_in_variant(product, variant)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_presence_of_unit_value_in_variant(product, variant)
|
||||
return unless %w(weight volume).include?(product.variant_unit)
|
||||
return if variant.unit_value.present?
|
||||
|
||||
@@ -79,12 +96,9 @@ class Spree::ProductSet < ModelSet
|
||||
end
|
||||
|
||||
def create_or_update_variant(product, variant_attributes)
|
||||
found_variant = product.variants_including_master.find do |variant|
|
||||
variant.id.to_s == variant_attributes[:id].to_s && variant.persisted?
|
||||
end
|
||||
|
||||
if found_variant.present?
|
||||
found_variant.update_attributes(variant_attributes.except(:id))
|
||||
variant = find_model(product.variants_including_master, variant_attributes[:id])
|
||||
if variant.present?
|
||||
variant.update_attributes(variant_attributes.except(:id))
|
||||
else
|
||||
create_variant(product, variant_attributes)
|
||||
end
|
||||
@@ -115,15 +129,9 @@ class Spree::ProductSet < ModelSet
|
||||
end
|
||||
end
|
||||
|
||||
def collection_attributes=(attributes)
|
||||
@collection = Spree::Product
|
||||
.where(id: attributes.each_value.map { |product| product[:id] })
|
||||
@collection_hash = attributes
|
||||
end
|
||||
|
||||
def save
|
||||
@collection_hash.each_value.all? do |product_attributes|
|
||||
update_attributes(product_attributes)
|
||||
def find_model(collection, model_id)
|
||||
collection.find do |model|
|
||||
model.id.to_s == model_id.to_s && model.persisted?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,19 +20,8 @@ module Spree
|
||||
merge(OrderCycle.active)
|
||||
}
|
||||
|
||||
after_save :refresh_products_cache
|
||||
|
||||
# When a Property is destroyed, dependent-destroy will destroy all ProductProperties,
|
||||
# which will take care of refreshing the products cache
|
||||
|
||||
def property
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
product_properties(:reload).each(&:refresh_products_cache)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
Spree::StockMovement.class_eval do
|
||||
after_save :refresh_products_cache
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
return if stock_item.variant.blank?
|
||||
OpenFoodNetwork::ProductsCache.variant_changed stock_item.variant
|
||||
end
|
||||
end
|
||||
@@ -4,8 +4,6 @@ Spree::Taxon.class_eval do
|
||||
attachment_definitions[:icon][:path] = 'public/images/spree/taxons/:id/:style/:basename.:extension'
|
||||
attachment_definitions[:icon][:url] = '/images/spree/taxons/:id/:style/:basename.:extension'
|
||||
|
||||
after_save :refresh_products_cache
|
||||
|
||||
# Indicate which filters should be used for this taxon
|
||||
def applicable_filters
|
||||
fs = []
|
||||
@@ -49,10 +47,4 @@ Spree::Taxon.class_eval do
|
||||
ts[t.enterprise_id.to_i] << t.id
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
products(:reload).each(&:refresh_products_cache)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
require 'open_food_network/enterprise_fee_calculator'
|
||||
require 'open_food_network/variant_and_line_item_naming'
|
||||
require 'concerns/variant_stock'
|
||||
require 'open_food_network/products_cache'
|
||||
|
||||
Spree::Variant.class_eval do
|
||||
extend Spree::LocalizedNumber
|
||||
@@ -30,7 +29,6 @@ Spree::Variant.class_eval do
|
||||
|
||||
before_validation :update_weight_from_unit_value, if: ->(v) { v.product.present? }
|
||||
after_save :update_units
|
||||
after_save :refresh_products_cache
|
||||
around_destroy :destruction
|
||||
|
||||
scope :with_order_cycles_inner, -> { joins(exchanges: :order_cycle) }
|
||||
@@ -108,14 +106,6 @@ Spree::Variant.class_eval do
|
||||
OpenFoodNetwork::EnterpriseFeeCalculator.new(distributor, order_cycle).fees_by_type_for self
|
||||
end
|
||||
|
||||
def refresh_products_cache
|
||||
if is_master?
|
||||
product.refresh_products_cache
|
||||
else
|
||||
OpenFoodNetwork::ProductsCache.variant_changed self
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_weight_from_unit_value
|
||||
@@ -123,21 +113,7 @@ Spree::Variant.class_eval do
|
||||
end
|
||||
|
||||
def destruction
|
||||
if is_master?
|
||||
exchange_variants(:reload).destroy_all
|
||||
yield
|
||||
product.refresh_products_cache
|
||||
|
||||
else
|
||||
OpenFoodNetwork::ProductsCache.variant_destroyed(self) do
|
||||
# Remove this association here instead of using dependent: :destroy because
|
||||
# dependent-destroy acts before this around_filter is called, so ProductsCache
|
||||
# has no way of knowing which exchanges the variant was a member of.
|
||||
exchange_variants(:reload).destroy_all
|
||||
|
||||
# Destroy the variant
|
||||
yield
|
||||
end
|
||||
end
|
||||
exchange_variants(:reload).destroy_all
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,9 +12,6 @@ class VariantOverride < ActiveRecord::Base
|
||||
# Default stock can be nil, indicating stock should not be reset or zero, meaning reset to zero. Need to ensure this can be set by the user.
|
||||
validates :default_stock, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
||||
|
||||
after_save :refresh_products_cache_from_save
|
||||
after_destroy :refresh_products_cache_from_destroy
|
||||
|
||||
default_scope where(permission_revoked_at: nil)
|
||||
|
||||
scope :for_hubs, lambda { |hubs|
|
||||
@@ -73,14 +70,4 @@ class VariantOverride < ActiveRecord::Base
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache_from_save
|
||||
OpenFoodNetwork::ProductsCache.variant_override_changed self
|
||||
end
|
||||
|
||||
def refresh_products_cache_from_destroy
|
||||
OpenFoodNetwork::ProductsCache.variant_override_destroyed self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -62,7 +62,7 @@ class Api::Admin::EnterpriseSerializer < ActiveModel::Serializer
|
||||
# # medium: LOGO_MEDIUM_URL
|
||||
# # }
|
||||
def attachment_urls(attachment, versions)
|
||||
return unless attachment.exists?
|
||||
return unless attachment.file?
|
||||
|
||||
versions.each_with_object({}) do |version, urls|
|
||||
urls[version] = attachment.url(version)
|
||||
|
||||
@@ -1,43 +1,11 @@
|
||||
require 'open_food_network/scope_variant_to_hub'
|
||||
|
||||
class Api::ProductSerializer < ActiveModel::Serializer
|
||||
# TODO
|
||||
# Prices can't be cached? How?
|
||||
def serializable_hash
|
||||
cached_serializer_hash.merge(uncached_serializer_hash)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cached_serializer_hash
|
||||
Api::CachedProductSerializer.new(object, @options).serializable_hash
|
||||
end
|
||||
|
||||
def uncached_serializer_hash
|
||||
Api::UncachedProductSerializer.new(object, @options).serializable_hash
|
||||
end
|
||||
end
|
||||
|
||||
class Api::UncachedProductSerializer < ActiveModel::Serializer
|
||||
attributes :price
|
||||
|
||||
def price
|
||||
if options[:enterprise_fee_calculator]
|
||||
object.master.price + options[:enterprise_fee_calculator].indexed_fees_for(object.master)
|
||||
else
|
||||
object.master.price_with_fees(options[:current_distributor], options[:current_order_cycle])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Api::CachedProductSerializer < ActiveModel::Serializer
|
||||
# cached
|
||||
# delegate :cache_key, to: :object
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
|
||||
attributes :id, :name, :permalink, :meta_keywords
|
||||
attributes :group_buy, :notes, :description, :description_html
|
||||
attributes :properties_with_values
|
||||
attributes :properties_with_values, :price
|
||||
|
||||
has_many :variants, serializer: Api::VariantSerializer
|
||||
has_one :master, serializer: Api::VariantSerializer
|
||||
@@ -70,4 +38,12 @@ class Api::CachedProductSerializer < ActiveModel::Serializer
|
||||
def master
|
||||
options[:master_variants][object.id].andand.first
|
||||
end
|
||||
|
||||
def price
|
||||
if options[:enterprise_fee_calculator]
|
||||
object.master.price + options[:enterprise_fee_calculator].indexed_fees_for(object.master)
|
||||
else
|
||||
object.master.price_with_fees(options[:current_distributor], options[:current_order_cycle])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
7
app/services/exchange_variant_deleter.rb
Normal file
7
app/services/exchange_variant_deleter.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
class ExchangeVariantDeleter
|
||||
def delete(product)
|
||||
ExchangeVariant.
|
||||
where(variant_id: product.variants.select(:id)).
|
||||
delete_all
|
||||
end
|
||||
end
|
||||
@@ -1,36 +1,42 @@
|
||||
# Returns a (paginatable) AR object for the products or variants in stock for a given shop and OC.
|
||||
# The stock-checking includes on_demand and stock level overrides from variant_overrides.
|
||||
class OrderCycleDistributedProducts
|
||||
def initialize(distributor, order_cycle)
|
||||
def initialize(distributor, order_cycle, customer)
|
||||
@distributor = distributor
|
||||
@order_cycle = order_cycle
|
||||
@customer = customer
|
||||
end
|
||||
|
||||
def products_relation
|
||||
Spree::Product.where(id: stocked_products)
|
||||
Spree::Product.where(id: stocked_products).group("spree_products.id")
|
||||
end
|
||||
|
||||
def variants_relation
|
||||
@order_cycle.
|
||||
variants_distributed_by(@distributor).
|
||||
merge(stocked_variants_and_overrides)
|
||||
order_cycle.
|
||||
variants_distributed_by(distributor).
|
||||
merge(stocked_variants_and_overrides).
|
||||
select("DISTINCT spree_variants.*")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :distributor, :order_cycle, :customer
|
||||
|
||||
def stocked_products
|
||||
@order_cycle.
|
||||
variants_distributed_by(@distributor).
|
||||
order_cycle.
|
||||
variants_distributed_by(distributor).
|
||||
merge(stocked_variants_and_overrides).
|
||||
select("DISTINCT spree_variants.product_id")
|
||||
end
|
||||
|
||||
def stocked_variants_and_overrides
|
||||
Spree::Variant.
|
||||
stocked_variants = Spree::Variant.
|
||||
joins("LEFT OUTER JOIN variant_overrides ON variant_overrides.variant_id = spree_variants.id
|
||||
AND variant_overrides.hub_id = #{@distributor.id}").
|
||||
AND variant_overrides.hub_id = #{distributor.id}").
|
||||
joins(:stock_items).
|
||||
where(query_stock_with_overrides)
|
||||
|
||||
ProductTagRulesFilterer.new(distributor, customer, stocked_variants).call
|
||||
end
|
||||
|
||||
def query_stock_with_overrides
|
||||
|
||||
111
app/services/product_tag_rules_filterer.rb
Normal file
111
app/services/product_tag_rules_filterer.rb
Normal file
@@ -0,0 +1,111 @@
|
||||
# Takes a Spree::Variant AR object and filters results based on applicable tag rules.
|
||||
# Tag rules exists in the context of enterprise, customer, and variant_overrides,
|
||||
# and are applied to variant_overrides only. Returns a Spree::Variant AR object.
|
||||
|
||||
class ProductTagRulesFilterer
|
||||
def initialize(distributor, customer, variants_relation)
|
||||
@distributor = distributor
|
||||
@customer = customer
|
||||
@variants_relation = variants_relation
|
||||
end
|
||||
|
||||
def call
|
||||
return variants_relation unless distributor_rules.any?
|
||||
|
||||
filter(variants_relation)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_accessor :distributor, :customer, :variants_relation
|
||||
|
||||
def distributor_rules
|
||||
@distributor_rules ||= TagRule::FilterProducts.prioritised.for(distributor).all
|
||||
end
|
||||
|
||||
def filter(variants_relation)
|
||||
return variants_relation unless overrides_to_hide.any?
|
||||
|
||||
variants_relation.where(query_with_tag_rules)
|
||||
end
|
||||
|
||||
def query_with_tag_rules
|
||||
"#{variant_not_overriden} OR ( #{variant_overriden}
|
||||
AND ( #{override_not_hidden_by_rule}
|
||||
OR #{override_shown_by_rule} ) )"
|
||||
end
|
||||
|
||||
def variant_not_overriden
|
||||
"variant_overrides.id IS NULL"
|
||||
end
|
||||
|
||||
def variant_overriden
|
||||
"variant_overrides.id IS NOT NULL"
|
||||
end
|
||||
|
||||
def override_not_hidden_by_rule
|
||||
return "FALSE" unless overrides_to_hide.any?
|
||||
"variant_overrides.id NOT IN (#{overrides_to_hide.join(',')})"
|
||||
end
|
||||
|
||||
def override_shown_by_rule
|
||||
return "FALSE" unless overrides_to_show.any?
|
||||
"variant_overrides.id IN (#{overrides_to_show.join(',')})"
|
||||
end
|
||||
|
||||
def overrides_to_hide
|
||||
@overrides_to_hide ||= VariantOverride.where(hub_id: distributor.id).
|
||||
tagged_with(default_rule_tags + hide_rule_tags, any: true).
|
||||
pluck(:id)
|
||||
end
|
||||
|
||||
def overrides_to_show
|
||||
@overrides_to_show ||= VariantOverride.where(hub_id: distributor.id).
|
||||
tagged_with(show_rule_tags, any: true).
|
||||
pluck(:id)
|
||||
end
|
||||
|
||||
def default_rule_tags
|
||||
default_rules.map(&:preferred_variant_tags)
|
||||
end
|
||||
|
||||
def hide_rule_tags
|
||||
hide_rules.map(&:preferred_variant_tags)
|
||||
end
|
||||
|
||||
def show_rule_tags
|
||||
show_rules.map(&:preferred_variant_tags)
|
||||
end
|
||||
|
||||
def default_rules
|
||||
# These rules hide a variant_override with tag X and apply to all customers
|
||||
distributor_rules.select(&:is_default?)
|
||||
end
|
||||
|
||||
def non_default_rules
|
||||
# These rules show or hide a variant_override with tag X for customer with tag Y
|
||||
distributor_rules.reject(&:is_default?)
|
||||
end
|
||||
|
||||
def customer_applicable_rules
|
||||
# Rules which apply specifically to the current customer
|
||||
@customer_applicable_rules ||= non_default_rules.select{ |rule| customer_tagged?(rule) }
|
||||
end
|
||||
|
||||
def hide_rules
|
||||
@hide_rules ||= customer_applicable_rules.
|
||||
select{ |rule| rule.preferred_matched_variants_visibility == 'hidden' }
|
||||
end
|
||||
|
||||
def show_rules
|
||||
customer_applicable_rules - hide_rules
|
||||
end
|
||||
|
||||
def customer_tagged?(rule)
|
||||
customer_tag_list.include? rule.preferred_customer_tags
|
||||
end
|
||||
|
||||
def customer_tag_list
|
||||
customer.andand.tag_list || []
|
||||
end
|
||||
end
|
||||
98
app/services/products_renderer.rb
Normal file
98
app/services/products_renderer.rb
Normal file
@@ -0,0 +1,98 @@
|
||||
require 'open_food_network/scope_product_to_hub'
|
||||
|
||||
class ProductsRenderer
|
||||
class NoProducts < RuntimeError; end
|
||||
DEFAULT_PAGE = 1
|
||||
DEFAULT_PER_PAGE = 10
|
||||
|
||||
def initialize(distributor, order_cycle, customer, args = {})
|
||||
@distributor = distributor
|
||||
@order_cycle = order_cycle
|
||||
@customer = customer
|
||||
@args = args
|
||||
end
|
||||
|
||||
def products_json
|
||||
raise NoProducts unless order_cycle && distributor && products
|
||||
|
||||
ActiveModel::ArraySerializer.new(products,
|
||||
each_serializer: Api::ProductSerializer,
|
||||
current_order_cycle: order_cycle,
|
||||
current_distributor: distributor,
|
||||
variants: variants_for_shop_by_id,
|
||||
master_variants: master_variants_for_shop_by_id,
|
||||
enterprise_fee_calculator: enterprise_fee_calculator).to_json
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :order_cycle, :distributor, :customer, :args
|
||||
|
||||
def products
|
||||
return unless order_cycle
|
||||
|
||||
@products ||= begin
|
||||
results = distributed_products.products_relation.order(taxon_order)
|
||||
|
||||
filter_and_paginate(results).
|
||||
each { |product| product_scoper.scope(product) } # Scope results with variant_overrides
|
||||
end
|
||||
end
|
||||
|
||||
def product_scoper
|
||||
OpenFoodNetwork::ScopeProductToHub.new(distributor)
|
||||
end
|
||||
|
||||
def enterprise_fee_calculator
|
||||
OpenFoodNetwork::EnterpriseFeeCalculator.new distributor, order_cycle
|
||||
end
|
||||
|
||||
def filter_and_paginate(query)
|
||||
query.
|
||||
ransack(args[:q]).
|
||||
result.
|
||||
page(args[:page] || DEFAULT_PAGE).
|
||||
per(args[:per_page] || DEFAULT_PER_PAGE)
|
||||
end
|
||||
|
||||
def distributed_products
|
||||
OrderCycleDistributedProducts.new(distributor, order_cycle, customer)
|
||||
end
|
||||
|
||||
def taxon_order
|
||||
if distributor.preferred_shopfront_taxon_order.present?
|
||||
distributor
|
||||
.preferred_shopfront_taxon_order
|
||||
.split(",").map { |id| "spree_products.primary_taxon_id=#{id} DESC" }
|
||||
.join(", ") + ", spree_products.name ASC, spree_products.id ASC"
|
||||
else
|
||||
"spree_products.name ASC"
|
||||
end
|
||||
end
|
||||
|
||||
def variants_for_shop
|
||||
@variants_for_shop ||= begin
|
||||
scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor)
|
||||
|
||||
distributed_products.variants_relation.
|
||||
includes(:default_price, :stock_locations, :product).
|
||||
where(product_id: products).
|
||||
each { |v| scoper.scope(v) } # Scope results with variant_overrides
|
||||
end
|
||||
end
|
||||
|
||||
def variants_for_shop_by_id
|
||||
index_by_product_id variants_for_shop.reject(&:is_master)
|
||||
end
|
||||
|
||||
def master_variants_for_shop_by_id
|
||||
index_by_product_id variants_for_shop.select(&:is_master)
|
||||
end
|
||||
|
||||
def index_by_product_id(variants)
|
||||
variants.each_with_object({}) do |v, vs|
|
||||
vs[v.product_id] ||= []
|
||||
vs[v.product_id] << v
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,31 +0,0 @@
|
||||
- content_for :page_title do
|
||||
= t(:cache_settings)
|
||||
|
||||
= form_tag main_app.admin_cache_settings_path, :method => :put do
|
||||
.field
|
||||
= hidden_field_tag 'preferences[enable_products_cache?]', '0'
|
||||
= check_box_tag 'preferences[enable_products_cache?]', '1', Spree::Config[:enable_products_cache?]
|
||||
= label_tag nil, t('.enable_products_cache')
|
||||
.form-buttons
|
||||
= button t(:update), 'icon-refresh'
|
||||
|
||||
%br
|
||||
%br
|
||||
|
||||
%h4= t(:cache_state)
|
||||
%br
|
||||
%table.index
|
||||
%thead
|
||||
%tr
|
||||
%th= t('.distributor')
|
||||
%th= t('.order_cycle')
|
||||
%th= t('.status')
|
||||
%th= t('.diff')
|
||||
%tbody
|
||||
- @results.each do |result|
|
||||
%tr
|
||||
%td= result[:distributor].name
|
||||
%td= result[:order_cycle].name
|
||||
%td= result[:status] ? t(:ok) : t('.error')
|
||||
%td
|
||||
%pre= result[:diff].to_s(:text)
|
||||
@@ -1,4 +1,4 @@
|
||||
= content_for :page_title do
|
||||
- content_for :page_title do
|
||||
= t 'admin_enterprise_groups'
|
||||
|
||||
- if admin_user?
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
.row
|
||||
.alpha.two.columns
|
||||
= f.label :name, t('.name')
|
||||
%span.required *
|
||||
.six.columns.omega
|
||||
- if viewing_as_coordinator_of?(@order_cycle)
|
||||
= f.text_field :name, 'ng-model' => 'order_cycle.name', 'required' => true, 'ng-disabled' => '!loaded()'
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
.row
|
||||
.alpha.two.columns
|
||||
= label_tag t('.ready_for')
|
||||
%span.required *
|
||||
.six.columns
|
||||
= text_field_tag 'order_cycle_outgoing_exchange_0_pickup_time', '', 'id' => 'order_cycle_outgoing_exchange_0_pickup_time', 'required' => 'required', 'placeholder' => t('.ready_for_placeholder'), 'ng-model' => 'outgoing_exchange.pickup_time', 'size' => 30, 'maxlength' => 35
|
||||
%span.icon-question-sign{'ofn-with-tip' => t('admin.order_cycles.exchange_form.pickup_time_tip')}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
= content_for :page_title do
|
||||
- content_for :page_title do
|
||||
= t :admin_order_cycles
|
||||
|
||||
- content_for :main_ng_app_name do
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#{t('admin.product_import.title')}
|
||||
|
||||
= render partial: 'ams_data'
|
||||
= render partial: 'admin/shared/product_sub_menu'
|
||||
= render partial: 'spree/admin/shared/product_sub_menu'
|
||||
|
||||
.import-wrapper{ng: {app: 'admin.productImport', controller: 'ImportFormCtrl'}}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- content_for :page_title do
|
||||
#{t('admin.product_import.title')}
|
||||
|
||||
= render partial: 'admin/shared/product_sub_menu'
|
||||
= render partial: 'spree/admin/shared/product_sub_menu'
|
||||
|
||||
= render 'upload_sidebar'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
= content_for :sub_menu do
|
||||
- content_for :sub_menu do
|
||||
%ul#sub_nav.inline-menu{"data-hook" => "admin_enterprise_sub_tabs"}
|
||||
= tab :enterprises, url: main_app.admin_enterprises_path
|
||||
= tab :enterprise_relationships, url: main_app.admin_enterprise_relationships_path
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
= content_for :sub_menu do
|
||||
%ul#sub_nav.inline-menu
|
||||
= tab :products, match_path: '/products'
|
||||
= tab :option_types, match_path: '/option_types'
|
||||
= tab :properties
|
||||
= tab :prototypes
|
||||
= tab :variant_overrides, url: main_app.admin_inventory_path, match_path: '/inventory'
|
||||
= tab :import, url: main_app.admin_product_import_path, match_path: '/product_import'
|
||||
@@ -1,4 +1,4 @@
|
||||
= content_for :sub_menu do
|
||||
- content_for :sub_menu do
|
||||
%ul#sub_nav.inline-menu{"data-hook" => "admin_user_sub_tabs"}
|
||||
= tab :users, url: spree.admin_users_path
|
||||
= tab :roles, url: main_app.admin_enterprise_roles_path, match_path: '/enterprise_roles'
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
%h1.page-title= t("admin.variant_overrides.index.title")
|
||||
%a.with-tip{ 'data-powertip' => "#{t("admin.variant_overrides.index.description")}" }=t('admin.whats_this')
|
||||
|
||||
= render partial: 'admin/shared/product_sub_menu'
|
||||
= render partial: 'spree/admin/shared/product_sub_menu'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%form{ name: 'variant_overrides_form', ng: { show: "views.inventory.visible" } }
|
||||
%save-bar{ dirty: "customers_form.$dirty", persist: "false" }
|
||||
%input.red{ type: "button", value: "Save Changes", ng: { click: "update()", disabled: "!variant_overrides_form.$dirty" } }
|
||||
%input.red{ type: "button", value: t(:save_changes), ng: { click: "update()", disabled: "!variant_overrides_form.$dirty" } }
|
||||
%table.index.bulk#variant-overrides
|
||||
%col.producer{ width: "20%", ng: { show: 'columns.producer.visible' } }
|
||||
%col.product{ width: "20%", ng: { show: 'columns.product.visible' } }
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
%td.reset{ ng: { show: 'columns.reset.visible' } }
|
||||
%input{name: 'variant-overrides-{{ variant.id }}-resettable', type: 'checkbox', ng: {model: 'variantOverrides[hub_id][variant.id].resettable'}, placeholder: '{{ variant.resettable }}', 'ofn-track-variant-override' => 'resettable'}
|
||||
%td.reset{ ng: { show: 'columns.reset.visible' } }
|
||||
%input{name: 'variant-overrides-{{ variant.id }}-default_stock', type: 'text', ng: {model: 'variantOverrides[hub_id][variant.id].default_stock'}, placeholder: '{{ variant.default_stock ? variant.default_stock : "Default stock"}}', 'ofn-track-variant-override' => 'default_stock'}
|
||||
%input{name: 'variant-overrides-{{ variant.id }}-default_stock', type: 'text', ng: {model: 'variantOverrides[hub_id][variant.id].default_stock'}, placeholder: '{{ variant.default_stock ? variant.default_stock : ("admin.variant_overrides.index.default_stock" | t)}}', 'ofn-track-variant-override' => 'default_stock'}
|
||||
%td.inheritance{ ng: { show: 'columns.inheritance.visible' } }
|
||||
%input.field{ :type => 'checkbox', name: 'variant-overrides-{{ variant.id }}-inherit', ng: { model: 'inherit' }, 'track-inheritance' => true }
|
||||
%td.tags{ ng: { show: 'columns.tags.visible' } }
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
%h3
|
||||
= "#{t(:email_welcome)}!"
|
||||
= "#{t(".email_welcome")}!"
|
||||
%p.lead
|
||||
%strong
|
||||
= @enterprise.name
|
||||
= "#{t(:email_registered)} #{ Spree::Config.site_name }!"
|
||||
= "#{t(".email_registered")} #{ Spree::Config.site_name }!"
|
||||
|
||||
%p
|
||||
= t :email_userguide_html, link: link_to('Open Food Network User Guide', ContentConfig.user_guide_link)
|
||||
= t(".email_userguide_html", link: link_to(t(".userguide"), ContentConfig.user_guide_link))
|
||||
%p
|
||||
= t(".email_admin_html", link: link_to(t(".admin_panel"), spree.admin_url))
|
||||
|
||||
%p
|
||||
= t :email_admin_html, link: link_to('Admin Panel', spree.admin_url)
|
||||
|
||||
%p
|
||||
= t :email_community_html, link: link_to(t(:join_community), ContentConfig.community_forum_url)
|
||||
= t(".email_community_html", link: link_to(t(".join_community"), ContentConfig.community_forum_url))
|
||||
|
||||
= render 'shared/mailers/signoff'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.filter-shopfront.taxon-selectors.text-right
|
||||
%single-line-selectors{ selectors: "taxonSelectors", objects: "Products.products | products:query | properties:activeProperties | taxonsOf", "active-selectors" => "activeTaxons"}
|
||||
.filter-shopfront.taxon-selectors.text-right{ng: {show: 'supplied_taxons != null'}}
|
||||
%single-line-selectors{ selectors: "taxonSelectors", objects: "supplied_taxons", "active-selectors" => "activeTaxons"}
|
||||
|
||||
.filter-shopfront.property-selectors.text-right
|
||||
%single-line-selectors{ selectors: "propertySelectors", objects: "Products.products | products:query | taxons:activeTaxons | propertiesOf", "active-selectors" => "activeProperties"}
|
||||
.filter-shopfront.property-selectors.text-right{ng: {show: 'supplied_properties != null'}}
|
||||
%single-line-selectors{ selectors: "propertySelectors", objects: "supplied_properties", "active-selectors" => "activeProperties"}
|
||||
|
||||
@@ -22,14 +22,14 @@
|
||||
.small-12.medium-6.large-5.columns
|
||||
%input#search.text{"ng-model" => "query",
|
||||
placeholder: t(:products_search),
|
||||
"ng-debounce" => "100",
|
||||
"ng-debounce" => "200",
|
||||
"ofn-disable-enter" => true}
|
||||
|
||||
.small-12.medium-6.large-6.large-offset-1.columns
|
||||
= render partial: "shop/products/filters"
|
||||
|
||||
%div.pad-top{ "infinite-scroll" => "incrementLimit()", "infinite-scroll-distance" => "1", "infinite-scroll-disabled" => 'filteredProducts.length <= limit' }
|
||||
%product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in visibleProducts track by product.id", "id" => "product-{{ product.id }}"}
|
||||
%div.pad-top{ "infinite-scroll" => "loadMore()", "infinite-scroll-distance" => "1", "infinite-scroll-disabled" => 'Products.loading' }
|
||||
%product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in Products.products track by product.id", "id" => "product-{{ product.id }}"}
|
||||
= render "shop/products/summary"
|
||||
%shop-variant{variant: 'variant', "ng-repeat" => "variant in product.variants | orderBy: ['name_to_display','unit_value'] track by variant.id", "id" => "variant-{{ variant.id }}", "ng-class" => "{'out-of-stock': !variant.on_demand && variant.on_hand == 0}"}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
.small-12.columns.text-center
|
||||
%img.spinner{ src: "/assets/spinning-circles.svg" }
|
||||
|
||||
%div{"ng-show" => "filteredProducts.length == 0 && !Products.loading"}
|
||||
%div{"ng-show" => "Products.products.length == 0 && !Products.loading"}
|
||||
.row.summary
|
||||
.small-12.columns
|
||||
%p.no-results
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
= line_item.single_money.to_html
|
||||
%td.item-qty-show.align-center
|
||||
- item.states.each do |state,count|
|
||||
= "#{count} x #{t(state.humanize.downcase)}"
|
||||
= "#{count} x #{t(state.humanize.downcase, scope: [:spree, :shipment_states], default: [:missing, "none"])}"
|
||||
- unless shipment.shipped?
|
||||
%td.item-qty-edit.hidden
|
||||
= number_field_tag :quantity, item.quantity, :min => 0, :class => "line_item_quantity", :size => 5
|
||||
|
||||
@@ -73,6 +73,8 @@
|
||||
{{'js.admin.orders.shipment_states.' + order.shipment_state | t}}
|
||||
%td
|
||||
= mail_to "{{order.email}}"
|
||||
%br
|
||||
{{order.full_name}}
|
||||
%td.align-center
|
||||
%span{'ng-bind-html' => 'order.display_total'}
|
||||
%td.actions
|
||||
|
||||
@@ -7,15 +7,11 @@
|
||||
- content_for :page_actions do
|
||||
%ul.tollbar.inline-menu
|
||||
%li
|
||||
= link_to_add_fields Spree.t(:add_product_properties), 'tbody#product_properties', class: 'icon-plus button'
|
||||
%li
|
||||
%span#new_ptype_link
|
||||
= link_to Spree.t(:select_from_prototype), available_admin_prototypes_url, remote: true, 'data-update' => 'prototypes', class: 'button icon-copy'
|
||||
= link_to_add_fields t('.add_product_properties'), 'tbody#product_properties', class: 'icon-plus button'
|
||||
|
||||
= form_for @product, url: admin_product_url(@product), method: :put do |f|
|
||||
%fieldset.no-border-top
|
||||
.add_product_properties
|
||||
#prototypes
|
||||
= image_tag 'select2-spinner.gif', plugin: 'spree', style: 'display:none;', id: 'busy_indicator'
|
||||
|
||||
%table.index.sortable{"data-sortable-link" => update_positions_admin_product_product_properties_url}
|
||||
@@ -31,7 +27,7 @@
|
||||
= render partial: 'product_property_fields', locals: { f: pp_form }
|
||||
|
||||
= f.check_box :inherits_properties
|
||||
= f.label :inherits_properties, t(".inherits_properties_checkbox_hint", supplier: @product.supplier.name)
|
||||
= f.label :inherits_properties, t('.inherits_properties_checkbox_hint', supplier: @product.supplier.name)
|
||||
%br
|
||||
%br
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
.row{"data-hook" => "admin_product_meta_form"}
|
||||
.alpha.eleven.columns
|
||||
= f.field_container :meta_keywords do
|
||||
= f.label :meta_keywords, t(:product_search_keywords)
|
||||
%span.icon-question-sign{ 'ofn-with-tip' => t('admin.products.product_search_tip') }
|
||||
= f.label :meta_keywords, t('admin.products.seo.product_search_keywords')
|
||||
%span.icon-question-sign{ 'ofn-with-tip' => t('admin.products.seo.product_search_tip') }
|
||||
%br/
|
||||
= f.text_field :meta_keywords, :class => 'fullwidth', :rows => 6
|
||||
= f.field_container :meta_description do
|
||||
= f.label :meta_description, t(:SEO_keywords)
|
||||
%span.icon-question-sign{ 'ofn-with-tip' => t('admin.products.seo_tip') }
|
||||
= f.label :meta_description, t('admin.products.seo.SEO_keywords')
|
||||
%span.icon-question-sign{ 'ofn-with-tip' => t('admin.products.seo.seo_tip') }
|
||||
%br/
|
||||
= f.text_field :meta_description, :class => 'fullwidth', :rows => 6
|
||||
.alpha.eleven.columns
|
||||
= f.field_container :notes do
|
||||
= f.label :notes, t(:notes)
|
||||
= f.text_area :notes, { :class => 'fullwidth', rows: 5 }
|
||||
= f.error_message_on :notes
|
||||
= f.error_message_on :notes
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
= render partial: 'admin/shared/product_sub_menu'
|
||||
= render partial: 'spree/admin/shared/product_sub_menu'
|
||||
= render :partial => 'spree/admin/shared/product_tabs', :locals => { :current => 'Group Buy Options' }
|
||||
= render :partial => 'spree/shared/error_messages', :locals => { :target => @product }
|
||||
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
%li#new_product_link
|
||||
= button_link_to t(:new_product), new_object_url, { :remote => true, :icon => 'icon-plus', :id => 'admin_new_product' }
|
||||
|
||||
= render partial: 'admin/shared/product_sub_menu'
|
||||
= render partial: 'spree/admin/shared/product_sub_menu'
|
||||
|
||||
%div#new_product(data-hook)
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
%div{ 'ng-show' => '!spree_api_key_ok' }
|
||||
{{ api_error_msg }}
|
||||
|
||||
%div.sixteen.columns.alpha#loading{ 'ng-if' => 'RequestMonitor.loading' }
|
||||
%br
|
||||
%img.spinner{ src: "/assets/spinning-circles.svg" }
|
||||
|
||||
@@ -101,19 +101,3 @@
|
||||
angular.element(document.getElementById("new_product")).ready(function() {
|
||||
angular.bootstrap(document.getElementById("new_product"), ['admin.products']);
|
||||
});
|
||||
:javascript
|
||||
(function($){
|
||||
var base_url = "#{admin_prototypes_url}";
|
||||
var prototype_select = $('#product_prototype_id');
|
||||
prototype_select.change(function() {
|
||||
var id = prototype_select.val();
|
||||
if (id.length) {
|
||||
$('#product-from-prototype').load([ base_url, id ].join("/"));
|
||||
} else {
|
||||
$('#product-from-prototype').empty();
|
||||
}
|
||||
})
|
||||
if (prototype_select.html() == "") {
|
||||
prototype_select.change();
|
||||
}
|
||||
})(jQuery);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
= render partial: 'admin/shared/product_sub_menu'
|
||||
= render :partial => 'spree/admin/shared/product_tabs', :locals => { :current => t(:Search) }
|
||||
= render partial: 'spree/admin/shared/product_sub_menu'
|
||||
= render :partial => 'spree/admin/shared/product_tabs', :locals => { :current => t(:search) }
|
||||
= render :partial => 'spree/shared/error_messages', :locals => { :target => @product }
|
||||
|
||||
%div{ 'ng-app' => 'ofn.admin' }
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
= configurations_sidebar_menu_item Spree.t(:shipping_categories), admin_shipping_categories_path
|
||||
= configurations_sidebar_menu_item t(:enterprise_fees), main_app.admin_enterprise_fees_path
|
||||
= configurations_sidebar_menu_item Spree.t(:analytics_trackers), admin_trackers_path
|
||||
= configurations_sidebar_menu_item t('admin.cache_settings.edit.title'), main_app.edit_admin_cache_settings_path
|
||||
= configurations_sidebar_menu_item t('admin.contents.edit.title'), main_app.edit_admin_contents_path
|
||||
= configurations_sidebar_menu_item t('admin.invoice_settings.edit.title'), main_app.edit_admin_invoice_settings_path
|
||||
= configurations_sidebar_menu_item t('admin.matomo_settings.edit.title'), main_app.edit_admin_matomo_settings_path
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.form-buttons.filter-actions.actions
|
||||
= button Spree.t('actions.update'), 'icon-refresh'
|
||||
%span.or= Spree.t(:or)
|
||||
= button_link_to Spree.t('actions.cancel'), collection_url, icon: 'icon-remove'
|
||||
= button t(:update), 'icon-refresh'
|
||||
%span.or= t(:or)
|
||||
= button_link_to t(:cancel), collection_url, icon: 'icon-remove'
|
||||
|
||||
@@ -33,14 +33,14 @@
|
||||
%dd#shipment_status
|
||||
- shipment_state_classes = "state #{@order.shipment_state}"
|
||||
%span{ class: shipment_state_classes }
|
||||
= t(@order.shipment_state, scope: :shipment_states, default: [:missing, "none"])
|
||||
= t(@order.shipment_state, scope: [:spree, :shipment_states], default: [:missing, "none"])
|
||||
%dt
|
||||
= t(:payment)
|
||||
\:
|
||||
%dd#payment_status
|
||||
- payment_state_classes = "state #{@order.payment_state}"
|
||||
%span{ class: payment_state_classes }
|
||||
= t(@order.payment_state, scope: :payment_states, default: [:missing, "none"])
|
||||
= t(@order.payment_state, scope: [:spree, :payment_states], default: [:missing, "none"])
|
||||
%dt
|
||||
= t(:date_completed)
|
||||
\:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
- content_for :sub_menu do
|
||||
%ul#sub_nav.inline-menu
|
||||
= tab :products, match_path: '/products'
|
||||
= tab :option_types, match_path: '/option_types'
|
||||
= tab :properties
|
||||
= tab :prototypes
|
||||
= tab :variant_overrides, url: main_app.admin_inventory_path, match_path: '/inventory'
|
||||
= tab :import, url: main_app.admin_product_import_path, match_path: '/product_import'
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
= content_for :page_title do
|
||||
= Spree.t(:editing_product)
|
||||
- content_for :page_title do
|
||||
= t('admin.products.editing_product')
|
||||
= "\"#{@product.name}\""
|
||||
|
||||
= content_for :sidebar_title do
|
||||
- content_for :sidebar_title do
|
||||
%span.sku
|
||||
= @product.sku
|
||||
|
||||
= content_for :sidebar do
|
||||
- content_for :sidebar do
|
||||
%nav.menu
|
||||
%ul
|
||||
- if can?(:admin, Spree::Product)
|
||||
- klass = current == 'Product Details' ? 'active' : ''
|
||||
%li{:class => klass}
|
||||
= link_to_with_icon 'icon-edit', Spree.t(:product_details), edit_admin_product_url(@product)
|
||||
= link_to_with_icon 'icon-edit', t('admin.products.tabs.product_details'), edit_admin_product_url(@product)
|
||||
- if can?(:admin, Spree::Image)
|
||||
- klass = current == 'Images' ? 'active' : ''
|
||||
%li{:class => klass}
|
||||
= link_to_with_icon 'icon-picture', Spree.t(:images), admin_product_images_url(@product)
|
||||
= link_to_with_icon 'icon-picture', t('admin.products.tabs.images'), admin_product_images_url(@product)
|
||||
- if can?(:admin, Spree::Variant)
|
||||
- klass = current == 'Variants' ? 'active' : ''
|
||||
%li{:class => klass}
|
||||
= link_to_with_icon 'icon-th-large', Spree.t(:variants), admin_product_variants_url(@product)
|
||||
= link_to_with_icon 'icon-th-large', t('admin.products.tabs.variants'), admin_product_variants_url(@product)
|
||||
- if can?(:admin, Spree::ProductProperty)
|
||||
- klass = current == 'Product Properties' ? 'active' : ''
|
||||
%li{:class => klass}
|
||||
= link_to_with_icon 'icon-tasks', Spree.t(:product_properties), admin_product_product_properties_url(@product)
|
||||
= link_to_with_icon 'icon-tasks', t('admin.products.tabs.product_properties'), admin_product_product_properties_url(@product)
|
||||
- klass = current == 'Group Buy Options' ? 'active' : ''
|
||||
%li{:class => klass}
|
||||
= link_to_with_icon 'icon-tasks', t('admin.products.group_buy_options'), group_buy_options_admin_product_url(@product)
|
||||
- klass = current == t(:Search) ? 'active' : ''
|
||||
= link_to_with_icon 'icon-tasks', t('admin.products.tabs.group_buy_options'), group_buy_options_admin_product_url(@product)
|
||||
- klass = current == t(:search) ? 'active' : ''
|
||||
%li{:class => klass}
|
||||
= link_to_with_icon 'icon-tasks', t(:Search), seo_admin_product_url(@product)
|
||||
= link_to_with_icon 'icon-tasks', t(:search), seo_admin_product_url(@product)
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
:variants_search => spree.admin_search_variants_path(:format => 'json'),
|
||||
:taxons_search => main_app.api_taxons_path(:format => 'json'),
|
||||
:user_search => spree.admin_search_users_path(:format => 'json'),
|
||||
:orders_api => spree.api_orders_path(:format => 'json')
|
||||
:orders_api => main_app.api_orders_path
|
||||
}.to_json %>;
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
= tab :dashboard, :route => :admin, :icon => 'icon-dashboard'
|
||||
= tab :products , :option_types, :properties, :prototypes, :variants, :product_properties, :taxons, :url => admin_products_path, :icon => 'icon-th-large'
|
||||
= tab :products, :option_types, :properties, :variants, :product_properties, :taxons, :url => admin_products_path, :icon => 'icon-th-large'
|
||||
= tab :order_cycles, :url => main_app.admin_order_cycles_path, :icon => 'icon-refresh'
|
||||
= tab :orders, :payments, :creditcard_payments, :shipments, :credit_cards, :return_authorizations, :url => admin_orders_path('q[s]' => 'completed_at desc'), :icon => 'icon-shopping-cart'
|
||||
= tab :reports, :icon => 'icon-file'
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
= label_tag nil, t("spree.tree")
|
||||
%br/
|
||||
:javascript
|
||||
Spree.routes.taxonomy_taxons_path = "#{spree.api_taxonomy_taxons_path(@taxonomy)}";
|
||||
Spree.routes.taxonomy_taxons_path = "#{main_app.api_taxonomy_taxons_path(@taxonomy)}";
|
||||
Spree.routes.admin_taxonomy_taxons_path = "#{spree.admin_taxonomy_taxons_path(@taxonomy)}";
|
||||
#taxonomy_tree.tree
|
||||
#progress{style: "display:none;"}
|
||||
|
||||
18
app/views/spree/admin/users/_api_fields.html.haml
Normal file
18
app/views/spree/admin/users/_api_fields.html.haml
Normal file
@@ -0,0 +1,18 @@
|
||||
%fieldset.omega.six.columns
|
||||
%legend= t('spree.api.access')
|
||||
- if @user.spree_api_key.present?
|
||||
.field
|
||||
= label_tag t('spree.api.key')
|
||||
= ":"
|
||||
= @user.spree_api_key
|
||||
.filter-actions.actions
|
||||
= form_tag spree.clear_api_key_admin_user_path(@user), method: :put do
|
||||
= button t('spree.api.clear_key'), 'icon-trash'
|
||||
%span.or= t(:or)
|
||||
= form_tag spree.generate_api_key_admin_user_path(@user), method: :put do
|
||||
= button t('spree.api.regenerate_key'), 'icon-refresh'
|
||||
- else
|
||||
.no-objects-found= t('spree.api.no_key')
|
||||
.filter-actions.actions
|
||||
= form_tag spree.generate_api_key_admin_user_path(@user), method: :put do
|
||||
= button t('spree.api.generate_key'), 'icon-key'
|
||||
@@ -13,3 +13,5 @@
|
||||
= render partial: "form", locals: { f: f }
|
||||
%div{"data-hook" => "admin_user_edit_form_button"}
|
||||
= render partial: "spree/admin/shared/edit_resource_links"
|
||||
|
||||
= render partial: 'spree/admin/users/api_fields'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
= content_for :page_title do
|
||||
- content_for :page_title do
|
||||
= Spree.t(:new_user)
|
||||
|
||||
= content_for :page_actions do
|
||||
- content_for :page_actions do
|
||||
%li
|
||||
= button_link_to Spree.t(:back_to_users_list), spree.admin_users_path, icon: 'icon-arrow-left'
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
.label-block.left.six.columns.alpha{'ng-app' => 'admin.products'}
|
||||
.field
|
||||
= f.label :display_name, t(:display_name)
|
||||
= f.label :display_name, t('.display_name')
|
||||
= f.text_field :display_name, class: "fullwidth"
|
||||
.field
|
||||
= f.label :display_as, t(:display_as)
|
||||
= f.label :display_as, t('.display_as')
|
||||
= f.text_field :display_as, class: "fullwidth"
|
||||
|
||||
- if product_has_variant_unit_option_type?(@product)
|
||||
@@ -29,13 +29,13 @@
|
||||
- if opt = @variant.option_values.detect {|o| o.option_type == option_type }.try(:presentation)
|
||||
= text_field(:new_variant, option_type.presentation, value: opt, disabled: 'disabled', class: 'fullwidth')
|
||||
.field
|
||||
= f.label :sku, Spree.t(:sku)
|
||||
= f.label :sku, t('.sku')
|
||||
= f.text_field :sku, class: 'fullwidth'
|
||||
.field
|
||||
= f.label :price, Spree.t(:price)
|
||||
= f.label :price, t('.price')
|
||||
= f.text_field :price, value: number_to_currency(@variant.price, unit: ''), class: 'fullwidth'
|
||||
.field
|
||||
= f.label :cost_price, Spree.t(:cost_price)
|
||||
= f.label :cost_price, t('.cost_price')
|
||||
= f.text_field :cost_price, value: number_to_currency(@variant.cost_price, unit: ''), class: 'fullwidth'
|
||||
|
||||
%div{ 'set-on-demand' => '' }
|
||||
@@ -53,7 +53,7 @@
|
||||
.right.six.columns.omega.label-block
|
||||
- if @product.variant_unit != 'weight'
|
||||
.field
|
||||
= f.label 'weight', t('weight')+' (kg)'
|
||||
= f.label 'weight', t(:weight)+' (kg)'
|
||||
- value = number_with_precision(@variant.weight, precision: 2)
|
||||
= f.text_field 'weight', value: value, class: 'fullwidth'
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
%col{style: "width: 15%"}/
|
||||
%thead
|
||||
%tr
|
||||
%th{colspan: "2"}= Spree.t(:options)
|
||||
%th= Spree.t(:price)
|
||||
%th= Spree.t(:sku)
|
||||
%th{colspan: "2"}= t('.options')
|
||||
%th= t('.price')
|
||||
%th= t('.sku')
|
||||
%th.actions
|
||||
%tbody
|
||||
- @variants.each do |variant|
|
||||
@@ -31,24 +31,24 @@
|
||||
= link_to_delete(variant, no_text: true) unless variant.deleted?
|
||||
- unless @product.has_variants?
|
||||
%tr
|
||||
%td{colspan: "5"}= Spree.t(:none)
|
||||
%td{colspan: "5"}= t(:none)
|
||||
|
||||
- else
|
||||
.alpha.twelve.columns.no-objects-found
|
||||
= Spree.t(:no_results)
|
||||
= t('.no_results')
|
||||
\.
|
||||
|
||||
- if @product.empty_option_values?
|
||||
%p.first_add_option_types.no-objects-found
|
||||
= Spree.t(:to_add_variants_you_must_first_define)
|
||||
= link_to Spree.t(:option_types), admin_product_url(@product)
|
||||
= Spree.t(:and)
|
||||
= link_to Spree.t(:option_values), admin_option_types_url
|
||||
= t('.to_add_variants_you_must_first_define')
|
||||
= link_to t('.option_types'), admin_product_url(@product)
|
||||
= t('.and')
|
||||
= link_to t('.option_values'), admin_option_types_url
|
||||
|
||||
- else
|
||||
- content_for :page_actions do
|
||||
%ul.inline-menu
|
||||
%li#new_var_link
|
||||
= link_to_with_icon('icon-plus', Spree.t(:new_variant), new_admin_product_variant_url(@product), remote: true, 'data-update' => 'new_variant', class: 'button')
|
||||
= link_to_with_icon('icon-plus', t('.new_variant'), new_admin_product_variant_url(@product), remote: true, 'data-update' => 'new_variant', class: 'button')
|
||||
|
||||
%li= link_to_with_icon('icon-filter', @deleted.blank? ? Spree.t(:show_deleted) : Spree.t(:show_active), admin_product_variants_url(@product, deleted: @deleted.blank? ? "on" : "off"), class: 'button')
|
||||
%li= link_to_with_icon('icon-filter', @deleted.blank? ? t('.show_deleted') : t('.show_active'), admin_product_variants_url(@product, deleted: @deleted.blank? ? "on" : "off"), class: 'button')
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
|
||||
= form_for [:admin, @product, @variant] do |f|
|
||||
%fieldset{'data-hook' => "admin_variant_new_form"}
|
||||
%legend{align: "center"}= Spree.t(:new_variant)
|
||||
%legend{align: "center"}= t('.new_variant')
|
||||
= render partial: 'form', locals: { f: f }
|
||||
= render partial: 'spree/admin/shared/new_resource_links'
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
object false
|
||||
node(:success) { "Use of API Authorised" }
|
||||
3157
config/locales/ar.yml
Normal file
3157
config/locales/ar.yml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -111,6 +111,14 @@ ca:
|
||||
subject: "Sisplau, confirma l'adreça electrònica d'%{enterprise}"
|
||||
welcome:
|
||||
subject: "%{enterprise} és ara %{sitename}"
|
||||
email_welcome: "Benvinguda"
|
||||
email_registered: "ara forma part de"
|
||||
email_userguide_html: "La Guia d'usuari amb suport detallat per configurar una productora o grup de consum és aquí: %{link}"
|
||||
userguide: "Guia d'usuaris d'Open Food Network"
|
||||
email_admin_html: "Pots gestionar el teu compte iniciant sessió a l'%{link} o fent clic a la pestanya a la part superior dreta de la pàgina d'inici i seleccionant Administració."
|
||||
admin_panel: "Tauler d’administració"
|
||||
email_community_html: "També tenim un fòrum en línia per debats de la comunitat relacionada amb el programari OFN i els desafiaments únics de triar endavant una organització alimentària. T'animem a unir-t'hi. Estem en constant evolució i les teves contribucions en aquest fòrum donaran forma al que passi a en el futur. %{link}"
|
||||
join_community: "Uneix-te a la comunitat"
|
||||
invite_manager:
|
||||
subject: "%{enterprise} t'ha convidat a ser administrador"
|
||||
producer_mailer:
|
||||
@@ -320,15 +328,6 @@ ca:
|
||||
number_localization:
|
||||
number_localization_settings: "Configuració de localització numèrica"
|
||||
enable_localized_number: "Utilitzeu l'estàndard internacional per separar milers/decimals"
|
||||
cache_settings:
|
||||
edit:
|
||||
title: "Emmagatzematge ocult"
|
||||
distributor: "Distribuïdora"
|
||||
order_cycle: "Cicle de comanda"
|
||||
status: "Estat"
|
||||
diff: "Diferència"
|
||||
error: "Error"
|
||||
enable_products_cache: "Habilitar la memòria cau de productes?"
|
||||
invoice_settings:
|
||||
edit:
|
||||
title: "Configuració de la factura"
|
||||
@@ -422,19 +421,23 @@ ca:
|
||||
av_on: "Disp. via"
|
||||
import_date: S'ha importat
|
||||
upload_an_image: Penja una imatge
|
||||
product_search_keywords: Paraules clau de cerca de producte
|
||||
product_search_tip: Escriviu paraules per ajudar-vos a cercar els vostres productes a les botigues. Utilitzeu espai per separar cada paraula clau.
|
||||
SEO_keywords: Paraules clau de SEO
|
||||
seo_tip: Escriviu paraules per ajudar-vos a cercar els vostres productes a la web. Utilitzeu espai per separar cada paraula clau.
|
||||
Search: Cerca
|
||||
seo:
|
||||
product_search_keywords: "Paraules clau de cerca de producte"
|
||||
product_search_tip: "Escriviu paraules per ajudar-vos a cercar els vostres productes a les botigues. Utilitzeu espai per separar cada paraula clau."
|
||||
SEO_keywords: "Paraules clau de SEO"
|
||||
seo_tip: "Escriviu paraules per ajudar-vos a cercar els vostres productes a la web. Utilitzeu espai per separar cada paraula clau."
|
||||
search: "Cerca"
|
||||
properties:
|
||||
property_name: Nom de la propietat
|
||||
inherited_property: Propietat heretada
|
||||
property_name: "Nom de la propietat"
|
||||
inherited_property: "Propietat heretada"
|
||||
variants:
|
||||
infinity: "Infinit"
|
||||
to_order_tip: "Els articles preparats per encàrrec no tenen un nivell fixat d'existències, com ara pa fet sota comanda."
|
||||
group_buy_options: "Opcions de compra en grup"
|
||||
back_to_products_list: "Torna a la llista de productes"
|
||||
tabs:
|
||||
group_buy_options: "Opcions de compra en grup"
|
||||
images: "Imatges"
|
||||
product_properties: "Propietats del producte"
|
||||
product_import:
|
||||
title: Importació de productes
|
||||
file_not_found: No s'ha trobat el fitxer o no s'ha pogut obrir
|
||||
@@ -536,6 +539,7 @@ ca:
|
||||
title: Inventari
|
||||
description: Utilitzeu aquesta pàgina per administrar els inventaris de la vostres organitzacions. Tots els detalls del producte aquí establerts substituiran els establerts a la pàgina "Productes"
|
||||
enable_reset?: Habilitar la restauració de valors de stock?
|
||||
default_stock: "Estoc per defecte"
|
||||
inherit?: Heredar?
|
||||
add: Afegeix
|
||||
hide: Amaga
|
||||
@@ -1379,13 +1383,7 @@ ca:
|
||||
products_in: "en %{oc}"
|
||||
products_at: "a %{distributor}"
|
||||
products_elsewhere: "Productes trobats en altres llocs"
|
||||
email_welcome: "Benvinguda"
|
||||
email_confirmed: "Gràcies per confirmar la teva adreça de correu electrònic."
|
||||
email_registered: "ara forma part de"
|
||||
email_userguide_html: "La Guia d'usuari amb suport detallat per configurar una productora o grup de consum és aquí: %{link}"
|
||||
email_admin_html: "Pots gestionar el teu compte iniciant sessió a l'%{link} o fent clic a la pestanya a la part superior dreta de la pàgina d'inici i seleccionant Administració."
|
||||
email_community_html: "També tenim un fòrum en línia per debats de la comunitat relacionada amb el programari OFN i els desafiaments únics de triar endavant una organització alimentària. T'animem a unir-t'hi. Estem en constant evolució i les teves contribucions en aquest fòrum donaran forma al que passi a en el futur. %{link}"
|
||||
join_community: "Uneix-te a la comunitat"
|
||||
email_confirmation_activate_account: "Abans de poder activar el compte nou hem de confirmar la teva adreça de correu electrònic."
|
||||
email_confirmation_greeting: "Hola, %{contact}!"
|
||||
email_confirmation_profile_created: "S'ha creat exitosament un perfil per %{name}. Per activar el teu perfil hem de confirmar aquesta adreça de correu electrònic."
|
||||
@@ -2814,7 +2812,6 @@ ca:
|
||||
products: "Productes "
|
||||
option_types: "Tipus d'opcions"
|
||||
properties: "Propietats"
|
||||
prototypes: "Prototips"
|
||||
variant_overrides: "Inventari"
|
||||
reports: "Informes"
|
||||
configuration: "Configuració"
|
||||
@@ -3015,6 +3012,15 @@ ca:
|
||||
email_confirmation:
|
||||
confirmation_pending: "La confirmació de correu electrònic està pendent. Hem enviat un correu electrònic de confirmació a %{address}."
|
||||
variants:
|
||||
index:
|
||||
sku: "Número de referència (SKU)"
|
||||
price: "Preu"
|
||||
no_results: "Sense resultats"
|
||||
option_types: "Tipus d'opcions"
|
||||
form:
|
||||
sku: "Número de referència (SKU)"
|
||||
price: "Preu"
|
||||
display_as: "Mostra com"
|
||||
autocomplete:
|
||||
producer_name: "Productor"
|
||||
unit: "Unitat"
|
||||
|
||||
@@ -111,6 +111,10 @@ de_DE:
|
||||
subject: "Bitte bestätigen Sie die E-Mail-Adresse für %{enterprise}"
|
||||
welcome:
|
||||
subject: "%{enterprise} ist jetzt auf %{sitename}"
|
||||
email_welcome: "Willkommen"
|
||||
email_registered: "ist jetzt Teil von"
|
||||
email_community_html: "Wir haben auch ein Online-Forum für Community-Diskussionen in Bezug auf OFN-Software und die einzigartigen Herausforderungen eines Lebensmittelunternehmens. Reden Sie doch mit. Wir entwickeln uns ständig weiter und Ihr Beitrag in diesem Forum prägt, was als nächstes passiert. %{link}"
|
||||
join_community: "Treten Sie der Community bei"
|
||||
invite_manager:
|
||||
subject: "%{enterprise} hat Sie eingeladen, ein Manager zu sein"
|
||||
producer_mailer:
|
||||
@@ -320,15 +324,6 @@ de_DE:
|
||||
number_localization:
|
||||
number_localization_settings: "Nummernlokalisierungseinstellungen"
|
||||
enable_localized_number: "Verwenden Sie die internationale Tausendertrennungslogik"
|
||||
cache_settings:
|
||||
edit:
|
||||
title: "Cachen"
|
||||
distributor: "Verteiler"
|
||||
order_cycle: "Bestellrunde"
|
||||
status: "Status"
|
||||
diff: "Diff"
|
||||
error: "Fehler"
|
||||
enable_products_cache: "Produktcache aktivieren?"
|
||||
invoice_settings:
|
||||
edit:
|
||||
title: "Rechnungseinstellungen"
|
||||
@@ -422,19 +417,23 @@ de_DE:
|
||||
av_on: "Verfüg. am"
|
||||
import_date: Importiert
|
||||
upload_an_image: Bild hochladen
|
||||
product_search_keywords: Stichwörter für die Produktsuche
|
||||
product_search_tip: Geben Sie Wörter ein, um Ihre Produkte in den Geschäften zu suchen. Verwenden Sie Leerzeichen, um jedes Keyword zu trennen.
|
||||
SEO_keywords: SEO Schlüsselwörter
|
||||
seo_tip: Geben Sie Wörter ein, um Ihre Produkte im Internet zu durchsuchen. Verwenden Sie Leerzeichen, um jedes Keyword zu trennen.
|
||||
Search: Suche
|
||||
seo:
|
||||
product_search_keywords: "Stichwörter für die Produktsuche"
|
||||
product_search_tip: "Geben Sie Wörter ein, um Ihre Produkte in den Geschäften zu suchen. Verwenden Sie Leerzeichen, um jedes Keyword zu trennen."
|
||||
SEO_keywords: "SEO Schlüsselwörter"
|
||||
seo_tip: "Geben Sie Wörter ein, um Ihre Produkte im Internet zu durchsuchen. Verwenden Sie Leerzeichen, um jedes Keyword zu trennen."
|
||||
search: "Suche"
|
||||
properties:
|
||||
property_name: Name der Eigenschaft
|
||||
inherited_property: Vererbte Eigenschaft
|
||||
property_name: "Name der Eigenschaft"
|
||||
inherited_property: "Vererbte Eigenschaft"
|
||||
variants:
|
||||
infinity: "Unendlichkeit"
|
||||
to_order_tip: "Artikel, die auf Bestellung hergestellt werden, haben keinen festgelegten Lagerbestand."
|
||||
group_buy_options: "Gruppenkaufoptionen"
|
||||
back_to_products_list: "Zurück zur Produktliste"
|
||||
tabs:
|
||||
group_buy_options: "Gruppenkaufoptionen"
|
||||
images: "Bilder"
|
||||
product_properties: "Produkteigenschaften"
|
||||
product_import:
|
||||
title: Produkte importieren
|
||||
file_not_found: Datei nicht gefunden oder konnte nicht geöffnet werden
|
||||
@@ -1379,13 +1378,7 @@ de_DE:
|
||||
products_in: "in %{oc}"
|
||||
products_at: "bei %{distributor}"
|
||||
products_elsewhere: "Produkte an anderer Stelle"
|
||||
email_welcome: "Willkommen"
|
||||
email_confirmed: "Vielen Dank für die Bestätigung Ihrer E-Mail-Adresse."
|
||||
email_registered: "ist jetzt Teil von"
|
||||
email_userguide_html: "Das Benutzerhandbuch mit ausführlicher Unterstützung für die Einrichtung Ihres Producer oder Hub finden Sie hier: %{link}"
|
||||
email_admin_html: "Sie können Ihr Konto verwalten, indem Sie sich bei %{link} anmelden oder indem Sie oben rechts auf der Startseite auf das Zahnrad klicken und Administration auswählen."
|
||||
email_community_html: "Wir haben auch ein Online-Forum für Community-Diskussionen in Bezug auf OFN-Software und die einzigartigen Herausforderungen eines Lebensmittelunternehmens. Reden Sie doch mit. Wir entwickeln uns ständig weiter und Ihr Beitrag in diesem Forum prägt, was als nächstes passiert. %{link}"
|
||||
join_community: "Treten Sie der Community bei"
|
||||
email_confirmation_activate_account: "Bevor wir Ihr neues Konto aktivieren können, müssen wir Ihre E-Mail-Adresse bestätigen."
|
||||
email_confirmation_greeting: "Hallo, %{contact}!"
|
||||
email_confirmation_profile_created: "Ein Profil für %{name} wurde erfolgreich erstellt! Um Ihr Profil zu aktivieren, müssen wir diese E-Mail-Adresse bestätigen."
|
||||
@@ -2814,7 +2807,6 @@ de_DE:
|
||||
products: "Produkte"
|
||||
option_types: "Optionstypen"
|
||||
properties: "Eigenschaften"
|
||||
prototypes: "Prototypen"
|
||||
variant_overrides: "Katalog"
|
||||
reports: "Berichte"
|
||||
configuration: "Aufbau"
|
||||
@@ -3015,6 +3007,13 @@ de_DE:
|
||||
email_confirmation:
|
||||
confirmation_pending: "E-Mail-Bestätigung steht aus. Wir haben eine Bestätigungs-E-Mail an %{address} gesendet."
|
||||
variants:
|
||||
index:
|
||||
sku: "Artikelnummer"
|
||||
price: "Preis"
|
||||
form:
|
||||
sku: "Artikelnummer"
|
||||
price: "Preis"
|
||||
display_as: "Angezeigt als"
|
||||
autocomplete:
|
||||
producer_name: "Produzent"
|
||||
unit: "Einheit"
|
||||
|
||||
@@ -136,6 +136,14 @@ en:
|
||||
subject: "Please confirm the email address for %{enterprise}"
|
||||
welcome:
|
||||
subject: "%{enterprise} is now on %{sitename}"
|
||||
email_welcome: "Welcome"
|
||||
email_registered: "is now part of"
|
||||
email_userguide_html: "The User Guide with detailed support for setting up your Producer or Hub is here: %{link}"
|
||||
userguide: "Open Food Network User Guide"
|
||||
email_admin_html: "You can manage your account by logging into the %{link} or by clicking on the cog in the top right hand side of the homepage, and selecting Administration."
|
||||
admin_panel: "Admin Panel"
|
||||
email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next. %{link}"
|
||||
join_community: "Join the community"
|
||||
invite_manager:
|
||||
subject: "%{enterprise} has invited you to be a manager"
|
||||
producer_mailer:
|
||||
@@ -365,16 +373,6 @@ en:
|
||||
number_localization_settings: "Number Localization Settings"
|
||||
enable_localized_number: "Use the international thousand/decimal separator logic"
|
||||
|
||||
cache_settings:
|
||||
edit:
|
||||
title: "Caching"
|
||||
distributor: "Distributor"
|
||||
order_cycle: "Order Cycle"
|
||||
status: "Status"
|
||||
diff: "Diff"
|
||||
error: "Error"
|
||||
enable_products_cache: "Enable Products Cache?"
|
||||
|
||||
invoice_settings:
|
||||
edit:
|
||||
title: "Invoice Settings"
|
||||
@@ -476,19 +474,26 @@ en:
|
||||
av_on: "Av. On"
|
||||
import_date: Imported
|
||||
upload_an_image: Upload an image
|
||||
product_search_keywords: Product Search Keywords
|
||||
product_search_tip: Type words to help search your products in the shops. Use space to separate each keyword.
|
||||
SEO_keywords: SEO Keywords
|
||||
seo_tip: Type words to help search your products in the web. Use space to separate each keyword.
|
||||
Search: Search
|
||||
seo:
|
||||
product_search_keywords: "Product Search Keywords"
|
||||
product_search_tip: "Type words to help search your products in the shops. Use space to separate each keyword."
|
||||
SEO_keywords: "SEO Keywords"
|
||||
seo_tip: "Type words to help search your products in the web. Use space to separate each keyword."
|
||||
search: "Search"
|
||||
properties:
|
||||
property_name: Property Name
|
||||
inherited_property: Inherited Property
|
||||
property_name: "Property Name"
|
||||
inherited_property: "Inherited Property"
|
||||
variants:
|
||||
infinity: "Infinity"
|
||||
to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order."
|
||||
group_buy_options: "Group Buy Options"
|
||||
back_to_products_list: "Back to products list"
|
||||
editing_product: "Editing Product"
|
||||
tabs:
|
||||
product_details: "Product Details"
|
||||
group_buy_options: "Group Buy Options"
|
||||
images: "Images"
|
||||
variants: "Variants"
|
||||
product_properties: "Product Properties"
|
||||
|
||||
product_import:
|
||||
title: Product Import
|
||||
@@ -592,6 +597,7 @@ en:
|
||||
title: Inventory
|
||||
description: Use this page to manage inventories for your enterprises. Any product details set here will override those set on the 'Products' page
|
||||
enable_reset?: Enable Stock Reset?
|
||||
default_stock: "Default stock"
|
||||
inherit?: Inherit?
|
||||
add: Add
|
||||
hide: Hide
|
||||
@@ -1476,15 +1482,7 @@ en:
|
||||
products_at: "at %{distributor}"
|
||||
products_elsewhere: "Products found elsewhere"
|
||||
|
||||
email_welcome: "Welcome"
|
||||
email_confirmed: "Thank you for confirming your email address."
|
||||
email_registered: "is now part of"
|
||||
email_userguide_html: "The User Guide with detailed support for setting up your Producer or Hub is here:
|
||||
%{link}"
|
||||
email_admin_html: "You can manage your account by logging into the %{link} or by clicking on the cog in the top right hand side of the homepage, and selecting Administration."
|
||||
email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next.
|
||||
%{link}"
|
||||
join_community: "Join the community"
|
||||
email_confirmation_activate_account: "Before we can activate your new account, we need to confirm your email address."
|
||||
email_confirmation_greeting: "Hi, %{contact}!"
|
||||
email_confirmation_profile_created: "A profile for %{name} has been successfully created!
|
||||
@@ -2940,6 +2938,13 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
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"
|
||||
|
||||
# TODO: remove `email` key once we get to Spree 2.0
|
||||
email: Email
|
||||
# TODO: remove 'account_updated' key once we get to Spree 2.0
|
||||
@@ -2953,6 +2958,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
zipcode: Postcode
|
||||
weight: Weight (per kg)
|
||||
error_user_destroy_with_orders: "Users with completed orders may not be deleted"
|
||||
options: "Options"
|
||||
|
||||
actions:
|
||||
update: "Update"
|
||||
@@ -2972,7 +2978,6 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
products: "Products"
|
||||
option_types: "Option Types"
|
||||
properties: "Properties"
|
||||
prototypes: "Prototypes"
|
||||
variant_overrides: "Inventory"
|
||||
reports: "Reports"
|
||||
configuration: "Configuration"
|
||||
@@ -2986,6 +2991,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
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"
|
||||
orders:
|
||||
index:
|
||||
listing_orders: "Listing Orders"
|
||||
@@ -3173,6 +3180,26 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
email_confirmation:
|
||||
confirmation_pending: "Email confirmation is pending. We've sent a confirmation email to %{address}."
|
||||
variants:
|
||||
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:
|
||||
cost_price: "Cost Price"
|
||||
sku: "SKU"
|
||||
price: "Price"
|
||||
display_as: "Display As"
|
||||
display_name: "Display Name"
|
||||
autocomplete:
|
||||
producer_name: "Producer"
|
||||
unit: "Unit"
|
||||
@@ -3312,3 +3339,19 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
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."
|
||||
|
||||
@@ -111,6 +111,12 @@ en_AU:
|
||||
subject: "Please confirm the email address for %{enterprise}"
|
||||
welcome:
|
||||
subject: "%{enterprise} is now on %{sitename}"
|
||||
email_welcome: "Welcome"
|
||||
email_registered: "is now part of"
|
||||
email_userguide_html: "The User Guide with detailed support for setting up your Producer or Hub is here: %{link}"
|
||||
email_admin_html: "You can manage your account by logging into the %{link} or by clicking on the cog in the top right hand side of the homepage, and selecting Administration."
|
||||
email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next. %{link}"
|
||||
join_community: "Join the community"
|
||||
invite_manager:
|
||||
subject: "%{enterprise} has invited you to be a manager"
|
||||
producer_mailer:
|
||||
@@ -319,15 +325,6 @@ en_AU:
|
||||
number_localization:
|
||||
number_localization_settings: "Number Localization Settings"
|
||||
enable_localized_number: "Use the international thousand/decimal separator logic"
|
||||
cache_settings:
|
||||
edit:
|
||||
title: "Caching"
|
||||
distributor: "Distributor"
|
||||
order_cycle: "Order Cycle"
|
||||
status: "Status"
|
||||
diff: "Diff"
|
||||
error: "Error"
|
||||
enable_products_cache: "Enable Products Cache?"
|
||||
invoice_settings:
|
||||
edit:
|
||||
title: "Invoice Settings"
|
||||
@@ -421,19 +418,23 @@ en_AU:
|
||||
av_on: "Av. On"
|
||||
import_date: Imported
|
||||
upload_an_image: Upload an image
|
||||
product_search_keywords: Product Search Keywords
|
||||
product_search_tip: Type words to help search your products in the shops. Use space to separate each keyword.
|
||||
SEO_keywords: SEO Keywords
|
||||
seo_tip: Type words to help search your products in the web. Use space to separate each keyword.
|
||||
Search: Search
|
||||
seo:
|
||||
product_search_keywords: "Product Search Keywords"
|
||||
product_search_tip: "Type words to help search your products in the shops. Use space to separate each keyword."
|
||||
SEO_keywords: "SEO Keywords"
|
||||
seo_tip: "Type words to help search your products in the web. Use space to separate each keyword."
|
||||
search: "Search"
|
||||
properties:
|
||||
property_name: Property Name
|
||||
inherited_property: Inherited Property
|
||||
property_name: "Property Name"
|
||||
inherited_property: "Inherited Property"
|
||||
variants:
|
||||
infinity: "Infinity"
|
||||
to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order."
|
||||
group_buy_options: "Group Buy Options"
|
||||
back_to_products_list: "Back to products list"
|
||||
tabs:
|
||||
group_buy_options: "Group Buy Options"
|
||||
images: "Images"
|
||||
product_properties: "Product Properties"
|
||||
product_import:
|
||||
title: Product Import
|
||||
file_not_found: File not found or could not be opened
|
||||
@@ -1376,13 +1377,7 @@ en_AU:
|
||||
products_in: "in %{oc}"
|
||||
products_at: "at %{distributor}"
|
||||
products_elsewhere: "Products found elsewhere"
|
||||
email_welcome: "Welcome"
|
||||
email_confirmed: "Thank you for confirming your email address."
|
||||
email_registered: "is now part of"
|
||||
email_userguide_html: "The User Guide with detailed support for setting up your Producer or Hub is here: %{link}"
|
||||
email_admin_html: "You can manage your account by logging into the %{link} or by clicking on the cog in the top right hand side of the homepage, and selecting Administration."
|
||||
email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next. %{link}"
|
||||
join_community: "Join the community"
|
||||
email_confirmation_activate_account: "Before we can activate your new account, we need to confirm your email address."
|
||||
email_confirmation_greeting: "Hi, %{contact}!"
|
||||
email_confirmation_profile_created: "A profile for %{name} has been successfully created! To activate your Profile we need to confirm this email address."
|
||||
@@ -2805,7 +2800,6 @@ en_AU:
|
||||
products: "Products"
|
||||
option_types: "Option Types"
|
||||
properties: "Properties"
|
||||
prototypes: "Prototypes"
|
||||
variant_overrides: "Inventory"
|
||||
reports: "Reports"
|
||||
configuration: "Configuration"
|
||||
@@ -3006,6 +3000,15 @@ en_AU:
|
||||
email_confirmation:
|
||||
confirmation_pending: "Email confirmation is pending. We've sent a confirmation email to %{address}."
|
||||
variants:
|
||||
index:
|
||||
sku: "SKU"
|
||||
price: "Price"
|
||||
no_results: "No results"
|
||||
option_types: "Option Types"
|
||||
form:
|
||||
sku: "SKU"
|
||||
price: "Price"
|
||||
display_as: "Display As"
|
||||
autocomplete:
|
||||
producer_name: "Producer"
|
||||
unit: "Unit"
|
||||
|
||||
@@ -110,6 +110,12 @@ en_BE:
|
||||
subject: "Please confirm the email address for %{enterprise}"
|
||||
welcome:
|
||||
subject: "%{enterprise} is now on %{sitename}"
|
||||
email_welcome: "Welcome"
|
||||
email_registered: "is now part of"
|
||||
email_userguide_html: "The User Guide with detailed support for setting up your Producer or Hub is here: %{link}"
|
||||
email_admin_html: "You can manage your account by logging into the %{link} or by clicking on the cog in the top right hand side of the homepage, and selecting Administration."
|
||||
email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next. %{link}"
|
||||
join_community: "Join the community"
|
||||
invite_manager:
|
||||
subject: "%{enterprise} has invited you to be a manager"
|
||||
producer_mailer:
|
||||
@@ -317,15 +323,6 @@ en_BE:
|
||||
number_localization:
|
||||
number_localization_settings: "Number Localization Settings"
|
||||
enable_localized_number: "Use the international thousand/decimal separator logic"
|
||||
cache_settings:
|
||||
edit:
|
||||
title: "Caching"
|
||||
distributor: "Distributor"
|
||||
order_cycle: "Order Cycle"
|
||||
status: "Status"
|
||||
diff: "Diff"
|
||||
error: "Error"
|
||||
enable_products_cache: "Enable Products Cache?"
|
||||
invoice_settings:
|
||||
edit:
|
||||
title: "Invoice Settings"
|
||||
@@ -419,19 +416,23 @@ en_BE:
|
||||
av_on: "Av. On"
|
||||
import_date: Imported
|
||||
upload_an_image: Upload an image
|
||||
product_search_keywords: Product Search Keywords
|
||||
product_search_tip: Type words to help search your products in the shops. Use space to separate each keyword.
|
||||
SEO_keywords: SEO Keywords
|
||||
seo_tip: Type words to help search your products in the web. Use space to separate each keyword.
|
||||
Search: Search
|
||||
seo:
|
||||
product_search_keywords: "Product Search Keywords"
|
||||
product_search_tip: "Type words to help search your products in the shops. Use space to separate each keyword."
|
||||
SEO_keywords: "SEO Keywords"
|
||||
seo_tip: "Type words to help search your products in the web. Use space to separate each keyword."
|
||||
search: "Search"
|
||||
properties:
|
||||
property_name: Property Name
|
||||
inherited_property: Inherited Property
|
||||
property_name: "Property Name"
|
||||
inherited_property: "Inherited Property"
|
||||
variants:
|
||||
infinity: "Infinity"
|
||||
to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order."
|
||||
group_buy_options: "Group Buy Options"
|
||||
back_to_products_list: "Back to products list"
|
||||
tabs:
|
||||
group_buy_options: "Group Buy Options"
|
||||
images: "Images"
|
||||
product_properties: "Product Properties"
|
||||
product_import:
|
||||
title: Product Import
|
||||
file_not_found: File not found or could not be opened
|
||||
@@ -1297,12 +1298,12 @@ en_BE:
|
||||
home_shop: Shop Now
|
||||
brandstory_headline: "Food, unincorporated."
|
||||
brandstory_intro: "Sometimes the best way to fix the system is to start a new one…"
|
||||
brandstory_part1: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can seriously change the world."
|
||||
brandstory_part2: "Then we need a way to make it real. A way to empower everyone who grows, sells and buys food. A way to tell all the stories, to handle all the logistics. A way to turn transaction into transformation every day."
|
||||
brandstory_part3: "So we build an online marketplace that levels the playing field. It’s transparent, so it creates real relationships. It’s open source, so it’s owned by everyone. It scales to regions and nations, so people start versions across the world."
|
||||
brandstory_part4: "It works everywhere. It changes everything."
|
||||
brandstory_part1: "This is what we do by creating a platform that wants to connect:\n● passionate farmers, committed to sustainable and regenerative agriculture,\n● distributors of local products, followers of short circuits, who act in complete transparency and ensure fair remuneration of producers,\n● buyers who want to change the world by eating better,"
|
||||
brandstory_part2: "This platform is called the Open Food Network."
|
||||
brandstory_part3: "Transparent and open source, it promotes fair relations between farmers and consumers. Its objective is to create a quality food network thanks to an efficient and constantly evolving IT tool.\nIt is deployed in several countries. In Belgium, it is developed by Oxfam-World Stores to empower those who grow, sell and buy food."
|
||||
brandstory_part4: "You are\n● A producer: register and present your products to promote them in your region. You energize the platform and connect with new customers.\n● A distributor: register and present your project to make it known and convince new customers.\n● A consumer: find products near you and discover their stories."
|
||||
brandstory_part5_strong: "We call it Open Food Network."
|
||||
brandstory_part6: "We all love food. Now we can love our food system too."
|
||||
brandstory_part6: "We can all participate in the construction of a fairer food system that respects mankind and preserves the planet."
|
||||
learn_body: "Explore models, stories and resources to support you to develop your fair food business or organisation. Find training, events and other opportunities to learn from peers."
|
||||
learn_cta: "Get Inspired"
|
||||
connect_body: "Search our full directories of producers, hubs and groups to find fair food traders near you. List your business or organisation on the OFN so buyers can find you. Join the community to get advice and solve problems together."
|
||||
@@ -1370,13 +1371,7 @@ en_BE:
|
||||
products_in: "in %{oc}"
|
||||
products_at: "at %{distributor}"
|
||||
products_elsewhere: "Products found elsewhere"
|
||||
email_welcome: "Welcome"
|
||||
email_confirmed: "Thank you for confirming your email address."
|
||||
email_registered: "is now part of"
|
||||
email_userguide_html: "The User Guide with detailed support for setting up your Producer or Hub is here: %{link}"
|
||||
email_admin_html: "You can manage your account by logging into the %{link} or by clicking on the cog in the top right hand side of the homepage, and selecting Administration."
|
||||
email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next. %{link}"
|
||||
join_community: "Join the community"
|
||||
email_confirmation_activate_account: "Before we can activate your new account, we need to confirm your email address."
|
||||
email_confirmation_greeting: "Hi, %{contact}!"
|
||||
email_confirmation_profile_created: "A profile for %{name} has been successfully created! To activate your Profile we need to confirm this email address."
|
||||
@@ -2798,7 +2793,6 @@ en_BE:
|
||||
products: "Products"
|
||||
option_types: "Option Types"
|
||||
properties: "Properties"
|
||||
prototypes: "Prototypes"
|
||||
variant_overrides: "Inventory"
|
||||
reports: "Reports"
|
||||
configuration: "Configuration"
|
||||
@@ -2999,6 +2993,15 @@ en_BE:
|
||||
email_confirmation:
|
||||
confirmation_pending: "Email confirmation is pending. We've sent a confirmation email to %{address}."
|
||||
variants:
|
||||
index:
|
||||
sku: "SKU"
|
||||
price: "Price"
|
||||
no_results: "No results"
|
||||
option_types: "Option Types"
|
||||
form:
|
||||
sku: "SKU"
|
||||
price: "Price"
|
||||
display_as: "Display As"
|
||||
autocomplete:
|
||||
producer_name: "Producer"
|
||||
unit: "Unit"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user