Compare commits

..

1 Commits

Author SHA1 Message Date
Kristina Lim
dd2f2b37d0 Specify API date format when converting date to string in JS 2019-08-30 09:13:48 +10:00
281 changed files with 3649 additions and 13518 deletions

66
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,66 @@
<!-- Provide a general summary of the issue in the Title above.
If your issue is not a bug, please use the Feature template instead:
https://github.com/openfoodfoundation/openfoodnetwork/wiki/Feature-template
-->
## Description
<!-- Provide a more detailed introduction to the issue itself, and why you consider it to be a bug -->
## Expected Behavior
<!-- Tell us what should happen -->
## Actual Behavior
<!-- Tell us what happens instead -->
## Steps to Reproduce
<!-- Provide an unambiguous set of steps to reproduce this bug -->
<!-- Include code to reproduce if relevant -->
1.
2.
3.
4.
## Animated Gif/Screenshot
<!-- Provide a screenshot or brief animated gif reproducing the bug. Linux users can use
[Peek](https://github.com/phw/peek#ubuntu) while Mac users can use [Recordit](http://recordit.co/) -->
## Context
<!-- How has this bug affected you? What were you trying to accomplish? -->
## Severity
<!-- Assign a label and explain the impact.
bug-s1: a critical feature is broken: checkout, payments, signup, login
bug-s2: a non-critical feature is broken, no workaround
bug-s3: a feature is broken but there is a workaround
bug-s4: it's annoying, but you can use it
bug-s5: we can live with it, only a few users impacted
https://github.com/openfoodfoundation/openfoodnetwork/wiki/Bug-severity
-->
## Your Environment
<!-- Include relevant details about the environment you experienced the bug in -->
* Version used:
* Browser name and version:
* Operating System and version (desktop or mobile):
## Possible Fix
<!-- Not obligatory, but suggest a fix or reason for the bug -->

View File

@@ -9,7 +9,6 @@ assignees: ''
## Description
<!-- Provide a more detailed introduction to the issue itself, and why you consider it to be a bug -->
<!-- How has this bug affected you? What were you trying to accomplish? -->
## Expected Behavior
@@ -23,8 +22,6 @@ assignees: ''
## Steps to Reproduce
<!-- Provide an unambiguous set of steps to reproduce this bug -->
<!-- Include code to reproduce if relevant -->
<!-- Include links -->
<!-- Include user ID -->
1.
2.
@@ -32,11 +29,13 @@ assignees: ''
4.
## Animated Gif/Screenshot
<!-- Provide a screenshot or brief video reproducing the bug. -->
<!-- Please try to have the dev tools opened on the network tab (press F12 to open the devtools of your browser -->
<!-- Provide a screenshot or brief animated gif reproducing the bug. Linux users can use
[Peek](https://github.com/phw/peek#ubuntu) while Mac users can use [Recordit](http://recordit.co/) -->
## Context
<!-- How has this bug affected you? What were you trying to accomplish? -->
## Workaround
<!-- Include a workaround for this bug (if relevant) -->
## Severity
<!-- Assign a label and explain the impact.
@@ -56,6 +55,7 @@ https://github.com/openfoodfoundation/openfoodnetwork/wiki/Bug-severity
* Version used:
* Browser name and version:
* Operating System and version (desktop or mobile):
* OFN Platform instance where you discovered the bug, and which version of the software they are using.
## Possible Fix
<!-- Not obligatory, but suggest a fix or reason for the bug -->

View File

@@ -14,12 +14,5 @@ assignees: ''
**- I want to be able to do:** (specify the desired behavior)
(Link to others issues or resources to provide context > only if really necessary). -->
## Acceptance Criteria & Tests
<!-- Document the outcomes that need to be achieved before this component can be considered complete.
-->
<!-- Provide an unambiguous set of steps a tester should do to validate the PR that will solve this issue -->
1.
2.
3.
4.
## Acceptance Criteria
<!-- Document the outcomes that need to be achieved before this component can be considered complete. -->

View File

@@ -341,6 +341,7 @@ Metrics/LineLength:
- spec/performance/orders_controller_spec.rb
- spec/performance/shop_controller_spec.rb
- spec/requests/checkout/failed_checkout_spec.rb
- spec/requests/checkout/stripe_connect_spec.rb
- spec/requests/embedded_shopfronts_headers_spec.rb
- spec/requests/shop_spec.rb
- spec/serializers/admin/customer_serializer_spec.rb
@@ -481,26 +482,63 @@ Metrics/AbcSize:
Metrics/BlockLength:
Max: 25
ExcludedMethods: [
"class_eval",
"collection",
"context",
"describe",
"feature",
"it",
"member",
"namespace",
"resource",
"resources",
"scenario"
]
ExcludedMethods: ["class_eval", "collection", "context", "describe", "it", "member", "namespace", "resource", "resources"]
Exclude:
- lib/tasks/data.rake
- lib/tasks/dev.rake
- spec/controllers/spree/admin/invoices_controller_spec.rb
- spec/factories/variant_factory.rb
- spec/features/admin/adjustments_spec.rb
- spec/features/admin/bulk_order_management_spec.rb
- spec/features/admin/bulk_product_update_spec.rb
- spec/features/admin/caching_spec.rb
- spec/features/admin/content_spec.rb
- spec/features/admin/customers_spec.rb
- spec/features/admin/enterprise_fees_spec.rb
- spec/features/admin/enterprise_groups_spec.rb
- spec/features/admin/enterprise_relationships_spec.rb
- spec/features/admin/enterprise_roles_spec.rb
- spec/features/admin/enterprises/images_spec.rb
- spec/features/admin/enterprises/index_spec.rb
- spec/features/admin/enterprises_spec.rb
- spec/features/admin/enterprise_user_spec.rb
- spec/features/admin/multilingual_spec.rb
- spec/features/admin/order_cycles_spec.rb
- spec/features/admin/orders_spec.rb
- spec/features/admin/overview_spec.rb
- spec/features/admin/payment_method_spec.rb
- spec/features/admin/product_import_spec.rb
- spec/features/admin/products_spec.rb
- spec/features/admin/reports/enterprise_fee_summaries_spec.rb
- spec/features/admin/reports_spec.rb
- spec/features/admin/schedules_spec.rb
- spec/features/admin/shipping_methods_spec.rb
- spec/features/admin/subscriptions_spec.rb
- spec/features/admin/tag_rules_spec.rb
- spec/features/admin/tax_settings_spec.rb
- spec/features/admin/users_spec.rb
- spec/features/admin/variant_overrides_spec.rb
- spec/features/admin/variants_spec.rb
- spec/features/consumer/account/cards_spec.rb
- spec/features/consumer/account/settings_spec.rb
- spec/features/consumer/account_spec.rb
- spec/features/consumer/authentication_spec.rb
- spec/features/consumer/cookies_spec.rb
- spec/features/consumer/external_services_spec.rb
- spec/features/consumer/groups_spec.rb
- spec/features/consumer/multilingual_spec.rb
- spec/features/consumer/producers_spec.rb
- spec/features/consumer/registration_spec.rb
- spec/features/consumer/shopping/cart_spec.rb
- spec/features/consumer/shopping/checkout_auth_spec.rb
- spec/features/consumer/shopping/checkout_spec.rb
- spec/features/consumer/shopping/embedded_groups_spec.rb
- spec/features/consumer/shopping/embedded_shopfronts_spec.rb
- spec/features/consumer/shopping/orders_spec.rb
- spec/features/consumer/shopping/products_spec.rb
- spec/features/consumer/shopping/shopping_spec.rb
- spec/features/consumer/shopping/variant_overrides_spec.rb
- spec/features/consumer/shops_spec.rb
- spec/lib/open_food_network/group_buy_report_spec.rb
- spec/models/tag_rule/discount_order_spec.rb
- spec/spec_helper.rb

View File

@@ -186,19 +186,7 @@ Metrics/AbcSize:
Metrics/BlockLength:
Max: 25
ExcludedMethods: [
"class_eval",
"collection",
"context",
"describe",
"feature",
"it",
"member",
"namespace",
"resource",
"resources",
"scenario"
]
ExcludedMethods: ["class_eval", "collection", "context", "describe", "it", "member", "namespace", "resource", "resources"]
Metrics/BlockNesting:
Max: 3

View File

@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config --exclude-limit 1400`
# on 2019-07-23 14:09:18 +0100 using RuboCop version 0.57.2.
# on 2019-05-28 16:29:07 +0100 using RuboCop version 0.57.2.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
@@ -32,6 +32,15 @@ Layout/EndAlignment:
Layout/IndentHash:
EnforcedStyle: consistent
# Offense count: 7
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, IndentationWidth.
# SupportedStyles: aligned, indented, indented_relative_to_receiver
Layout/MultilineMethodCallIndentation:
Exclude:
- 'app/models/spree/line_item_decorator.rb'
- 'app/models/spree/product_decorator.rb'
# Offense count: 4
Lint/AmbiguousOperator:
Exclude:
@@ -46,7 +55,7 @@ Lint/DuplicateMethods:
- 'lib/discourse/single_sign_on.rb'
- 'lib/open_food_network/subscription_summary.rb'
# Offense count: 8
# Offense count: 15
Lint/IneffectiveAccessModifier:
Exclude:
- 'app/models/column_preference.rb'
@@ -70,13 +79,7 @@ Lint/UnderscorePrefixedVariableName:
Exclude:
- 'spec/support/cancan_helper.rb'
# Offense count: 1
# Cop supports --auto-correct.
Lint/UnneededCopDisableDirective:
Exclude:
- 'app/models/product_import/entry_validator.rb'
# Offense count: 5
# Offense count: 6
# Configuration parameters: ContextCreatingMethods, MethodCreatingMethods.
Lint/UselessAccessModifier:
Exclude:
@@ -86,47 +89,28 @@ Lint/UselessAccessModifier:
- 'lib/open_food_network/reports/bulk_coop_report.rb'
- 'spec/lib/open_food_network/reports/report_spec.rb'
# Offense count: 8
# Offense count: 91
# Configuration parameters: CheckForMethodsWithNoSideEffects.
Lint/Void:
Exclude:
- 'app/serializers/api/enterprise_serializer.rb'
- 'spec/features/admin/bulk_product_update_spec.rb'
- 'spec/features/admin/enterprise_groups_spec.rb'
- 'spec/features/admin/enterprises/index_spec.rb'
- 'spec/features/admin/enterprises_spec.rb'
- 'spec/features/admin/order_cycles_spec.rb'
- 'spec/features/admin/payment_method_spec.rb'
- 'spec/features/admin/products_spec.rb'
- 'spec/features/admin/variant_overrides_spec.rb'
- 'spec/features/admin/variants_spec.rb'
- 'spec/features/consumer/shopping/checkout_spec.rb'
- 'spec/features/consumer/shopping/shopping_spec.rb'
- 'spec/features/consumer/shopping/variant_overrides_spec.rb'
# Offense count: 15
Metrics/AbcSize:
Max: 36
# Offense count: 13
# Offense count: 109
# Configuration parameters: CountComments, ExcludedMethods.
Metrics/BlockLength:
Max: 115
# Offense count: 2
# Configuration parameters: CountComments.
Metrics/ClassLength:
Max: 169
# Offense count: 1
Metrics/CyclomaticComplexity:
Max: 8
# Offense count: 8
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 31
# Offense count: 2
# Configuration parameters: CountComments.
Metrics/ModuleLength:
Max: 208
# Offense count: 2
Metrics/PerceivedComplexity:
Max: 11
Max: 774
# Offense count: 7
Naming/AccessorMethodName:
@@ -176,7 +160,7 @@ Naming/PredicateName:
- 'lib/open_food_network/packing_report.rb'
- 'lib/tasks/data.rake'
# Offense count: 11
# Offense count: 12
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
# AllowedNames: io, id, to, by, on, in, at
Naming/UncommunicativeMethodParamName:
@@ -304,12 +288,13 @@ Style/CaseEquality:
- 'app/helpers/angular_form_helper.rb'
- 'spec/models/spree/payment_spec.rb'
# Offense count: 78
# Offense count: 79
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, EnforcedStyle.
# SupportedStyles: nested, compact
Style/ClassAndModuleChildren:
Exclude:
- 'app/controllers/spree/store_controller_decorator.rb'
- 'app/helpers/angular_form_helper.rb'
- 'app/models/calculator/flat_percent_per_item.rb'
- 'app/models/spree/concerns/payment_method_distributors.rb'
@@ -394,27 +379,11 @@ Style/CommentedKeyword:
Exclude:
- 'app/controllers/application_controller.rb'
# Offense count: 4
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions.
# SupportedStyles: assign_to_condition, assign_inside_condition
Style/ConditionalAssignment:
Exclude:
- 'app/controllers/spree/api/products_controller.rb'
- 'app/controllers/spree/api/taxons_controller.rb'
- 'app/controllers/spree/api/variants_controller.rb'
# Offense count: 2
Style/DateTime:
Exclude:
- 'lib/open_food_network/users_and_enterprises_report.rb'
# Offense count: 1
# Cop supports --auto-correct.
Style/EachWithObject:
Exclude:
- 'app/controllers/spree/api/base_controller.rb'
# Offense count: 5
# Configuration parameters: EnforcedStyle.
# SupportedStyles: annotated, template, unannotated
@@ -424,7 +393,7 @@ Style/FormatStringToken:
- 'lib/open_food_network/sales_tax_report.rb'
- 'spec/models/enterprise_spec.rb'
# Offense count: 68
# Offense count: 69
# Configuration parameters: MinBodyLength.
Style/GuardClause:
Exclude:
@@ -441,9 +410,7 @@ Style/GuardClause:
- 'app/controllers/spree/admin/products_controller_decorator.rb'
- 'app/controllers/spree/admin/resource_controller_decorator.rb'
- 'app/controllers/spree/admin/variants_controller_decorator.rb'
- 'app/controllers/spree/api/base_controller.rb'
- 'app/controllers/spree/checkout_controller.rb'
- 'app/controllers/spree/orders_controller.rb'
- 'app/controllers/spree/orders_controller_decorator.rb'
- 'app/controllers/spree/paypal_controller_decorator.rb'
- 'app/jobs/products_cache_integrity_checker_job.rb'
- 'app/models/enterprise.rb'
@@ -467,23 +434,12 @@ Style/GuardClause:
- 'spec/support/request/distribution_helper.rb'
- 'spec/support/request/shop_workflow.rb'
# Offense count: 6
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols.
# SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys
Style/HashSyntax:
Exclude:
- 'app/controllers/spree/api/base_controller.rb'
- 'app/controllers/spree/checkout_controller.rb'
- 'app/controllers/spree/orders_controller.rb'
# Offense count: 4
# Offense count: 3
Style/IfInsideElse:
Exclude:
- 'app/controllers/admin/column_preferences_controller.rb'
- 'app/controllers/admin/variant_overrides_controller.rb'
- 'app/controllers/spree/admin/products_controller_decorator.rb'
- 'app/controllers/spree/api/taxons_controller.rb'
# Offense count: 1
Style/MissingRespondToMissing:
@@ -536,10 +492,9 @@ Style/RegexpLiteral:
- 'spec/mailers/subscription_mailer_spec.rb'
- 'spec/models/content_configuration_spec.rb'
# Offense count: 244
# Offense count: 243
Style/Send:
Exclude:
- 'app/controllers/spree/checkout_controller.rb'
- 'app/models/spree/shipping_method_decorator.rb'
- 'spec/controllers/admin/subscriptions_controller_spec.rb'
- 'spec/controllers/checkout_controller_spec.rb'
@@ -586,9 +541,3 @@ Style/Send:
Style/StructInheritance:
Exclude:
- 'lib/open_food_network/enterprise_fee_applicator.rb'
# Offense count: 1
# Cop supports --auto-correct.
Style/UnlessElse:
Exclude:
- 'app/controllers/spree/api/variants_controller.rb'

View File

@@ -1 +1 @@
2.1.9
2.1.5

View File

@@ -6,7 +6,7 @@ RUN apt-get update && apt-get install -y curl git build-essential software-prope
# Setup ENV variables
ENV PATH /usr/local/src/rbenv/shims:/usr/local/src/rbenv/bin:$PATH
ENV RBENV_ROOT /usr/local/src/rbenv
ENV RUBY_VERSION 2.1.9
ENV RUBY_VERSION 2.1.5
ENV CONFIGURE_OPTS --disable-install-doc
# Rbenv & Ruby part

View File

@@ -11,7 +11,7 @@ The following guides are located in the wiki and provide more OS-specific step-b
### Dependencies
* Rails 3.2.x
* Ruby 2.1.9
* Ruby 2.1.5
* PostgreSQL database
* PhantomJS (for testing)
* See Gemfile for a list of gems required

16
Gemfile
View File

@@ -1,5 +1,5 @@
source 'https://rubygems.org'
ruby "2.1.9"
ruby "2.1.5"
git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" }
gem 'i18n', '~> 0.6.11'
@@ -49,10 +49,6 @@ gem 'delayed_job_web'
# When merged, revert to upstream gem
gem 'simple_form', github: 'RohanM/simple_form'
# Spree's default pagination gem (locked to the current version used by Spree)
# We use it's methods in OFN code as well, so this is a direct dependency
gem 'kaminari', '~> 0.14.1'
gem 'andand'
gem 'angularjs-rails', '1.5.5'
gem 'aws-sdk'
@@ -85,6 +81,7 @@ gem 'paperclip', '~> 3.4.1'
gem 'rack-rewrite'
gem 'rack-ssl', require: 'rack/ssl'
gem 'roadie-rails', '~> 1.1.1'
gem 'skylight', '< 2.0'
gem 'spinjs-rails'
gem 'combine_pdf'
@@ -104,10 +101,7 @@ group :assets do
gem 'coffee-rails', '~> 3.2.1'
gem 'compass-rails'
gem 'mini_racer', '0.1.15'
# Previously we found that libv8 6.7.288.46.1 breakis the compilation of mini_racer.
# Now we see that we need to set the version explicitly. Nothing else depends on libv8.
gem 'libv8', '6.3.292.48.1'
gem 'therubyracer', '=0.12.0'
gem 'uglifier', '>= 1.0.3'
@@ -159,6 +153,10 @@ end
group :development do
gem 'byebug', '~> 9.0.0' # 9.1 requires ruby 2.2
gem 'debugger-linecache'
gem 'guard'
gem 'guard-livereload'
gem 'guard-rails'
gem 'guard-rspec', '~> 4.7.3'
gem 'listen', '3.0.8' # 3.1.0 requires ruby 2.2
gem "newrelic_rpm", "~> 3.0"
gem 'pry-byebug', '>= 3.4.3'

View File

@@ -141,8 +141,8 @@ GEM
activerecord (>= 3.2, < 5)
acts_as_list (0.2.0)
activerecord (>= 3.0)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
addressable (2.6.0)
public_suffix (>= 2.0.2, < 4.0)
andand (1.3.3)
angular-rails-templates (0.3.0)
railties (>= 3.1)
@@ -164,7 +164,7 @@ GEM
bcrypt-ruby (3.1.5)
bcrypt (>= 3.1.3)
blockenspiel (0.5.0)
bugsnag (6.12.1)
bugsnag (6.11.1)
concurrent-ruby (~> 1.0)
builder (3.0.4)
byebug (9.0.6)
@@ -210,7 +210,7 @@ GEM
compass (~> 1.0.0)
sass-rails (< 5.1)
sprockets (< 4.0)
concurrent-ruby (1.1.5)
concurrent-ruby (1.1.4)
connection_pool (2.2.2)
crack (0.4.3)
safe_yaml (~> 1.0.0)
@@ -223,7 +223,7 @@ GEM
activerecord (>= 3.2.0, < 5.0)
fog (~> 1.0)
rails (>= 3.2.0, < 5.0)
ddtrace (0.27.0)
ddtrace (0.26.0)
msgpack
debugger-linecache (1.2.0)
deface (1.0.2)
@@ -231,10 +231,10 @@ GEM
nokogiri (~> 1.6.0)
polyglot
rails (>= 3.1)
delayed_job (4.1.8)
activesupport (>= 3.0, < 6.1)
delayed_job_active_record (4.1.4)
activerecord (>= 3.0, < 6.1)
delayed_job (4.1.5)
activesupport (>= 3.0, < 5.3)
delayed_job_active_record (4.1.3)
activerecord (>= 3.0, < 5.3)
delayed_job (>= 3.0, < 5)
delayed_job_web (1.4.3)
activerecord (> 3.0.0)
@@ -252,6 +252,9 @@ GEM
diffy (3.3.0)
docile (1.3.2)
dry-inflector (0.1.2)
em-websocket (0.5.1)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0)
erubis (2.7.0)
eventmachine (1.2.7)
excon (0.62.0)
@@ -429,11 +432,34 @@ GEM
ruby-progressbar (~> 1.4)
geocoder (1.1.8)
gmaps4rails (1.5.6)
guard (2.15.0)
formatador (>= 0.2.4)
listen (>= 2.7, < 4.0)
lumberjack (>= 1.0.12, < 2.0)
nenv (~> 0.1)
notiffany (~> 0.0)
pry (>= 0.9.12)
shellany (~> 0.0)
thor (>= 0.18.1)
guard-compat (1.2.1)
guard-livereload (2.5.2)
em-websocket (~> 0.5)
guard (~> 2.8)
guard-compat (~> 1.0)
multi_json (~> 1.8)
guard-rails (0.7.2)
guard (~> 2.11)
guard-compat (~> 1.0)
guard-rspec (4.7.3)
guard (~> 2.1)
guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0)
haml (4.0.7)
tilt
hashdiff (1.0.0)
highline (1.6.18)
hike (1.2.3)
http_parser.rb (0.6.0)
httparty (0.16.2)
multi_xml (>= 0.5.2)
i18n (0.6.11)
@@ -459,16 +485,17 @@ GEM
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
kgio (2.11.2)
knapsack (1.18.0)
knapsack (1.17.2)
rake
launchy (2.4.3)
addressable (~> 2.3)
letter_opener (1.7.0)
launchy (~> 2.2)
libv8 (6.3.292.48.1)
libv8 (3.16.14.19)
listen (3.0.8)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
lumberjack (1.0.13)
mail (2.5.5)
mime-types (~> 1.16)
treetop (~> 1.4.8)
@@ -476,8 +503,6 @@ GEM
mime-types (1.25.1)
mini_mime (1.0.1)
mini_portile2 (2.1.0)
mini_racer (0.1.15)
libv8 (~> 6.3)
momentjs-rails (2.20.1)
railties (>= 3.1)
money (5.1.1)
@@ -486,11 +511,15 @@ GEM
multi_json (1.13.1)
multi_xml (0.6.0)
multipart-post (2.1.1)
nenv (0.3.0)
net-http-persistent (3.1.0)
connection_pool (~> 2.2)
newrelic_rpm (3.18.1.330)
nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0)
notiffany (0.1.1)
nenv (~> 0.1)
shellany (~> 0.0)
oauth2 (1.4.1)
faraday (>= 0.8, < 0.16.0)
jwt (>= 1.0, < 3.0)
@@ -581,6 +610,7 @@ GEM
rdoc (3.12.2)
json (~> 1.4)
redcarpet (3.5.0)
ref (2.0.0)
request_store (1.4.1)
rack (>= 1.4)
roadie (3.4.0)
@@ -643,9 +673,10 @@ GEM
selenium-webdriver (3.141.0)
childprocess (~> 0.5)
rubyzip (~> 1.2, >= 1.2.2)
shellany (0.0.1)
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
simplecov (0.17.1)
simplecov (0.17.0)
docile (~> 1.1)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
@@ -654,6 +685,8 @@ GEM
rack (~> 1.4)
rack-protection (~> 1.4)
tilt (>= 1.3, < 3)
skylight (1.7.2)
activesupport (>= 3.0.0)
spinjs-rails (1.4)
rails (>= 3.1)
spreadsheet (1.1.7)
@@ -671,6 +704,9 @@ GEM
stripe (4.24.0)
faraday (~> 0.13)
net-http-persistent (~> 3.0)
therubyracer (0.12.0)
libv8 (~> 3.16.14.0)
ref
thor (0.20.3)
tilt (1.4.1)
timecop (0.9.1)
@@ -683,7 +719,7 @@ GEM
railties (> 3.2.8, < 4.0.0)
sprockets (>= 2.2.0)
tzinfo (0.3.55)
uglifier (4.2.0)
uglifier (4.1.20)
execjs (>= 0.3.0, < 3)
unicode-display_width (1.3.2)
unicorn (5.5.1)
@@ -703,7 +739,7 @@ GEM
nokogiri (~> 1.6)
rubyzip (~> 1.0)
selenium-webdriver (~> 3.0)
webmock (3.7.6)
webmock (3.6.2)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
@@ -760,6 +796,10 @@ DEPENDENCIES
fuubar (~> 2.4.1)
geocoder
gmaps4rails
guard
guard-livereload
guard-rails
guard-rspec (~> 4.7.3)
haml
i18n (~> 0.6.11)
i18n-js (~> 3.3.0)
@@ -768,12 +808,9 @@ DEPENDENCIES
jquery-rails (= 3.0.4)
json_spec (~> 1.1.4)
jwt (~> 2.2)
kaminari (~> 0.14.1)
knapsack
letter_opener (>= 1.4.1)
libv8 (= 6.3.292.48.1)
listen (= 3.0.8)
mini_racer (= 0.1.15)
momentjs-rails
newrelic_rpm (~> 3.0)
nokogiri (>= 1.6.7.1)
@@ -805,6 +842,7 @@ DEPENDENCIES
shoulda-matchers
simple_form!
simplecov
skylight (< 2.0)
spinjs-rails
spree_api!
spree_backend!
@@ -814,6 +852,7 @@ DEPENDENCIES
spring (= 1.7.2)
spring-commands-rspec
stripe
therubyracer (= 0.12.0)
timecop
truncate_html
turbo-sprockets-rails3
@@ -828,7 +867,7 @@ DEPENDENCIES
wkhtmltopdf-binary
RUBY VERSION
ruby 2.1.9p490
ruby 2.1.5p273
BUNDLED WITH
1.17.2

11
Guardfile Normal file
View File

@@ -0,0 +1,11 @@
# A sample Guardfile
# More info at https://github.com/guard/guard#readme
guard 'livereload' do
watch(%r{app/views/.+\.(erb|haml|slim)$})
watch(%r{app/helpers/.+\.rb})
watch(%r{public/.+\.(css|js|html)})
# Rails Assets Pipeline
watch(%r{(app|vendor)(/assets/\w+/(.+\.(css|js|html|png|jpg))).*}) { |m| "/assets/#{m[3]}" }
end

View File

@@ -1,5 +1,6 @@
[![Build Status](https://semaphoreci.com/api/v1/openfoodfoundation/openfoodnetwork-2/branches/master/badge.svg)](https://semaphoreci.com/openfoodfoundation/openfoodnetwork-2)
[![Code Climate](https://codeclimate.com/github/openfoodfoundation/openfoodnetwork.png)](https://codeclimate.com/github/openfoodfoundation/openfoodnetwork)
[![View performance data on Skylight](https://badges.skylight.io/status/EiXQ6sSKij8y.svg)](https://oss.skylight.io/app/applications/EiXQ6sSKij8y)
# Open Food Network
@@ -35,7 +36,7 @@ We use [BrowserStack](https://www.browserstack.com/) as a manual testing tool. B
Copyright (c) 2012 - 2019 Open Food Foundation, released under the AGPL licence.
[survey]: https://docs.google.com/a/eaterprises.com.au/forms/d/1zxR5vSiU9CigJ9cEaC8-eJLgYid8CR8er7PPH9Mc-30/edit#
[slack-invite]: https://join.slack.com/t/openfoodnetwork/shared_invite/enQtNzY3NDEwNzM2MDM0LWFmNGRhNDUwYzNmNWNkYmFkMzgxNDg1OTg1ODNjNWY4Y2FhNDIwNmE4ZWI0OThiMGNmZjFkODczNGZiYTJmNWI
[slack-invite]: https://openfoodnetwork.org/slack-invite
[contributor-guide]: https://ofn-user-guide.gitbook.io/ofn-contributor-guide/who-are-we
[ofn-install]: https://github.com/openfoodfoundation/ofn-install
[super-admin-guide]: https://ofn-user-guide.gitbook.io/ofn-super-admin-guide

View File

@@ -1,287 +1,267 @@
angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $filter, $http, $window, BulkProducts, DisplayProperties, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth, Columns, tax_categories, RequestMonitor) ->
$scope.StatusMessage = StatusMessage
angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $http, $window, BulkProducts, DisplayProperties, dataFetcher, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth, Columns, tax_categories) ->
$scope.loading = true
$scope.loadingAllPages = true
$scope.columns = Columns.columns
$scope.StatusMessage = StatusMessage
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
$scope.columns = Columns.columns
$scope.RequestMonitor = RequestMonitor
$scope.pagination = BulkProducts.pagination
$scope.per_page_options = [
{id: 15, name: t('js.admin.orders.index.per_page', results: 15)},
{id: 50, name: t('js.admin.orders.index.per_page', results: 50)},
{id: 100, name: t('js.admin.orders.index.per_page', results: 100)}
]
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
$scope.filterableColumns = [
{ name: t("label_producers"), db_column: "producer_name" },
{ name: t("name"), db_column: "name" }
]
$scope.filterableColumns = [
{ name: t("label_producers"), db_column: "producer_name" },
{ name: t("name"), db_column: "name" }
]
$scope.filterTypes = [
{ name: t("equals"), predicate: "eq" },
{ name: t("contains"), predicate: "cont" }
]
$scope.filterTypes = [
{ name: t("equals"), predicate: "eq" },
{ name: t("contains"), predicate: "cont" }
]
$scope.optionTabs =
filters: { title: t("filter_products"), visible: false }
$scope.producers = producers
$scope.taxons = Taxons.all
$scope.tax_categories = tax_categories
$scope.producerFilter = ""
$scope.categoryFilter = ""
$scope.importDateFilter = ""
$scope.page = 1
$scope.per_page = 15
$scope.products = BulkProducts.products
$scope.query = ""
$scope.DisplayProperties = DisplayProperties
$scope.initialise = ->
SpreeApiAuth.authorise()
.then ->
$scope.spree_api_key_ok = true
$scope.fetchProducts()
.catch (message) ->
$scope.api_error_msg = message
$scope.$watchCollection '[query, producerFilter, categoryFilter, importDateFilter, per_page]', ->
$scope.page = 1 # Reset page when changing filters for new search
$scope.changePage = (newPage) ->
$scope.page = newPage
$scope.fetchProducts()
$scope.fetchProducts = ->
removeClearedValues()
params = {
'q[name_cont]': $scope.query,
'q[supplier_id_eq]': $scope.producerFilter,
'q[primary_taxon_id_eq]': $scope.categoryFilter,
import_date: $scope.importDateFilter,
page: $scope.page,
per_page: $scope.per_page
}
RequestMonitor.load(BulkProducts.fetch(params).$promise).then ->
$scope.resetProducts()
removeClearedValues = ->
delete $scope.producerFilter if $scope.producerFilter == "0"
delete $scope.categoryFilter if $scope.categoryFilter == "0"
delete $scope.importDateFilter if $scope.importDateFilter == "0"
$timeout ->
if $scope.showLatestImport
$scope.importDateFilter = $scope.importDates[1].id
$scope.resetProducts = ->
DirtyProducts.clear()
StatusMessage.clear()
$scope.updateOnHand = (product) ->
on_demand_variants = []
if product.variants
on_demand_variants = (variant for id, variant of product.variants when variant.on_demand)
unless product.on_demand || on_demand_variants.length > 0
product.on_hand = $scope.onHand(product)
$scope.optionTabs =
filters: { title: t("filter_products"), visible: false }
$scope.onHand = (product) ->
onHand = 0
if product.hasOwnProperty("variants") and product.variants instanceof Object
for id, variant of product.variants
onHand = onHand + parseInt(if variant.on_hand > 0 then variant.on_hand else 0)
else
onHand = "error"
onHand
$scope.shiftTab = (tab) ->
$scope.visibleTab.visible = false unless $scope.visibleTab == tab || $scope.visibleTab == undefined
tab.visible = !tab.visible
$scope.visibleTab = tab
$scope.resetSelectFilters = ->
$scope.query = ""
$scope.producers = producers
$scope.taxons = Taxons.all
$scope.tax_categories = tax_categories
$scope.filterProducers = [{id: "0", name: ""}].concat $scope.producers
$scope.filterTaxons = [{id: "0", name: ""}].concat $scope.taxons
$scope.producerFilter = "0"
$scope.categoryFilter = "0"
$scope.importDateFilter = "0"
$scope.products = BulkProducts.products
$scope.filteredProducts = []
$scope.currentFilters = []
$scope.limit = 15
$scope.query = ""
$scope.DisplayProperties = DisplayProperties
$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"
$scope.initialise = ->
SpreeApiAuth.authorise()
.then ->
$scope.spree_api_key_ok = true
$scope.fetchProducts()
.catch (message) ->
$scope.api_error_msg = message
$scope.$watchCollection '[query, producerFilter, categoryFilter, importDateFilter]', ->
$scope.limit = 15 # Reset limit whenever searching
$scope.toggleShowAllVariants = ->
showVariants = !DisplayProperties.showVariants 0
$scope.products.forEach (product) ->
DisplayProperties.setShowVariants product.id, showVariants
DisplayProperties.setShowVariants 0, showVariants
$scope.fetchProducts = ->
$scope.loading = true
$scope.loadingAllPages = true
BulkProducts.fetch($scope.currentFilters, ->
$scope.loadingAllPages = false
).then ->
$scope.resetProducts()
$scope.loading = false
$scope.addVariant = (product) ->
product.variants.push
id: $scope.nextVariantId()
unit_value: null
unit_description: null
on_demand: false
display_as: null
display_name: null
on_hand: null
price: null
DisplayProperties.setShowVariants product.id, true
$timeout ->
if $scope.showLatestImport
$scope.importDateFilter = $scope.importDates[1].id
$scope.nextVariantId = ->
$scope.variantIdCounter = 0 unless $scope.variantIdCounter?
$scope.variantIdCounter -= 1
$scope.variantIdCounter
$scope.deleteProduct = (product) ->
if confirm("Are you sure?")
$http(
method: "DELETE"
url: "/api/products/" + product.id + "/soft_delete"
).success (data) ->
$scope.products.splice $scope.products.indexOf(product), 1
DirtyProducts.deleteProduct product.id
$scope.displayDirtyProducts()
$scope.deleteVariant = (product, variant) ->
if product.variants.length > 1
if !$scope.variantSaved(variant)
$scope.removeVariant(product, variant)
else
if confirm(t("are_you_sure"))
$http(
method: "DELETE"
url: "/api/products/" + product.permalink_live + "/variants/" + variant.id + "/soft_delete"
).success (data) ->
$scope.removeVariant(product, variant)
else
alert(t("delete_product_variant"))
$scope.removeVariant = (product, variant) ->
product.variants.splice product.variants.indexOf(variant), 1
DirtyProducts.deleteVariant product.id, variant.id
$scope.displayDirtyProducts()
$scope.cloneProduct = (product) ->
BulkProducts.cloneProduct product
$scope.hasVariants = (product) ->
product.variants.length > 0
$scope.hasUnit = (product) ->
product.variant_unit_with_scale?
$scope.variantSaved = (variant) ->
variant.hasOwnProperty('id') && variant.id > 0
$scope.hasOnDemandVariants = (product) ->
(variant for id, variant of product.variants when variant.on_demand).length > 0
$scope.submitProducts = ->
# Pack pack $scope.products, so they will match the list returned from the server,
# then pack $scope.dirtyProducts, ensuring that the correct product info is sent to the server.
$scope.packProduct product for id, product of $scope.products
$scope.packProduct product for id, product of DirtyProducts.all()
productsToSubmit = filterSubmitProducts(DirtyProducts.all())
if productsToSubmit.length > 0
$scope.updateProducts productsToSubmit # Don't submit an empty list
else
StatusMessage.display 'alert', t("products_change")
$scope.updateProducts = (productsToSubmit) ->
$scope.displayUpdating()
$http(
method: "POST"
url: "/admin/products/bulk_update"
data:
products: productsToSubmit
filters:
'q[name_cont]': $scope.query
'q[supplier_id_eq]': $scope.producerFilter
'q[primary_taxon_id_eq]': $scope.categoryFilter
import_date: $scope.importDateFilter
page: $scope.page
per_page: $scope.per_page
).success((data) ->
$scope.resetProducts = ->
DirtyProducts.clear()
BulkProducts.updateVariantLists(data.products || [])
$timeout -> $scope.displaySuccess()
).error (data, status) ->
if status == 400 && data.errors? && data.errors.length > 0
errors = error + "\n" for error in data.errors
alert t("products_update_error") + "\n" + errors
$scope.displayFailure t("products_update_error")
StatusMessage.clear()
$scope.updateOnHand = (product) ->
on_demand_variants = []
if product.variants
on_demand_variants = (variant for id, variant of product.variants when variant.on_demand)
unless product.on_demand || on_demand_variants.length > 0
product.on_hand = $scope.onHand(product)
$scope.onHand = (product) ->
onHand = 0
if product.hasOwnProperty("variants") and product.variants instanceof Object
for id, variant of product.variants
onHand = onHand + parseInt(if variant.on_hand > 0 then variant.on_hand else 0)
else
$scope.displayFailure t("products_update_error_data") + status
onHand = "error"
onHand
$scope.cancel = (destination) ->
$window.location = destination
$scope.shiftTab = (tab) ->
$scope.visibleTab.visible = false unless $scope.visibleTab == tab || $scope.visibleTab == undefined
tab.visible = !tab.visible
$scope.visibleTab = tab
$scope.packProduct = (product) ->
if product.variant_unit_with_scale
match = product.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/)
if match
product.variant_unit = match[1]
product.variant_unit_scale = parseFloat(match[2])
$scope.resetSelectFilters = ->
$scope.query = ""
$scope.producerFilter = "0"
$scope.categoryFilter = "0"
$scope.importDateFilter = "0"
$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"
$scope.toggleShowAllVariants = ->
showVariants = !DisplayProperties.showVariants 0
$scope.filteredProducts.forEach (product) ->
DisplayProperties.setShowVariants product.id, showVariants
DisplayProperties.setShowVariants 0, showVariants
$scope.addVariant = (product) ->
product.variants.push
id: $scope.nextVariantId()
unit_value: null
unit_description: null
on_demand: false
display_as: null
display_name: null
on_hand: null
price: null
DisplayProperties.setShowVariants product.id, true
$scope.nextVariantId = ->
$scope.variantIdCounter = 0 unless $scope.variantIdCounter?
$scope.variantIdCounter -= 1
$scope.variantIdCounter
$scope.deleteProduct = (product) ->
if confirm("Are you sure?")
$http(
method: "DELETE"
url: "/api/products/" + product.id + "/soft_delete"
).success (data) ->
$scope.products.splice $scope.products.indexOf(product), 1
DirtyProducts.deleteProduct product.id
$scope.displayDirtyProducts()
$scope.deleteVariant = (product, variant) ->
if product.variants.length > 1
if !$scope.variantSaved(variant)
$scope.removeVariant(product, variant)
else
if confirm(t("are_you_sure"))
$http(
method: "DELETE"
url: "/api/products/" + product.permalink_live + "/variants/" + variant.id + "/soft_delete"
).success (data) ->
$scope.removeVariant(product, variant)
else
product.variant_unit = product.variant_unit_with_scale
product.variant_unit_scale = null
else
product.variant_unit = product.variant_unit_scale = null
alert(t("delete_product_variant"))
$scope.packVariant product, product.master if product.master
if product.variants
for id, variant of product.variants
$scope.packVariant product, variant
$scope.removeVariant = (product, variant) ->
product.variants.splice product.variants.indexOf(variant), 1
DirtyProducts.deleteVariant product.id, variant.id
$scope.displayDirtyProducts()
$scope.packVariant = (product, variant) ->
if variant.hasOwnProperty("unit_value_with_description")
match = variant.unit_value_with_description.match(/^([\d\.]+(?= |$)|)( |)(.*)$/)
if match
product = BulkProducts.find product.id
variant.unit_value = parseFloat(match[1])
variant.unit_value = null if isNaN(variant.unit_value)
variant.unit_value *= product.variant_unit_scale if variant.unit_value && product.variant_unit_scale
variant.unit_description = match[3]
$scope.cloneProduct = (product) ->
BulkProducts.cloneProduct product
$scope.incrementLimit = ->
if $scope.limit < $scope.products.length
$scope.limit = $scope.limit + 5
$scope.hasVariants = (product) ->
product.variants.length > 0
$scope.displayUpdating = ->
StatusMessage.display 'progress', t("saving")
$scope.hasUnit = (product) ->
product.variant_unit_with_scale?
$scope.displaySuccess = ->
StatusMessage.display 'success',t("products_changes_saved")
$scope.bulk_product_form.$setPristine()
$scope.variantSaved = (variant) ->
variant.hasOwnProperty('id') && variant.id > 0
$scope.displayFailure = (failMessage) ->
StatusMessage.display 'failure', t("products_update_error_msg") + "#{failMessage}"
$scope.hasOnDemandVariants = (product) ->
(variant for id, variant of product.variants when variant.on_demand).length > 0
$scope.displayDirtyProducts = ->
count = DirtyProducts.count()
switch count
when 0 then StatusMessage.clear()
when 1 then StatusMessage.display 'notice', t("one_product_unsaved")
else StatusMessage.display 'notice', t("products_unsaved", n: count)
$scope.submitProducts = ->
# Pack pack $scope.products, so they will match the list returned from the server,
# then pack $scope.dirtyProducts, ensuring that the correct product info is sent to the server.
$scope.packProduct product for id, product of $scope.products
$scope.packProduct product for id, product of DirtyProducts.all()
productsToSubmit = filterSubmitProducts(DirtyProducts.all())
if productsToSubmit.length > 0
$scope.updateProducts productsToSubmit # Don't submit an empty list
else
StatusMessage.display 'alert', t("products_change")
$scope.updateProducts = (productsToSubmit) ->
$scope.displayUpdating()
$http(
method: "POST"
url: "/admin/products/bulk_update"
data:
products: productsToSubmit
filters: $scope.currentFilters
).success((data) ->
DirtyProducts.clear()
BulkProducts.updateVariantLists(data.products || [])
$timeout -> $scope.displaySuccess()
).error (data, status) ->
if status == 400 && data.errors? && data.errors.length > 0
errors = error + "\n" for error in data.errors
alert t("products_update_error") + "\n" + errors
$scope.displayFailure t("products_update_error")
else
$scope.displayFailure t("products_update_error_data") + status
$scope.cancel = (destination) ->
$window.location = destination
$scope.packProduct = (product) ->
if product.variant_unit_with_scale
match = product.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/)
if match
product.variant_unit = match[1]
product.variant_unit_scale = parseFloat(match[2])
else
product.variant_unit = product.variant_unit_with_scale
product.variant_unit_scale = null
else
product.variant_unit = product.variant_unit_scale = null
$scope.packVariant product, product.master if product.master
if product.variants
for id, variant of product.variants
$scope.packVariant product, variant
$scope.packVariant = (product, variant) ->
if variant.hasOwnProperty("unit_value_with_description")
match = variant.unit_value_with_description.match(/^([\d\.]+(?= |$)|)( |)(.*)$/)
if match
product = BulkProducts.find product.id
variant.unit_value = parseFloat(match[1])
variant.unit_value = null if isNaN(variant.unit_value)
variant.unit_value *= product.variant_unit_scale if variant.unit_value && product.variant_unit_scale
variant.unit_description = match[3]
$scope.incrementLimit = ->
if $scope.limit < $scope.products.length
$scope.limit = $scope.limit + 5
$scope.displayUpdating = ->
StatusMessage.display 'progress', t("saving")
$scope.displaySuccess = ->
StatusMessage.display 'success',t("products_changes_saved")
$scope.bulk_product_form.$setPristine()
$scope.displayFailure = (failMessage) ->
StatusMessage.display 'failure', t("products_update_error_msg") + "#{failMessage}"
$scope.displayDirtyProducts = ->
count = DirtyProducts.count()
switch count
when 0 then StatusMessage.clear()
when 1 then StatusMessage.display 'notice', t("one_product_unsaved")
else StatusMessage.display 'notice', t("products_unsaved", n: count)
filterSubmitProducts = (productsToFilter) ->

View File

@@ -23,7 +23,7 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, RequestMonitor,
$scope.fetchResults = (page=1) ->
$scope.resetSelected()
params = {
Orders.index({
'q[completed_at_lt]': $scope['q']['completed_at_lt'],
'q[completed_at_gt]': $scope['q']['completed_at_gt'],
'q[state_eq]': $scope['q']['state_eq'],
@@ -39,8 +39,7 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, RequestMonitor,
'q[s]': $scope.sorting || 'completed_at desc',
per_page: $scope.per_page,
page: page
}
RequestMonitor.load(Orders.index(params).$promise)
})
$scope.resetSelected = ->
$scope.selected_orders.length = 0

View File

@@ -1,6 +0,0 @@
angular.module("admin.resources").factory 'ProductResource', ($resource) ->
$resource('/admin/product/:id/:action.json', {}, {
'index':
url: '/api/products/bulk_products.json'
method: 'GET'
})

View File

@@ -1,13 +1,15 @@
angular.module("ofn.admin").factory "BulkProducts", (ProductResource, dataFetcher, $http) ->
angular.module("ofn.admin").factory "BulkProducts", (PagedFetcher, dataFetcher, $http) ->
new class BulkProducts
products: []
pagination: {}
fetch: (params) ->
ProductResource.index params, (data) =>
@products.length = 0
@addProducts data.products
angular.extend(@pagination, data.pagination)
fetch: (filters, onComplete) ->
queryString = filters.reduce (qs,f) ->
return qs + "q[#{f.property.db_column}_#{f.predicate.predicate}]=#{f.value};"
, ""
url = "/api/products/bulk_products?page=::page::;per_page=20;#{queryString}"
processData = (data) => @addProducts data.products
PagedFetcher.fetch url, processData, onComplete
cloneProduct: (product) ->
$http.post("/api/products/" + product.id + "/clone").success (data) =>

View File

@@ -3,7 +3,7 @@
.select2-container {
.select2-choice {
.select2-search-choice-close {
display: none !important;
display: none;
}
.select2-arrow {
width: 22px;

View File

@@ -1,146 +0,0 @@
require 'open_food_network/permissions'
module Api
class ProductsController < Api::BaseController
respond_to :json
DEFAULT_PAGE = 1
DEFAULT_PER_PAGE = 15
skip_authorization_check only: [:show, :bulk_products, :overridable]
def show
@product = find_product(params[:id])
render json: @product, serializer: Api::Admin::ProductSerializer
end
def create
authorize! :create, Spree::Product
params[:product][:available_on] ||= Time.zone.now
@product = Spree::Product.new(params[:product])
begin
if @product.save
render json: @product, serializer: Api::Admin::ProductSerializer, status: 201
else
invalid_resource!(@product)
end
rescue ActiveRecord::RecordNotUnique
@product.permalink = nil
retry
end
end
def update
authorize! :update, Spree::Product
@product = find_product(params[:id])
if @product.update_attributes(params[:product])
render json: @product, serializer: Api::Admin::ProductSerializer, status: 200
else
invalid_resource!(@product)
end
end
def destroy
authorize! :delete, Spree::Product
@product = find_product(params[:id])
@product.update_attribute(:deleted_at, Time.zone.now)
@product.variants_including_master.update_all(deleted_at: Time.zone.now)
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)
if params[:import_date].present?
product_query = product_query.imported_on(params[:import_date]).group_by_products_id
end
@products = product_query.order('created_at DESC').
ransack(params[:q]).result.
page(params[:page] || DEFAULT_PAGE).per(params[:per_page] || DEFAULT_PER_PAGE)
render_paged_products @products
end
def overridable
producers = OpenFoodNetwork::Permissions.new(current_api_user).
variant_override_producers.by_name
@products = paged_products_for_producers producers
render_paged_products @products
end
def soft_delete
authorize! :delete, Spree::Product
@product = find_product(params[:product_id])
authorize! :delete, @product
@product.destroy
render json: @product, serializer: Api::Admin::ProductSerializer, status: 204
end
# POST /api/products/:product_id/clone
#
def clone
authorize! :create, Spree::Product
original_product = find_product(params[:product_id])
authorize! :update, original_product
@product = original_product.duplicate
render json: @product, serializer: Api::Admin::ProductSerializer, status: 201
end
private
# Copied and modified from SpreeApi::BaseController to allow
# enterprise users to access inactive products
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]
scope = scope.with_deleted
end
else
scope = Spree::Product.active
end
scope.includes(:master)
end
def paged_products_for_producers(producers)
Spree::Product.scoped.
merge(product_scope).
where(supplier_id: producers).
by_producer.by_name.
ransack(params[:q]).result.
page(params[:page]).per(params[:per_page])
end
def render_paged_products(products)
serializer = ActiveModel::ArraySerializer.new(
products,
each_serializer: ::Api::Admin::ProductSerializer
)
render text: {
products: serializer,
# This line is used by the PagedFetcher JS service (inventory).
pages: products.num_pages,
# This hash is used by the BulkProducts JS service.
pagination: pagination_data(products)
}.to_json
end
def pagination_data(results)
{
results: results.total_count,
pages: results.num_pages,
page: (params[:page] || DEFAULT_PAGE).to_i,
per_page: (params[:per_page] || DEFAULT_PER_PAGE).to_i
}
end
end
end

View File

@@ -1,105 +0,0 @@
require 'open_food_network/scope_variant_to_hub'
module Api
class ShipmentsController < Api::BaseController
respond_to :json
before_filter :find_order
before_filter :find_and_update_shipment, only: [:ship, :ready, :add, :remove]
def create
variant = scoped_variant(params[:variant_id])
quantity = params[:quantity].to_i
@shipment = get_or_create_shipment(params[:stock_location_id])
@order.contents.add(variant, quantity, nil, @shipment)
@shipment.refresh_rates
@shipment.save!
render json: @shipment.reload, serializer: Api::ShipmentSerializer, status: :ok
end
def update
authorize! :read, Spree::Shipment
@shipment = @order.shipments.find_by_number!(params[:id])
params[:shipment] ||= []
unlock = params[:shipment].delete(:unlock)
if unlock == 'yes'
@shipment.adjustment.open
end
@shipment.update_attributes(params[:shipment])
if unlock == 'yes'
@shipment.adjustment.close
end
render json: @shipment.reload, serializer: Api::ShipmentSerializer, status: :ok
end
def ready
authorize! :read, Spree::Shipment
unless @shipment.ready?
if @shipment.can_ready?
@shipment.ready!
else
render(json: { error: I18n.t(:cannot_ready, scope: "spree.api.shipment") },
status: :unprocessable_entity) && return
end
end
render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok
end
def ship
authorize! :read, Spree::Shipment
unless @shipment.shipped?
@shipment.ship!
end
render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok
end
def add
variant = scoped_variant(params[:variant_id])
quantity = params[:quantity].to_i
@order.contents.add(variant, quantity, nil, @shipment)
render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok
end
def remove
variant = scoped_variant(params[:variant_id])
quantity = params[:quantity].to_i
@order.contents.remove(variant, quantity, @shipment)
@shipment.reload if @shipment.persisted?
render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok
end
private
def find_order
@order = Spree::Order.find_by_number!(params[:order_id])
authorize! :read, @order
end
def find_and_update_shipment
@shipment = @order.shipments.find_by_number!(params[:id])
@shipment.update_attributes(params[:shipment])
@shipment.reload
end
def scoped_variant(variant_id)
variant = Spree::Variant.find(variant_id)
OpenFoodNetwork::ScopeVariantToHub.new(@order.distributor).scope(variant)
variant
end
def get_or_create_shipment(stock_location_id)
@order.shipment || @order.shipments.create(stock_location_id: stock_location_id)
end
end
end

View File

@@ -1,12 +0,0 @@
module Api
class TaxonomiesController < Api::BaseController
respond_to :json
skip_authorization_check only: :jstree
def jstree
@taxonomy = Spree::Taxonomy.find(params[:id])
render json: @taxonomy.root, serializer: Api::TaxonJstreeSerializer
end
end
end

View File

@@ -1,71 +0,0 @@
module Api
class TaxonsController < Api::BaseController
respond_to :json
skip_authorization_check only: [:index, :show, :jstree]
def index
if taxonomy
@taxons = taxonomy.root.children
else
if params[:ids]
@taxons = Spree::Taxon.where(id: params[:ids].split(","))
else
@taxons = Spree::Taxon.ransack(params[:q]).result
end
end
render json: @taxons, each_serializer: Api::TaxonSerializer
end
def jstree
@taxon = taxon
render json: @taxon.children, each_serializer: Api::TaxonJstreeSerializer
end
def create
authorize! :create, Spree::Taxon
@taxon = Spree::Taxon.new(params[:taxon])
@taxon.taxonomy_id = params[:taxonomy_id]
taxonomy = Spree::Taxonomy.find_by_id(params[:taxonomy_id])
if taxonomy.nil?
@taxon.errors[:taxonomy_id] = I18n.t(:invalid_taxonomy_id, scope: 'spree.api')
invalid_resource!(@taxon) && return
end
@taxon.parent_id = taxonomy.root.id unless params[:taxon][:parent_id]
if @taxon.save
render json: @taxon, serializer: Api::TaxonSerializer, status: :created
else
invalid_resource!(@taxon)
end
end
def update
authorize! :update, Spree::Taxon
if taxon.update_attributes(params[:taxon])
render json: taxon, serializer: Api::TaxonSerializer, status: :ok
else
invalid_resource!(taxon)
end
end
def destroy
authorize! :delete, Spree::Taxon
taxon.destroy
render json: taxon, serializer: Api::TaxonSerializer, status: :no_content
end
private
def taxonomy
return if params[:taxonomy_id].blank?
@taxonomy ||= Spree::Taxonomy.find(params[:taxonomy_id])
end
def taxon
@taxon ||= taxonomy.taxons.find(params[:id])
end
end
end

View File

@@ -1,79 +0,0 @@
module Api
class VariantsController < Api::BaseController
respond_to :json
skip_authorization_check only: [:index, :show]
before_filter :product
def index
@variants = scope.includes(:option_values).ransack(params[:q]).result
render json: @variants, each_serializer: Api::VariantSerializer
end
def show
@variant = scope.includes(:option_values).find(params[:id])
render json: @variant, serializer: Api::VariantSerializer
end
def create
authorize! :create, Spree::Variant
@variant = scope.new(params[:variant])
if @variant.save
render json: @variant, serializer: Api::VariantSerializer, status: 201
else
invalid_resource!(@variant)
end
end
def update
authorize! :update, Spree::Variant
@variant = scope.find(params[:id])
if @variant.update_attributes(params[:variant])
render json: @variant, serializer: Api::VariantSerializer, status: 200
else
invalid_resource!(@product)
end
end
def soft_delete
@variant = scope.find(params[:variant_id])
authorize! :delete, @variant
VariantDeleter.new.delete(@variant)
render json: @variant, serializer: Api::VariantSerializer, status: 204
end
def destroy
authorize! :delete, Spree::Variant
@variant = scope.find(params[:id])
@variant.destroy
render json: @variant, serializer: Api::VariantSerializer, status: 204
end
private
def product
@product ||= Spree::Product.find_by_permalink(params[:product_id]) if params[:product_id]
end
def scope
if @product
unless current_api_user.has_spree_role?("admin") || params[:show_deleted]
variants = @product.variants_including_master
else
variants = @product.variants_including_master.with_deleted
end
else
variants = Spree::Variant.scoped
if current_api_user.has_spree_role?("admin")
unless params[:show_deleted]
variants = Spree::Variant.active
end
else
variants = variants.active
end
end
variants
end
end
end

View File

@@ -152,7 +152,7 @@ class CheckoutController < Spree::CheckoutController
end
def update_failed
current_order.updater.shipping_address_from_distributor
clear_ship_address
RestartCheckout.new(@order).call
respond_to do |format|
@@ -165,6 +165,15 @@ class CheckoutController < Spree::CheckoutController
end
end
# When we have a pickup Shipping Method,
# we clone the distributor address into ship_address before_save
# We don't want this data in the form, so we clear it out
def clear_ship_address
unless current_order.shipping_method.andand.require_ship_address
current_order.ship_address = Spree::Address.default
end
end
def load_order
@order = current_order
redirect_to(main_app.shop_path) && return unless @order && @order.checkout_allowed?

View File

@@ -1,9 +0,0 @@
module Spree
module Admin
class CountriesController < ResourceController
def collection
super.order(:name)
end
end
end
end

View File

@@ -3,6 +3,7 @@ require 'open_food_network/spree_api_key_loader'
Spree::Admin::OrdersController.class_eval do
include OpenFoodNetwork::SpreeApiKeyLoader
helper CheckoutHelper
before_filter :load_spree_api_key, only: :bulk_management
before_filter :load_order, only: %i[show edit update fire resend invoice print print_ticket]
before_filter :load_distribution_choices, only: [:new, :edit, :update]
@@ -26,10 +27,6 @@ Spree::Admin::OrdersController.class_eval do
# within the page then fetches the data it needs from Api::OrdersController
end
def bulk_management
load_spree_api_key
end
def edit
@order.shipments.map &:refresh_rates

View File

@@ -1,6 +1,5 @@
require 'open_food_network/spree_api_key_loader'
require 'open_food_network/referer_parser'
require 'open_food_network/permissions'
Spree::Admin::ProductsController.class_eval do
include OpenFoodNetwork::SpreeApiKeyLoader
@@ -55,7 +54,7 @@ Spree::Admin::ProductsController.class_eval do
product_set.collection.each { |p| authorize! :update, p }
if product_set.save
redirect_to main_app.bulk_products_api_products_path( bulk_index_query(params) )
redirect_to "/api/products/bulk_products?page=1;per_page=500;#{bulk_index_query(params)}"
else
if product_set.errors.present?
render json: { errors: product_set.errors }, status: :bad_request
@@ -110,7 +109,13 @@ Spree::Admin::ProductsController.class_eval do
end
def bulk_index_query(params)
params[:filters].to_h.merge(page: params[:page], per_page: params[:per_page])
params[:filters] ||= {}
params[:filters].reduce("") do |string, filter|
filter_db_column = filter[:property][:db_column]
filter_predicate = filter[:predicate][:predicate]
filter_value = filter[:value]
"#{string}q[#{filter_db_column}_#{filter_predicate}]=#{filter_value};"
end
end
def load_form_data

View File

@@ -126,8 +126,7 @@ Spree::Admin::ReportsController.class_eval do
@include_blank = I18n.t(:all)
# -- Build Report with Order Grouper
@report = OpenFoodNetwork::OrdersAndFulfillmentsReport.new(permissions,
params, render_content?)
@report = OpenFoodNetwork::OrdersAndFulfillmentsReport.new spree_current_user, params, render_content?
@table = order_grouper_table
csv_file_name = "#{params[:report_type]}_#{timestamp}.csv"

View File

@@ -1,6 +0,0 @@
module Spree
module Admin
class ShippingCategoriesController < ResourceController
end
end
end

View File

@@ -1,79 +0,0 @@
module Spree
module Admin
class ShippingMethodsController < ResourceController
before_filter :load_data, except: [:index]
before_filter :set_shipping_category, only: [:create, :update]
before_filter :set_zones, only: [:create, :update]
before_filter :load_hubs, only: [:new, :edit, :create, :update]
# Sort shipping methods by distributor name
def collection
collection = super
collection = collection.managed_by(spree_current_user).by_name
if params.key? :enterprise_id
distributor = Enterprise.find params[:enterprise_id]
collection = collection.for_distributor(distributor)
end
collection
end
def destroy
# Our reports are not adapted to soft deleted shipping_methods so here we prevent
# the deletion (even soft) of shipping_methods that are referenced in orders
if order = order_referenced_by_shipping_method
flash[:error] = I18n.t(:shipping_method_destroy_error, number: order.number)
redirect_to(collection_url) && return
end
@object.touch :deleted_at
flash[:success] = flash_message_for(@object, :successfully_removed)
respond_with(@object) do |format|
format.html { redirect_to collection_url }
end
end
private
def order_referenced_by_shipping_method
Order.joins(shipments: :shipping_rates)
.where( spree_shipping_rates: { shipping_method_id: @object } )
.first
end
def load_hubs
# rubocop:disable Style/TernaryParentheses
@hubs = Enterprise.managed_by(spree_current_user).is_distributor.sort_by! do |d|
[(@shipping_method.has_distributor? d) ? 0 : 1, d.name]
end
# rubocop:enable Style/TernaryParentheses
end
def set_shipping_category
return true if params["shipping_method"][:shipping_categories] == ""
@shipping_method.shipping_categories =
Spree::ShippingCategory.where(id: params["shipping_method"][:shipping_categories])
@shipping_method.save
params[:shipping_method].delete(:shipping_categories)
end
def set_zones
return true if params["shipping_method"][:zones] == ""
@shipping_method.zones = Spree::Zone.where(id: params["shipping_method"][:zones])
@shipping_method.save
params[:shipping_method].delete(:zones)
end
def location_after_save
edit_admin_shipping_method_path(@shipping_method)
end
def load_data
@available_zones = Zone.order(:name)
@calculators = ShippingMethod.calculators.sort_by(&:name)
end
end
end
end

View File

@@ -0,0 +1,42 @@
module Spree
module Admin
ShippingMethodsController.class_eval do
before_filter :do_not_destroy_referenced_shipping_methods, only: :destroy
before_filter :load_hubs, only: [:new, :edit, :create, :update]
# Sort shipping methods by distributor name
def collection
collection = super
collection = collection.managed_by(spree_current_user).by_name
if params.key? :enterprise_id
distributor = Enterprise.find params[:enterprise_id]
collection = collection.for_distributor(distributor)
end
collection
end
# Spree allows soft deletes of shipping_methods but our reports are not adapted to that
# Here we prevent the deletion (even soft) of shipping_methods that are referenced in orders
def do_not_destroy_referenced_shipping_methods
order = Order.joins(shipments: :shipping_rates)
.where( spree_shipping_rates: { shipping_method_id: @object } )
.first
return unless order
flash[:error] = I18n.t(:shipping_method_destroy_error, number: order.number)
redirect_to(collection_url) && return
end
private
def load_hubs
# rubocop:disable Style/TernaryParentheses
@hubs = Enterprise.managed_by(spree_current_user).is_distributor.sort_by! do |d|
[(@shipping_method.has_distributor? d) ? 0 : 1, d.name]
end
# rubocop:enable Style/TernaryParentheses
end
end
end
end

View File

@@ -1,29 +0,0 @@
module Spree
module Admin
class StatesController < ResourceController
belongs_to 'spree/country'
before_filter :load_data
def index
respond_with(@collection) do |format|
format.html
format.js { render partial: 'state_list' }
end
end
protected
def location_after_save
admin_country_states_url(@country)
end
def collection
super.order(:name)
end
def load_data
@countries = Country.order(:name)
end
end
end
end

View File

@@ -1,19 +0,0 @@
module Spree
module Admin
class TaxCategoriesController < ResourceController
def destroy
if @object.destroy
flash[:success] = flash_message_for(@object, :successfully_removed)
respond_with(@object) do |format|
format.html { redirect_to collection_url }
format.js { render partial: "spree/admin/shared/destroy" }
end
else
respond_with(@object) do |format|
format.html { redirect_to collection_url }
end
end
end
end
end
end

View File

@@ -1,26 +0,0 @@
module Spree
module Admin
class TaxRatesController < ResourceController
before_filter :load_data
update.after :update_after
create.after :create_after
private
def load_data
@available_zones = Zone.order(:name)
@available_categories = TaxCategory.order(:name)
@calculators = TaxRate.calculators.sort_by(&:name)
end
def update_after
Rails.cache.delete('vat_rates')
end
def create_after
Rails.cache.delete('vat_rates')
end
end
end
end

View File

@@ -1,15 +0,0 @@
module Spree
module Admin
class TaxSettingsController < Spree::Admin::BaseController
def update
Spree::Config.set(params[:preferences])
respond_to do |format|
format.html {
redirect_to edit_admin_tax_settings_path
}
end
end
end
end
end

View File

@@ -1,21 +0,0 @@
module Spree
module Admin
class TaxonomiesController < ResourceController
respond_to :json, :only => [:get_children]
def get_children
@taxons = Taxon.find(params[:parent_id]).children
end
private
def location_after_save
if @taxonomy.created_at == @taxonomy.updated_at
edit_admin_taxonomy_url(@taxonomy)
else
admin_taxonomies_url
end
end
end
end
end

View File

@@ -1,118 +0,0 @@
module Spree
module Admin
class TaxonsController < Spree::Admin::BaseController
respond_to :html, :json, :js
def search
if params[:ids]
@taxons = Spree::Taxon.where(id: params[:ids].split(','))
else
@taxons = Spree::Taxon.limit(20).search(name_cont: params[:q]).result
end
end
def create
@taxonomy = Taxonomy.find(params[:taxonomy_id])
@taxon = @taxonomy.taxons.build(params[:taxon])
if @taxon.save
respond_with(@taxon) do |format|
format.json { render json: @taxon.to_json }
end
else
flash[:error] = Spree.t('errors.messages.could_not_create_taxon')
respond_with(@taxon) do |format|
format.html do
if redirect_to @taxonomy
edit_admin_taxonomy_url(@taxonomy)
else
admin_taxonomies_url
end
end
end
end
end
def edit
@taxonomy = Taxonomy.find(params[:taxonomy_id])
@taxon = @taxonomy.taxons.find(params[:id])
@permalink_part = @taxon.permalink.split("/").last
end
def update
@taxonomy = Taxonomy.find(params[:taxonomy_id])
@taxon = @taxonomy.taxons.find(params[:id])
parent_id = params[:taxon][:parent_id]
new_position = params[:taxon][:position]
if parent_id || new_position # taxon is being moved
new_parent = parent_id.nil? ? @taxon.parent : Taxon.find(parent_id.to_i)
new_position = new_position.nil? ? -1 : new_position.to_i
# Bellow is a very complicated way of finding where in nested set we
# should actually move the taxon to achieve sane results,
# JS is giving us the desired position, which was awesome for previous setup,
# but now it's quite complicated to find where we should put it as we have
# to differenciate between moving to the same branch, up down and into
# first position.
new_siblings = new_parent.children
if new_position <= 0 && new_siblings.empty?
@taxon.move_to_child_of(new_parent)
elsif new_parent.id != @taxon.parent_id
if new_position.zero?
@taxon.move_to_left_of(new_siblings.first)
else
@taxon.move_to_right_of(new_siblings[new_position - 1])
end
elsif new_position < new_siblings.index(@taxon)
@taxon.move_to_left_of(new_siblings[new_position]) # we move up
else
@taxon.move_to_right_of(new_siblings[new_position - 1]) # we move down
end
# Reset legacy position, if any extensions still rely on it
new_parent.children.reload.each{ |t| t.update_column(:position, t.position) }
if parent_id
@taxon.reload
@taxon.set_permalink
@taxon.save!
@update_children = true
end
end
if params.key? "permalink_part"
parent_permalink = @taxon.permalink.split("/")[0...-1].join("/")
parent_permalink += "/" if parent_permalink.present?
params[:taxon][:permalink] = parent_permalink + params[:permalink_part]
end
# check if we need to rename child taxons if parent name or permalink changes
if params[:taxon][:name] != @taxon.name || params[:taxon][:permalink] != @taxon.permalink
@update_children = true
end
if @taxon.update_attributes(params[:taxon])
flash[:success] = flash_message_for(@taxon, :successfully_updated)
end
# rename child taxons
if @update_children
@taxon.descendants.each do |taxon|
taxon.reload
taxon.set_permalink
taxon.save!
end
end
respond_with(@taxon) do |format|
format.html { redirect_to edit_admin_taxonomy_url(@taxonomy) }
format.json { render json: @taxon.to_json }
end
end
def destroy
@taxon = Taxon.find(params[:id])
@taxon.destroy
respond_with(@taxon) { |format| format.json { render json: '' } }
end
end
end
end

View File

@@ -46,12 +46,7 @@ module Spree
@user.spree_roles = roles.reject(&:blank?).collect{ |r| Spree::Role.find(r) }
end
message = if new_email_unconfirmed?
Spree.t(:email_updated)
else
Spree.t(:account_updated)
end
flash.now[:success] = message
flash.now[:success] = Spree.t(:account_updated)
end
render :edit
end
@@ -131,10 +126,6 @@ module Spree
def load_roles
@roles = Spree::Role.scoped
end
def new_email_unconfirmed?
params[:user][:email] != @user.email
end
end
end
end

View File

@@ -1,26 +0,0 @@
module Spree
module Admin
class ZonesController < ResourceController
before_filter :load_data, except: [:index]
def new
@zone.zone_members.build
end
protected
def collection
params[:q] ||= {}
params[:q][:s] ||= "ascend_by_name"
@search = super.ransack(params[:q])
@zones = @search.result.page(params[:page]).per(Spree::Config[:orders_per_page])
end
def load_data
@countries = Country.order(:name)
@states = State.order(:name)
@zones = Zone.order(:name)
end
end
end
end

View File

@@ -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

View File

@@ -0,0 +1,13 @@
Spree::Api::LineItemsController.class_eval do
around_filter :apply_enterprise_fees_with_lock, only: :update
private
def apply_enterprise_fees_with_lock
authorize! :read, order
order.with_lock do
yield
order.update_distribution_charge!
end
end
end

View File

@@ -0,0 +1,86 @@
require 'open_food_network/permissions'
Spree::Api::ProductsController.class_eval do
def managed
authorize! :admin, Spree::Product
authorize! :read, Spree::Product
@products = product_scope.ransack(params[:q]).result.managed_by(current_api_user).page(params[:page]).per(params[:per_page])
respond_with(@products, default_template: :index)
end
# TODO: This should be named 'managed'. Is the action above used? Maybe we should remove it.
def bulk_products
@products = OpenFoodNetwork::Permissions.new(current_api_user).editable_products.
merge(product_scope).
order('created_at DESC').
ransack(params[:q]).result.
page(params[:page]).per(params[:per_page])
render_paged_products @products
end
def overridable
producers = OpenFoodNetwork::Permissions.new(current_api_user).
variant_override_producers.by_name
@products = paged_products_for_producers producers
render_paged_products @products
end
def soft_delete
authorize! :delete, Spree::Product
@product = find_product(params[:product_id])
authorize! :delete, @product
@product.destroy
respond_with(@product, status: 204)
end
# POST /api/products/:product_id/clone
#
def clone
authorize! :create, Spree::Product
original_product = find_product(params[:product_id])
authorize! :update, original_product
@product = original_product.duplicate
respond_with(@product, status: 201, default_template: :show)
end
private
# Copied and modified from Spree::Api::BaseController to allow
# enterprise users to access inactive products
def product_scope
if current_api_user.has_spree_role?("admin") || current_api_user.enterprises.present? # This line modified
scope = Spree::Product
if params[:show_deleted]
scope = scope.with_deleted
end
else
scope = Spree::Product.active
end
scope.includes(:master)
end
def paged_products_for_producers(producers)
Spree::Product.scoped.
merge(product_scope).
where(supplier_id: producers).
by_producer.by_name.
ransack(params[:q]).result.
page(params[:page]).per(params[:per_page])
end
def render_paged_products(products)
serializer = ActiveModel::ArraySerializer.new(
products,
each_serializer: Api::Admin::ProductSerializer
)
render text: { products: serializer, pages: products.num_pages }.to_json
end
end

View File

@@ -0,0 +1,47 @@
require 'open_food_network/scope_variant_to_hub'
Spree::Api::ShipmentsController.class_eval do
def create
variant = scoped_variant(params[:variant_id])
quantity = params[:quantity].to_i
@shipment = get_or_create_shipment(params[:stock_location_id])
@order.contents.add(variant, quantity, nil, @shipment)
@shipment.refresh_rates
@shipment.save!
respond_with(@shipment.reload, default_template: :show)
end
def add
variant = scoped_variant(params[:variant_id])
quantity = params[:quantity].to_i
@order.contents.add(variant, quantity, nil, @shipment)
respond_with(@shipment, default_template: :show)
end
def remove
variant = scoped_variant(params[:variant_id])
quantity = params[:quantity].to_i
@order.contents.remove(variant, quantity, @shipment)
@shipment.reload if @shipment.persisted?
respond_with(@shipment, default_template: :show)
end
private
def scoped_variant(variant_id)
variant = Spree::Variant.find(variant_id)
OpenFoodNetwork::ScopeVariantToHub.new(@order.distributor).scope(variant)
variant
end
def get_or_create_shipment(stock_location_id)
@order.shipment || @order.shipments.create(stock_location_id: stock_location_id)
end
end

View File

@@ -0,0 +1,9 @@
Spree::Api::VariantsController.class_eval do
def soft_delete
@variant = scope.find(params[:variant_id])
authorize! :delete, @variant
VariantDeleter.new.delete(@variant)
respond_with @variant, status: 204
end
end

View File

@@ -29,7 +29,7 @@ module Spree
def load_order
@order = current_order
redirect_to(main_app.cart_path) && return unless @order
redirect_to main_app.cart_path && return unless @order
if params[:state]
redirect_to checkout_state_path(@order.state) if @order.can_go_to_state?(params[:state])

View File

@@ -38,6 +38,10 @@ module Spree
end
end
def nav_bar
render partial: 'spree/shared/nav_bar'
end
private
def accurate_title

View File

@@ -1,9 +0,0 @@
module Spree
module Admin
module TaxonsHelper
def taxon_path(taxon)
taxon.ancestors.reverse.collect(&:name).join( " >> ")
end
end
end
end

View File

@@ -1,120 +0,0 @@
module Spree
module Api
module ApiHelpers
def required_fields_for(model)
required_fields = model._validators.select do |_field, validations|
validations.any? { |v| v.is_a?(ActiveModel::Validations::PresenceValidator) }
end.map(&:first) # get fields that are invalid
# Permalinks presence is validated, but are really automatically generated
# Therefore we shouldn't tell API clients that they MUST send one through
required_fields.map!(&:to_s).delete("permalink")
required_fields
end
def product_attributes
[:id, :name, :description, :price, :available_on, :permalink, :meta_description,
:meta_keywords, :shipping_category_id, :taxon_ids]
end
def product_property_attributes
[:id, :product_id, :property_id, :value, :property_name]
end
def variant_attributes
[:id, :name, :sku, :price, :weight, :height, :width, :depth,
:is_master, :cost_price, :permalink]
end
def image_attributes
[:id, :position, :attachment_content_type, :attachment_file_name,
:type, :attachment_updated_at, :attachment_width, :attachment_height, :alt]
end
def option_value_attributes
[:id, :name, :presentation, :option_type_name, :option_type_id]
end
def order_attributes
[:id, :number, :item_total, :total, :state, :adjustment_total, :user_id,
:created_at, :updated_at, :completed_at, :payment_total,
:shipment_state, :payment_state, :email, :special_instructions, :token]
end
def line_item_attributes
[:id, :quantity, :price, :variant_id]
end
def option_type_attributes
[:id, :name, :presentation, :position]
end
def payment_attributes
[:id, :source_type, :source_id, :amount, :payment_method_id,
:response_code, :state, :avs_response, :created_at, :updated_at]
end
def payment_method_attributes
[:id, :name, :description]
end
def shipment_attributes
[:id, :tracking, :number, :cost, :shipped_at, :state]
end
def taxonomy_attributes
[:id, :name]
end
def taxon_attributes
[:id, :name, :pretty_name, :permalink, :position, :parent_id, :taxonomy_id]
end
def inventory_unit_attributes
[:id, :lock_version, :state, :variant_id, :shipment_id, :return_authorization_id]
end
def return_authorization_attributes
[:id, :number, :state, :amount, :order_id, :reason, :created_at, :updated_at]
end
def country_attributes
[:id, :iso_name, :iso, :iso3, :name, :numcode]
end
def state_attributes
[:id, :name, :abbr, :country_id]
end
def adjustment_attributes
[:id, :source_type, :source_id, :adjustable_type, :adjustable_id, :originator_type,
:originator_id, :amount, :label, :mandatory, :locked, :eligible, :created_at, :updated_at]
end
def creditcard_attributes
[:id, :month, :year, :cc_type, :last_digits, :first_name, :last_name,
:gateway_customer_profile_id, :gateway_payment_profile_id]
end
def user_attributes
[:id, :email, :created_at, :updated_at]
end
def property_attributes
[:id, :name, :presentation]
end
def stock_location_attributes
[:id, :name, :address1, :address2, :city,
:state_id, :state_name, :country_id, :zipcode, :phone, :active]
end
def stock_movement_attributes
[:id, :quantity, :stock_item_id]
end
def stock_item_attributes
[:id, :count_on_hand, :backorderable, :lock_version, :stock_location_id, :variant_id]
end
end
end
end

View File

@@ -13,43 +13,8 @@ module Calculator
def compute(object)
line_items = line_items_for object
(total_weight(line_items) * preferred_per_kg).round(2)
end
private
def total_weight(line_items)
line_items.sum do |line_item|
line_item_weight(line_item)
end
end
def line_item_weight(line_item)
if line_item.final_weight_volume.present?
weight_per_final_weight_volume(line_item)
else
weight_per_variant(line_item) * line_item.quantity
end
end
def weight_per_variant(line_item)
line_item.variant.andand.weight || 0
end
def weight_per_final_weight_volume(line_item)
if line_item.variant.product.andand.variant_unit == 'weight'
# Divided by 1000 because grams is the base weight unit and the calculator price is per_kg
line_item.final_weight_volume / 1000.0
else
weight_per_variant(line_item) * quantity_implied_in_final_weight_volume(line_item)
end
end
# Example: 2 (line_item.quantity) wine glasses of 125mL (line_item.variant.unit_value)
# Customer ends up getting 350mL (line_item.final_weight_volume) of wine
# that represent 2.8 (quantity_implied_in_final_weight_volume) glasses of wine
def quantity_implied_in_final_weight_volume(line_item)
(1.0 * line_item.final_weight_volume / line_item.variant.unit_value).round(3)
total_weight = line_items.sum { |li| ((li.variant.andand.weight || 0) * li.quantity) }
total_weight * preferred_per_kg
end
end
end

View File

@@ -19,7 +19,7 @@ module OrderShipment
#
# @return [ShippingMethod]
def shipping_method
return if shipments.blank?
return if shipments.empty?
shipments.first.shipping_method
end

View File

@@ -25,21 +25,36 @@ module VariantStock
#
# @return [Float|Integer]
def on_hand
warn_deprecation(__method__, '#total_on_hand')
total_on_hand
end
# Sets the stock level of the variant.
# This will only work if there is a stock item for the variant.
# This will only work if `track_inventory_levels` config is set
# and if there is a stock item for the variant.
#
# @raise [StandardError] when the variant has no stock item
# @raise [StandardError] when the track_inventory_levels config key is not set
# and when the variant has no stock item
def on_hand=(new_level)
warn_deprecation(__method__, '#total_on_hand')
error = 'Cannot set on_hand value when Spree::Config[:track_inventory_levels] is false'
raise error unless Spree::Config.track_inventory_levels
raise_error_if_no_stock_item_available
overwrite_stock_levels(new_level)
end
# Checks whether this variant is produced on demand.
#
# In Spree 2.0 this attribute is removed in favour of
# track_inventory_levels only. It was initially introduced in
# https://github.com/openfoodfoundation/spree/commit/20b5ad9835dca7f41a40ad16c7b45f987eea6dcc
def on_demand
warn_deprecation(__method__, 'StockItem#backorderable?')
# A variant that has not been saved yet, doesn't have a stock item
# This provides a default value for variant.on_demand using Spree::StockLocation.backorderable_default
return Spree::StockLocation.first.backorderable_default if stock_items.empty?
@@ -54,6 +69,8 @@ module VariantStock
#
# @raise [StandardError] when the variant has no stock item yet
def on_demand=(new_value)
warn_deprecation(__method__, 'StockItem#backorderable=')
raise_error_if_no_stock_item_available
# There should be only one at the default stock location.
@@ -72,6 +89,8 @@ module VariantStock
# Here we depend only on variant.total_on_hand and variant.on_demand.
# This way, variant_overrides only need to override variant.total_on_hand and variant.on_demand.
def can_supply?(quantity)
return true unless Spree::Config[:track_inventory_levels]
on_demand || total_on_hand >= quantity
end
@@ -81,13 +100,13 @@ module VariantStock
# Here we depend only on variant.total_on_hand and variant.on_demand.
# This way, variant_overrides only need to override variant.total_on_hand and variant.on_demand.
def fill_status(quantity)
on_hand = if total_on_hand >= quantity || on_demand
quantity
else
[0, total_on_hand].max
end
backordered = 0
if on_hand >= quantity
on_hand = quantity
backordered = 0
else
on_hand = [0, total_on_hand].max
backordered = on_demand ? (quantity - on_hand) : 0
end
[on_hand, backordered]
end
@@ -98,9 +117,6 @@ module VariantStock
def move(quantity, originator = nil)
raise_error_if_no_stock_item_available
# Don't change variant stock if variant is on_demand
return if on_demand
# Creates a stock movement: it updates stock_item.count_on_hand and fills backorders
#
# This is the original Spree::StockLocation#move,
@@ -139,4 +155,11 @@ module VariantStock
def stock_item
stock_items.first
end
def warn_deprecation(method_name, new_method_name)
ActiveSupport::Deprecation.warn(
"`##{method_name}` is deprecated and will be removed. " \
"Please use `#{new_method_name}` instead."
)
end
end

View File

@@ -90,6 +90,7 @@ class Enterprise < ActiveRecord::Base
validates :permalink, uniqueness: true, presence: true
validate :shopfront_taxons
validate :enforce_ownership_limit, if: lambda { owner_id_changed? && !owner_id.nil? }
validates :description, length: { maximum: 255 }
before_validation :initialize_permalink, if: lambda { permalink.nil? }
before_validation :ensure_owner_is_manager, if: lambda { owner_id_changed? && !owner_id.nil? }

View File

@@ -1,22 +1,9 @@
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 }
end
private
def check_price
if currency.nil?
self.currency = Spree::Config[:currency]
end
end
def refresh_products_cache
variant.andand.refresh_products_cache
end

View File

@@ -56,12 +56,6 @@ Spree::Product.class_eval do
ON (o_order_cycles.id = o_exchanges.order_cycle_id)")
}
scope :imported_on, lambda { |import_date|
import_date = Time.zone.parse import_date if import_date.is_a? String
import_date = import_date.to_date
joins(:variants).merge(Spree::Variant.where(import_date: import_date.beginning_of_day..import_date.end_of_day))
}
scope :with_order_cycles_inner, -> {
joins(variants_including_master: { exchanges: :order_cycle })
}

View File

@@ -32,7 +32,7 @@ module Spree
# NOTE: This is an override of spree's method, needed to allow orders
# without line items (ie. user invoices) to not have inventory units
def require_inventory
return false unless line_items.count > 0 # This line altered
return false unless Spree::Config[:track_inventory_levels] && line_items.count > 0 # This line altered
order.completed? && !order.canceled?
end
end

View File

@@ -136,16 +136,6 @@ module Spree
has_spree_role?('admin')
end
def generate_spree_api_key!
self.spree_api_key = SecureRandom.hex(24)
save!
end
def clear_spree_api_key!
self.spree_api_key = nil
save!
end
protected
def password_required?

View File

@@ -91,11 +91,6 @@ Spree::Variant.class_eval do
can_supply?(quantity)
end
# Allow variant to access associated soft-deleted prices.
def default_price
Spree::Price.unscoped { super }
end
def price_with_fees(distributor, order_cycle)
price + fees_for(distributor, order_cycle)
end

View File

@@ -10,11 +10,6 @@ class SubscriptionLineItem < ActiveRecord::Base
(price_estimate || 0) * (quantity || 0)
end
# Ensure SubscriptionLineItem always has access to soft-deleted Variant attribute
def variant
Spree::Variant.unscoped { super }
end
# Used to calculators to estimate fees
alias_method :amount, :total_estimate

View File

@@ -0,0 +1,6 @@
Deface::Override.new(virtual_path: "spree/layouts/admin",
name: "user_admin_tabs",
insert_bottom: "[data-hook='admin_tabs'], #admin_tabs[data-hook]",
partial: "spree/admin/users_tab",
disabled: false,
original: '031652cf5a054796022506622082ab6d2693699f')

View File

@@ -0,0 +1,5 @@
Deface::Override.new(virtual_path: "spree/layouts/admin",
name: "auth_admin_login_navigation_bar",
insert_top: "[data-hook='admin_login_navigation_bar'], #admin_login_navigation_bar[data-hook]",
partial: "spree/layouts/admin/login_nav",
original: '841227d0aedf7909d62237d8778df99100087715')

View File

@@ -0,0 +1,6 @@
Deface::Override.new(virtual_path: "spree/shared/_nav_bar",
name: "auth_shared_login_bar",
insert_before: "li#search-bar",
partial: "spree/shared/login_bar",
disabled: false,
original: 'eb3fa668cd98b6a1c75c36420ef1b238a1fc55ac')

View File

@@ -0,0 +1,3 @@
Deface::Override.new(virtual_path: "spree/shared/_nav_bar",
remove: "#search-bar",
name: "search_removal")

View File

@@ -0,0 +1,3 @@
Deface::Override.new(virtual_path: "spree/shared/_sidebar",
remove: "#sidebar",
name: "sidebar_removal")

View File

@@ -0,0 +1,5 @@
Deface::Override.new(virtual_path: "spree/layouts/admin",
insert_bottom: "[data-hook='admin_inside_head']",
partial: "layouts/auth_token_script",
name: "set_auth_token_in_backend",
original: '6bc2c5de1c8f7542d033548557437c9fe4b3ba02')

View File

@@ -0,0 +1,5 @@
Deface::Override.new(virtual_path: "spree/layouts/spree_application",
insert_bottom: "[data-hook='inside_head']",
partial: "layouts/auth_token_script",
name: "set_auth_token_in_frontend",
original: '5659ac7dbf6ac6469907b005b85285b894677815')

View File

@@ -0,0 +1,6 @@
/ replace_contents "td.property_name"
- if spree_current_user.admin?
= f.text_field :property_name, :class => 'autocomplete'
- else
= f.select :property_name, @properties, { :include_blank => true }, { class: 'select2 fullwidth' }

View File

@@ -0,0 +1,26 @@
/ insert_after 'table.index.sortable'
=f.check_box :inherits_properties
=f.label :inherits_properties, t(".inherits_properties_checkbox_hint", supplier: @product.supplier.name)
%br
%br
#inherited_properties
%table.index
%thead
%tr{"data-hook" => "producer_properties_header"}
%th= t('admin.products.properties.inherited_property')
%th= t('admin.description')
%th.actions
%tbody#producer_properties{"data-hook" => ""}
- @product.supplier.producer_properties.each do |producer_property|
%tr
%td= producer_property.property.presentation
%td= producer_property.value
%td.actions
:coffee
$(document).ready ->
$("#inherited_properties").toggle $("input#product_inherits_properties").is(':checked')
$("input#product_inherits_properties").change ->
$("#inherited_properties").toggle $(this).is(':checked')

View File

@@ -0,0 +1,5 @@
/ replace "tr[data-hook='product_properties_header']"
%tr{"data-hook" => "product_properties_header"}
%th= t('admin.products.properties.property_name')
%th= t('admin.description')
%th.actions

View File

@@ -0,0 +1,5 @@
<!-- surround_contents 'body' -->
<div <%= yield(:app_wrapper_attrs).strip.html_safe %>>
<%= render_original %>
</div>

View File

@@ -0,0 +1,19 @@
/ replace "footer"
-#= render partial: "shared/footer"
%footer
%hr/
-#.row
-#.seven.columns.offset-by-three
-#%center
-#%h1 What is open food network?
-#%p
-#%i
-#Open food network is an online space in which people can connect with, trade and sell food directly with
-#Australian farmers. We aim to reconnect people with their food.
-#%hr/
-#.row
-#.five.columns.secondary.offset-by-seven.secondary
-#All rights reserved. &copy; 2013 Open Food Foundation

View File

@@ -0,0 +1,8 @@
/ replace "#logo"
%figure#logo.columns.eight
- if current_distributor
%h1= link_to current_distributor.name, main_app.enterprise_shop_path(current_distributor)
.change-location= link_to 'Change Location', root_path
- else
%h1= link_to "OPEN FOOD NETWORK", root_path

View File

@@ -0,0 +1,2 @@
/ set_attributes '#top-nav-bar'
/ attributes({:class => "columns eight"})

View File

@@ -0,0 +1,5 @@
/ insert_before "#password-credentials"
- if @unconfirmed_email
%p.alert-box
= t('spree.users.show.unconfirmed_email', unconfirmed_email: @unconfirmed_email)

View File

@@ -1,13 +0,0 @@
module Api
class ShipmentSerializer < ActiveModel::Serializer
attributes :id, :tracking, :number, :order_id, :cost, :shipped_at, :stock_location_name, :state
def order_id
object.order.number
end
def stock_location_name
object.stock_location.name
end
end
end

View File

@@ -1,5 +0,0 @@
module Api
class TaxonJstreeAttributeSerializer < ActiveModel::Serializer
attributes :id, :name
end
end

View File

@@ -1,18 +0,0 @@
module Api
class TaxonJstreeSerializer < ActiveModel::Serializer
attributes :data, :state
has_one :attr, serializer: TaxonJstreeAttributeSerializer
def data
object.name
end
def attr
object
end
def state
"closed"
end
end
end

View File

@@ -2,7 +2,7 @@ class Api::TaxonSerializer < ActiveModel::Serializer
cached
delegate :cache_key, to: :object
attributes :id, :name, :permalink, :icon, :pretty_name, :position, :parent_id, :taxonomy_id
attributes :id, :name, :permalink, :icon
def icon
object.icon(:original)

View File

@@ -1,8 +1,6 @@
class Api::VariantSerializer < ActiveModel::Serializer
attributes :id, :is_master, :product_name, :sku
attributes :options_text, :unit_value, :unit_description, :unit_to_display
attributes :display_as, :display_name, :name_to_display
attributes :price, :on_demand, :on_hand, :fees, :price_with_fees
attributes :id, :is_master, :on_hand, :name_to_display, :unit_to_display, :unit_value
attributes :options_text, :on_demand, :price, :fees, :price_with_fees, :product_name
attributes :tag_list
delegate :price, to: :object

View File

@@ -73,9 +73,7 @@ class EmbeddedPageService
def current_referer
return if @request.referer.blank?
uri = URI(@request.referer)
return if uri.host.blank?
uri.host.downcase
URI(@request.referer).host.downcase
end
def current_referer_without_www

View File

@@ -1,75 +1,41 @@
# 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.
# Finds valid products distributed by a particular distributor in an order cycle
#
# If a product without variants is added to an order cycle, and then some
# variants are added to that product, but not the order cycle, then the master
# variant should not available for customers to purchase. This class filters
# out such products so that the customer cannot purchase them.
class OrderCycleDistributedProducts
def initialize(distributor, order_cycle)
@distributor = distributor
def initialize(order_cycle, distributor)
@order_cycle = order_cycle
@distributor = distributor
end
def products_relation
Spree::Product.where(id: stocked_products)
end
# Returns an ActiveRecord relation without invalid products. Check
# #valid_products_distributed_by for details
#
# @return [ActiveRecord::Relation<Spree::Product>]
def relation
variants = order_cycle.variants_distributed_by(distributor)
products = variants.map(&:product).uniq
def variants_relation
@order_cycle.
variants_distributed_by(@distributor).
merge(stocked_variants_and_overrides)
valid_products = products.reject do |product|
product_has_only_obsolete_master_in_distribution?(product, variants)
end
product_ids = valid_products.map(&:id)
Spree::Product.where(id: product_ids)
end
private
def stocked_products
@order_cycle.
variants_distributed_by(@distributor).
merge(stocked_variants_and_overrides).
select("DISTINCT spree_variants.product_id")
end
attr_reader :order_cycle, :distributor
def stocked_variants_and_overrides
Spree::Variant.
joins("LEFT OUTER JOIN variant_overrides ON variant_overrides.variant_id = spree_variants.id
AND variant_overrides.hub_id = #{@distributor.id}").
joins(:stock_items).
where(query_stock_with_overrides)
end
def query_stock_with_overrides
"( #{variant_not_overriden} AND ( #{variant_on_demand} OR #{variant_in_stock} ) )
OR ( #{variant_overriden} AND ( #{override_on_demand} OR #{override_in_stock} ) )
OR ( #{variant_overriden} AND ( #{override_on_demand_null} AND #{variant_on_demand} ) )
OR ( #{variant_overriden} AND ( #{override_on_demand_null}
AND #{variant_not_on_demand} AND #{variant_in_stock} ) )"
end
def variant_not_overriden
"variant_overrides.id IS NULL"
end
def variant_overriden
"variant_overrides.id IS NOT NULL"
end
def variant_in_stock
"spree_stock_items.count_on_hand > 0"
end
def variant_on_demand
"spree_stock_items.backorderable IS TRUE"
end
def variant_not_on_demand
"spree_stock_items.backorderable IS FALSE"
end
def override_on_demand
"variant_overrides.on_demand IS TRUE"
end
def override_in_stock
"variant_overrides.count_on_hand > 0"
end
def override_on_demand_null
"variant_overrides.on_demand IS NULL"
# If a product without variants is added to an order cycle, and then some variants are added
# to that product, but not the order cycle, then the master variant should not available for
# customers to purchase.
def product_has_only_obsolete_master_in_distribution?(product, distributed_variants)
product.has_variants? &&
distributed_variants.include?(product.master) &&
(product.variants & distributed_variants).empty?
end
end

View File

@@ -9,8 +9,8 @@ class SubscriptionValidator
attr_reader :subscription
validates :shop, :customer, :schedule, :shipping_method, :payment_method, presence: true
validates :bill_address, :ship_address, :begins_at, presence: true
validates_presence_of :shop, :customer, :schedule, :shipping_method, :payment_method
validates_presence_of :bill_address, :ship_address, :begins_at
validate :shipping_method_allowed?
validate :payment_method_allowed?
validate :payment_method_type_allowed?

View File

@@ -2,8 +2,8 @@
%h1.page-title
=t :customers
- content_for :main_ng_app_name do
= "admin.customers"
- content_for :app_wrapper_attrs do
= "ng-app='admin.customers'"
- content_for :page_actions do
%li

View File

@@ -4,8 +4,8 @@
= t('.editing')
= @enterprise.name
- content_for :main_ng_app_name do
= "admin.enterprises"
- content_for :app_wrapper_attrs do
= "ng-app='admin.enterprises'"
- content_for :page_actions do
%li= select :enterprise, :id, options_for_select(editable_enterprises.collect {|e| [e.name, e.id, {:'data-url' => "#{main_app.edit_admin_enterprise_path(e.permalink)}", :'ng-selected' => "selected==#{e.id}"}]}, @enterprise.id ), {}, {:'enterprise-switcher' => '', 'data-initial' => "#{@enterprise.id}", :'ng-init' => "selected='#{@enterprise.id}'", :'ng-model' => 'selected', :id => 'enterprise_switcher', :class => 'select2'}

View File

@@ -1,8 +1,8 @@
- content_for :page_title do
= t('.title')
- content_for :main_ng_app_name do
= "admin.enterprises"
- content_for :app_wrapper_attrs do
= "ng-app='admin.enterprises'"
- content_for :page_actions do
= render 'admin/shared/user_guide_link'

View File

@@ -6,8 +6,8 @@
- content_for :page_actions do
%li= button_link_to t('.back_link'), main_app.admin_enterprises_path, icon: 'icon-arrow-left'
- content_for :main_ng_app_name do
= "admin.enterprises"
- content_for :app_wrapper_attrs do
= "ng-app='admin.enterprises'"
= admin_inject_available_countries(module: 'admin.enterprises')
= admin_inject_json "admin.enterprises", "defaultCountryID", Spree::Config[:default_country_id]

View File

@@ -1,8 +1,8 @@
= content_for :page_title do
= t :admin_order_cycles
- content_for :main_ng_app_name do
= "admin.orderCycles"
- content_for :app_wrapper_attrs do
= "ng-app='admin.orderCycles'"
= content_for :page_actions do
- if subscriptions_enabled?

View File

@@ -1,8 +1,8 @@
- content_for :page_title do
= t('admin.subscriptions.subscriptions')
- content_for :main_ng_app_name do
= "admin.subscriptions"
- content_for :app_wrapper_attrs do
= "ng-app='admin.subscriptions'"
- content_for :page_actions do
%li

View File

@@ -0,0 +1,3 @@
object @enterprise
attributes :id, :name

View File

@@ -146,3 +146,13 @@
= t '.footer_data_text_without_privacy_policy_html', {cookies_policy: cookies_policy_link.html_safe }
.medium-2.columns.text-center
/ Placeholder
-if ENV['SKYLIGHT_PUBLIC_DASHBOARD_URL'].present?
.row
.small-12.medium-8.medium-offset-2.columns.text-center
%hr.hr-light
%br
.row
.small-12.medium-8.medium-offset-2.columns.text-center
.text.small
= t '.footer_skylight_dashboard_html', {dashboard: link_to('Skylight', ENV['SKYLIGHT_PUBLIC_DASHBOARD_URL'], target: "_blank")}

View File

@@ -0,0 +1,2 @@
- if can? :admin, Spree::User
= tab(:users, url: spree.admin_users_path, icon: 'icon-user')

View File

@@ -2,12 +2,12 @@
- content_for :page_title do
%i.icon-arrow-right
= t(:adjustments)
= Spree.t(:adjustments)
- content_for :page_actions do
%li= button_link_to t(:new_adjustment), new_admin_order_adjustment_url(@order), :icon => 'icon-plus'
%li= button_link_to t(:back_to_orders_list), admin_orders_path, :icon => 'icon-arrow-left'
%li= button_link_to Spree.t(:new_adjustment), new_admin_order_adjustment_url(@order), :icon => 'icon-plus'
%li= button_link_to Spree.t(:back_to_orders_list), admin_orders_path, :icon => 'icon-arrow-left'
= render :partial => 'adjustments_table'
= button_link_to t(:continue), @order.cart? ? new_admin_order_payment_url(@order) : admin_orders_url, :icon => 'icon-arrow-right'
= button_link_to Spree.t(:continue), @order.cart? ? new_admin_order_payment_url(@order) : admin_orders_url, :icon => 'icon-arrow-right'

View File

@@ -1,14 +0,0 @@
.row
.alpha.four.columns
.field
= f.label :name, t("spree.name")
= f.text_field :name, class: 'fullwidth'
.four.columns
.field
= f.label :iso_name, t("spree.iso_name")
= f.text_field :iso_name, class: 'fullwidth'
.omega.four.columns
.field.checkbox
%label
= f.check_box :states_required
= t("spree.states_required")

View File

@@ -1,16 +0,0 @@
= render partial: 'spree/admin/shared/configuration_menu'
- content_for :page_title do
= t("spree.editing_country")
- content_for :page_actions do
%li
= button_link_to t("spree.back_to_countries_list"), spree.admin_countries_path, icon: 'icon-arrow-left'
= render partial: 'spree/shared/error_messages', locals: { target: @country }
= form_for [:admin, @country] do |f|
%fieldset.no-border-top
= render partial: 'form', locals: { f: f }
.clear
= render partial: 'spree/admin/shared/edit_resource_links'

View File

@@ -1,27 +0,0 @@
= render partial: 'spree/admin/shared/configuration_menu'
- content_for :page_title do
= t("spree.listing_countries")
%table#listing_countries.index
%colgroup
%col{style: "width: 35%"}/
%col{style: "width: 35%"}/
%col{style: "width: 20%"}/
%col{style: "width: 10%"}/
%thead
%tr
%th= t("spree.country_name")
%th= t("spree.iso_name")
%th= t("spree.states_required")
%th.actions
%tbody
- @countries.each do |country|
- tr_class = cycle('odd', 'even')
- tr_id = spree_dom_id(country)
%tr{class: tr_class, id: tr_id}
%td= country.name
%td= country.iso_name
%td.align-center= country.states_required.to_s.titleize
%td.actions
= link_to_edit country, no_text: true

View File

@@ -1,5 +1,5 @@
.per-page{'ng-show' => '!RequestMonitor.loading && orders.length > 0'}
%input.per-page-select.ofn-select2{type: 'number', data: 'per_page_options', 'min-search' => 999, 'ng-model' => 'per_page', 'ng-change' => 'fetchResults()'}
%input.per-page-select.ofn-select2{type: 'number', data: 'per_page_options', 'ng-model' => 'per_page', 'ng-change' => 'fetchResults()'}
%span.per-page-feedback
{{ 'spree.admin.orders.index.results_found' | t:{number: pagination.results} }}

View File

@@ -8,7 +8,7 @@
= Spree.t("shipment_states.#{shipment.state}")
- if shipment.ready? and can? :update, shipment
= "-"
= link_to t(:ship), '#', :class => 'ship button icon-arrow-right', :data => { 'shipment-number' => shipment.number }
= link_to 'ship', '#', :class => 'ship button icon-arrow-right', :data => { 'shipment-number' => shipment.number }
%table.stock-contents.index{ "data-hook" => "stock-contents" }
%colgroup

View File

@@ -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 #{state.humanize.downcase}"
- unless shipment.shipped?
%td.item-qty-edit.hidden
= number_field_tag :quantity, item.quantity, :min => 0, :class => "line_item_quantity", :size => 5

View File

@@ -1,5 +1,5 @@
- content_for :main_ng_app_name do
= "admin.lineItems"
- content_for :app_wrapper_attrs do
= "ng-app='admin.lineItems'"
- content_for :page_title do
%h1.page-title

View File

@@ -13,8 +13,8 @@
#select-customer{"data-hook" => ""}
%fieldset.no-border-bottom
%legend{:align => "center"}= Spree.t(:customer_search)
- content_for :main_ng_app_name do
= "admin.orders"
- content_for :app_wrapper_attrs do
= 'ng-app=admin.orders'
= hidden_field_tag :customer_search_override, nil, distributor_id: @order.distributor_id, :class => 'fullwidth title customer-search-override'
= render :partial => "spree/admin/orders/customer_details/autocomplete", :formats => :js

View File

@@ -6,7 +6,7 @@
%li= button_link_to Spree.t(:resend), resend_admin_order_url(@order), :method => :post, :icon => 'icon-email'
%li.links-dropdown#links-dropdown{ links: order_links(@order).to_json }
- if can?(:admin, Spree::Order)
%li= button_link_to t(:back_to_orders_list), admin_orders_path, :icon => 'icon-arrow-left'
%li= button_link_to Spree.t(:back_to_orders_list), admin_orders_path, :icon => 'icon-arrow-left'
= render :partial => 'spree/admin/shared/order_tabs', :locals => { :current => 'Order Details' }

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