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
101 changed files with 1145 additions and 3693 deletions

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

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

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.0)
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)
@@ -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)
@@ -485,7 +485,7 @@ 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)
@@ -739,7 +739,7 @@ GEM
nokogiri (~> 1.6)
rubyzip (~> 1.0)
selenium-webdriver (~> 3.0)
webmock (3.7.1)
webmock (3.6.2)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
@@ -808,7 +808,6 @@ DEPENDENCIES
jquery-rails (= 3.0.4)
json_spec (~> 1.1.4)
jwt (~> 2.2)
kaminari (~> 0.14.1)
knapsack
letter_opener (>= 1.4.1)
listen (= 3.0.8)

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

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

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

@@ -1,108 +0,0 @@
require 'open_food_network/scope_variant_to_hub'
module Spree
module Api
class ShipmentsController < Spree::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!
respond_with(@shipment.reload, default_template: :show)
end
def update
authorize! :read, 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
@shipment.reload
respond_with(@shipment, default_template: :show)
end
def ready
authorize! :read, Shipment
unless @shipment.ready?
if @shipment.can_ready?
@shipment.ready!
else
render "spree/api/shipments/cannot_ready_shipment", status: :unprocessable_entity
return
end
end
respond_with(@shipment, default_template: :show)
end
def ship
authorize! :read, Shipment
unless @shipment.shipped?
@shipment.ship!
end
respond_with(@shipment, 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 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
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

@@ -1,75 +0,0 @@
module Spree
module Api
class TaxonsController < Spree::Api::BaseController
respond_to :json
def index
if taxonomy
@taxons = taxonomy.root.children
else
if params[:ids]
@taxons = Taxon.where(id: params[:ids].split(","))
else
@taxons = Taxon.ransack(params[:q]).result
end
end
respond_with(@taxons)
end
def show
@taxon = taxon
respond_with(@taxon)
end
def jstree
show
end
def create
authorize! :create, Taxon
@taxon = Taxon.new(params[:taxon])
@taxon.taxonomy_id = params[:taxonomy_id]
taxonomy = 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
respond_with(@taxon, status: 201, default_template: :show)
else
invalid_resource!(@taxon)
end
end
def update
authorize! :update, Taxon
if taxon.update_attributes(params[:taxon])
respond_with(taxon, status: 200, default_template: :show)
else
invalid_resource!(taxon)
end
end
def destroy
authorize! :delete, Taxon
taxon.destroy
respond_with(taxon, status: 204)
end
private
def taxonomy
return if params[:taxonomy_id].blank?
@taxonomy ||= Taxonomy.find(params[:taxonomy_id])
end
def taxon
@taxon ||= taxonomy.taxons.find(params[:id])
end
end
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

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

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

@@ -4,12 +4,6 @@ module Spree
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

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

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

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

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

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

@@ -1,14 +0,0 @@
%tr.product_property.fields{"data-hook" => "product_property", id: "spree_#{dom_id(f.object)}"}
%td.no-border
%span.handle
= f.hidden_field :id
%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' }
%td.value
= f.text_field :value, class: 'autocomplete'
%td.actions
- if f.object.persisted?
= link_to_delete f.object, no_text: true

View File

@@ -1,71 +0,0 @@
= render partial: 'spree/admin/shared/product_sub_menu'
= render partial: 'spree/admin/shared/product_tabs', locals: { current: 'Product Properties' }
= render partial: 'spree/shared/error_messages', locals: { target: @product }
- content_for :page_actions do
%ul.tollbar.inline-menu
%li
= link_to_add_fields Spree.t(:add_product_properties), 'tbody#product_properties', class: 'icon-plus button'
%li
%span#new_ptype_link
= link_to Spree.t(:select_from_prototype), available_admin_prototypes_url, remote: true, 'data-update' => 'prototypes', class: 'button icon-copy'
= form_for @product, url: admin_product_url(@product), method: :put do |f|
%fieldset.no-border-top
.add_product_properties
#prototypes
= image_tag 'select2-spinner.gif', plugin: 'spree', style: 'display:none;', id: 'busy_indicator'
%table.index.sortable{"data-sortable-link" => update_positions_admin_product_product_properties_url}
%thead
%tr
%th.no-border
%th= t('admin.products.properties.property_name')
%th= t('admin.description')
%th.actions
%tbody#product_properties
= f.fields_for :product_properties do |pp_form|
= render partial: 'product_property_fields', locals: { f: pp_form }
= f.check_box :inherits_properties
= f.label :inherits_properties, t(".inherits_properties_checkbox_hint", supplier: @product.supplier.name)
%br
%br
#inherited_properties
%table.index
%thead
%tr
%th= t('admin.products.properties.inherited_property')
%th= t('admin.description')
%th.actions
%tbody#producer_properties
- @product.supplier.producer_properties.each do |producer_property|
%tr
%td= producer_property.property.presentation
%td= producer_property.value
%td.actions
= render partial: 'spree/admin/shared/edit_resource_links'
= hidden_field_tag 'clear_product_properties', 'true'
:coffee
$(document).ready ->
$("#inherited_properties").toggle $("input#product_inherits_properties").is(':checked')
$("input#product_inherits_properties").change ->
$("#inherited_properties").toggle $(this).is(':checked')
:javascript
var properties = #{raw(@properties.to_json)};
$("#product_properties input.autocomplete").live("keydown", function(){
already_auto_completed = $(this).is('ac_input');
if (!already_auto_completed) {
$(this).autocomplete({source: properties});
$(this).focus();
}
});

View File

@@ -4,9 +4,7 @@
%div{ ng: { app: 'ofn.admin', controller: 'AdminProductEditCtrl', init: 'initialise()' } }
= render 'spree/admin/products/index/filters'
%hr.divider.sixteen.columns.alpha.omega
= render 'spree/admin/products/index/actions'
= render 'spree/admin/products/index/indicators'
= render 'spree/admin/products/index/products'
%div{'ng-show' => "!RequestMonitor.loading && products.length > 0" }
= render partial: 'admin/shared/angular_pagination'

View File

@@ -1,11 +1,3 @@
.controls.sixteen.columns.alpha{ 'ng-hide' => 'RequestMonitor.loading || products.length == 0' }
.ten.columns.alpha.index-controls
.per-page{'ng-show' => '!RequestMonitor.loading && products.length > 0'}
%input.per-page-select.ofn-select2{type: 'number', data: 'per_page_options', 'min-search' => 999, 'ng-model' => 'per_page', 'ng-change' => 'fetchProducts()'}
%span.per-page-feedback
{{ 'spree.admin.orders.index.results_found' | t:{number: pagination.results} }}
{{ 'spree.admin.orders.index.viewing' | t:{start: ((pagination.page -1) * pagination.per_page) +1, end: ((pagination.page -1) * pagination.per_page) + products.length} }}
.six.columns.omega
%columns-dropdown{ action: "#{controller_name}_#{action_name}" }
.controls.sixteen.columns.alpha{ 'ng-hide' => 'loading || products.length == 0' }
.thirteen.columns
%columns-dropdown{ action: "#{controller_name}_#{action_name}" }

View File

@@ -1,35 +1,25 @@
%fieldset
%legend{align: 'center'}= t(:search)
.filters.sixteen.columns.alpha.omega
.quick_search.three.columns.alpha
%label{ for: 'quick_filter' }
%br
%input.quick-search.fullwidth{ ng: {model: 'query'}, name: "quick_filter", type: 'text', placeholder: t('admin.quick_search') }
.one.columns &nbsp;
.filter_select.three.columns
%label{ for: 'producer_filter' }= t 'producer'
%br
%select.fullwidth{ id: 'producer_filter', 'ofn-select2-min-search' => 5, ng: {model: 'producerFilter', options: 'producer.id as producer.name for producer in filterProducers'} }
.filter_select.three.columns
%label{ for: 'category_filter' }= t 'category'
%br
%select.fullwidth{ id: 'category_filter', 'ofn-select2-min-search' => 5, ng: {model: 'categoryFilter', options: 'taxon.id as taxon.name for taxon in filterTaxons'} }
.filter_select.three.columns
%label{ for: 'import_filter' } Import Date
%br
%select.fullwidth{ id: 'import_date_filter', 'ofn-select2-min-search' => 5, ng: {model: 'importDateFilter', init: "importDates = #{@import_dates}; showLatestImport = #{@show_latest_import}"}}
%option{value: "{{date.id}}", ng: {repeat: "date in importDates track by date.id" }}
{{date.name}}
.filters.sixteen.columns.alpha.omega
.quick_search.three.columns.alpha
%label{ for: 'quick_filter' }
%br
%input.quick-search.fullwidth{ ng: {model: 'query'}, name: "quick_filter", type: 'text', placeholder: t('admin.quick_search') }
.one.columns &nbsp;
.filter_select.three.columns
%label{ for: 'producer_filter' }= t 'producer'
%br
%select.fullwidth{ id: 'producer_filter', 'ofn-select2-min-search' => 5, ng: {model: 'producerFilter', options: 'producer.id as producer.name for producer in producers'} }
.filter_select.three.columns
%label{ for: 'category_filter' }= t 'category'
%br
%select.fullwidth{ id: 'category_filter', 'ofn-select2-min-search' => 5, ng: {model: 'categoryFilter', options: 'taxon.id as taxon.name for taxon in taxons'} }
.filter_select.three.columns
%label{ for: 'import_filter' } Import Date
%br
%select.fullwidth{ id: 'import_date_filter', 'ofn-select2-min-search' => 5, ng: {model: 'importDateFilter', init: "importDates = #{@import_dates}; showLatestImport = #{@show_latest_import}"}}
%option{value: "{{date.id}}", ng: {repeat: "date in importDates" }}
{{date.name}}
.filter_clear.three.columns.omega
%label{ for: 'clear_all_filters' }
%br
%input.fullwidth.red{ :type => 'button', :id => 'clear_all_filters', :value => t('admin.clear_filters'), 'ng-click' => "resetSelectFilters()" }
.clearfix
.actions.filter-actions
%div
%a.button.icon-search{'ng-click' => 'fetchProducts()'}
= t(:filter_results)
.filter_clear.three.columns.omega
%label{ for: 'clear_all_filters' }
%br
%input.fullwidth.red{ :type => 'button', :id => 'clear_all_filters', :value => t('admin.clear_filters'), 'ng-click' => "resetSelectFilters()" }

View File

@@ -1,15 +1,14 @@
%div{ 'ng-show' => '!spree_api_key_ok' }
{{ api_error_msg }}
%div.sixteen.columns.alpha#loading{ 'ng-if' => 'RequestMonitor.loading' }
%br
%div.sixteen.columns.alpha#loading{ 'ng-if' => 'loading' }
%img.spinner{ src: "/assets/spinning-circles.svg" }
%h1= t('.title')
%div.sixteen.columns.alpha{ 'ng-show' => '!RequestMonitor.loading && products.length == 0 && query.length==0' }
%div.sixteen.columns.alpha{ 'ng-show' => '!loadingAllPages && filteredProducts.length == 0 && query.length==0' }
%h1#no_results= t('.no_products')
%div.sixteen.columns.alpha{ 'ng-show' => '!RequestMonitor.loading && products.length == 0 && query.length!=0' }
%div.sixteen.columns.alpha{ 'ng-show' => '!loadingAllPages && filteredProducts.length == 0 && query.length!=0' }
%h1#no_results
= t('.no_results')
'

View File

@@ -1,14 +1,14 @@
%div.sixteen.columns.alpha{ 'ng-hide' => 'RequestMonitor.loading || products.length == 0' }
%div.sixteen.columns.alpha{ 'ng-hide' => 'loading || filteredProducts.length == 0' }
%form{ name: 'bulk_product_form' }
%save-bar{ dirty: "bulk_product_form.$dirty", persist: "false" }
%input.red{ type: "button", value: t(:save_changes), ng: { click: "submitProducts()", disabled: "!bulk_product_form.$dirty" } }
%input{ type: "button", value: t(:close), 'ng-click' => "cancel('#{admin_products_path}')" }
%table.index#listing_products.bulk
%table.index#listing_products.bulk{ "infinite-scroll" => "incrementLimit()", "infinite-scroll-distance" => "1" }
= render 'spree/admin/products/index/products_head'
%tbody{ 'ng-repeat' => 'product in products', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" }
%tbody{ 'ng-repeat' => 'product in filteredProducts = ( products | filter:query | producer: producerFilter | category: categoryFilter | importDate: importDateFilter | limitTo:limit )', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" }
= render 'spree/admin/products/index/products_product'
= render 'spree/admin/products/index/products_variant'

View File

@@ -1,3 +1,3 @@
%div.sixteen.columns.alpha{ 'ng-hide' => 'RequestMonitor.loading || products.length == 0', style: "margin-bottom: 10px" }
%div.sixteen.columns.alpha{ 'ng-hide' => 'loading || products.length == 0', style: "margin-bottom: 10px" }
%div.four.columns.alpha
%input.four.columns.alpha{ :type => 'button', :value => t(:save_changes), 'ng-click' => 'submitProducts()'}

View File

@@ -1,4 +0,0 @@
.form-buttons.filter-actions.actions
= button Spree.t('actions.update'), 'icon-refresh'
%span.or= Spree.t(:or)
= button_link_to Spree.t('actions.cancel'), collection_url, icon: 'icon-remove'

View File

@@ -1,6 +0,0 @@
- content_for :sub_menu do
%ul#sub_nav.inline-menu
= tab :products, match_path: '/products'
= tab :option_types, match_path: '/option_types'
= tab :properties
= tab :prototypes

View File

@@ -3,6 +3,11 @@
:variants_search => spree.admin_search_variants_path(:format => 'json'),
:taxons_search => spree.api_taxons_path(:format => 'json'),
:user_search => spree.admin_search_users_path(:format => 'json'),
:orders_api => spree.api_orders_path(:format => 'json')
:product_search => spree.api_products_path(:format => 'json'),
:option_type_search => spree.api_option_types_path(:format => 'json'),
:states_search => spree.api_states_path(:format => 'json'),
:orders_api => spree.api_orders_path(:format => 'json'),
:stock_locations_api => spree.api_stock_locations_path(:format => 'json'),
:variants_api => spree.api_variants_path(:format => 'json')
}.to_json %>;
</script>

View File

@@ -0,0 +1,2 @@
collection @products.order('id ASC')
extends "spree/api/products/bulk_show"

View File

@@ -0,0 +1,27 @@
object @product
# TODO: This is used by bulk product edit when a product is cloned.
# But the list of products is serialized by Api::Admin::ProductSerializer.
# This should probably be unified.
attributes :id, :name, :sku, :variant_unit, :variant_unit_scale, :variant_unit_name, :on_demand, :inherits_properties
attributes :on_hand, :price, :available_on, :permalink_live, :tax_category_id
# Infinity is not a valid JSON object, but Rails encodes it anyway
node( :taxon_ids ) { |p| p.taxons.map(&:id).join(",") }
node( :on_hand ) { |p| p.on_hand.nil? ? 0 : p.on_hand.to_f.finite? ? p.on_hand : t(:on_demand) }
node( :price ) { |p| p.price.nil? ? '0.0' : p.price }
node( :available_on ) { |p| p.available_on.blank? ? "" : p.available_on.strftime("%F %T") }
node( :permalink_live, &:permalink )
node( :producer_id, &:supplier_id )
node( :category_id, &:primary_taxon_id )
node( :supplier ) do |p|
partial 'api/enterprises/bulk_show', object: p.supplier
end
node( :variants ) do |p|
partial 'spree/api/variants/bulk_index', object: p.variants.reorder('spree_variants.id ASC')
end
node( :master ) do |p|
partial 'spree/api/variants/bulk_show', object: p.master
end

View File

@@ -0,0 +1,2 @@
collection @variants
extends "spree/api/variants/bulk_show"

View File

@@ -0,0 +1,7 @@
object @variant
attributes :id, :options_text, :unit_value, :unit_description, :on_demand, :display_as, :display_name
# Infinity is not a valid JSON object, but Rails encodes it anyway
node( :on_hand ) { |v| v.on_hand.nil? ? 0 : ( v.on_hand.to_f.finite? ? v.on_hand : t(:on_demand) ) }
node( :price ) { |v| v.price.nil? ? 0.to_f : v.price }

View File

@@ -0,0 +1,4 @@
= address.address1
= ", #{address.address2}" unless address.address2.blank?
%br/
= [address.city, address.state_text, address.zipcode, address.country.name].compact.join ', '

View File

@@ -1,11 +0,0 @@
- if target && target.errors.any?
#errorExplanation.errorExplanation
%h2
= Spree.t(:errors_prohibited_this_record_from_being_saved, count: target.errors.count)
\:
%p
= Spree.t(:there_were_problems_with_the_following_fields)
\:
%ul
- target.errors.full_messages.each do |msg|
%li= msg

View File

@@ -0,0 +1,15 @@
- filters = @taxon ? @taxon.applicable_filters : []
- unless filters.empty?
%nav#filters
- params[:search] ||= {}
- filters.each do |filter|
- labels = filter[:labels] || filter[:conds].map {|m,c| [m,m]}
- next if labels.empty?
%h6.filter_name= "Shop by #{filter[:name]}"
%ul.filter_choices
- labels.each do |nm,val|
%li.nowrap
- active = params[:search][filter[:scope]] && params[:search][filter[:scope]].include?(val.to_s)
= link_to nm, "?search[#{filter[:scope].to_s}][]=#{CGI.escape(val)}"

View File

@@ -0,0 +1,23 @@
- paginated_products = @searcher.retrieve_products if params.key?(:keywords)
- paginated_products ||= products
- if products.empty?
= t(:no_products_found)
- elsif params.key?(:keywords)
%h6.search-results-title= t(:search_results, :keywords => h(params[:keywords]))
- if products.any?
%ul#products.inline.product-listing{"data-hook" => ""}
- reset_cycle('default')
- products.each do |product|
- if Spree::Config[:show_zero_stock_products] || product.has_stock?
%li{:class => "columns three #{cycle("alpha", "secondary", "", "omega secondary")}", "data-hook" => "products_list_item", :id => "product_#{product.id}", :itemscope => "", :itemtype => "http://schema.org/Product"}
.product-image
= link_to small_image(product, :itemprop => "image"), product, :itemprop => 'url'
= link_to truncate(product.name, :length => 50), product, :class => 'info', :itemprop => "name", :title => product.name
%span.price.selling{:itemprop => "price"}= spree_number_to_currency(product.price)
- if paginated_products.respond_to?(:num_pages)
- params.delete(:search)
- params.delete(:taxon)
= paginate paginated_products

View File

@@ -113,7 +113,6 @@ module Openfoodnetwork
)
config.paths["config/routes"] = %w(
config/routes/api.rb
config/routes.rb
config/routes/admin.rb
config/routes/spree.rb

View File

@@ -1,5 +0,0 @@
# Sets a maximum number of returned records when Kaminari pagination is used on a query but no
# per_page value has been passed to the #per method.
Kaminari.configure do |config|
config.max_per_page = 100
end

View File

@@ -244,7 +244,6 @@ ca:
reset_password_token: Reinicia el token de contrasenya
expired: ha caducat, si us plau, sol·liciteu-ne un de nou
back_to_payments_list: "Torna a la llista de pagaments"
maestro_or_solo_cards: "Targetes Maestro/Solo"
actions:
create_and_add_another: "Crea i afegeix-ne una altra"
create: "Crear"
@@ -453,8 +452,6 @@ ca:
encoding_error: "Comproveu la configuració de l'idioma del vostre fitxer d'origen i assegureu-vos que es desa amb la codificació UTF-8"
unexpected_error: "La importació de productes ha detectat un error inesperat mentre obria el fitxer: %{error_message}"
index:
notice: "Avís"
beta_notice: "Aquesta funcionalitat continua en fase beta: podeu experimentar alguns errors mentre l'utilitzeu. No dubteu en contactar amb nosaltres."
select_file: Selecciona un full de càlcul per pujar-lo
spreadsheet: Full de càlcul
choose_import_type: Selecciona el tipus d'importació

View File

@@ -14,9 +14,7 @@ en_US:
spree/product:
primary_taxon: "Product Category"
supplier: "Supplier"
shipping_category_id: "Shipping Category"
variant_unit: "Variant Unit"
variant_unit_name: "Variant Unit Name"
order_cycle:
orders_close_at: Close Date
errors:
@@ -77,8 +75,6 @@ en_US:
user_confirmations:
spree_user:
send_instructions: "You will receive an email with instructions about how to confirm your account in a few minutes."
confirmation_sent: "Email confirmation sent"
confirmation_not_sent: "Error sending confirmation email"
user_registrations:
spree_user:
signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please open the link to activate your account."
@@ -89,12 +85,9 @@ en_US:
Were you a guest last time? Perhaps you need to create an account or reset your password.
unconfirmed: "You have to confirm your account before continuing."
already_registered: "This email address is already registered. Please log in to continue, or go back and use another email address."
success:
logged_in_succesfully: "Logged in successfully"
user_passwords:
spree_user:
updated_not_active: "Your password has been reset, but your email has not been confirmed yet."
updated: "Your password was changed successfully. You are now signed in."
send_instructions: "You will receive an email with instructions about how to confirm your account in a few minutes."
models:
order_cycle:
@@ -244,7 +237,6 @@ en_US:
reset_password_token: Reset password token
expired: has expired, please request a new one
back_to_payments_list: "Back to Payments List"
maestro_or_solo_cards: "Debit cards"
actions:
create_and_add_another: "Create and Add Another"
create: "Create"
@@ -329,7 +321,6 @@ en_US:
invoice_settings:
edit:
title: "Invoice Settings"
enable_invoices?: "Enable Invoices?"
invoice_style2?: "Use the alternative invoice model that includes total tax breakdown per rate and tax rate info per item (not yet suitable for countries displaying prices excluding tax)"
enable_receipt_printing?: "Show options for printing receipts using thermal printers in order dropdown?"
stripe_connect_settings:
@@ -398,7 +389,6 @@ en_US:
calculator: "Calculator"
calculator_values: "Calculator Values"
search: "Search"
name_placeholder: "e.g. packing fee"
enterprise_groups:
index:
new_button: New Enterprise Group
@@ -453,8 +443,6 @@ en_US:
encoding_error: "Please check the language setting of your source file and ensure it is saved with UTF-8 encoding"
unexpected_error: "Product Import encountered an unexpected error when opening the file: %{error_message}"
index:
notice: "Notice"
beta_notice: "This feature is still in beta: you may experience some errors while using it. Please don't hesitate to contact support."
select_file: Select a spreadsheet to upload
spreadsheet: Spreadsheet
choose_import_type: Select import type
@@ -659,8 +647,6 @@ en_US:
permalink_tip: "This permalink is used to create the url to your shop: %{link}your-shop-name/shop"
link_to_front: Link to shop front
link_to_front_tip: A direct link to your shopfront on the Open Food Network.
ofn_uid: OFN UID
ofn_uid_tip: The unique id used to identify the enterprise on Open Food Network.
shipping_methods:
name: Name
applies: Applies?
@@ -842,7 +828,6 @@ en_US:
add_distributor: 'Add distributor'
advanced_settings:
title: Advanced Settings
choose_product_tip: You can restrict products incoming and outgoing to only %{inventory}'s inventory.
preferred_product_selection_from_coordinator_inventory_only_here: Coordinator's Inventory Only
preferred_product_selection_from_coordinator_inventory_only_all: All Available Products
save_reload: Save and Reload Page
@@ -978,8 +963,6 @@ en_US:
pause_subscription: Pause Subscription
unpause_subscription: Unpause Subscription
cancel_subscription: Cancel Subscription
filters:
query_placeholder: "Search by email..."
setup_explanation:
just_a_few_more_steps: 'Just a few more steps before you can begin:'
enable_subscriptions: "Enable subscriptions for at least one of your shops"
@@ -1016,8 +999,6 @@ en_US:
charges_not_allowed: Charges are not allowed by this customer
no_default_card: Customer has no cards available to charge
card_ok: Customer has a card available to charge
begins_at_placeholder: "Select a Date"
ends_at_placeholder: "Optional"
loading_flash:
loading: LOADING SUBSCRIPTIONS
review:
@@ -1031,7 +1012,6 @@ en_US:
saved: "SAVED"
product_already_in_order: This product has already been added to the order. Please edit the quantity directly.
stock:
insufficient_stock: "Insufficient stock available"
out_of_stock: "Out of Stock"
orders:
number: Number
@@ -1040,7 +1020,6 @@ en_US:
cancel_failure_msg: "Sorry, cancellation failed!"
confirm_pause_msg: "Are you sure you want to pause this subscription?"
pause_failure_msg: "Sorry, pausing failed!"
confirm_unpause_msg: "If you have an open Order Cycle in this subscription's schedule, an order will be created for this customer. Are you sure you want to unpause this subscription?"
unpause_failure_msg: "Sorry, unpausing failed!"
confirm_cancel_open_orders_msg: "Some orders for this subscription are currently open. The customer has already been notified that the order will be placed. Would you like to cancel these order(s) or keep them?"
resume_canceled_orders_msg: "Some orders for this subscription can be resumed right now. You can resume them from the orders dropdown."
@@ -1078,12 +1057,8 @@ en_US:
show_on_map: "Show all on the map"
shared:
menu:
cart:
cart: "Cart"
signed_in:
profile: "Profile"
mobile_menu:
cart: "Cart"
joyride:
checkout: "Checkout now"
already_ordered_products: "Already ordered in this order cycle"
@@ -1120,11 +1095,7 @@ en_US:
shop:
messages:
login: "login"
signup: "signup"
contact: "contact"
require_customer_login: "Only approved customers can access this shop."
require_login_html: "If you're already an approved customer, %{login} or %{signup} to proceed. Want to start shopping here? Please %{contact} %{enterprise} and ask about joining."
require_customer_html: "If you'd like to start shopping here, please %{contact} %{enterprise} to ask about joining."
card_could_not_be_updated: Card could not be updated
card_could_not_be_saved: card could not be saved
spree_gateway_error_flash_for_checkout: "There was a problem with your payment information: %{error}"
@@ -1714,7 +1685,6 @@ en_US:
introduction:
registration_greeting: "Hi there!"
registration_intro: "You can now create a profile for your Producer or Hub"
registration_checklist: "What do I need?"
registration_time: "5-10 minutes"
registration_enterprise_address: "Address"
registration_contact_details: "Primary contact details"
@@ -1969,7 +1939,6 @@ en_US:
spree_admin_supplier: Supplier
spree_admin_product_category: Product Category
spree_admin_variant_unit_name: Variant unit name
unit_name: "Unit name"
change_package: "Change Package"
spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under"
spree_admin_eg_pickup_from_school: "eg. 'Pick up at Community Center'"
@@ -2205,7 +2174,6 @@ en_US:
payment_methods: "Payment Methods"
payment_method_fee: "Transaction fee"
payment_processing_failed: "Payment could not be processed, please check the details you entered"
payment_method_not_supported: "That payment method is unsupported. Please choose another one."
payment_updated: "Payment Updated"
inventory_settings: "Inventory Settings"
tag_rules: "Tag Rules"
@@ -2396,18 +2364,8 @@ en_US:
severity: Severity
description: Description
resolve: Resolve
tag_rules:
shipping_method_tagged_top: "Shipping methods tagged"
shipping_method_tagged_bottom: "are:"
payment_method_tagged_top: "Payment methods tagged"
payment_method_tagged_bottom: "are:"
order_cycle_tagged_top: "Order Cycles tagged"
order_cycle_tagged_bottom: "are:"
inventory_tagged_top: "Inventory variants tagged"
inventory_tagged_bottom: "are:"
new_tag_rule_dialog:
select_rule_type: "Select a rule type:"
add_rule: "Add Rule"
enterprise_fees:
inherit_from_product: "Inherit From Product"
orders:
@@ -2664,7 +2622,6 @@ en_US:
fill_in_customer_info: "Please fill in customer info"
new_payment: "New Payment"
capture: "Capture"
void: "Void"
configurations: "Configurations"
general_settings: "General Settings"
site_name: "Site Name"
@@ -2766,16 +2723,7 @@ en_US:
no_results: "No results"
create: "Create"
loading: "Loading"
flat_percent: "Flat Percent"
per_kg: "Per Kg"
amount: "Amount"
currency: "Currency"
first_item: "First Item Cost"
additional_item: "Additional Item Cost"
max_items: "Max Items"
minimal_amount: "Minimal Amount"
normal_amount: "Normal Amount"
discount_amount: "Discount Amount"
email: Email
account_updated: "Account updated!"
my_account: "My account"
@@ -2785,7 +2733,6 @@ en_US:
inventory: Inventory
zipcode: Zipcode
weight: Weight (per lb)
error_user_destroy_with_orders: "Users with completed orders may not be deleted"
actions:
update: "Update"
errors:
@@ -2924,7 +2871,6 @@ en_US:
error_saving_payment: Error saving payment
submitting_payment: Submitting payment...
products:
image_upload_error: "The product image was not recognised. Please upload an image in PNG or JPG format."
new:
title: 'New Product'
unit_name_placeholder: 'eg. bunches'

View File

@@ -244,7 +244,6 @@ en_ZA:
reset_password_token: Reset password
expired: has expired, please request a new one
back_to_payments_list: "Back to Payments List"
maestro_or_solo_cards: "Maestro/Solo cards"
actions:
create_and_add_another: "Create and Add Another"
create: "Create"
@@ -453,8 +452,6 @@ en_ZA:
encoding_error: "Please check the language setting of your source file and ensure it is saved with UTF-8 encoding"
unexpected_error: "Product Import encountered an unexpected error whilst opening the file: %{error_message}"
index:
notice: "Notice"
beta_notice: "This feature is still in beta: you may experience some errors while using it. Please don't hesitate to contact support."
select_file: Select a spreadsheet to upload
spreadsheet: Spreadsheet
choose_import_type: Select import type
@@ -1512,7 +1509,7 @@ en_ZA:
components_filters_clearfilters: "Clear all filters"
groups_title: Groups
groups_headline: Groups / regions
groups_text: "OFN groups are collectives of producers, hubs and distributors who share something in common such as location or philosophy. Groups also enable consumers to browse producers that are registered with various industry bodies e.g. organic / biodynamic certification. Specifically in South Africa, groups also enable non-profit organisations to highlight emerging farmers or enterprises that they may be assisting. In this way we can help these producers to enjoy the benefits of the wider OFN network."
groups_text: "Every producer is unique. Every business has something different to offer. Our groups are collectives of producers, hubs and distributors who share something in common like location, farmers market or philosophy. This makes your shopping experience easier. So explore our groups and have the curating done for you."
groups_search: "Search name or keyword"
groups_no_groups: "No groups found"
groups_about: "About Us"

View File

@@ -244,7 +244,6 @@ es:
reset_password_token: token de restablecimiento de contraseña
expired: ha expirado, por favor solicite una nueva
back_to_payments_list: "Volver a la lista de pagos"
maestro_or_solo_cards: "Tarjetas Maestro/Solo"
actions:
create_and_add_another: "Crear y agregar otro"
create: "Crear"
@@ -453,8 +452,6 @@ es:
encoding_error: "Verifique la configuración de idioma de su archivo fuente y asegúrese de que esté guardado con la codificación UTF-8"
unexpected_error: "La importación de productos encontró un error inesperado al abrir el archivo: %{error_message}"
index:
notice: "aviso"
beta_notice: "Esta funcionalidad aún está en versión beta: puede experimentar algunos errores mientras la usa. Por favor no dude en ponerse en contacto con nosotros."
select_file: Selecciona una hoja de cálculo para subir
spreadsheet: Hoja de cálculo
choose_import_type: Seleccionar tipo de importación

View File

@@ -88,6 +88,38 @@ Openfoodnetwork::Application.routes.draw do
get '/:id/shop', to: 'enterprises#shop', as: 'enterprise_shop'
get "/enterprises/:permalink", to: redirect("/") # Legacy enterprise URL
namespace :api do
resources :enterprises do
post :update_image, on: :member
get :managed, on: :collection
get :accessible, on: :collection
resource :logo, only: [:destroy]
resource :promo_image, only: [:destroy]
member do
get :shopfront
end
end
resources :order_cycles do
get :managed, on: :collection
get :accessible, on: :collection
end
resources :orders, only: [:index]
resource :status do
get :job_queue
end
resources :customers, only: [:index, :update]
resources :enterprise_fees, only: [:destroy]
post '/product_images/:product_id', to: 'product_images#update_product_image'
end
get 'sitemap.xml', to: 'sitemap#index', defaults: { format: 'xml' }
# Mount engine routes

View File

@@ -1,48 +0,0 @@
Openfoodnetwork::Application.routes.draw do
namespace :api do
resources :products do
collection do
get :bulk_products
get :overridable
end
delete :soft_delete
post :clone
resources :variants do
delete :soft_delete
end
end
resources :variants, :only => [:index]
resources :enterprises do
post :update_image, on: :member
get :managed, on: :collection
get :accessible, on: :collection
resource :logo, only: [:destroy]
resource :promo_image, only: [:destroy]
member do
get :shopfront
end
end
resources :order_cycles do
get :managed, on: :collection
get :accessible, on: :collection
end
resources :orders, only: [:index]
resource :status do
get :job_queue
end
resources :customers, only: [:index, :update]
resources :enterprise_fees, only: [:destroy]
post '/product_images/:product_id', to: 'product_images#update_product_image'
end
end

View File

@@ -62,27 +62,22 @@ Spree::Core::Engine.routes.prepend do
get :authorise_api, on: :collection
end
resources :orders do
get :managed, on: :collection
resources :products do
collection do
get :managed
get :bulk_products
get :overridable
end
delete :soft_delete
post :clone
resources :shipments, :only => [:create, :update] do
member do
put :ready
put :ship
put :add
put :remove
end
resources :variants do
delete :soft_delete
end
end
resources :taxons, :only => [:index]
resources :taxonomies do
resources :taxons do
member do
get :jstree
end
end
resources :orders do
get :managed, on: :collection
end
end
@@ -110,13 +105,6 @@ Spree::Core::Engine.routes.prepend do
end
end
end
resources :users do
member do
put :generate_api_key
put :clear_api_key
end
end
end
resources :orders do

View File

@@ -12,7 +12,7 @@ job_type :run_file, "cd :path; :environment_variable=:environment bundle exec sc
job_type :enqueue_job, "cd :path; :environment_variable=:environment bundle exec script/enqueue :task :priority :output"
every 1.day, at: '01:00am' do
every 1.hour do
rake 'ofn:cache:check_products_integrity'
end

View File

@@ -1,17 +0,0 @@
class ConvertStringFieldsToText < ActiveRecord::Migration
def up
change_column :enterprises, :description, :text
change_column :enterprises, :pickup_times, :text
change_column :exchanges, :pickup_time, :text
change_column :exchanges, :pickup_instructions, :text
change_column :exchanges, :receival_instructions, :text
end
def down
change_column :enterprises, :description, :string
change_column :enterprises, :pickup_times, :string
change_column :exchanges, :pickup_time, :string
change_column :exchanges, :pickup_instructions, :string
change_column :exchanges, :receival_instructions, :string
end
end

View File

@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20190701002454) do
ActiveRecord::Schema.define(:version => 20190506194625) do
create_table "adjustment_metadata", :force => true do |t|
t.integer "adjustment_id"
@@ -175,7 +175,7 @@ ActiveRecord::Schema.define(:version => 20190701002454) do
create_table "enterprises", :force => true do |t|
t.string "name"
t.text "description"
t.string "description"
t.text "long_description"
t.boolean "is_primary_producer"
t.string "contact_name"
@@ -185,7 +185,7 @@ ActiveRecord::Schema.define(:version => 20190701002454) do
t.string "abn"
t.string "acn"
t.integer "address_id"
t.text "pickup_times"
t.string "pickup_times"
t.string "next_collection_at"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
@@ -247,12 +247,12 @@ ActiveRecord::Schema.define(:version => 20190701002454) do
t.integer "order_cycle_id"
t.integer "sender_id"
t.integer "receiver_id"
t.text "pickup_time"
t.text "pickup_instructions"
t.string "pickup_time"
t.string "pickup_instructions"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.boolean "incoming", :default => false, :null => false
t.text "receival_instructions"
t.string "receival_instructions"
end
add_index "exchanges", ["order_cycle_id"], :name => "index_exchanges_on_order_cycle_id"

View File

@@ -54,16 +54,14 @@ module OpenFoodNetwork
end
def all_variants_for_shop
@all_variants_for_shop ||= begin
# We use the in_stock? method here instead of the in_stock scope
# because we need to look up the stock as overridden by
# VariantOverrides, and the scope method is not affected by them.
scoper = OpenFoodNetwork::ScopeVariantToHub.new(@distributor)
Spree::Variant.
for_distribution(@order_cycle, @distributor).
each { |v| scoper.scope(v) }.
select(&:in_stock?)
end
# We use the in_stock? method here instead of the in_stock scope because we need to
# look up the stock as overridden by VariantOverrides, and the scope method is not affected
# by them.
scoper = OpenFoodNetwork::ScopeVariantToHub.new(@distributor)
Spree::Variant.
for_distribution(@order_cycle, @distributor).
each { |v| scoper.scope(v) }.
select(&:in_stock?)
end
def variants_for_shop_by_id

View File

@@ -1,33 +0,0 @@
require 'spree/api/responders'
module Spree
module Api
module ControllerSetup
def self.included(klass)
klass.class_eval do
include AbstractController::Rendering
include AbstractController::ViewPaths
include AbstractController::Callbacks
include AbstractController::Helpers
include ActiveSupport::Rescuable
include ActionController::Rendering
include ActionController::ImplicitRender
include ActionController::Rescue
include ActionController::MimeResponds
include ActionController::Head
include CanCan::ControllerAdditions
include Spree::Core::ControllerHelpers::Auth
prepend_view_path Rails.root + "app/views"
append_view_path File.expand_path("../../../app/views", File.dirname(__FILE__))
self.responder = Spree::Api::Responders::AppResponder
respond_to :json
end
end
end
end
end

View File

@@ -0,0 +1,7 @@
require 'spree/api/testing_support/helpers'
Spree::Api::TestingSupport::Helpers.class_eval do
def current_api_user
@current_api_user ||= Spree::LegacyUser.new(email: "spree@example.com", enterprises: [])
end
end

View File

@@ -3,6 +3,7 @@ require 'spec_helper'
module Api
describe CustomersController, type: :controller do
include AuthenticationWorkflow
include OpenFoodNetwork::ApiHelper
render_views
let(:user) { create(:user) }

View File

@@ -1,4 +1,5 @@
require 'spec_helper'
require 'spree/api/testing_support/helpers'
module Api
describe OrdersController, type: :controller do

View File

@@ -1,4 +1,5 @@
require 'spec_helper'
require 'spree/api/testing_support/helpers'
module Api
describe ProductImagesController, type: :controller do

View File

@@ -1,287 +0,0 @@
require 'spec_helper'
describe Api::ProductsController, type: :controller do
render_views
let(:supplier) { create(:supplier_enterprise) }
let(:supplier2) { create(:supplier_enterprise) }
let!(:product) { create(:product, supplier: supplier) }
let!(:inactive_product) { create(:product, available_on: Time.zone.now.tomorrow, name: "inactive") }
let(:product_other_supplier) { create(:product, supplier: supplier2) }
let(:product_with_image) { create(:product_with_image, supplier: supplier) }
let(:attributes) { ["id", "name", "supplier", "price", "on_hand", "available_on", "permalink_live"] }
let(:all_attributes) { ["id", "name", "price", "available_on", "variants"] }
let(:variants_attributes) { ["id", "options_text", "unit_value", "unit_description", "unit_to_display", "on_demand", "display_as", "display_name", "name_to_display", "sku", "on_hand", "price"] }
let(:current_api_user) { build(:user) }
before do
allow(controller).to receive(:spree_current_user) { current_api_user }
end
context "as a normal user" do
before do
allow(current_api_user)
.to receive(:has_spree_role?).with("admin").and_return(false)
end
it "gets a single product" do
product.master.images.create!(attachment: image("thinking-cat.jpg"))
product.variants.create!(unit_value: "1", unit_description: "thing")
product.variants.first.images.create!(attachment: image("thinking-cat.jpg"))
product.set_property("spree", "rocks")
api_get :show, id: product.to_param
expect(all_attributes.all?{ |attr| json_response.keys.include? attr }).to eq(true)
expect(variants_attributes.all?{ |attr| json_response['variants'].first.keys.include? attr }).to eq(true)
end
context "finds a product by permalink first then by id" do
let!(:other_product) { create(:product, permalink: "these-are-not-the-droids-you-are-looking-for") }
before do
product.update_attribute(:permalink, "#{other_product.id}-and-1-ways")
end
specify do
api_get :show, id: product.to_param
expect(json_response["permalink_live"]).to match(/and-1-ways/)
product.destroy
api_get :show, id: other_product.id
expect(json_response["permalink_live"]).to match(/droids/)
end
end
it "cannot see inactive products" do
api_get :show, id: inactive_product.to_param
expect(json_response["error"]).to eq("The resource you were looking for could not be found.")
expect(response.status).to eq(404)
end
it "returns a 404 error when it cannot find a product" do
api_get :show, id: "non-existant"
expect(json_response["error"]).to eq("The resource you were looking for could not be found.")
expect(response.status).to eq(404)
end
include_examples "modifying product actions are restricted"
end
context "as an enterprise user" do
let(:current_api_user) { supplier_enterprise_user(supplier) }
it "soft deletes my products" do
spree_delete :soft_delete, product_id: product.to_param, format: :json
expect(response.status).to eq(204)
expect { product.reload }.not_to raise_error
expect(product.deleted_at).not_to be_nil
end
it "is denied access to soft deleting another enterprises' product" do
spree_delete :soft_delete, product_id: product_other_supplier.to_param, format: :json
assert_unauthorized!
expect { product_other_supplier.reload }.not_to raise_error
expect(product_other_supplier.deleted_at).to be_nil
end
end
context "as an administrator" do
before do
allow(current_api_user)
.to receive(:has_spree_role?).with("admin").and_return(true)
end
it "soft deletes a product" do
spree_delete :soft_delete, product_id: product.to_param, format: :json
expect(response.status).to eq(204)
expect { product.reload }.not_to raise_error
expect(product.deleted_at).not_to be_nil
end
it "can create a new product" do
api_post :create, product: { name: "The Other Product",
price: 19.99,
shipping_category_id: create(:shipping_category).id,
supplier_id: supplier.id,
primary_taxon_id: FactoryBot.create(:taxon).id,
variant_unit: "items",
variant_unit_name: "things",
unit_description: "things" }
expect(all_attributes.all?{ |attr| json_response.keys.include? attr }).to eq(true)
expect(response.status).to eq(201)
end
it "cannot create a new product with invalid attributes" do
api_post :create, product: {}
expect(response.status).to eq(422)
expect(json_response["error"]).to eq("Invalid resource. Please fix errors and try again.")
errors = json_response["errors"]
expect(errors.keys).to match_array(["name", "price", "primary_taxon", "shipping_category_id", "supplier", "variant_unit"])
end
it "can update a product" do
api_put :update, id: product.to_param, product: { name: "New and Improved Product!" }
expect(response.status).to eq(200)
end
it "cannot update a product with an invalid attribute" do
api_put :update, id: product.to_param, product: { name: "" }
expect(response.status).to eq(422)
expect(json_response["error"]).to eq("Invalid resource. Please fix errors and try again.")
expect(json_response["errors"]["name"]).to eq(["can't be blank"])
end
it "can delete a product" do
expect(product.deleted_at).to be_nil
api_delete :destroy, id: product.to_param
expect(response.status).to eq(204)
expect(product.reload.deleted_at).not_to be_nil
end
end
describe '#clone' do
context 'as a normal user' do
before do
allow(current_api_user)
.to receive(:has_spree_role?).with("admin").and_return(false)
end
it 'denies access' do
spree_post :clone, product_id: product.id, format: :json
assert_unauthorized!
end
end
context 'as an enterprise user' do
let(:current_api_user) { supplier_enterprise_user(supplier) }
it 'responds with a successful response' do
spree_post :clone, product_id: product.id, format: :json
expect(response.status).to eq(201)
end
it 'clones the product' do
spree_post :clone, product_id: product.id, format: :json
expect(json_response['name']).to eq("COPY OF #{product.name}")
end
it 'clones a product with image' do
spree_post :clone, product_id: product_with_image.id, format: :json
expect(response.status).to eq(201)
expect(json_response['name']).to eq("COPY OF #{product_with_image.name}")
end
end
context 'as an administrator' do
before do
allow(current_api_user)
.to receive(:has_spree_role?).with("admin").and_return(true)
end
it 'responds with a successful response' do
spree_post :clone, product_id: product.id, format: :json
expect(response.status).to eq(201)
end
it 'clones the product' do
spree_post :clone, product_id: product.id, format: :json
expect(json_response['name']).to eq("COPY OF #{product.name}")
end
it 'clones a product with image' do
spree_post :clone, product_id: product_with_image.id, format: :json
expect(response.status).to eq(201)
expect(json_response['name']).to eq("COPY OF #{product_with_image.name}")
end
end
describe '#bulk_products' do
context "as an enterprise user" do
let!(:taxon) { create(:taxon) }
let!(:product2) { create(:product, supplier: supplier, primary_taxon: taxon) }
let!(:product3) { create(:product, supplier: supplier2, primary_taxon: taxon) }
let!(:product4) { create(:product, supplier: supplier2) }
let(:current_api_user) { supplier_enterprise_user(supplier) }
before { current_api_user.enterprise_roles.create(enterprise: supplier2) }
it "returns a list of products" do
api_get :bulk_products, { page: 1, per_page: 15 }, format: :json
expect(returned_product_ids).to eq [product4.id, product3.id, product2.id, inactive_product.id, product.id]
end
it "returns pagination data" do
api_get :bulk_products, { page: 1, per_page: 15 }, format: :json
expect(json_response['pagination']).to eq "results" => 5, "pages" => 1, "page" => 1, "per_page" => 15
end
it "uses defaults when page and per_page are not supplied" do
api_get :bulk_products, format: :json
expect(json_response['pagination']).to eq "results" => 5, "pages" => 1, "page" => 1, "per_page" => 15
end
it "returns paginated products by page" do
api_get :bulk_products, { page: 1, per_page: 2 }, format: :json
expect(returned_product_ids).to eq [product4.id, product3.id]
api_get :bulk_products, { page: 2, per_page: 2 }, format: :json
expect(returned_product_ids).to eq [product2.id, inactive_product.id]
end
it "filters results by supplier" do
api_get :bulk_products, { page: 1, per_page: 15, q: {supplier_id_eq: supplier.id} }, format: :json
expect(returned_product_ids).to eq [product2.id, inactive_product.id, product.id]
end
it "filters results by product category" do
api_get :bulk_products, { page: 1, per_page: 15, q: {primary_taxon_id_eq: taxon.id} }, format: :json
expect(returned_product_ids).to eq [product3.id, product2.id]
end
it "filters results by import_date" do
product.variants.first.import_date = 1.day.ago
product2.variants.first.import_date = 2.days.ago
product3.variants.first.import_date = 1.day.ago
product.save
product2.save
product3.save
api_get :bulk_products, { page: 1, per_page: 15, import_date: 1.day.ago.to_date.to_s }, format: :json
expect(returned_product_ids).to eq [product3.id, product.id]
end
end
end
end
private
def supplier_enterprise_user(enterprise)
user = create(:user)
user.enterprise_roles.create(enterprise: enterprise)
user
end
def returned_product_ids
json_response['products'].map{ |obj| obj['id'] }
end
end

View File

@@ -1,197 +0,0 @@
require 'spec_helper'
describe Api::VariantsController, type: :controller do
render_views
let(:supplier) { FactoryBot.create(:supplier_enterprise) }
let!(:variant1) { FactoryBot.create(:variant) }
let!(:variant2) { FactoryBot.create(:variant) }
let!(:variant3) { FactoryBot.create(:variant) }
let(:attributes) { [:id, :options_text, :price, :on_hand, :unit_value, :unit_description, :on_demand, :display_as, :display_name] }
before do
allow(controller).to receive(:spree_current_user) { current_api_user }
end
context "as a normal user" do
sign_in_as_user!
let!(:product) { create(:product) }
let!(:variant) do
variant = product.master
variant.option_values << create(:option_value)
variant
end
it "retrieves a list of variants with appropriate attributes" do
spree_get :index, format: :json
keys = json_response.first.keys.map(&:to_sym)
expect(attributes.all?{ |attr| keys.include? attr }).to eq(true)
end
it "is denied access when trying to delete a variant" do
product = create(:product)
variant = product.master
spree_delete :soft_delete, variant_id: variant.to_param, product_id: product.to_param, format: :json
assert_unauthorized!
expect { variant.reload }.not_to raise_error
expect(variant.deleted_at).to be_nil
end
it 'can query the results through a parameter' do
expected_result = create(:variant, sku: 'FOOBAR')
api_get :index, q: { sku_cont: 'FOO' }
expect(json_response.size).to eq(1)
expect(json_response.first['sku']).to eq expected_result.sku
end
# Regression test for spree#2141
context "a deleted variant" do
before do
variant.update_column(:deleted_at, Time.zone.now)
end
it "is not returned in the results" do
api_get :index
expect(json_response.count).to eq(10) # there are 11 variants
end
it "is not returned even when show_deleted is passed" do
api_get :index, show_deleted: true
expect(json_response.count).to eq(10) # there are 11 variants
end
end
it "can see a single variant" do
api_get :show, id: variant.to_param
keys = json_response.keys.map(&:to_sym)
expect((attributes).all?{ |attr| keys.include? attr }).to eq(true)
end
it "cannot create a new variant if not an admin" do
api_post :create, variant: { sku: "12345" }
assert_unauthorized!
end
it "cannot update a variant" do
api_put :update, id: variant.to_param, variant: { sku: "12345" }
assert_unauthorized!
end
it "cannot delete a variant" do
api_delete :destroy, id: variant.to_param
assert_unauthorized!
expect { variant.reload }.not_to raise_error
end
end
context "as an enterprise user" do
sign_in_as_enterprise_user! [:supplier]
let(:supplier_other) { create(:supplier_enterprise) }
let(:product) { create(:product, supplier: supplier) }
let(:variant) { product.master }
let(:product_other) { create(:product, supplier: supplier_other) }
let(:variant_other) { product_other.master }
it "soft deletes a variant" do
spree_delete :soft_delete, variant_id: variant.to_param, product_id: product.to_param, format: :json
expect(response.status).to eq(204)
expect { variant.reload }.not_to raise_error
expect(variant.deleted_at).to be_present
end
it "is denied access to soft deleting another enterprises' variant" do
spree_delete :soft_delete, variant_id: variant_other.to_param, product_id: product_other.to_param, format: :json
assert_unauthorized!
expect { variant.reload }.not_to raise_error
expect(variant.deleted_at).to be_nil
end
context 'when the variant is not the master' do
before { variant.update_attribute(:is_master, false) }
it 'refreshes the cache' do
expect(OpenFoodNetwork::ProductsCache).to receive(:variant_destroyed).with(variant)
spree_delete :soft_delete, variant_id: variant.id, product_id: variant.product.permalink, format: :json
end
end
end
context "as an administrator" do
sign_in_as_admin!
let(:product) { create(:product) }
let(:variant) { product.master }
let(:resource_scoping) { { product_id: variant.product.to_param } }
it "soft deletes a variant" do
spree_delete :soft_delete, variant_id: variant.to_param, product_id: product.to_param, format: :json
expect(response.status).to eq(204)
expect { variant.reload }.not_to raise_error
expect(variant.deleted_at).not_to be_nil
end
it "doesn't delete the only variant of the product" do
product = create(:product)
variant = product.variants.first
spree_delete :soft_delete, variant_id: variant.to_param, product_id: product.to_param, format: :json
expect(variant.reload).to_not be_deleted
expect(assigns(:variant).errors[:product]).to include "must have at least one variant"
end
context 'when the variant is not the master' do
before { variant.update_attribute(:is_master, false) }
it 'refreshes the cache' do
expect(OpenFoodNetwork::ProductsCache).to receive(:variant_destroyed).with(variant)
spree_delete :soft_delete, variant_id: variant.id, product_id: variant.product.permalink, format: :json
end
end
context "deleted variants" do
before do
variant.update_column(:deleted_at, Time.zone.now)
end
it "are visible by admin" do
api_get :index, show_deleted: 1
expect(json_response.count).to eq(2)
end
end
it "can create a new variant" do
original_number_of_variants = variant.product.variants.count
api_post :create, variant: { sku: "12345", unit_value: "weight", unit_description: "L" }
expect(attributes.all?{ |attr| json_response.include? attr.to_s }).to eq(true)
expect(response.status).to eq(201)
expect(json_response["sku"]).to eq("12345")
expect(variant.product.variants.count).to eq(original_number_of_variants + 1)
end
it "can update a variant" do
api_put :update, id: variant.to_param, variant: { sku: "12345" }
expect(response.status).to eq(200)
end
it "can delete a variant" do
api_delete :destroy, id: variant.to_param
expect(response.status).to eq(204)
expect { Spree::Variant.find(variant.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end

View File

@@ -150,16 +150,15 @@ describe Spree::Admin::ProductsController, type: :controller do
describe "when user uploads an image in an unsupported format" do
it "does not throw an exception" do
product_image = ActionDispatch::Http::UploadedFile.new(
filename: 'unsupported_image_format.exr',
content_type: 'application/octet-stream',
tempfile: Tempfile.new('unsupported_image_format.exr')
)
product_image = ActionDispatch::Http::UploadedFile.new({
:filename => 'unsupported_image_format.exr',
:content_type => 'application/octet-stream',
:tempfile => Tempfile.new('unsupported_image_format.exr')
})
product_attrs_with_image = product_attrs.merge(
images_attributes: {
'0' => { attachment: product_image }
}
)
})
expect do
spree_put :create, product: product_attrs_with_image

View File

@@ -1,64 +0,0 @@
require 'spec_helper'
describe Spree::Api::BaseController do
render_views
controller(Spree::Api::BaseController) do
def index
render text: { "products" => [] }.to_json
end
def spree_current_user; end
end
context "signed in as a user using an authentication extension" do
before do
allow(controller).to receive_messages try_spree_current_user:
double(email: "spree@example.com")
end
it "can make a request" do
api_get :index
expect(json_response).to eq( "products" => [] )
expect(response.status).to eq(200)
end
end
context "cannot make a request to the API" do
it "without an API key" do
api_get :index
expect(json_response).to eq( "error" => "You must specify an API key." )
expect(response.status).to eq(401)
end
it "with an invalid API key" do
request.env["X-Spree-Token"] = "fake_key"
get :index, {}
expect(json_response).to eq( "error" => "Invalid API key (fake_key) specified." )
expect(response.status).to eq(401)
end
it "using an invalid token param" do
get :index, token: "fake_key"
expect(json_response).to eq( "error" => "Invalid API key (fake_key) specified." )
end
end
it 'handles exceptions' do
expect(subject).to receive(:authenticate_user).and_return(true)
expect(subject).to receive(:index).and_raise(Exception.new("no joy"))
get :index, token: "fake_key"
expect(json_response).to eq( "exception" => "no joy" )
end
it "maps symantec keys to nested_attributes keys" do
klass = double(nested_attributes_options: { line_items: {},
bill_address: {} })
attributes = { 'line_items' => { id: 1 },
'bill_address' => { id: 2 },
'name' => 'test order' }
mapped = subject.map_nested_attributes_keys(klass, attributes)
expect(mapped.key?('line_items_attributes')).to be_truthy
expect(mapped.key?('name')).to be_truthy
end
end

View File

@@ -0,0 +1,35 @@
require 'spec_helper'
module Spree
describe Spree::Api::LineItemsController, type: :controller do
render_views
before do
allow(controller).to receive(:spree_current_user) { current_api_user }
end
# test that when a line item is updated, an order's fees are updated too
context "as an admin user" do
sign_in_as_admin!
let(:order) { FactoryBot.create(:order, state: 'complete', completed_at: Time.zone.now) }
let(:line_item) { FactoryBot.create(:line_item_with_shipment, order: order, final_weight_volume: 500) }
context "as a line item is updated" do
before { allow(controller).to receive(:order) { order } }
it "update distribution charge on the order" do
line_item_params = {
order_id: order.number,
id: line_item.id,
line_item: { id: line_item.id, final_weight_volume: 520 },
format: :json
}
expect(order).to receive(:update_distribution_charge!)
spree_post :update, line_item_params
end
end
end
end
end

View File

@@ -0,0 +1,180 @@
require 'spec_helper'
module Spree
describe Spree::Api::ProductsController, type: :controller do
render_views
let(:supplier) { create(:supplier_enterprise) }
let(:supplier2) { create(:supplier_enterprise) }
let!(:product1) { create(:product, supplier: supplier) }
let(:product_other_supplier) { create(:product, supplier: supplier2) }
let(:product_with_image) { create(:product_with_image, supplier: supplier) }
let(:attributes) { [:id, :name, :supplier, :price, :on_hand, :available_on, :permalink_live] }
let(:current_api_user) { build(:user) }
before do
allow(controller).to receive(:spree_current_user) { current_api_user }
end
context "as a normal user" do
before do
allow(current_api_user)
.to receive(:has_spree_role?).with("admin").and_return(false)
end
it "should deny me access to managed products" do
spree_get :managed, template: 'bulk_index', format: :json
assert_unauthorized!
end
end
context "as an enterprise user" do
let(:current_api_user) do
user = create(:user)
user.enterprise_roles.create(enterprise: supplier)
user
end
it "retrieves a list of managed products" do
spree_get :managed, template: 'bulk_index', format: :json
keys = json_response.first.keys.map(&:to_sym)
expect(attributes.all?{ |attr| keys.include? attr }).to eq(true)
end
it "soft deletes my products" do
spree_delete :soft_delete, product_id: product1.to_param, format: :json
expect(response.status).to eq(204)
expect { product1.reload }.not_to raise_error
expect(product1.deleted_at).not_to be_nil
end
it "is denied access to soft deleting another enterprises' product" do
spree_delete :soft_delete, product_id: product_other_supplier.to_param, format: :json
assert_unauthorized!
expect { product_other_supplier.reload }.not_to raise_error
expect(product_other_supplier.deleted_at).to be_nil
end
end
context "as an administrator" do
before do
allow(current_api_user)
.to receive(:has_spree_role?).with("admin").and_return(true)
end
it "retrieves a list of managed products" do
spree_get :managed, template: 'bulk_index', format: :json
keys = json_response.first.keys.map(&:to_sym)
expect(attributes.all?{ |attr| keys.include? attr }).to eq(true)
end
it "retrieves a list of products with appropriate attributes" do
spree_get :index, template: 'bulk_index', format: :json
keys = json_response.first.keys.map(&:to_sym)
expect(attributes.all?{ |attr| keys.include? attr }).to eq(true)
end
it "sorts products in ascending id order" do
FactoryBot.create(:product, supplier: supplier)
FactoryBot.create(:product, supplier: supplier)
spree_get :index, template: 'bulk_index', format: :json
ids = json_response.map{ |product| product['id'] }
expect(ids[0]).to be < ids[1]
expect(ids[1]).to be < ids[2]
end
it "formats available_on to 'yyyy-mm-dd hh:mm'" do
spree_get :index, template: 'bulk_index', format: :json
expect(json_response.map{ |product| product['available_on'] }.all?{ |a| a.match("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$") }).to eq(true)
end
it "returns permalink as permalink_live" do
spree_get :index, template: 'bulk_index', format: :json
expect(json_response.detect{ |product| product['id'] == product1.id }['permalink_live']).to eq(product1.permalink)
end
it "should allow available_on to be nil" do
spree_get :index, template: 'bulk_index', format: :json
expect(json_response.size).to eq(1)
product5 = FactoryBot.create(:product)
product5.available_on = nil
product5.save!
spree_get :index, template: 'bulk_index', format: :json
expect(json_response.size).to eq(2)
end
it "soft deletes a product" do
spree_delete :soft_delete, product_id: product1.to_param, format: :json
expect(response.status).to eq(204)
expect { product1.reload }.not_to raise_error
expect(product1.deleted_at).not_to be_nil
end
end
describe '#clone' do
context 'as a normal user' do
before do
allow(current_api_user)
.to receive(:has_spree_role?).with("admin").and_return(false)
end
it 'denies access' do
spree_post :clone, product_id: product1.id, format: :json
assert_unauthorized!
end
end
context 'as an enterprise user' do
let(:current_api_user) do
user = create(:user)
user.enterprise_roles.create(enterprise: supplier)
user
end
it 'responds with a successful response' do
spree_post :clone, product_id: product1.id, format: :json
expect(response.status).to eq(201)
end
it 'clones the product' do
spree_post :clone, product_id: product1.id, format: :json
expect(json_response['name']).to eq("COPY OF #{product1.name}")
end
it 'clones a product with image' do
spree_post :clone, product_id: product_with_image.id, format: :json
expect(response.status).to eq(201)
expect(json_response['name']).to eq("COPY OF #{product_with_image.name}")
end
end
context 'as an administrator' do
before do
allow(current_api_user)
.to receive(:has_spree_role?).with("admin").and_return(true)
end
it 'responds with a successful response' do
spree_post :clone, product_id: product1.id, format: :json
expect(response.status).to eq(201)
end
it 'clones the product' do
spree_post :clone, product_id: product1.id, format: :json
expect(json_response['name']).to eq("COPY OF #{product1.name}")
end
it 'clones a product with image' do
spree_post :clone, product_id: product_with_image.id, format: :json
expect(response.status).to eq(201)
expect(json_response['name']).to eq("COPY OF #{product_with_image.name}")
end
end
end
end
end

View File

@@ -5,27 +5,12 @@ describe Spree::Api::ShipmentsController, type: :controller do
let!(:shipment) { create(:shipment) }
let!(:attributes) { [:id, :tracking, :number, :cost, :shipped_at, :stock_location_name, :order_id, :shipping_rates, :shipping_method, :inventory_units] }
let!(:resource_scoping) { { order_id: shipment.order.to_param, id: shipment.to_param } }
let(:current_api_user) { build(:user) }
before do
allow(controller).to receive(:spree_current_user) { current_api_user }
end
context "as a non-admin" do
it "cannot make a shipment ready" do
api_put :ready
assert_unauthorized!
end
it "cannot make a shipment shipped" do
api_put :ship
assert_unauthorized!
end
end
context "as an admin" do
let(:current_api_user) { build(:admin_user) }
let!(:order) { shipment.order }
let(:order_ship_address) { create(:address) }
let!(:stock_location) { create(:stock_location_with_items) }
@@ -45,6 +30,8 @@ describe Spree::Api::ShipmentsController, type: :controller do
shipment.shipping_method.distributors << variant.product.supplier
end
sign_in_as_admin!
context '#create' do
it 'creates a shipment if order does not have a shipment' do
order.shipment.destroy
@@ -90,62 +77,6 @@ describe Spree::Api::ShipmentsController, type: :controller do
end
end
it "can make a shipment ready" do
allow_any_instance_of(Spree::Order).to receive_messages(paid?: true, complete?: true)
api_put :ready
expect(attributes.all?{ |attr| json_response.key? attr.to_s }).to be_truthy
expect(json_response["state"]).to eq("ready")
expect(shipment.reload.state).to eq("ready")
end
it "cannot make a shipment ready if the order is unpaid" do
allow_any_instance_of(Spree::Order).to receive_messages(paid?: false)
api_put :ready
expect(json_response["error"]).to eq("Cannot ready shipment.")
expect(response.status).to eq(422)
end
context 'for completed shipments' do
let(:order) { create :completed_order_with_totals }
let!(:resource_scoping) { { order_id: order.to_param, id: order.shipments.first.to_param } }
it 'adds a variant to a shipment' do
api_put :add, variant_id: variant.to_param, quantity: 2
expect(response.status).to eq(200)
expect(json_response['inventory_units'].select { |h| h['variant_id'] == variant.id }.size).to eq(2)
end
it 'removes a variant from a shipment' do
order.contents.add(variant, 2)
api_put :remove, variant_id: variant.to_param, quantity: 1
expect(response.status).to eq(200)
expect(json_response['inventory_units'].select { |h| h['variant_id'] == variant.id }.size).to eq(1)
end
end
context "can transition a shipment from ready to ship" do
before do
allow_any_instance_of(Spree::Order).to receive_messages(paid?: true, complete?: true)
# For the shipment notification email
Spree::Config[:mails_from] = "spree@example.com"
shipment.update!(shipment.order)
expect(shipment.state).to eq("ready")
allow_any_instance_of(Spree::ShippingRate).to receive_messages(cost: 5)
end
it "can transition a shipment from ready to ship" do
shipment.reload
api_put :ship, order_id: shipment.order.to_param, id: shipment.to_param, shipment: { tracking: "123123" }
expect(attributes.all?{ |attr| json_response.key? attr.to_s }).to be_truthy
expect(json_response["state"]).to eq("shipped")
end
end
context 'for a completed order with shipment' do
let(:order) { create :completed_order_with_totals }

View File

@@ -1,135 +0,0 @@
require 'spec_helper'
module Spree
describe Api::TaxonsController do
render_views
let(:taxonomy) { create(:taxonomy) }
let(:taxon) { create(:taxon, name: "Ruby", taxonomy: taxonomy) }
let(:taxon2) { create(:taxon, name: "Rails", taxonomy: taxonomy) }
let(:attributes) {
["id", "name", "pretty_name", "permalink", "position", "parent_id", "taxonomy_id"]
}
before do
allow(controller).to receive(:spree_current_user) { current_api_user }
taxon2.children << create(:taxon, name: "3.2.2", taxonomy: taxonomy)
taxon.children << taxon2
taxonomy.root.children << taxon
end
context "as a normal user" do
let(:current_api_user) { build(:user) }
it "gets all taxons for a taxonomy" do
api_get :index, taxonomy_id: taxonomy.id
expect(json_response.first['name']).to eq taxon.name
children = json_response.first['taxons']
expect(children.count).to eq 1
expect(children.first['name']).to eq taxon2.name
expect(children.first['taxons'].count).to eq 1
end
it "gets all taxons" do
api_get :index
expect(json_response.first['name']).to eq taxonomy.root.name
children = json_response.first['taxons']
expect(children.count).to eq 1
expect(children.first['name']).to eq taxon.name
expect(children.first['taxons'].count).to eq 1
end
it "can search for a single taxon" do
api_get :index, q: { name_cont: "Ruby" }
expect(json_response.count).to eq(1)
expect(json_response.first['name']).to eq "Ruby"
end
it "gets a single taxon" do
api_get :show, id: taxon.id, taxonomy_id: taxonomy.id
expect(json_response['name']).to eq taxon.name
expect(json_response['taxons'].count).to eq 1
end
it "gets all taxons in JSTree form" do
api_get :jstree, taxonomy_id: taxonomy.id, id: taxon.id
response = json_response.first
response["data"].should eq(taxon2.name)
response["attr"].should eq("name" => taxon2.name, "id" => taxon2.id)
response["state"].should eq("closed")
end
it "can learn how to create a new taxon" do
api_get :new, taxonomy_id: taxonomy.id
expect(json_response["attributes"]).to eq(attributes.map(&:to_s))
required_attributes = json_response["required_attributes"]
expect(required_attributes).to include("name")
end
it "cannot create a new taxon if not an admin" do
api_post :create, taxonomy_id: taxonomy.id, taxon: { name: "Location" }
assert_unauthorized!
end
it "cannot update a taxon" do
api_put :update, taxonomy_id: taxonomy.id,
id: taxon.id,
taxon: { name: "I hacked your store!" }
assert_unauthorized!
end
it "cannot delete a taxon" do
api_delete :destroy, taxonomy_id: taxonomy.id, id: taxon.id
assert_unauthorized!
end
end
context "as an admin" do
let(:current_api_user) { build(:admin_user) }
it "can create" do
api_post :create, taxonomy_id: taxonomy.id, taxon: { name: "Colors" }
expect(attributes.all? { |a| json_response.include? a }).to be true
expect(response.status).to eq(201)
expect(taxonomy.reload.root.children.count).to eq 2
expect(Spree::Taxon.last.parent_id).to eq taxonomy.root.id
expect(Spree::Taxon.last.taxonomy_id).to eq taxonomy.id
end
it "cannot create a new taxon with invalid attributes" do
api_post :create, taxonomy_id: taxonomy.id, taxon: {}
expect(response.status).to eq(422)
expect(json_response["error"]).to eq("Invalid resource. Please fix errors and try again.")
errors = json_response["errors"]
expect(taxonomy.reload.root.children.count).to eq 1
end
it "cannot create a new taxon with invalid taxonomy_id" do
api_post :create, taxonomy_id: 1000, taxon: { name: "Colors" }
expect(response.status).to eq(422)
expect(json_response["error"]).to eq("Invalid resource. Please fix errors and try again.")
errors = json_response["errors"]
expect(errors["taxonomy_id"]).not_to be_nil
expect(errors["taxonomy_id"].first).to eq "Invalid taxonomy id."
expect(taxonomy.reload.root.children.count).to eq 1
end
it "can destroy" do
api_delete :destroy, taxonomy_id: taxonomy.id, id: taxon2.id
expect(response.status).to eq(204)
end
end
end
end

View File

@@ -0,0 +1,102 @@
require 'spec_helper'
module Spree
describe Spree::Api::VariantsController, type: :controller do
render_views
let(:supplier) { FactoryBot.create(:supplier_enterprise) }
let!(:variant1) { FactoryBot.create(:variant) }
let!(:variant2) { FactoryBot.create(:variant) }
let!(:variant3) { FactoryBot.create(:variant) }
let(:attributes) { [:id, :options_text, :price, :on_hand, :unit_value, :unit_description, :on_demand, :display_as, :display_name] }
before do
allow(controller).to receive(:spree_current_user) { current_api_user }
end
context "as a normal user" do
sign_in_as_user!
it "retrieves a list of variants with appropriate attributes" do
spree_get :index, template: 'bulk_index', format: :json
keys = json_response.first.keys.map(&:to_sym)
expect(attributes.all?{ |attr| keys.include? attr }).to eq(true)
end
it "is denied access when trying to delete a variant" do
product = create(:product)
variant = product.master
spree_delete :soft_delete, variant_id: variant.to_param, product_id: product.to_param, format: :json
assert_unauthorized!
expect { variant.reload }.not_to raise_error
expect(variant.deleted_at).to be_nil
end
end
context "as an enterprise user" do
sign_in_as_enterprise_user! [:supplier]
let(:supplier_other) { create(:supplier_enterprise) }
let(:product) { create(:product, supplier: supplier) }
let(:variant) { product.master }
let(:product_other) { create(:product, supplier: supplier_other) }
let(:variant_other) { product_other.master }
it "soft deletes a variant" do
spree_delete :soft_delete, variant_id: variant.to_param, product_id: product.to_param, format: :json
expect(response.status).to eq(204)
expect { variant.reload }.not_to raise_error
expect(variant.deleted_at).to be_present
end
it "is denied access to soft deleting another enterprises' variant" do
spree_delete :soft_delete, variant_id: variant_other.to_param, product_id: product_other.to_param, format: :json
assert_unauthorized!
expect { variant.reload }.not_to raise_error
expect(variant.deleted_at).to be_nil
end
context 'when the variant is not the master' do
before { variant.update_attribute(:is_master, false) }
it 'refreshes the cache' do
expect(OpenFoodNetwork::ProductsCache).to receive(:variant_destroyed).with(variant)
spree_delete :soft_delete, variant_id: variant.id, product_id: variant.product.permalink, format: :json
end
end
end
context "as an administrator" do
sign_in_as_admin!
let(:product) { create(:product) }
let(:variant) { product.master }
it "soft deletes a variant" do
spree_delete :soft_delete, variant_id: variant.to_param, product_id: product.to_param, format: :json
expect(response.status).to eq(204)
expect { variant.reload }.not_to raise_error
expect(variant.deleted_at).not_to be_nil
end
it "doesn't delete the only variant of the product" do
product = create(:product)
variant = product.variants.first
spree_delete :soft_delete, variant_id: variant.to_param, product_id: product.to_param, format: :json
expect(variant.reload).to_not be_deleted
expect(assigns(:variant).errors[:product]).to include "must have at least one variant"
end
context 'when the variant is not the master' do
before { variant.update_attribute(:is_master, false) }
it 'refreshes the cache' do
expect(OpenFoodNetwork::ProductsCache).to receive(:variant_destroyed).with(variant)
spree_delete :soft_delete, variant_id: variant.id, product_id: variant.product.permalink, format: :json
end
end
end
end
end

View File

@@ -1,4 +1,5 @@
require 'spec_helper'
require 'spree/api/testing_support/helpers'
require 'support/request/authentication_workflow'
describe Spree::CheckoutController, type: :controller do

View File

@@ -1,4 +1,5 @@
require 'spec_helper'
require 'spree/api/testing_support/helpers'
describe Spree::UsersController, type: :controller do
include AuthenticationWorkflow

View File

@@ -1,4 +1,5 @@
require 'spec_helper'
require 'spree/api/testing_support/helpers'
describe UserPasswordsController, type: :controller do
include OpenFoodNetwork::EmailHelper

View File

@@ -1,4 +1,5 @@
require 'spec_helper'
require 'spree/api/testing_support/helpers'
describe UserRegistrationsController, type: :controller do
include OpenFoodNetwork::EmailHelper

View File

@@ -41,6 +41,6 @@ FactoryBot.modify do
factory :shipping_method, parent: :base_shipping_method do
distributors { [Enterprise.is_distributor.first || FactoryBot.create(:distributor_enterprise)] }
display_on ''
zones { [] }
zones { |a| [] }
end
end

View File

@@ -437,7 +437,6 @@ feature '
visit spree.admin_products_path
select2_select s1.name, from: "producer_filter"
apply_filters
expect(page).to have_no_field "product_name", with: p2.name
fill_in "product_name", with: "new product1"
@@ -610,7 +609,6 @@ feature '
# Set a filter
select2_select s1.name, from: "producer_filter"
apply_filters
# Products are hidden when filtered out
expect(page).to have_field "product_name", with: p1.name
@@ -618,7 +616,6 @@ feature '
# Clearing filters
click_button "Clear Filters"
apply_filters
# All products are shown again
expect(page).to have_field "product_name", with: p1.name
@@ -792,8 +789,4 @@ feature '
expect(page).to have_selector "div.reveal-modal"
end
end
def apply_filters
page.find('.button.icon-search').click
end
end

View File

@@ -1,34 +0,0 @@
require "spec_helper"
feature "Managing inventory", js: true do
include AdminHelper
include AuthenticationWorkflow
include WebHelper
it "shows more than 100 products" do
supplier = create(:supplier_enterprise, sells: "own")
inventory_items = (1..101).map do
product = create(:simple_product, supplier: supplier)
InventoryItem.create!(
enterprise: supplier,
variant: product.variants.first,
visible: true
)
end
first_variant = inventory_items.first.variant
last_variant = inventory_items.last.variant
first_variant.product.update_attributes!(name: "A First Product")
last_variant.product.update_attributes!(name: "Z Last Product")
quick_login_as supplier.users.first
visit admin_inventory_path
expect(page).to have_text first_variant.name
expect(page).to have_selector "tr.product", count: 10
expect(page).to have_button "Show more"
expect(page).to have_button "Show all (91 More)"
click_button "Show all (91 More)"
expect(page).to have_selector "tr.product", count: 101
expect(page).to have_text last_variant.name
end
end

View File

@@ -170,7 +170,6 @@ feature "Product Import", js: true do
expect(page).to have_selector 'div#s2id_import_date_filter'
import_time = carrots.import_date.to_date.to_formatted_s(:long)
select2_select import_time, from: "import_date_filter"
page.find('.button.icon-search').click
expect(page).to have_field "product_name", with: carrots.name
expect(page).to have_field "product_name", with: potatoes.name

View File

@@ -27,7 +27,7 @@ feature "Packing Reports", js: true do
select oc.name, from: "q_order_cycle_id_in"
find('#q_completed_at_gt').click
select_date(Time.zone.today - 1.day)
select_date(Time.zone.today - 1.days)
find('#q_completed_at_lt').click
select_date(Time.zone.today)

View File

@@ -299,6 +299,13 @@ describe "AdminProductEditCtrl", ->
$scope.$digest()
expect($scope.resetProducts).toHaveBeenCalled()
it "sets the loading property to true before fetching products and unsets it when loading is complete", ->
$scope.fetchProducts()
expect($scope.loading).toEqual true
$scope.$digest()
expect($scope.loading).toEqual false
describe "resetting products", ->
beforeEach ->
spyOn DirtyProducts, "clear"

View File

@@ -8,6 +8,35 @@ describe "BulkProducts service", ->
BulkProducts = _BulkProducts_
$httpBackend = _$httpBackend_
describe "fetching products", ->
beforeEach ->
spyOn BulkProducts, 'addProducts'
it "makes a standard call to dataFetcher when no filters exist", ->
$httpBackend.expectGET("/api/products/bulk_products?page=1;per_page=20;").respond "list of products"
BulkProducts.fetch [], ->
$httpBackend.flush()
it "makes more calls to dataFetcher if more pages exist", ->
$httpBackend.expectGET("/api/products/bulk_products?page=1;per_page=20;").respond { products: [], pages: 2 }
$httpBackend.expectGET("/api/products/bulk_products?page=2;per_page=20;").respond { products: ["list of products"] }
BulkProducts.fetch [], ->
$httpBackend.flush()
it "applies filters when they are supplied", ->
filter =
property:
name: "Name"
db_column: "name"
predicate:
name: "Equals"
predicate: "eq"
value: "Product1"
$httpBackend.expectGET("/api/products/bulk_products?page=1;per_page=20;q[name_eq]=Product1;").respond "list of products"
BulkProducts.fetch [filter], ->
$httpBackend.flush()
describe "cloning products", ->
it "clones products using a http post request to /api/products/(id)/clone", ->
BulkProducts.products = [

View File

@@ -114,6 +114,7 @@ describe Enterprise do
subject { FactoryBot.create(:distributor_enterprise) }
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_uniqueness_of(:permalink) }
it { is_expected.to ensure_length_of(:description).is_at_most(255) }
it "requires an owner" do
expect{

View File

@@ -250,7 +250,7 @@ describe ProductImport::ProductImporter do
describe "updating an exiting variant" do
let(:csv_data) {
CSV.generate do |csv|
csv << ["name", "producer", "description", "category", "on_hand", "price", "units", "unit_type", "display_name", "shipping_category"]
csv << ["name", "producer", "description" ,"category", "on_hand", "price", "units", "unit_type", "display_name", "shipping_category"]
csv << ["Hypothetical Cake", "Another Enterprise", "New Description", "Cake", "5", "5.50", "500", "g", "Preexisting Banana", shipping_category.name]
end
}
@@ -530,6 +530,7 @@ describe ProductImport::ProductImporter do
}
let(:importer) { import_data csv_data, import_into: 'inventories' }
it "updates inventory item correctly" do
importer.save_entries
@@ -547,8 +548,8 @@ describe ProductImport::ProductImporter do
let!(:inventory) { InventoryItem.create(variant_id: product4.variants.first.id, enterprise_id: enterprise2.id, visible: false) }
let(:csv_data) {
CSV.generate do |csv|
csv << ["name", "distributor", "producer", "on_hand", "price", "units", "variant_unit_name"]
csv << ["Cabbage", "Another Enterprise", "User Enterprise", "900", "", "1", "Whole"]
csv << ["name", "distributor", "producer", "on_hand", "price", "units", "variant_unit_name"]
csv << ["Cabbage", "Another Enterprise", "User Enterprise", "900", "", "1", "Whole"]
end
}
let(:importer) { import_data csv_data, import_into: 'inventories' }

View File

@@ -385,7 +385,7 @@ describe Spree::Order do
let(:distributor) { create(:distributor_enterprise) }
let(:order_cycle) do
create(:order_cycle).tap do
create(:order_cycle).tap do |record|
create(:exchange, variants: [v1], incoming: true)
create(:exchange, variants: [v1], incoming: false, receiver: distributor)
end

View File

@@ -390,17 +390,6 @@ module Spree
expect(stockable_products).to_not include p3
end
end
describe "imported_on" do
let!(:v1) { create(:variant, import_date: 1.day.ago) }
let!(:v2) { create(:variant, import_date: 2.days.ago) }
let!(:v3) { create(:variant, import_date: 1.day.ago) }
it "returns products imported on given day" do
imported_products = Spree::Product.imported_on(1.day.ago.to_date)
expect(imported_products).to include v1.product, v3.product
end
end
end
describe "properties" do

View File

@@ -3,27 +3,11 @@ require 'spec_helper'
describe "checking out an order with a Stripe Connect payment method", type: :request do
include ShopWorkflow
include AuthenticationWorkflow
include OpenFoodNetwork::ApiHelper
let!(:order_cycle) { create(:simple_order_cycle) }
let!(:enterprise) { create(:distributor_enterprise) }
let!(:exchange) do
create(
:exchange,
order_cycle: order_cycle,
sender: order_cycle.coordinator,
receiver: enterprise,
incoming: false,
pickup_time: "Monday"
)
end
let!(:shipping_method) do
create(
:shipping_method,
calculator: Spree::Calculator::FlatRate.new(preferred_amount: 0),
distributors: [enterprise]
)
end
let!(:exchange) { create(:exchange, order_cycle: order_cycle, sender: order_cycle.coordinator, receiver: enterprise, incoming: false, pickup_time: "Monday") }
let!(:shipping_method) { create(:shipping_method, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 0), distributors: [enterprise]) }
let!(:payment_method) { create(:stripe_payment_method, distributors: [enterprise]) }
let!(:stripe_account) { create(:stripe_account, enterprise: enterprise) }
let!(:line_item) { create(:line_item, price: 12.34) }
@@ -33,48 +17,19 @@ describe "checking out an order with a Stripe Connect payment method", type: :re
let(:new_token) { "newtoken123" }
let(:card_id) { "card_XyZ456" }
let(:customer_id) { "cus_A123" }
let(:payments_attributes) do
{
payment_method_id: payment_method.id,
source_attributes: {
gateway_payment_profile_id: token,
cc_type: "visa",
last_digits: "4242",
month: 10,
year: 2025,
first_name: 'Jill',
last_name: 'Jeffreys'
}
}
end
let(:allowed_address_attributes) do
[
"firstname",
"lastname",
"address1",
"address2",
"phone",
"city",
"zipcode",
"state_id",
"country_id"
]
end
let(:params) do
{
format: :json, order: {
shipping_method_id: shipping_method.id,
payments_attributes: [payments_attributes],
bill_address_attributes: address.attributes.slice(*allowed_address_attributes),
ship_address_attributes: address.attributes.slice(*allowed_address_attributes)
}
}
{ format: :json, order: {
shipping_method_id: shipping_method.id,
payments_attributes: [{ payment_method_id: payment_method.id, source_attributes: { gateway_payment_profile_id: token, cc_type: "visa", last_digits: "4242", month: 10, year: 2025, first_name: 'Jill', last_name: 'Jeffreys' } }],
bill_address_attributes: address.attributes.slice("firstname", "lastname", "address1", "address2", "phone", "city", "zipcode", "state_id", "country_id"),
ship_address_attributes: address.attributes.slice("firstname", "lastname", "address1", "address2", "phone", "city", "zipcode", "state_id", "country_id")
} }
end
before do
order_cycle_distributed_variants = double(:order_cycle_distributed_variants)
allow(OrderCycleDistributedVariants).to receive(:new) { order_cycle_distributed_variants }
allow(order_cycle_distributed_variants).to receive(:distributes_order_variants?) { true }
allow(OrderCycleDistributedVariants).to receive(:new).and_return(order_cycle_distributed_variants)
allow(order_cycle_distributed_variants).to receive(:distributes_order_variants?).and_return(true)
allow(Stripe).to receive(:api_key) { "sk_test_12345" }
order.update_attributes(distributor_id: enterprise.id, order_cycle_id: order_cycle.id)
@@ -83,22 +38,9 @@ describe "checking out an order with a Stripe Connect payment method", type: :re
end
context "when a new card is submitted" do
let(:store_response_mock) do
{
status: 200,
body: JSON.generate(
id: customer_id,
default_card: card_id,
sources: { data: [{ id: "1" }] }
)
}
end
let(:token_response_mock) do
{ status: 200, body: JSON.generate(id: new_token) }
end
let(:charge_response_mock) do
{ status: 200, body: JSON.generate(id: "ch_1234", object: "charge", amount: 2000) }
end
let(:store_response_mock) { { status: 200, body: JSON.generate(id: customer_id, default_card: card_id, sources: { data: [{ id: "1" }] }) } }
let(:token_response_mock) { { status: 200, body: JSON.generate(id: new_token) } }
let(:charge_response_mock) { { status: 200, body: JSON.generate(id: "ch_1234", object: "charge", amount: 2000) } }
context "and the user doesn't request that the card is saved for later" do
before do
@@ -111,12 +53,10 @@ describe "checking out an order with a Stripe Connect payment method", type: :re
context "and the charge request is successful" do
it "should process the payment without storing card details" do
put update_checkout_path, params
json_response = JSON.parse(response.body)
expect(json_response["path"]).to eq spree.order_path(order)
expect(order.payments.completed.count).to be 1
card = order.payments.completed.first.source
expect(card.gateway_customer_profile_id).to eq nil
expect(card.gateway_payment_profile_id).to eq token
expect(card.cc_type).to eq "visa"
@@ -127,15 +67,12 @@ describe "checking out an order with a Stripe Connect payment method", type: :re
end
context "when the charge request returns an error message" do
let(:charge_response_mock) do
{ status: 402, body: JSON.generate(error: { message: "charge-failure" }) }
end
let(:charge_response_mock) { { status: 402, body: JSON.generate(error: { message: "charge-failure" }) } }
it "should not process the payment" do
put update_checkout_path, params
expect(response.status).to be 400
json_response = JSON.parse(response.body)
expect(json_response["flash"]["error"]).to eq "charge-failure"
expect(order.payments.completed.count).to be 0
end
@@ -144,8 +81,7 @@ describe "checking out an order with a Stripe Connect payment method", type: :re
context "and the customer requests that the card is saved for later" do
before do
source_attributes = params[:order][:payments_attributes][0][:source_attributes]
source_attributes[:save_requested_by_customer] = '1'
params[:order][:payments_attributes][0][:source_attributes][:save_requested_by_customer] = '1'
# Saves the card against the user
stub_request(:post, "https://api.stripe.com/v1/customers")
@@ -159,21 +95,16 @@ describe "checking out an order with a Stripe Connect payment method", type: :re
# Charges the card
stub_request(:post, "https://api.stripe.com/v1/charges")
.with(
basic_auth: ["sk_test_12345", ""],
body: /#{token}.*#{order.number}/
).to_return(charge_response_mock)
.with(basic_auth: ["sk_test_12345", ""], body: /#{token}.*#{order.number}/).to_return(charge_response_mock)
end
context "and the store, token and charge requests are successful" do
it "should process the payment, and stores the card/customer details" do
put update_checkout_path, params
json_response = JSON.parse(response.body)
expect(json_response["path"]).to eq spree.order_path(order)
expect(order.payments.completed.count).to be 1
card = order.payments.completed.first.source
expect(card.gateway_customer_profile_id).to eq customer_id
expect(card.gateway_payment_profile_id).to eq card_id
expect(card.cc_type).to eq "visa"
@@ -184,47 +115,37 @@ describe "checking out an order with a Stripe Connect payment method", type: :re
end
context "when the store request returns an error message" do
let(:store_response_mock) do
{ status: 402, body: JSON.generate(error: { message: "store-failure" }) }
end
let(:store_response_mock) { { status: 402, body: JSON.generate(error: { message: "store-failure" }) } }
it "should not process the payment" do
put update_checkout_path, params
expect(response.status).to be 400
expect(json_response["flash"]["error"])
.to eq(I18n.t(:spree_gateway_error_flash_for_checkout, error: 'store-failure'))
json_response = JSON.parse(response.body)
expect(json_response["flash"]["error"]).to eq I18n.t(:spree_gateway_error_flash_for_checkout, error: 'store-failure')
expect(order.payments.completed.count).to be 0
end
end
context "when the charge request returns an error message" do
let(:charge_response_mock) do
{ status: 402, body: JSON.generate(error: { message: "charge-failure" }) }
end
let(:charge_response_mock) { { status: 402, body: JSON.generate(error: { message: "charge-failure" }) } }
it "should not process the payment" do
put update_checkout_path, params
expect(response.status).to be 400
json_response = JSON.parse(response.body)
expect(json_response["flash"]["error"]).to eq "charge-failure"
expect(order.payments.completed.count).to be 0
end
end
context "when the token request returns an error message" do
let(:token_response_mock) do
{ status: 402, body: JSON.generate(error: { message: "token-failure" }) }
end
let(:token_response_mock) { { status: 402, body: JSON.generate(error: { message: "token-failure" }) } }
# Note, no requests have been stubbed
it "should not process the payment" do
put update_checkout_path, params
expect(response.status).to be 400
json_response = JSON.parse(response.body)
expect(json_response["flash"]["error"]).to eq "token-failure"
expect(order.payments.completed.count).to be 0
end
@@ -248,9 +169,7 @@ describe "checking out an order with a Stripe Connect payment method", type: :re
end
let(:token_response_mock) { { status: 200, body: JSON.generate(id: new_token) } }
let(:charge_response_mock) do
{ status: 200, body: JSON.generate(id: "ch_1234", object: "charge", amount: 2000) }
end
let(:charge_response_mock) { { status: 200, body: JSON.generate(id: "ch_1234", object: "charge", amount: 2000) } }
before do
params[:order][:existing_card_id] = credit_card.id
@@ -270,12 +189,10 @@ describe "checking out an order with a Stripe Connect payment method", type: :re
context "and the charge and token requests are accepted" do
it "should process the payment, and keep the profile ids and other card details" do
put update_checkout_path, params
json_response = JSON.parse(response.body)
expect(json_response["path"]).to eq spree.order_path(order)
expect(order.payments.completed.count).to be 1
card = order.payments.completed.first.source
expect(card.gateway_customer_profile_id).to eq customer_id
expect(card.gateway_payment_profile_id).to eq card_id
expect(card.cc_type).to eq "master"
@@ -286,30 +203,24 @@ describe "checking out an order with a Stripe Connect payment method", type: :re
end
context "when the charge request returns an error message" do
let(:charge_response_mock) do
{ status: 402, body: JSON.generate(error: { message: "charge-failure" }) }
end
let(:charge_response_mock) { { status: 402, body: JSON.generate(error: { message: "charge-failure" }) } }
it "should not process the payment" do
put update_checkout_path, params
expect(response.status).to be 400
json_response = JSON.parse(response.body)
expect(json_response["flash"]["error"]).to eq "charge-failure"
expect(order.payments.completed.count).to be 0
end
end
context "when the token request returns an error message" do
let(:token_response_mock) do
{ status: 402, body: JSON.generate(error: { message: "token-error" }) }
end
let(:token_response_mock) { { status: 402, body: JSON.generate(error: { message: "token-error" }) } }
it "should not process the payment" do
put update_checkout_path, params
expect(response.status).to be 400
json_response = JSON.parse(response.body)
expect(json_response["flash"]["error"]).to eq "token-error"
expect(order.payments.completed.count).to be 0
end

View File

@@ -39,9 +39,10 @@ Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
require 'spree/testing_support/controller_requests'
require 'spree/testing_support/capybara_ext'
require 'spree/api/testing_support/setup'
require 'spree/api/testing_support/helpers'
require 'spree/api/testing_support/helpers_decorator'
require 'spree/testing_support/authorization_helpers'
require 'spree/testing_support/preferences'
require 'support/api_helper'
# Capybara config
require 'selenium-webdriver'
@@ -126,6 +127,8 @@ RSpec.configure do |config|
spree_config.shipping_instructions = true
spree_config.auto_capture = true
end
Spree::Api::Config[:requires_authentication] = true
end
# Helpers
@@ -137,7 +140,7 @@ RSpec.configure do |config|
config.include Spree::TestingSupport::Preferences
config.include Devise::TestHelpers, type: :controller
config.extend Spree::Api::TestingSupport::Setup, type: :controller
config.include OpenFoodNetwork::ApiHelper, type: :controller
config.include Spree::Api::TestingSupport::Helpers, type: :controller
config.include OpenFoodNetwork::ControllerHelper, type: :controller
config.include Features::DatepickerHelper, type: :feature
config.include OpenFoodNetwork::FeatureToggleHelper

View File

@@ -11,18 +11,5 @@ module OpenFoodNetwork
json_response
end
end
def current_api_user
@current_api_user ||= Spree::LegacyUser.new(email: "spree@example.com", enterprises: [])
end
def assert_unauthorized!
expect(json_response).to eq("error" => "You are not authorized to perform that action.")
expect(response.status).to eq 401
end
def image(filename)
File.open(Rails.root + "spec/support/fixtures" + filename)
end
end
end

View File

@@ -1,34 +0,0 @@
require 'active_support/all'
module ControllerHacks
def api_get(action, params = {}, session = nil, flash = nil)
api_process(action, params, session, flash, "GET")
end
def api_post(action, params = {}, session = nil, flash = nil)
api_process(action, params, session, flash, "POST")
end
def api_put(action, params = {}, session = nil, flash = nil)
api_process(action, params, session, flash, "PUT")
end
def api_delete(action, params = {}, session = nil, flash = nil)
api_process(action, params, session, flash, "DELETE")
end
def api_process(action, params = {}, session = nil, flash = nil, method = "get")
scoping = respond_to?(:resource_scoping) ? resource_scoping : {}
process(action,
params.
merge(scoping).
reverse_merge!(use_route: :spree, format: :json),
session,
flash,
method)
end
end
RSpec.configure do |config|
config.include ControllerHacks, type: :controller
end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1,5 +1,5 @@
# From: https://robots.thoughtbot.com/better-tests-through-internationalization
I18n.exception_handler = lambda do |_exception, _locale, key, _options|
I18n.exception_handler = lambda do |exception, locale, key, options|
raise "missing translation: #{key}"
end

View File

@@ -8,22 +8,5 @@ module OpenFoodNetwork
ensure
Spree::Config.products_require_tax_category = original_value
end
shared_examples "modifying product actions are restricted" do
it "cannot create a new product if not an admin" do
api_post :create, product: { name: "Brand new product!" }
assert_unauthorized!
end
it "cannot update a product" do
api_put :update, id: product.to_param, product: { name: "I hacked your store!" }
assert_unauthorized!
end
it "cannot delete a product" do
api_delete :destroy, id: product.to_param
assert_unauthorized!
end
end
end
end

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