mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-05 22:26:07 +00:00
Merge branch 'master' into laura_and_will
This commit is contained in:
6
Gemfile
6
Gemfile
@@ -7,7 +7,11 @@ gem 'pg'
|
||||
gem 'spree', :github => 'openfoodfoundation/spree', :branch => '1-3-stable'
|
||||
gem 'spree_i18n', :github => 'spree/spree_i18n'
|
||||
gem 'spree_auth_devise', :github => 'spree/spree_auth_devise', :branch => '1-3-stable'
|
||||
gem 'spree_paypal_express', :github => "spree-contrib/better_spree_paypal_express", :branch => "1-3-stable"
|
||||
|
||||
# Waiting on merge of PR #117
|
||||
# https://github.com/spree-contrib/better_spree_paypal_express/pull/117
|
||||
gem 'spree_paypal_express', :github => "openfoodfoundation/better_spree_paypal_express", :branch => "1-3-stable"
|
||||
#gem 'spree_paypal_express', :github => "spree-contrib/better_spree_paypal_express", :branch => "1-3-stable"
|
||||
|
||||
gem 'comfortable_mexican_sofa'
|
||||
|
||||
|
||||
18
Gemfile.lock
18
Gemfile.lock
@@ -12,6 +12,15 @@ GIT
|
||||
specs:
|
||||
custom_error_message (1.1.1)
|
||||
|
||||
GIT
|
||||
remote: git://github.com/openfoodfoundation/better_spree_paypal_express.git
|
||||
revision: cdd61161ccd27cd8d183f9321422c7be113796b8
|
||||
branch: 1-3-stable
|
||||
specs:
|
||||
spree_paypal_express (2.0.3)
|
||||
paypal-sdk-merchant (= 1.106.1)
|
||||
spree_core (~> 1.3.4)
|
||||
|
||||
GIT
|
||||
remote: git://github.com/openfoodfoundation/spree.git
|
||||
revision: bbe5e779bcb883a1726ad4006d7c06b06c3f5372
|
||||
@@ -54,15 +63,6 @@ GIT
|
||||
spree_sample (1.3.6.beta)
|
||||
spree_core (= 1.3.6.beta)
|
||||
|
||||
GIT
|
||||
remote: git://github.com/spree-contrib/better_spree_paypal_express.git
|
||||
revision: db135b89a289aaab951c1228bcc55871de0cbba7
|
||||
branch: 1-3-stable
|
||||
specs:
|
||||
spree_paypal_express (2.0.3)
|
||||
paypal-sdk-merchant (= 1.106.1)
|
||||
spree_core (~> 1.3.4)
|
||||
|
||||
GIT
|
||||
remote: git://github.com/spree/deface.git
|
||||
revision: 1110a1336252109bce7f98f9182042e0bc2930ae
|
||||
|
||||
@@ -9,7 +9,6 @@ Supported by the Open Food Foundation, we are proudly open source and not-for-pr
|
||||
|
||||
We're part of global movement - get involved!
|
||||
|
||||
* We're crowd-funding RIGHT NOW - please help out at http://startsomegood.com/openfoodnetwork
|
||||
* Fill in this short survey to tell us who you are and what you want to do with OFN: https://docs.google.com/a/eaterprises.com.au/forms/d/1zxR5vSiU9CigJ9cEaC8-eJLgYid8CR8er7PPH9Mc-30/edit#
|
||||
* Find out more and join in the conversation - http://openfoodnetwork.org
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
angular.module("ofn.admin", ["ngResource", "ngAnimate", "ofn.dropdown", "admin.products"]).config ($httpProvider) ->
|
||||
angular.module("ofn.admin", ["ngResource", "ngAnimate", "ofn.dropdown", "admin.products", "infinite-scroll"]).config ($httpProvider) ->
|
||||
$httpProvider.defaults.headers.common["X-CSRF-Token"] = $("meta[name=csrf-token]").attr("content")
|
||||
$httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"
|
||||
@@ -16,6 +16,7 @@
|
||||
//= require admin/spree_auth
|
||||
//= require admin/spree_promo
|
||||
//= require admin/spree_paypal_express
|
||||
//= require ../shared/ng-infinite-scroll.min.js
|
||||
//= require ./admin
|
||||
//= require ./enterprises/enterprises
|
||||
//= require ./payment_methods/payment_methods
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
"$scope", "$timeout", "$http", "dataFetcher", "DirtyProducts", "VariantUnitManager",
|
||||
($scope, $timeout, $http, dataFetcher, DirtyProducts, VariantUnitManager) ->
|
||||
"$scope", "$timeout", "$http", "dataFetcher", "DirtyProducts", "VariantUnitManager", "producers", "Taxons",
|
||||
($scope, $timeout, $http, dataFetcher, DirtyProducts, VariantUnitManager, producers, Taxons) ->
|
||||
$scope.updateStatusMessage =
|
||||
text: ""
|
||||
style: {}
|
||||
|
||||
$scope.columns =
|
||||
supplier: {name: "Supplier", visible: true}
|
||||
producer: {name: "Producer", visible: true}
|
||||
name: {name: "Name", visible: true}
|
||||
unit: {name: "Unit", visible: true}
|
||||
price: {name: "Price", visible: true}
|
||||
on_hand: {name: "On Hand", visible: true}
|
||||
taxons: {name: "Taxons", visible: false}
|
||||
category: {name: "Category", visible: false}
|
||||
available_on: {name: "Available On", visible: false}
|
||||
|
||||
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
|
||||
|
||||
$scope.filterableColumns = [
|
||||
{ name: "Supplier", db_column: "supplier_name" },
|
||||
{ name: "Producer", db_column: "producer_name" },
|
||||
{ name: "Name", db_column: "name" }
|
||||
]
|
||||
|
||||
@@ -28,25 +28,20 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
|
||||
$scope.optionTabs =
|
||||
filters: { title: "Filter Products", visible: false }
|
||||
column_toggle: { title: "Toggle Columns", visible: false }
|
||||
|
||||
$scope.perPage = 25
|
||||
$scope.currentPage = 1
|
||||
|
||||
$scope.producers = producers
|
||||
$scope.taxons = Taxons.taxons
|
||||
$scope.filterProducers = [{id: "0", name: ""}].concat $scope.producers
|
||||
$scope.filterTaxons = [{id: "0", name: ""}].concat $scope.taxons
|
||||
$scope.producerFilter = "0"
|
||||
$scope.categoryFilter = "0"
|
||||
$scope.products = []
|
||||
$scope.filteredProducts = []
|
||||
$scope.currentFilters = []
|
||||
$scope.totalCount = -> $scope.filteredProducts.length
|
||||
$scope.totalPages = -> Math.ceil($scope.totalCount()/$scope.perPage)
|
||||
$scope.firstVisibleProduct = -> ($scope.currentPage-1)*$scope.perPage+1
|
||||
$scope.lastVisibleProduct = -> Math.min($scope.totalCount(),$scope.currentPage*$scope.perPage)
|
||||
$scope.setPage = (page) -> $scope.currentPage = page
|
||||
$scope.minPage = -> Math.max(1,Math.min($scope.totalPages()-4,$scope.currentPage-2))
|
||||
$scope.maxPage = -> Math.min($scope.totalPages(),Math.max(5,$scope.currentPage+2))
|
||||
$scope.limit = 15
|
||||
$scope.productsWithUnsavedVariants = []
|
||||
|
||||
$scope.$watch ->
|
||||
$scope.totalPages()
|
||||
, (newVal, oldVal) ->
|
||||
$scope.currentPage = Math.max $scope.totalPages(), 1 if newVal != oldVal && $scope.totalPages() < $scope.currentPage
|
||||
|
||||
$scope.initialise = (spree_api_key) ->
|
||||
authorise_api_reponse = ""
|
||||
@@ -55,24 +50,29 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
$scope.spree_api_key_ok = data.hasOwnProperty("success") and data["success"] == "Use of API Authorised"
|
||||
if $scope.spree_api_key_ok
|
||||
$http.defaults.headers.common["X-Spree-Token"] = spree_api_key
|
||||
dataFetcher("/api/enterprises/managed?template=bulk_index&q[is_primary_producer_eq]=true").then (data) ->
|
||||
$scope.suppliers = data
|
||||
# Need to have suppliers before we get products so we can match suppliers to product.supplier
|
||||
$scope.fetchProducts()
|
||||
$scope.fetchProducts()
|
||||
else if authorise_api_reponse.hasOwnProperty("error")
|
||||
$scope.api_error_msg = authorise_api_reponse("error")
|
||||
else
|
||||
api_error_msg = "You don't have an API key yet. An attempt was made to generate one, but you are currently not authorised, please contact your site administrator for access."
|
||||
|
||||
$scope.$watchCollection '[query, producerFilter, categoryFilter]', ->
|
||||
$scope.limit = 15 # Reset limit whenever searching
|
||||
|
||||
$scope.fetchProducts = -> # WARNING: returns a promise
|
||||
$scope.loading = true
|
||||
queryString = $scope.currentFilters.reduce (qs,f) ->
|
||||
return qs + "q[#{f.property.db_column}_#{f.predicate.predicate}]=#{f.value};"
|
||||
, ""
|
||||
return dataFetcher("/api/products/managed?template=bulk_index;page=1;per_page=500;#{queryString}").then (data) ->
|
||||
$scope.resetProducts data
|
||||
return dataFetcher("/api/products/bulk_products?page=1;per_page=20;#{queryString}").then (data) ->
|
||||
$scope.resetProducts data.products
|
||||
$scope.loading = false
|
||||
if data.pages > 1
|
||||
for page in [2..data.pages]
|
||||
dataFetcher("/api/products/bulk_products?page=#{page};per_page=20;#{queryString}").then (data) ->
|
||||
for product in data.products
|
||||
$scope.unpackProduct product
|
||||
$scope.products.push product
|
||||
|
||||
|
||||
$scope.resetProducts = (data) ->
|
||||
@@ -87,17 +87,15 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
$scope.unpackProduct = (product) ->
|
||||
$scope.displayProperties ||= {}
|
||||
$scope.displayProperties[product.id] ||= showVariants: false
|
||||
$scope.matchSupplier product
|
||||
#$scope.matchProducer product
|
||||
$scope.loadVariantUnit product
|
||||
|
||||
|
||||
$scope.matchSupplier = (product) ->
|
||||
for i of $scope.suppliers
|
||||
supplier = $scope.suppliers[i]
|
||||
if angular.equals(supplier, product.supplier)
|
||||
product.supplier = supplier
|
||||
break
|
||||
|
||||
# $scope.matchProducer = (product) ->
|
||||
# for producer in $scope.producers
|
||||
# if angular.equals(producer.id, product.producer)
|
||||
# product.producer = producer
|
||||
# break
|
||||
|
||||
$scope.loadVariantUnit = (product) ->
|
||||
product.variant_unit_with_scale =
|
||||
@@ -108,13 +106,14 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
else
|
||||
null
|
||||
|
||||
if product.variants
|
||||
for variant in product.variants
|
||||
$scope.loadVariantVariantUnit product, variant
|
||||
$scope.loadVariantVariantUnit product, product.master if product.master
|
||||
$scope.loadVariantUnitValues product if product.variants
|
||||
$scope.loadVariantUnitValue product, product.master if product.master
|
||||
|
||||
$scope.loadVariantUnitValues = (product) ->
|
||||
for variant in product.variants
|
||||
$scope.loadVariantUnitValue product, variant
|
||||
|
||||
$scope.loadVariantVariantUnit = (product, variant) ->
|
||||
$scope.loadVariantUnitValue = (product, variant) ->
|
||||
unit_value = $scope.variantUnitValue product, variant
|
||||
unit_value = if unit_value? then unit_value else ''
|
||||
variant.unit_value_with_description = "#{unit_value} #{variant.unit_description || ''}".trim()
|
||||
@@ -153,29 +152,10 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
tab.visible = !tab.visible
|
||||
$scope.visibleTab = tab
|
||||
|
||||
$scope.addFilter = (filter) ->
|
||||
existingfilterIndex = $scope.indexOfFilter filter
|
||||
if $scope.filterableColumns.indexOf(filter.property) >= 0 && $scope.filterTypes.indexOf(filter.predicate) >= 0 && filter.value != "" && filter.value != undefined
|
||||
if (DirtyProducts.count() > 0 and confirm("Unsaved changes will be lost. Continue anyway?")) or (DirtyProducts.count() == 0)
|
||||
if existingfilterIndex == -1
|
||||
$scope.currentFilters.push filter
|
||||
$scope.fetchProducts()
|
||||
else if confirm("'#{filter.predicate.name}' filter already exists on column '#{filter.property.name}'. Replace it?")
|
||||
$scope.currentFilters[existingfilterIndex] = filter
|
||||
$scope.fetchProducts()
|
||||
else
|
||||
alert("Please ensure all filter fields are filled in before adding a filter.")
|
||||
|
||||
$scope.removeFilter = (filter) ->
|
||||
index = $scope.currentFilters.indexOf(filter)
|
||||
if index != -1
|
||||
$scope.currentFilters.splice index, 1
|
||||
$scope.fetchProducts()
|
||||
|
||||
$scope.indexOfFilter = (filter) ->
|
||||
for existingFilter, i in $scope.currentFilters
|
||||
return i if filter.property == existingFilter.property && filter.predicate == existingFilter.predicate
|
||||
return -1
|
||||
$scope.resetSelectFilters = ->
|
||||
$scope.query = ""
|
||||
$scope.producerFilter = "0"
|
||||
$scope.categoryFilter = "0"
|
||||
|
||||
$scope.editWarn = (product, variant) ->
|
||||
if (DirtyProducts.count() > 0 and confirm("Unsaved changes will be lost. Continue anyway?")) or (DirtyProducts.count() == 0)
|
||||
@@ -192,6 +172,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
display_name: null
|
||||
on_hand: null
|
||||
price: null
|
||||
$scope.productsWithUnsavedVariants.push product
|
||||
$scope.displayProperties[product.id].showVariants = true
|
||||
|
||||
|
||||
@@ -200,6 +181,11 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
$scope.variantIdCounter -= 1
|
||||
$scope.variantIdCounter
|
||||
|
||||
$scope.updateVariantLists = (server_products) ->
|
||||
for product in $scope.productsWithUnsavedVariants
|
||||
server_product = $scope.findProduct(product.id, server_products)
|
||||
product.variants = server_product.variants
|
||||
$scope.loadVariantUnitValues product
|
||||
|
||||
$scope.deleteProduct = (product) ->
|
||||
if confirm("Are you sure?")
|
||||
@@ -244,7 +230,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
|
||||
|
||||
$scope.hasVariants = (product) ->
|
||||
Object.keys(product.variants).length > 0
|
||||
product.variants.length > 0
|
||||
|
||||
|
||||
$scope.hasUnit = (product) ->
|
||||
@@ -269,7 +255,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
if productsToSubmit.length > 0
|
||||
$scope.updateProducts productsToSubmit # Don't submit an empty list
|
||||
else
|
||||
$scope.setMessage $scope.updateStatusMessage, "No changes to update.", color: "grey", 3000
|
||||
$scope.setMessage $scope.updateStatusMessage, "No changes to save.", color: "grey", 3000
|
||||
|
||||
|
||||
$scope.updateProducts = (productsToSubmit) ->
|
||||
@@ -281,22 +267,16 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
products: productsToSubmit
|
||||
filters: $scope.currentFilters
|
||||
).success((data) ->
|
||||
# TODO: remove this check altogether, need to write controller tests if we want to test this behaviour properly
|
||||
# Note: Rob implemented subset(), which is a simpler alternative to productsWithoutDerivedAttributes(). However, it
|
||||
# conflicted with some changes I made before merging my work, so for now I've reverted to the old way of
|
||||
# doing things. TODO: Review together and decide on strategy here. -- Rohan, 14-1-2014
|
||||
#if subset($scope.productsWithoutDerivedAttributes(), data)
|
||||
if $scope.productListsMatch $scope.products, data
|
||||
$scope.resetProducts data
|
||||
$timeout -> $scope.displaySuccess()
|
||||
else
|
||||
# console.log angular.toJson($scope.productsWithoutDerivedAttributes($scope.products))
|
||||
# console.log "---"
|
||||
# console.log angular.toJson($scope.productsWithoutDerivedAttributes(data))
|
||||
# console.log "---"
|
||||
$scope.displayFailure "Product lists do not match."
|
||||
DirtyProducts.clear()
|
||||
$scope.updateVariantLists(data.products)
|
||||
$timeout -> $scope.displaySuccess()
|
||||
).error (data, status) ->
|
||||
$scope.displayFailure "Server returned with error status: " + status
|
||||
if status == 400 && data.errors? && data.errors.length > 0
|
||||
errors = error + "\n" for error in data.errors
|
||||
alert "Saving failed with the following error(s):\n" + errors
|
||||
$scope.displayFailure "Save failed due to invalid data"
|
||||
else
|
||||
$scope.displayFailure "Server returned with error status: " + status
|
||||
|
||||
|
||||
$scope.packProduct = (product) ->
|
||||
@@ -322,58 +302,19 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
if variant.hasOwnProperty("unit_value_with_description")
|
||||
match = variant.unit_value_with_description.match(/^([\d\.]+(?= |$)|)( |)(.*)$/)
|
||||
if match
|
||||
product = $scope.findProduct(product.id)
|
||||
product = $scope.findProduct(product.id, $scope.products)
|
||||
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.productListsMatch = (clientProducts, serverProducts) ->
|
||||
$scope.copyNewVariantIds clientProducts, serverProducts
|
||||
angular.toJson($scope.productsWithoutDerivedAttributes(clientProducts)) == angular.toJson($scope.productsWithoutDerivedAttributes(serverProducts))
|
||||
|
||||
|
||||
# When variants are created clientside, they are given a negative id. The server
|
||||
# responds with a real id, which would cause the productListsMatch() check to fail.
|
||||
# To avoid that false negative, we copy the server variant id to the client for any
|
||||
# negative ids.
|
||||
$scope.copyNewVariantIds = (clientProducts, serverProducts) ->
|
||||
if clientProducts?
|
||||
for product, i in clientProducts
|
||||
if product.variants?
|
||||
for variant, j in product.variants
|
||||
if variant.id < 0
|
||||
variant.id = serverProducts[i].variants[j].id
|
||||
|
||||
|
||||
$scope.productsWithoutDerivedAttributes = (products) ->
|
||||
products_filtered = []
|
||||
if products
|
||||
products_filtered = $scope.deepCopyProducts products
|
||||
for product in products_filtered
|
||||
delete product.variant_unit_with_scale
|
||||
if product.variants
|
||||
for variant in product.variants
|
||||
delete variant.unit_value_with_description
|
||||
# If we end up live-updating this field, we might want to reinstate its verification here
|
||||
delete variant.options_text
|
||||
delete product.master
|
||||
products_filtered
|
||||
|
||||
|
||||
$scope.deepCopyProducts = (products) ->
|
||||
copied_products = (angular.extend {}, product for product in products)
|
||||
for product in copied_products
|
||||
if product.variants
|
||||
product.variants = (angular.extend {}, variant for variant in product.variants)
|
||||
copied_products
|
||||
|
||||
|
||||
$scope.findProduct = (id) ->
|
||||
products = (product for product in $scope.products when product.id == id)
|
||||
$scope.findProduct = (id, product_list) ->
|
||||
products = (product for product in product_list when product.id == id)
|
||||
if products.length == 0 then null else products[0]
|
||||
|
||||
$scope.incrementLimit = ->
|
||||
if $scope.limit < $scope.products.length
|
||||
$scope.limit = $scope.limit + 5
|
||||
|
||||
$scope.setMessage = (model, text, style, timeout) ->
|
||||
model.text = text
|
||||
@@ -386,26 +327,27 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
|
||||
|
||||
$scope.displayUpdating = ->
|
||||
$scope.setMessage $scope.updateStatusMessage, "Updating...",
|
||||
color: "orange"
|
||||
$scope.setMessage $scope.updateStatusMessage, "Saving...",
|
||||
color: "#FF9906"
|
||||
, false
|
||||
|
||||
|
||||
$scope.displaySuccess = ->
|
||||
$scope.setMessage $scope.updateStatusMessage, "Update complete",
|
||||
color: "green"
|
||||
$scope.setMessage $scope.updateStatusMessage, "Changes saved.",
|
||||
color: "#9fc820"
|
||||
, 3000
|
||||
|
||||
|
||||
$scope.displayFailure = (failMessage) ->
|
||||
$scope.setMessage $scope.updateStatusMessage, "Updating failed. " + failMessage,
|
||||
color: "red"
|
||||
, 10000
|
||||
$scope.setMessage $scope.updateStatusMessage, "Saving failed. " + failMessage,
|
||||
color: "#DA5354"
|
||||
, false
|
||||
|
||||
|
||||
$scope.displayDirtyProducts = ->
|
||||
if DirtyProducts.count() > 0
|
||||
$scope.setMessage $scope.updateStatusMessage, "Changes to " + DirtyProducts.count() + " products remain unsaved.",
|
||||
message = if DirtyProducts.count() == 1 then "one product" else DirtyProducts.count() + " products"
|
||||
$scope.setMessage $scope.updateStatusMessage, "Changes to " + message + " remain unsaved.",
|
||||
color: "gray"
|
||||
, false
|
||||
else
|
||||
@@ -442,8 +384,8 @@ filterSubmitProducts = (productsToFilter) ->
|
||||
if product.hasOwnProperty("name")
|
||||
filteredProduct.name = product.name
|
||||
hasUpdatableProperty = true
|
||||
if product.hasOwnProperty("supplier")
|
||||
filteredProduct.supplier_id = product.supplier.id
|
||||
if product.hasOwnProperty("producer")
|
||||
filteredProduct.supplier_id = product.producer
|
||||
hasUpdatableProperty = true
|
||||
if product.hasOwnProperty("price")
|
||||
filteredProduct.price = product.price
|
||||
@@ -458,8 +400,8 @@ filterSubmitProducts = (productsToFilter) ->
|
||||
if product.hasOwnProperty("on_hand") and filteredVariants.length == 0 #only update if no variants present
|
||||
filteredProduct.on_hand = product.on_hand
|
||||
hasUpdatableProperty = true
|
||||
if product.hasOwnProperty("taxon_ids")
|
||||
filteredProduct.taxon_ids = product.taxon_ids
|
||||
if product.hasOwnProperty("category")
|
||||
filteredProduct.primary_taxon_id = product.category
|
||||
hasUpdatableProperty = true
|
||||
if product.hasOwnProperty("available_on")
|
||||
filteredProduct.available_on = product.available_on
|
||||
@@ -510,11 +452,3 @@ toObjectWithIDKeys = (array) ->
|
||||
object[array[i].id].variants = toObjectWithIDKeys(array[i].variants) if array[i].hasOwnProperty("variants") and array[i].variants instanceof Array
|
||||
|
||||
object
|
||||
|
||||
subset = (bigArray,smallArray) ->
|
||||
if smallArray instanceof Array && bigArray instanceof Array && smallArray.length > 0
|
||||
for item in smallArray
|
||||
return false if angular.toJson(bigArray).indexOf(angular.toJson(item)) == -1
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
angular.module("ofn.admin").controller "ProvidersCtrl", ($scope, paymentMethod) ->
|
||||
if paymentMethod.type
|
||||
$scope.include_html = "/admin/payment_methods/show_provider_preferences?" +
|
||||
"provider_type=#{paymentMethod.type};" +
|
||||
"pm_id=#{paymentMethod.id};"
|
||||
else
|
||||
$scope.include_html = ""
|
||||
@@ -0,0 +1,7 @@
|
||||
angular.module("ofn.admin").directive "providerPrefsFor", ($http) ->
|
||||
link: (scope, element, attrs) ->
|
||||
element.on "change blur load", ->
|
||||
scope.$apply ->
|
||||
scope.include_html = "/admin/payment_methods/show_provider_preferences?" +
|
||||
"provider_type=#{element.val()};" +
|
||||
"pm_id=#{attrs.providerPrefsFor};"
|
||||
@@ -4,18 +4,16 @@ angular.module("ofn.admin").directive "ofnTaxonAutocomplete", (Taxons) ->
|
||||
link: (scope,element,attrs,ngModel) ->
|
||||
setTimeout ->
|
||||
element.select2
|
||||
placeholder: Spree.translations.taxon_placeholder
|
||||
multiple: true
|
||||
placeholder: "Category"
|
||||
multiple: false
|
||||
initSelection: (element, callback) ->
|
||||
Taxons.findByIDs(element.val()).$promise.then (result) ->
|
||||
callback Taxons.cleanTaxons(result)
|
||||
callback Taxons.findByID(scope.product.category)
|
||||
query: (query) ->
|
||||
Taxons.findByTerm(query.term).$promise.then (result) ->
|
||||
query.callback { results: Taxons.cleanTaxons(result) }
|
||||
query.callback { results: Taxons.findByTerm(query.term) }
|
||||
formatResult: (taxon) ->
|
||||
taxon.pretty_name
|
||||
taxon.name
|
||||
formatSelection: (taxon) ->
|
||||
taxon.pretty_name
|
||||
taxon.name
|
||||
element.on "change", ->
|
||||
scope.$apply ->
|
||||
ngModel.$setViewValue element.val()
|
||||
@@ -0,0 +1,4 @@
|
||||
angular.module("ofn.admin").filter "category", ($filter) ->
|
||||
return (products, taxonID) ->
|
||||
return products if taxonID == "0"
|
||||
return $filter('filter')( products, { category: taxonID }, true )
|
||||
@@ -0,0 +1,4 @@
|
||||
angular.module("ofn.admin").filter "producer", ($filter) ->
|
||||
return (products, producerID) ->
|
||||
return products if producerID == "0"
|
||||
$filter('filter')( products, { producer: producerID }, true )
|
||||
@@ -0,0 +1,7 @@
|
||||
angular.module("ofn.admin").filter "taxonsTermFilter", ->
|
||||
return (lineItems,selectedSupplier,selectedDistributor,selectedOrderCycle) ->
|
||||
filtered = []
|
||||
filtered.push lineItem for lineItem in lineItems when (angular.equals(selectedSupplier,"0") || lineItem.supplier.id == selectedSupplier) &&
|
||||
(angular.equals(selectedDistributor,"0") || lineItem.order.distributor.id == selectedDistributor) &&
|
||||
(angular.equals(selectedOrderCycle,"0") || lineItem.order.order_cycle.id == selectedOrderCycle)
|
||||
filtered
|
||||
@@ -1,13 +1,15 @@
|
||||
angular.module("ofn.admin").factory "Taxons", ($resource) ->
|
||||
resource = $resource "/admin/taxons/search"
|
||||
angular.module("ofn.admin").factory "Taxons", (taxons, $filter) ->
|
||||
new class Taxons
|
||||
constructor: ->
|
||||
@taxons = taxons
|
||||
|
||||
return {
|
||||
# For finding a single Taxon
|
||||
findByID: (id) ->
|
||||
$filter('filter')(@taxons, {id: id}, true)[0]
|
||||
|
||||
# For finding multiple Taxons represented by comma delimited string
|
||||
findByIDs: (ids) ->
|
||||
resource.get { ids: ids }
|
||||
taxon for taxon in @taxons when taxon.id.toString() in ids.split(",")
|
||||
|
||||
findByTerm: (term) ->
|
||||
resource.get { q: term }
|
||||
|
||||
cleanTaxons: (data) ->
|
||||
data['taxons'].map (result) -> result
|
||||
}
|
||||
$filter('filter')(@taxons, term)
|
||||
@@ -11,7 +11,6 @@
|
||||
#= require lodash.underscore.js
|
||||
#= require angular-scroll.min.js
|
||||
#= require angular-google-maps.min.js
|
||||
#= require angular-timer.min.js
|
||||
#= require ../shared/mm-foundation-tpls-0.2.2.min.js
|
||||
#= require ../shared/bindonce.min.js
|
||||
#= require ../shared/ng-infinite-scroll.min.js
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
Darkswarm.controller "LoginCtrl", ($scope, $http, AuthenticationService, Redirections, Loading) ->
|
||||
Darkswarm.controller "LoginCtrl", ($scope, $http, $window, AuthenticationService, Redirections, Loading) ->
|
||||
$scope.path = "/login"
|
||||
|
||||
$scope.submit = ->
|
||||
Loading.message = "Hold on a moment, we're logging you in"
|
||||
$http.post("/user/spree_user/sign_in", {spree_user: $scope.spree_user}).success (data)->
|
||||
if Redirections.after_login
|
||||
location.href = location.origin + Redirections.after_login
|
||||
$window.location.href = $window.location.origin + Redirections.after_login
|
||||
else
|
||||
location.href = location.origin + location.pathname # Strips out hash fragments
|
||||
$window.location.href = $window.location.origin + $window.location.pathname # Strips out hash fragments
|
||||
.error (data) ->
|
||||
Loading.clear()
|
||||
$scope.errors = data.message
|
||||
|
||||
@@ -3,7 +3,7 @@ Darkswarm.controller "CheckoutCtrl", ($scope, storage, Checkout, CurrentUser, Cu
|
||||
|
||||
# Bind to local storage
|
||||
$scope.fieldsToBind = ["bill_address", "email", "payment_method_id", "shipping_method_id", "ship_address"]
|
||||
prefix = "order_#{Checkout.order.id}#{Checkout.order.user_id}#{CurrentHub.hub.id}"
|
||||
prefix = "order_#{Checkout.order.id}#{CurrentUser?.id}#{CurrentHub.hub.id}"
|
||||
|
||||
for field in $scope.fieldsToBind
|
||||
storage.bind $scope, "Checkout.order.#{field}",
|
||||
|
||||
@@ -5,7 +5,6 @@ window.Darkswarm = angular.module("Darkswarm", ["ngResource",
|
||||
'infinite-scroll',
|
||||
'angular-flash.service',
|
||||
'templates',
|
||||
'timer',
|
||||
'ngSanitize',
|
||||
'ngAnimate',
|
||||
'google-maps',
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
%li{"bo-if" => "variant.fees.transport"}
|
||||
.right {{ variant.fees.transport | currency }}
|
||||
Transport fee
|
||||
%li{"bo-if" => "variant.fees.fundraising"}
|
||||
.right {{ variant.fees.fundraising | currency }}
|
||||
Fundraising fee
|
||||
%li
|
||||
%strong
|
||||
.right = {{ variant.price | currency }}
|
||||
|
||||
@@ -140,6 +140,24 @@ table#listing_enterprise_groups {
|
||||
}
|
||||
}
|
||||
|
||||
#no_results {
|
||||
font-weight:bold;
|
||||
color: #DA5354;
|
||||
}
|
||||
|
||||
|
||||
#loading {
|
||||
text-align: center;
|
||||
img.spinner {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
h1 {
|
||||
margin-top: 20px;
|
||||
color: gray;
|
||||
}
|
||||
}
|
||||
|
||||
.ofn_drop_down {
|
||||
padding: 7px 15px;
|
||||
border-radius: 3px;
|
||||
|
||||
@@ -2,59 +2,6 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
div.pagination {
|
||||
div.pagenav {
|
||||
margin: 0px;
|
||||
span.first, span.prev, span.next, span.last {
|
||||
padding: 5px 0px;
|
||||
display:inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.pagination_info {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
|
||||
div.applied_filter {
|
||||
margin-bottom: 5px;
|
||||
border: solid 2px #5498da;
|
||||
padding: 5px 0px;
|
||||
border-radius: 5px;
|
||||
div.four.columns {
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
div.option_tabs {
|
||||
div.applied_filters, div.filters, div.column_toggle {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
div.option_tab_titles {
|
||||
h6 {
|
||||
border-radius: 3px;
|
||||
border: 1px solid #cee1f4;
|
||||
padding: 3px;
|
||||
text-align: center;
|
||||
color: darken(#cee1f4, 3);
|
||||
cursor: pointer;
|
||||
-moz-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
h6.selected {
|
||||
border: 1px solid #5498da;
|
||||
color: #5498da;
|
||||
}
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
tbody.odd {
|
||||
tr.product { td { background-color: white; } }
|
||||
tr.variant.odd { td { background-color: lighten(#eff5fc, 3); } }
|
||||
@@ -76,41 +23,41 @@ th.left-actions, td.left-actions {
|
||||
border-right: 1px solid #cee1f4 !important;
|
||||
}
|
||||
|
||||
li.column-list-item {
|
||||
border-radius: 3px;
|
||||
padding: 2px 20px;
|
||||
margin: 2px 1px;
|
||||
background-color: white;
|
||||
border: 2px solid lightgray;
|
||||
color: darkgray;
|
||||
font-size: 100%;
|
||||
cursor: default;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
-moz-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
li.column-list-item.selected {
|
||||
border: 2px solid #5498da;
|
||||
background-color: #5498da;
|
||||
color: white;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
ul.column-list {
|
||||
list-style: none;
|
||||
#update-status-message {
|
||||
margin: 4px 0px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table#listing_products.bulk {
|
||||
clear: both;
|
||||
|
||||
td.supplier {
|
||||
select {
|
||||
width: 125px;
|
||||
colgroup col {
|
||||
&.producer {
|
||||
width: 18%;
|
||||
}
|
||||
&.name {
|
||||
width: 18%;
|
||||
}
|
||||
&.unit {
|
||||
width: 14%;
|
||||
}
|
||||
&.display_as {
|
||||
width: 12%;
|
||||
}
|
||||
&.price {
|
||||
width: 10%;
|
||||
}
|
||||
&.on_hand {
|
||||
width: 10%;
|
||||
}
|
||||
&.category {
|
||||
width: 15%;
|
||||
}
|
||||
&.available_on {
|
||||
width: 15%;
|
||||
}
|
||||
&.actions {
|
||||
width: 3%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,24 +7,19 @@
|
||||
background-color: black
|
||||
background-image: url("/assets/home/ofn_bg_1.jpg")
|
||||
@include fullbg
|
||||
height: 500px
|
||||
height: 400px
|
||||
padding: 40px 0px
|
||||
h1, h2, span, small, timer
|
||||
h1, h2, p
|
||||
color: white
|
||||
p
|
||||
color: $clr-brick-light
|
||||
h1
|
||||
margin-bottom: 3rem
|
||||
margin-bottom: 1em
|
||||
h2
|
||||
font-size: 1.6875rem
|
||||
max-width: 610px
|
||||
margin: 0 auto
|
||||
padding-bottom: 0.5rem
|
||||
|
||||
a
|
||||
color: white
|
||||
&:hover, &:active, &:focus
|
||||
color: $clr-brick-light-bright
|
||||
@include textsoftpress
|
||||
a.button.primary
|
||||
color: white
|
||||
@@ -1,25 +1,62 @@
|
||||
Spree::Admin::PaymentMethodsController.class_eval do
|
||||
# Only show payment methods that user has access to and sort by distributor name
|
||||
# ! Redundant code copied from Spree::Admin::ResourceController with modifications marked
|
||||
def collection
|
||||
return parent.send(controller_name) if parent_data.present?
|
||||
collection = if model_class.respond_to?(:accessible_by) &&
|
||||
!current_ability.has_block?(params[:action], model_class)
|
||||
module Spree
|
||||
module Admin
|
||||
PaymentMethodsController.class_eval do
|
||||
skip_before_filter :load_resource, only: [:show_provider_preferences]
|
||||
before_filter :load_hubs, only: [:new, :edit, :update]
|
||||
create.before :load_hubs
|
||||
|
||||
model_class.accessible_by(current_ability, action)
|
||||
# Only show payment methods that user has access to and sort by distributor name
|
||||
# ! Redundant code copied from Spree::Admin::ResourceController with modifications marked
|
||||
def collection
|
||||
return parent.send(controller_name) if parent_data.present?
|
||||
collection = if model_class.respond_to?(:accessible_by) &&
|
||||
!current_ability.has_block?(params[:action], model_class)
|
||||
|
||||
else
|
||||
model_class.scoped
|
||||
end
|
||||
model_class.accessible_by(current_ability, action)
|
||||
|
||||
collection = collection.managed_by(spree_current_user).by_name # This line added
|
||||
else
|
||||
model_class.scoped
|
||||
end
|
||||
|
||||
# This block added
|
||||
if params.key? :enterprise_id
|
||||
distributor = Enterprise.find params[:enterprise_id]
|
||||
collection = collection.for_distributor(distributor)
|
||||
collection = collection.managed_by(spree_current_user).by_name # This line added
|
||||
|
||||
# This block added
|
||||
if params.key? :enterprise_id
|
||||
distributor = Enterprise.find params[:enterprise_id]
|
||||
collection = collection.for_distributor(distributor)
|
||||
end
|
||||
|
||||
collection
|
||||
end
|
||||
|
||||
def show_provider_preferences
|
||||
if params[:pm_id].present?
|
||||
@payment_method = PaymentMethod.find(params[:pm_id])
|
||||
authorize! :show_provider_preferences, @payment_method
|
||||
payment_method_type = params[:provider_type]
|
||||
if @payment_method['type'].to_s != payment_method_type
|
||||
@payment_method.update_column(:type, payment_method_type)
|
||||
@payment_method = PaymentMethod.find(params[:pm_id])
|
||||
end
|
||||
else
|
||||
@payment_method = params[:provider_type].constantize.new()
|
||||
end
|
||||
render partial: 'provider_settings'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_data
|
||||
if spree_current_user.admin? || Rails.env.test?
|
||||
@providers = Gateway.providers.sort{|p1, p2| p1.name <=> p2.name }
|
||||
else
|
||||
@providers = Gateway.providers.reject{ |p| p.name.include? "Bogus" }.sort{|p1, p2| p1.name <=> p2.name }
|
||||
end
|
||||
end
|
||||
|
||||
def load_hubs
|
||||
@hubs = Enterprise.managed_by(spree_current_user).is_distributor.sort_by!{ |d| [(@payment_method.has_distributor? d) ? 0 : 1, d.name] }
|
||||
end
|
||||
end
|
||||
|
||||
collection
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Spree::Admin::ProductsController.class_eval do
|
||||
before_filter :load_spree_api_key, :only => :bulk_edit
|
||||
before_filter :load_bpe_data, :only => :bulk_edit
|
||||
|
||||
alias_method :location_after_save_original, :location_after_save
|
||||
|
||||
@@ -31,9 +31,13 @@ Spree::Admin::ProductsController.class_eval do
|
||||
end
|
||||
|
||||
if product_set.save
|
||||
redirect_to "/api/products/managed?template=bulk_index;page=1;per_page=500;#{bulk_index_query}"
|
||||
redirect_to "/api/products/bulk_products?page=1;per_page=500;#{bulk_index_query}"
|
||||
else
|
||||
render :nothing => true, :status => 418
|
||||
if product_set.errors.present?
|
||||
render json: { errors: product_set.errors }, status: 400
|
||||
else
|
||||
render :nothing => true, :status => 500
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -78,8 +82,10 @@ Spree::Admin::ProductsController.class_eval do
|
||||
|
||||
private
|
||||
|
||||
def load_spree_api_key
|
||||
def load_bpe_data
|
||||
current_user.generate_spree_api_key! unless spree_current_user.spree_api_key
|
||||
@spree_api_key = spree_current_user.spree_api_key
|
||||
@producers = Enterprise.managed_by(spree_current_user).is_primary_producer.order(:name)
|
||||
@taxons = Spree::Taxon.order(:name)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -386,12 +386,23 @@ Spree::Admin::ReportsController.class_eval do
|
||||
table_items = @line_items
|
||||
@include_blank = 'All'
|
||||
|
||||
header = ["Producer", "Product", "Variant", "Amount", "Curr. Cost per Unit", "Total Cost", "Status", "Incoming Transport"]
|
||||
header = ["Producer", "Product", "Variant", "Amount", "Total Units", "Curr. Cost per Unit", "Total Cost", "Status", "Incoming Transport"]
|
||||
|
||||
ovn = OpenFoodNetwork::OptionValueNamer.new()
|
||||
|
||||
columns = [ proc { |line_items| line_items.first.variant.product.supplier.name },
|
||||
proc { |line_items| line_items.first.variant.product.name },
|
||||
proc { |line_items| line_items.first.variant.full_name },
|
||||
proc { |line_items| line_items.sum { |li| li.quantity } },
|
||||
proc { |line_items| ovn.name(OpenStruct.new({
|
||||
unit_value: ( line_items.map{ |li| li.variant.unit_value.nil? }.any? ? 0 : line_items.sum { |li| li.quantity * li.variant.unit_value } ),
|
||||
unit_description: line_items.first.variant.unit_description,
|
||||
product: OpenStruct.new({
|
||||
variant_unit: line_items.first.product.variant_unit,
|
||||
variant_unit_scale: line_items.first.product.variant_unit_scale,
|
||||
variant_unit_name: line_items.first.product.variant_unit_name
|
||||
})
|
||||
}))},
|
||||
proc { |line_items| line_items.first.variant.price },
|
||||
proc { |line_items| line_items.sum { |li| li.quantity * li.price } },
|
||||
proc { |line_items| "" },
|
||||
|
||||
@@ -7,6 +7,10 @@ Spree::Api::ProductsController.class_eval do
|
||||
respond_with(@products, default_template: :index)
|
||||
end
|
||||
|
||||
def bulk_products
|
||||
@products = product_scope.ransack(params[:q]).result.managed_by(current_api_user).page(params[:page]).per(params[:per_page])
|
||||
render text: { products: ActiveModel::ArraySerializer.new(@products, each_serializer: Spree::Api::ProductSerializer), pages: @products.num_pages }.to_json
|
||||
end
|
||||
|
||||
def soft_delete
|
||||
authorize! :delete, Spree::Product
|
||||
|
||||
@@ -12,6 +12,14 @@ module Admin
|
||||
admin_inject_json_ams_array "admin.shipping_methods", "shippingMethods", @shipping_methods, Api::Admin::IdNameSerializer
|
||||
end
|
||||
|
||||
def admin_inject_producers
|
||||
admin_inject_json_ams_array "ofn.admin", "producers", @producers, Api::Admin::IdNameSerializer
|
||||
end
|
||||
|
||||
def admin_inject_taxons
|
||||
admin_inject_json_ams_array "ofn.admin", "taxons", @taxons, Api::Admin::TaxonSerializer
|
||||
end
|
||||
|
||||
def admin_inject_json_ams(ngModule, name, data, serializer, opts = {})
|
||||
json = serializer.new(data).to_json
|
||||
render partial: "admin/json/injection_ams", locals: {ngModule: ngModule, name: name, json: json}
|
||||
|
||||
@@ -22,7 +22,7 @@ module CheckoutHelper
|
||||
end
|
||||
|
||||
def checkout_cart_total_with_adjustments(order)
|
||||
current_order.display_item_total.money.to_f + checkout_adjustments_total(current_order).money.to_f
|
||||
order.display_item_total.money.to_f + checkout_adjustments_total(order).money.to_f
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ class EnterpriseFee < ActiveRecord::Base
|
||||
|
||||
attr_accessible :enterprise_id, :fee_type, :name, :calculator_type
|
||||
|
||||
FEE_TYPES = %w(packing transport admin sales)
|
||||
FEE_TYPES = %w(packing transport admin sales fundraising)
|
||||
PER_ORDER_CALCULATORS = ['Spree::Calculator::FlatRate', 'Spree::Calculator::FlexiRate']
|
||||
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ class AbilityDecorator
|
||||
|
||||
# Enterprise User can only access payment methods for their distributors
|
||||
can [:index, :create], Spree::PaymentMethod
|
||||
can [:admin, :read, :update, :fire, :resend, :destroy], Spree::PaymentMethod do |payment_method|
|
||||
can [:admin, :read, :update, :fire, :resend, :destroy, :show_provider_preferences], Spree::PaymentMethod do |payment_method|
|
||||
(user.enterprises & payment_method.distributors).any?
|
||||
end
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ Spree::PaymentMethod.class_eval do
|
||||
|
||||
attr_accessible :distributor_ids
|
||||
|
||||
validates :distributors, presence: { message: "^At least one hub must be selected" }
|
||||
|
||||
# -- Scopes
|
||||
scope :managed_by, lambda { |user|
|
||||
if user.has_spree_role?('admin')
|
||||
@@ -30,4 +32,19 @@ end
|
||||
# Ensure that all derived classes also allow distributor_ids
|
||||
Spree::Gateway.providers.each do |p|
|
||||
p.attr_accessible :distributor_ids
|
||||
p.instance_eval do
|
||||
def clean_name
|
||||
case name
|
||||
when "Spree::PaymentMethod::Check"
|
||||
"Cash/EFT/etc. (payments for which automatic validation is not required)"
|
||||
when "Spree::Gateway::Migs"
|
||||
"MasterCard Internet Gateway Service (MIGS)"
|
||||
when "Spree::Gateway::PayPalExpress"
|
||||
"PayPal Express"
|
||||
else
|
||||
i = name.rindex('::') + 2
|
||||
name[i..-1]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
/ insert_before '[data-hook="environment"]'
|
||||
|
||||
= f.field_container :distributors do
|
||||
= f.label :distributors
|
||||
%br
|
||||
- distributors = Enterprise.is_distributor.managed_by(spree_current_user) | f.object.distributors
|
||||
= f.collection_select(:distributor_ids, distributors, :id, :name, {include_blank: false}, {class: "select2 fullwidth", multiple: true})
|
||||
= f.error_message_on :distributors
|
||||
@@ -0,0 +1 @@
|
||||
remove "div.clear"
|
||||
@@ -0,0 +1,36 @@
|
||||
/ replace "div[data-hook='admin_payment_method_form_fields']"
|
||||
|
||||
%div.alpha.eleven.columns
|
||||
.row
|
||||
.alpha.three.columns
|
||||
= label_tag nil, t(:name)
|
||||
.omega.eight.columns
|
||||
= text_field :payment_method, :name, :class => 'fullwidth'
|
||||
.row
|
||||
.alpha.three.columns
|
||||
= label_tag nil, t(:description)
|
||||
.omega.eight.columns
|
||||
= text_area :payment_method, :description, {:cols => 60, :rows => 6, :class => 'fullwidth'}
|
||||
- if spree_current_user.admin?
|
||||
.row
|
||||
.alpha.three.columns
|
||||
= label_tag nil, t(:environment)
|
||||
.omega.eight.columns
|
||||
= collection_select(:payment_method, :environment, Rails.configuration.database_configuration.keys.sort, :to_s, :titleize, {}, {:id => 'gtwy-env', :class => 'select2 fullwidth'})
|
||||
.row
|
||||
.alpha.three.columns
|
||||
= label_tag nil, t(:display)
|
||||
.omega.eight.columns
|
||||
= select(:payment_method, :display_on, Spree::PaymentMethod::DISPLAY.collect { |display| [t(display), display == :both ? nil : display.to_s] }, {}, {:class => 'select2 fullwidth'})
|
||||
.row
|
||||
.alpha.three.columns
|
||||
= label_tag nil, t(:active)
|
||||
.two.columns
|
||||
= radio_button :payment_method, :active, true
|
||||
|
||||
= label_tag nil, t(:say_yes)
|
||||
.omega.six.columns
|
||||
= radio_button :payment_method, :active, false
|
||||
|
||||
= label_tag nil, t(:say_no)
|
||||
= render 'providers'
|
||||
@@ -0,0 +1,5 @@
|
||||
/ insert_after "code[erb-loud]:contains(\"render :partial => 'form', :locals => { :f => f }\")"
|
||||
|
||||
.one.column
|
||||
= render :partial => 'spree/admin/shared/hubs_sidebar', :locals => { f: f, klass: :payment_method }
|
||||
.clear
|
||||
@@ -0,0 +1 @@
|
||||
remove "code[erb-loud]:contains(\"render :partial => 'spree/admin/shared/configuration_menu'\")"
|
||||
@@ -0,0 +1,5 @@
|
||||
/ insert_after "code[erb-loud]:contains(\"render :partial => 'form', :locals => { :f => f }\")"
|
||||
|
||||
.one.column
|
||||
= render :partial => 'spree/admin/shared/hubs_sidebar', :locals => { f: f, klass: :payment_method }
|
||||
.clear
|
||||
@@ -0,0 +1 @@
|
||||
remove "code[erb-loud]:contains(\"render :partial => 'spree/admin/shared/configuration_menu'\")"
|
||||
@@ -1,6 +1,6 @@
|
||||
/ replace "div[data-hook='admin_shipping_method_form_fields']"
|
||||
|
||||
.alpha.twelve.columns{"data-hook" => "admin_shipping_method_form_fields"}
|
||||
.alpha.eleven.columns{"data-hook" => "admin_shipping_method_form_fields"}
|
||||
.row
|
||||
.alpha.three.columns
|
||||
= f.label :name, t(:name)
|
||||
@@ -41,7 +41,7 @@
|
||||
= f.radio_button :require_ship_address, true
|
||||
|
||||
= f.label :delivery, t(:delivery)
|
||||
.six.columns
|
||||
.omega.six.columns
|
||||
= f.radio_button :require_ship_address, false
|
||||
|
||||
= f.label :pick_up, t(:pick_up)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/ insert_after "code[erb-loud]:contains(\"render :partial => 'form', :locals => { :f => f }\")"
|
||||
|
||||
= render :partial => 'spree/admin/shared/hubs_sidebar', :locals => { :f => f }
|
||||
.one.column
|
||||
= render :partial => 'spree/admin/shared/hubs_sidebar', :locals => { f: f, klass: :shipping_method }
|
||||
@@ -1,3 +1,4 @@
|
||||
/ insert_after "code[erb-loud]:contains(\"render :partial => 'form', :locals => { :f => f }\")"
|
||||
|
||||
= render :partial => 'spree/admin/shared/hubs_sidebar', :locals => { :f => f }
|
||||
.one.column
|
||||
= render :partial => 'spree/admin/shared/hubs_sidebar', :locals => { f: f, klass: :shipping_method }
|
||||
3
app/serializers/api/admin/taxon_serializer.rb
Normal file
3
app/serializers/api/admin/taxon_serializer.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class Api::Admin::TaxonSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :pretty_name
|
||||
end
|
||||
26
app/serializers/spree/api/product_serializer.rb
Normal file
26
app/serializers/spree/api/product_serializer.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
class Spree::Api::ProductSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :variant_unit, :variant_unit_scale, :variant_unit_name, :on_demand
|
||||
|
||||
attributes :on_hand, :price, :available_on, :permalink_live
|
||||
|
||||
has_one :supplier, key: :producer, embed: :id
|
||||
has_one :primary_taxon, key: :category, embed: :id
|
||||
has_many :variants, key: :variants, serializer: Spree::Api::VariantSerializer # embed: ids
|
||||
has_one :master, serializer: Spree::Api::VariantSerializer
|
||||
|
||||
def on_hand
|
||||
object.on_hand.nil? ? 0 : object.on_hand.to_f.finite? ? object.on_hand : "On demand"
|
||||
end
|
||||
|
||||
def price
|
||||
object.price.nil? ? '0.0' : object.price
|
||||
end
|
||||
|
||||
def available_on
|
||||
object.available_on.blank? ? "" : object.available_on.strftime("%F %T")
|
||||
end
|
||||
|
||||
def permalink_live
|
||||
object.permalink
|
||||
end
|
||||
end
|
||||
12
app/serializers/spree/api/variant_serializer.rb
Normal file
12
app/serializers/spree/api/variant_serializer.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
class Spree::Api::VariantSerializer < ActiveModel::Serializer
|
||||
attributes :id, :options_text, :unit_value, :unit_description, :on_demand, :display_as, :display_name
|
||||
attributes :on_hand, :price
|
||||
|
||||
def on_hand
|
||||
object.on_hand.nil? ? 0 : ( object.on_hand.to_f.finite? ? object.on_hand : "On demand" )
|
||||
end
|
||||
|
||||
def price
|
||||
object.price.nil? ? 0.to_f : object.price
|
||||
end
|
||||
end
|
||||
@@ -2,16 +2,10 @@
|
||||
.row
|
||||
.small-12.text-center.columns
|
||||
%h1= image_tag "ofn_logo_beta.png", title: "Open Food Network (beta)"
|
||||
%h2 We're crowdfunding right now!
|
||||
%h5
|
||||
%timer{"end-time" => '1407679200000'}
|
||||
{{days}} days, {{hours}} hrs, {{minutes}} mins & {{seconds}} secs to go
|
||||
%p Help us make Open Food Network the best it can be:
|
||||
%a.button.primary{href: "http://startsomegood.com/openfoodnetwork", target:"_blank"} Support now
|
||||
/ %h2 An open marketplace that makes it easy to find, buy, sell and move sustainable local food.
|
||||
%br
|
||||
%ofn-modal{title: "Learn more"}
|
||||
= render partial: "modals/learn_more"
|
||||
%h2 An open marketplace that makes it easy to find, buy, sell and move sustainable local food.
|
||||
|
||||
%ofn-modal{title: "Learn more"}
|
||||
= render partial: "modals/learn_more"
|
||||
|
||||
= render partial: "home/hubs"
|
||||
|
||||
|
||||
@@ -92,10 +92,11 @@
|
||||
%div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true }
|
||||
%span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "✓" || !column.visible && " " }}
|
||||
%span{ :class => 'two columns omega' } {{column.name }}
|
||||
%div.loading{ :class => "sixteen columns alpha", 'ng-show' => 'loading' }
|
||||
%h4 Loading Line Items...
|
||||
%div.sixteen.columns.alpha#loading{ 'ng-if' => 'loading' }
|
||||
%img.spinner{ src: "/assets/loading.gif" }
|
||||
%h1 LOADING ORDERS
|
||||
%div{ :class => "sixteen columns alpha", 'ng-show' => '!loading && filteredLineItems.length == 0'}
|
||||
%h4{ :style => 'color:red;' } No matching line items found.
|
||||
%h1#no_results No orders found.
|
||||
%div{ 'ng-hide' => 'loading || filteredLineItems.length == 0' }
|
||||
%table.index#listing_orders.bulk{ :class => "sixteen columns alpha" }
|
||||
%thead
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
- if @payment_method.preferences.present?
|
||||
%fieldset.alpha.eleven.columns.no-border-bottom#gateway_fields
|
||||
%legend{ align: "center"}
|
||||
= t(:provider_settings)
|
||||
.preference-settings
|
||||
= fields_for :payment_method, @payment_method do |payment_method_form|
|
||||
= preference_fields(@payment_method, payment_method_form)
|
||||
10
app/views/spree/admin/payment_methods/_providers.html.haml
Normal file
10
app/views/spree/admin/payment_methods/_providers.html.haml
Normal file
@@ -0,0 +1,10 @@
|
||||
:javascript
|
||||
angular.module('ofn.admin').value('paymentMethod', #{ { id: @payment_method.id, type: @payment_method.type }.to_json })
|
||||
#provider-settings{ ng: { app: "ofn.admin", controller: "ProvidersCtrl" } }
|
||||
.row
|
||||
.alpha.three.columns
|
||||
= label :payment_method, :type, t(:provider)
|
||||
.omega.eight.columns
|
||||
= collection_select(:payment_method, :type, @providers, :to_s, :clean_name, (!@object.persisted? ? { :selected => "Spree::PaymentMethod::Check"} : {}), { class: 'select2 fullwidth', 'provider-prefs-for' => "#{@object.id}"})
|
||||
|
||||
%div{"ng-include" => "include_html" }
|
||||
@@ -12,117 +12,87 @@
|
||||
%div#new_product(data-hook)
|
||||
|
||||
|
||||
|
||||
=admin_inject_producers
|
||||
=admin_inject_taxons
|
||||
%div{ 'ng-app' => 'ofn.admin', 'ng-controller' => 'AdminProductEditCtrl', 'ng-init' => "initialise('#{@spree_api_key}');loading=true;" }
|
||||
%div.sixteen.columns.alpha
|
||||
%div.quick_search{ :class => "four columns alpha" }
|
||||
%label{ :for => 'quick_filter' }
|
||||
%br
|
||||
%input.search{ :class => "four columns alpha", 'ng-model' => 'query', :name => "quick_filter", :type => 'text', 'placeholder' => 'Quick Search' }
|
||||
.filter_select{ :class => "four columns" }
|
||||
%label{ :for => 'producer_filter' }Producer
|
||||
%br
|
||||
%select{ :class => "four columns alpha", :id => 'producer_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'producerFilter', 'ng-options' => 'producer.id as producer.name for producer in filterProducers' }
|
||||
.filter_select{ :class => "four columns" }
|
||||
%label{ :for => 'category_filter' }Category
|
||||
%br
|
||||
%select{ :class => "four columns alpha", :id => 'category_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'categoryFilter', 'ng-options' => 'taxon.id as taxon.name for taxon in filterTaxons'}
|
||||
%div{ :class => "one column" }
|
||||
.filter_clear{ :class => "three columns omega" }
|
||||
%label{ :for => 'clear_all_filters' }
|
||||
%br
|
||||
%input.fullwidth{ :type => 'button', :id => 'clear_all_filters', :value => "Clear Filters", 'ng-click' => "resetSelectFilters()" }
|
||||
%hr.sixteen.columns.alpha
|
||||
%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 => 'Save Changes', 'ng-click' => 'submitProducts()'}
|
||||
%div.nine.columns
|
||||
%h6{ id: "update-status-message", ng: { style: 'updateStatusMessage.style' } }
|
||||
{{ updateStatusMessage.text || " " }}
|
||||
%div.three.columns.omega
|
||||
%div.ofn_drop_down.three.columns.omega{ 'ng-controller' => "DropDownCtrl", :id => "columns_dropdown", 'ofn-drop-down' => true, :style => 'float:right;' }
|
||||
%span{ :class => 'icon-reorder' } Columns
|
||||
%span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
|
||||
%div.menu{ 'ng-show' => "expanded" }
|
||||
%div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true }
|
||||
%span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "✓" || !column.visible && " " }}
|
||||
%span{ :class => 'two columns omega' } {{column.name }}
|
||||
%div{ 'ng-show' => '!spree_api_key_ok' }
|
||||
{{ api_error_msg }}
|
||||
%div.option_tab_titles{ :class => "sixteen columns alpha" }
|
||||
%h6{ :class => "three columns alpha", 'ng-repeat' => "tab in optionTabs", "ng-click" => "shiftTab(tab)", "ng-class" => "tab.visible && 'selected' || !tab.visible && 'unselected'"}
|
||||
{{ tab.title }}
|
||||
%div.option_tabs{ :class => "sixteen columns alpha" }
|
||||
%div.filters{ :class => "sixteen columns alpha", "ng-show" => 'optionTabs.filters.visible' }
|
||||
%div{ :class => "four columns alpha" }
|
||||
Column:
|
||||
%br.clear
|
||||
%select.fullwidth{ 'ng-model' => 'filterProperty', :id => "filter_property", 'ng-options' => 'fc.name for fc in filterableColumns', 'ofn-select2-min-search' => 10 }
|
||||
%div{ :class => "four columns omega" }
|
||||
Filter Type:
|
||||
%br.clear
|
||||
%select.fullwidth{ 'ng-model' => 'filterPredicate', :id => "filter_predicate", 'ng-options' => 'ft.name for ft in filterTypes', 'ofn-select2-min-search' => 10 }
|
||||
%div{ :class => "six columns omega" }
|
||||
Value:
|
||||
%br.clear
|
||||
%input{ :class => "four columns alpha", 'ng-model' => 'filterValue', :id => "filter_value", :type => "text", 'placeholder' => 'Filter Value' }
|
||||
%div{ :class => "two columns omega" }
|
||||
|
||||
%input.fullwidth{ :name => "add_filter", :value => "Apply Filter", :type => "button", "ng-click" => "addFilter({property:filterProperty,predicate:filterPredicate,value:filterValue})" }
|
||||
%div.applied_filters{ :class => "sixteen columns alpha", "ng-show" => 'optionTabs.filters.visible && currentFilters.length > 0' }
|
||||
%div.applied_filter{ :class => "sixteen columns alpha", 'ng-repeat' => 'filter in currentFilters' }
|
||||
%div{ :class => "four columns alpha" }
|
||||
{{ filter.property.name }}
|
||||
%div{ :class => "four columns omega" }
|
||||
{{ filter.predicate.name }}
|
||||
%div{ :class => "six columns omega" }
|
||||
{{ filter.value }}
|
||||
%div{ :class => "two columns omega" }
|
||||
%a{ :href => "#", 'ng-click' => "removeFilter(filter)" } Remove Filter
|
||||
%div.column_toggle{ :class => "sixteen columns alpha", "ng-show" => 'optionTabs.column_toggle.visible' }
|
||||
%ul.column-list{ :class => "sixteen columns alpha" }
|
||||
%li.column-list-item{ :class => "three columns alpha", 'ofn-toggle-column' => 'column', 'ng-repeat' => 'column in columns' }
|
||||
{{ column.name }}
|
||||
%hr
|
||||
%div.sixteen.columns.alpha.loading{ 'ng-show' => 'loading' }
|
||||
%h4 Loading Products...
|
||||
%div.sixteen.columns.alpha{ 'ng-show' => '!loading && products.length == 0' }
|
||||
%h4{ :style => 'color:red;' } No matching products found.
|
||||
%div.sixteen.columns.alpha{ 'ng-show' => '!loading && products.length == 500' }
|
||||
%h6 Search returned too many products to display (500+), please apply more search filters to reduce the number of matching products
|
||||
%div.sixteen.columns.alpha{ 'ng-hide' => 'loading || products.length == 500 || products.length == 0' }
|
||||
%div.quick_search{ :class => "five columns omega" }
|
||||
%input.search{ :class => "four columns alpha", 'ng-model' => 'query', :name => "quick_filter", :type => 'text', 'placeholder' => 'Quick Search' }
|
||||
%div.pagination{ :class => "seven columns omega" }
|
||||
%div.pagenav{ :class => "two columns alpha" }
|
||||
%span.first
|
||||
%a{ :href => "#", 'ng-click' => "currentPage = 1", 'ng-show' => "currentPage > 1" }
|
||||
« First
|
||||
%span.prev
|
||||
%a{ :href => "#", 'ng-click' => "currentPage = currentPage - 1", 'ng-show' => "currentPage > 1" }
|
||||
‹ Prev
|
||||
%div.pagenav{ :class => "columns omega" }
|
||||
%span.page{ 'ng-repeat' => "page in [] | rangeArray:minPage():maxPage()", 'ng-class' => "{current: currentPage==page}" }
|
||||
%a{ :href => "#", 'ng-click' => "setPage(page)" }
|
||||
{{page}}
|
||||
%span{ 'ng-show' => "maxPage() < totalPages()" } ...
|
||||
%div.pagenav{ :class => "two columns omega" }
|
||||
%span.next
|
||||
%a{ :href => "#", 'ng-click' => "currentPage = currentPage + 1", 'ng-show' => "currentPage < totalPages()" }
|
||||
Next ›
|
||||
%span.last
|
||||
%a{ :href => "#", 'ng-click' => "currentPage = totalPages()", 'ng-show' => "currentPage < totalPages()" }
|
||||
Last »
|
||||
%div.pagination_info{ :class => 'four columns alpha' }
|
||||
Show
|
||||
%select{ 'ng-model' => 'perPage', :name => 'perPage', 'ng-options' => 'pp for pp in [25,50,100,200]'}
|
||||
per page
|
||||
%br
|
||||
%span Displaying {{firstVisibleProduct()}}-{{lastVisibleProduct()}} of {{totalCount()}} products
|
||||
%table.index#listing_products.bulk
|
||||
%div.sixteen.columns.alpha#loading{ 'ng-if' => 'loading' }
|
||||
%img.spinner{ src: "/assets/loading.gif" }
|
||||
%h1 LOADING PRODUCTS
|
||||
%div.sixteen.columns.alpha{ 'ng-show' => '!loading && filteredProducts.length == 0' }
|
||||
%h1#no_results No products found.
|
||||
%div.sixteen.columns.alpha{ 'ng-hide' => 'loading || filteredProducts.length == 0' }
|
||||
%table.index#listing_products.bulk{ "infinite-scroll" => "incrementLimit()", "infinite-scroll-distance" => "1" }
|
||||
%colgroup
|
||||
%col
|
||||
%col
|
||||
%col{'style' => 'width: 20%;'}
|
||||
%col{'style' => 'width: 12%;'}
|
||||
%col{'style' => 'width: 12%;'}
|
||||
%col{'style' => 'width: 12%;'}
|
||||
%col
|
||||
%col
|
||||
%col
|
||||
%col
|
||||
%col
|
||||
%col
|
||||
%col.actions
|
||||
%col.producer{ ng: { show: 'columns.producer.visible' } }
|
||||
%col.name{ ng: { show: 'columns.name.visible' } }
|
||||
%col.unit{ ng: { show: 'columns.unit.visible' } }
|
||||
%col.display_as{ ng: { show: 'columns.unit.visible' } }
|
||||
%col.price{ ng: { show: 'columns.price.visible'} }
|
||||
%col.on_hand{ ng: { show: 'columns.on_hand.visible' } }
|
||||
%col.category{ ng: { show: 'columns.category.visible' } }
|
||||
%col.available_on{ ng: { show: 'columns.available_on.visible' } }
|
||||
%col.actions
|
||||
%col.actions
|
||||
%col.actions
|
||||
|
||||
%thead
|
||||
%tr
|
||||
%th.left-actions
|
||||
%th{ 'ng-show' => 'columns.supplier.visible' } Supplier
|
||||
%th{ 'ng-show' => 'columns.name.visible' } Name
|
||||
%th{ 'ng-show' => 'columns.unit.visible' } Unit / Value
|
||||
%th{ 'ng-show' => 'columns.unit.visible' } Display As
|
||||
%th{ 'ng-show' => 'columns.price.visible' } Price
|
||||
%th{ 'ng-show' => 'columns.on_hand.visible' } On Hand
|
||||
%th{ 'ng-show' => 'columns.taxons.visible' } Taxons
|
||||
%th{ 'ng-show' => 'columns.available_on.visible' } Av. On
|
||||
%th.producer{ 'ng-show' => 'columns.producer.visible' } Producer
|
||||
%th.name{ 'ng-show' => 'columns.name.visible' } Name
|
||||
%th.unit{ 'ng-show' => 'columns.unit.visible' } Unit / Value
|
||||
%th.display_as{ 'ng-show' => 'columns.unit.visible' } Display As
|
||||
%th.price{ 'ng-show' => 'columns.price.visible' } Price
|
||||
%th.on_hand{ 'ng-show' => 'columns.on_hand.visible' } On Hand
|
||||
%th.category{ 'ng-show' => 'columns.category.visible' } Category
|
||||
%th.available_on{ 'ng-show' => 'columns.available_on.visible' } Av. On
|
||||
%th.actions
|
||||
%th.actions
|
||||
%th.actions
|
||||
%tbody{ 'ng-repeat' => 'product in filteredProducts = (products | filter:query)', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", 'ng-show' => "$index >= perPage*(currentPage-1) && $index < perPage*currentPage" }
|
||||
%tbody{ 'ng-repeat' => 'product in filteredProducts = ( products | filter:query | producer: producerFilter | category: categoryFilter | limitTo:limit )', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" }
|
||||
%tr.product{ :id => "p_{{product.id}}" }
|
||||
%td.left-actions
|
||||
%a{ 'ofn-toggle-variants' => 'true', :class => "view-variants icon-chevron-right", 'ng-show' => 'hasVariants(product)' }
|
||||
%a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "!hasVariants(product) && hasUnit(product)" }
|
||||
%td.supplier{ 'ng-show' => 'columns.supplier.visible' }
|
||||
%select.select2{ 'ng-model' => 'product.supplier', :name => 'supplier', 'ofn-track-product' => 'supplier', 'ng-options' => 's.name for s in suppliers' }
|
||||
%td{ 'ng-show' => 'columns.name.visible' }
|
||||
%td.producer{ 'ng-show' => 'columns.producer.visible' }
|
||||
%select.select2.fullwidth{ 'ng-model' => 'product.producer', :name => 'producer', 'ofn-track-product' => 'producer', 'ng-options' => 'producer.id as producer.name for producer in producers' }
|
||||
%td.name{ 'ng-show' => 'columns.name.visible' }
|
||||
%input{ 'ng-model' => "product.name", :name => 'product_name', 'ofn-track-product' => 'name', :type => 'text' }
|
||||
%td.unit{ 'ng-show' => 'columns.unit.visible' }
|
||||
%select.select2{ 'ng-model' => 'product.variant_unit_with_scale', :name => 'variant_unit_with_scale', 'ofn-track-product' => 'variant_unit_with_scale', 'ng-options' => 'unit[1] as unit[0] for unit in variant_unit_options' }
|
||||
@@ -131,14 +101,14 @@
|
||||
%input{ 'ng-model' => 'product.variant_unit_name', :name => 'variant_unit_name', 'ofn-track-product' => 'variant_unit_name', :placeholder => 'unit', 'ng-show' => "product.variant_unit_with_scale == 'items'", :type => 'text' }
|
||||
%td.display_as{ 'ng-show' => 'columns.unit.visible' }
|
||||
%input{ 'ofn-display-as' => 'product.master', name: 'display_as', 'ofn-track-master' => 'display_as', type: 'text', placeholder: '{{ placeholder_text }}', ng: { hide: 'hasVariants(product)', model: 'product.master.display_as' } }
|
||||
%td{ 'ng-show' => 'columns.price.visible' }
|
||||
%td.price{ 'ng-show' => 'columns.price.visible' }
|
||||
%input{ 'ng-model' => 'product.price', 'ofn-decimal' => :true, :name => 'price', 'ofn-track-product' => 'price', :type => 'text', 'ng-hide' => 'hasVariants(product)' }
|
||||
%td{ 'ng-show' => 'columns.on_hand.visible' }
|
||||
%td.on_hand{ 'ng-show' => 'columns.on_hand.visible' }
|
||||
%span{ 'ng-bind' => 'product.on_hand', :name => 'on_hand', 'ng-show' => '!hasOnDemandVariants(product) && (hasVariants(product) || product.on_demand)' }
|
||||
%input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-hide' => 'hasVariants(product) || product.on_demand', :type => 'number' }
|
||||
%td{ 'ng-if' => 'columns.taxons.visible' }
|
||||
%input.fullwidth{ :type => 'text', 'ng-model' => 'product.taxon_ids', 'ofn-taxon-autocomplete' => '', 'ofn-track-product' => 'taxon_ids' }
|
||||
%td{ 'ng-show' => 'columns.available_on.visible' }
|
||||
%td.category{ 'ng-if' => 'columns.category.visible' }
|
||||
%input.fullwidth{ :type => 'text', id: "p{{product.id}}_category", 'ng-model' => 'product.category', 'ofn-taxon-autocomplete' => '', 'ofn-track-product' => 'category' }
|
||||
%td.available_on{ 'ng-show' => 'columns.available_on.visible' }
|
||||
%input{ 'ng-model' => 'product.available_on', :name => 'available_on', 'ofn-track-product' => 'available_on', 'datetimepicker' => 'product.available_on', type: "text" }
|
||||
%td.actions
|
||||
%a{ 'ng-click' => 'editWarn(product)', :class => "edit-product icon-edit no-text" }
|
||||
@@ -146,11 +116,11 @@
|
||||
%a{ 'ng-click' => 'cloneProduct(product)', :class => "clone-product icon-copy no-text" }
|
||||
%td.actions
|
||||
%a{ 'ng-click' => 'deleteProduct(product)', :class => "delete-product icon-trash no-text" }
|
||||
%tr.variant{ 'ng-repeat' => 'variant in product.variants', 'ng-show' => 'displayProperties[product.id].showVariants', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" }
|
||||
%tr.variant{ :id => "v_{{variant.id}}", 'ng-repeat' => 'variant in product.variants', 'ng-show' => 'displayProperties[product.id].showVariants', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" }
|
||||
%td.left-actions
|
||||
%a{ :class => "variant-item icon-caret-right", 'ng-hide' => "$last" }
|
||||
%a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "$last" }
|
||||
%td{ 'ng-show' => 'columns.supplier.visible' }
|
||||
%td{ 'ng-show' => 'columns.producer.visible' }
|
||||
%td{ 'ng-show' => 'columns.name.visible' }
|
||||
%input{ 'ng-model' => 'variant.display_name', :name => 'variant_display_name', 'ofn-track-variant' => 'display_name', :type => 'text', placeholder: "{{ product.name }}" }
|
||||
%td.unit_value{ 'ng-show' => 'columns.unit.visible' }
|
||||
@@ -162,13 +132,10 @@
|
||||
%td{ 'ng-show' => 'columns.on_hand.visible' }
|
||||
%input.field{ 'ng-model' => 'variant.on_hand', 'ng-change' => 'updateOnHand(product)', :name => 'variant_on_hand', 'ng-hide' => 'variant.on_demand', 'ofn-track-variant' => 'on_hand', :type => 'number' }
|
||||
%span{ 'ng-bind' => 'variant.on_hand', :name => 'variant_on_hand', 'ng-show' => 'variant.on_demand' }
|
||||
%td{ 'ng-show' => 'columns.taxons.visible' }
|
||||
%td{ 'ng-show' => 'columns.category.visible' }
|
||||
%td{ 'ng-show' => 'columns.available_on.visible' }
|
||||
%td.actions
|
||||
%a{ 'ng-click' => 'editWarn(product,variant)', :class => "edit-variant icon-edit no-text", 'ng-show' => "variantSaved(variant)" }
|
||||
%td.actions
|
||||
%td.actions
|
||||
%a{ 'ng-click' => 'deleteVariant(product,variant)', :class => "delete-variant icon-trash no-text" }
|
||||
%input{ :type => 'button', :value => 'Update', 'ng-click' => 'submitProducts()'}
|
||||
%span{ id: "update-status-message", 'ng-style' => 'updateStatusMessage.style' }
|
||||
{{ updateStatusMessage.text }}
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
%span.four.columns.alpha.centered Hubs
|
||||
.four.columns.alpha.list{ class: "#{hubs_color}" }
|
||||
- if @hubs.count > 0
|
||||
= f.hidden_field :distributor_ids, :multiple => true, value: nil
|
||||
= hidden_field klass, :distributor_ids, :multiple => true, value: nil
|
||||
- @hubs.each do |hub|
|
||||
%span.four.columns.alpha.list-item{ class: "#{cycle('odd','even')}" }
|
||||
%a.three.columns.alpha{ href: "#{main_app.edit_admin_enterprise_path(hub)}" }
|
||||
= hub.name
|
||||
%span.one.column.omega
|
||||
= f.check_box :distributor_ids, { multiple: true }, hub.id, nil
|
||||
= check_box klass, :distributor_ids, { multiple: true }, hub.id, nil
|
||||
- else
|
||||
.four.columns.alpha.list-item
|
||||
%span.three.columns.alpha None Available
|
||||
|
||||
@@ -10,8 +10,8 @@ Order for: <%= @order.bill_address.full_name %>
|
||||
<%= item.variant.sku %> <%= raw(item.variant.product.supplier.name) %> <%= raw(item.variant.product.name) %> <%= raw(item.variant.options_text) -%> (QTY: <%=item.quantity%>) @ <%= item.single_money %> = <%= item.display_amount %>
|
||||
<% end %>
|
||||
============================================================
|
||||
Subtotal: <%= @order.display_item_total %>
|
||||
<% checkout_adjustments_for_summary(@order).each do |adjustment| %>
|
||||
Subtotal: <%= number_to_currency checkout_cart_total_with_adjustments(@order) %>
|
||||
<% checkout_adjustments_for_summary(@order, exclude: [:distribution]).each do |adjustment| %>
|
||||
<%= raw(adjustment.label) %> <%= adjustment.display_amount %>
|
||||
<% end %>
|
||||
Order Total: <%= @order.display_total %>
|
||||
|
||||
@@ -107,6 +107,7 @@ Spree::Core::Engine.routes.prepend do
|
||||
match '/admin/reports/products_and_inventory' => 'admin/reports#products_and_inventory', :as => "products_and_inventory_admin_reports", :via => [:get, :post]
|
||||
match '/admin/reports/customers' => 'admin/reports#customers', :as => "customers_admin_reports", :via => [:get, :post]
|
||||
match '/admin', :to => 'admin/overview#index', :as => :admin
|
||||
match '/admin/payment_methods/show_provider_preferences' => 'admin/payment_methods#show_provider_preferences', :via => :get
|
||||
|
||||
|
||||
namespace :api, :defaults => { :format => 'json' } do
|
||||
@@ -116,6 +117,7 @@ Spree::Core::Engine.routes.prepend do
|
||||
|
||||
resources :products do
|
||||
get :managed, on: :collection
|
||||
get :bulk_products, on: :collection
|
||||
delete :soft_delete
|
||||
|
||||
resources :variants do
|
||||
|
||||
@@ -553,9 +553,9 @@ ActiveRecord::Schema.define(:version => 20140723023713) do
|
||||
t.string "email"
|
||||
t.text "special_instructions"
|
||||
t.integer "distributor_id"
|
||||
t.integer "order_cycle_id"
|
||||
t.string "currency"
|
||||
t.string "last_ip_address"
|
||||
t.integer "order_cycle_id"
|
||||
t.integer "cart_id"
|
||||
end
|
||||
|
||||
|
||||
@@ -1,27 +1,34 @@
|
||||
module OpenFoodNetwork
|
||||
class OptionValueNamer < Struct.new(:variant)
|
||||
def name
|
||||
value, unit = self.option_value_value_unit
|
||||
separator = self.value_scaled? ? '' : ' '
|
||||
class OptionValueNamer
|
||||
def initialize(variant = nil)
|
||||
@variant = variant
|
||||
end
|
||||
|
||||
def name(obj = nil)
|
||||
@variant = obj unless obj.nil?
|
||||
value, unit = option_value_value_unit
|
||||
separator = value_scaled? ? '' : ' '
|
||||
|
||||
name_fields = []
|
||||
name_fields << "#{value}#{separator}#{unit}" if value.present? && unit.present?
|
||||
name_fields << variant.unit_description if variant.unit_description.present?
|
||||
name_fields << @variant.unit_description if @variant.unit_description.present?
|
||||
name_fields.join ' '
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def value_scaled?
|
||||
variant.product.variant_unit_scale.present?
|
||||
@variant.product.variant_unit_scale.present?
|
||||
end
|
||||
|
||||
def option_value_value_unit
|
||||
if variant.unit_value.present?
|
||||
if %w(weight volume).include? variant.product.variant_unit
|
||||
value, unit_name = self.option_value_value_unit_scaled
|
||||
if @variant.unit_value.present?
|
||||
if %w(weight volume).include? @variant.product.variant_unit
|
||||
value, unit_name = option_value_value_unit_scaled
|
||||
|
||||
else
|
||||
value = variant.unit_value
|
||||
unit_name = variant.product.variant_unit_name
|
||||
value = @variant.unit_value
|
||||
unit_name = @variant.product.variant_unit_name
|
||||
unit_name = unit_name.pluralize if value > 1
|
||||
end
|
||||
|
||||
@@ -35,9 +42,9 @@ module OpenFoodNetwork
|
||||
end
|
||||
|
||||
def option_value_value_unit_scaled
|
||||
unit_scale, unit_name = self.scale_for_unit_value
|
||||
unit_scale, unit_name = scale_for_unit_value
|
||||
|
||||
value = variant.unit_value / unit_scale
|
||||
value = @variant.unit_value / unit_scale
|
||||
|
||||
[value, unit_name]
|
||||
end
|
||||
@@ -48,10 +55,10 @@ module OpenFoodNetwork
|
||||
|
||||
# Find the largest available unit where unit_value comes to >= 1 when expressed in it.
|
||||
# If there is none available where this is true, use the smallest available unit.
|
||||
unit = units[variant.product.variant_unit].select { |scale, unit_name|
|
||||
variant.unit_value / scale >= 1
|
||||
unit = units[@variant.product.variant_unit].select { |scale, unit_name|
|
||||
@variant.unit_value / scale >= 1
|
||||
}.to_a.last
|
||||
unit = units[variant.product.variant_unit].first if unit.nil?
|
||||
unit = units[@variant.product.variant_unit].first if unit.nil?
|
||||
|
||||
unit
|
||||
end
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Spree::Admin::PaymentMethodsController do
|
||||
context "Requesting provider preference fields" do
|
||||
let(:enterprise) { create(:distributor_enterprise) }
|
||||
let(:user) do
|
||||
new_user = create(:user, email: 'enterprise@hub.com', password: 'blahblah', :password_confirmation => 'blahblah', )
|
||||
new_user.spree_roles = [] # for some reason unbeknown to me, this new user gets admin permissions by default.
|
||||
new_user.enterprise_roles.build(enterprise: enterprise).save
|
||||
new_user.save
|
||||
new_user
|
||||
end
|
||||
|
||||
before do
|
||||
controller.stub spree_current_user: user
|
||||
end
|
||||
|
||||
context "on an existing payment method" do
|
||||
let(:payment_method) { create(:payment_method) }
|
||||
|
||||
context "where I have permission" do
|
||||
before do
|
||||
payment_method.distributors << user.enterprises.is_distributor.first
|
||||
end
|
||||
|
||||
context "without an altered provider type" do
|
||||
it "renders provider settings with same payment method" do
|
||||
spree_get :show_provider_preferences, {
|
||||
pm_id: payment_method.id,
|
||||
provider_type: "Spree::PaymentMethod::Check"
|
||||
}
|
||||
expect(assigns(:payment_method)).to eq payment_method
|
||||
expect(response).to render_template partial: '_provider_settings'
|
||||
end
|
||||
end
|
||||
|
||||
context "with an altered provider type" do
|
||||
it "renders provider settings with a different payment method" do
|
||||
spree_get :show_provider_preferences, {
|
||||
pm_id: payment_method.id,
|
||||
provider_type: "Spree::Gateway::Bogus"
|
||||
}
|
||||
expect(assigns(:payment_method)).not_to eq payment_method
|
||||
expect(response).to render_template partial: '_provider_settings'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "where I do not have permission" do
|
||||
before do
|
||||
payment_method.distributors = []
|
||||
end
|
||||
|
||||
it "renders unauthorised" do
|
||||
spree_get :show_provider_preferences, {
|
||||
pm_id: payment_method.id,
|
||||
provider_type: "Spree::PaymentMethod::Check"
|
||||
}
|
||||
expect(assigns(:payment_method)).to eq payment_method
|
||||
expect(flash[:error]).to eq "Authorization Failure"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "on a new payment method" do
|
||||
it "renders provider settings with a new payment method of type" do
|
||||
spree_get :show_provider_preferences, {
|
||||
pm_id: "",
|
||||
provider_type: "Spree::Gateway::Bogus"
|
||||
}
|
||||
expect(assigns(:payment_method)).to be_a_new Spree::Gateway::Bogus
|
||||
expect(response).to render_template partial: '_provider_settings'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -24,7 +24,7 @@ feature %q{
|
||||
|
||||
it "displays a message when number of line items is zero" do
|
||||
visit '/admin/orders/bulk_management'
|
||||
page.should have_text "No matching line items found."
|
||||
page.should have_text "No orders found."
|
||||
|
||||
end
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -113,7 +113,7 @@ feature %q{
|
||||
e2 = create(:enterprise)
|
||||
eg1 = create(:enterprise_group, name: 'eg1')
|
||||
eg2 = create(:enterprise_group, name: 'eg2')
|
||||
payment_method = create(:payment_method, distributors: [])
|
||||
payment_method = create(:payment_method, distributors: [e2])
|
||||
shipping_method = create(:shipping_method, distributors: [e2])
|
||||
enterprise_fee = create(:enterprise_fee, enterprise: @enterprise )
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ feature %q{
|
||||
|
||||
fill_in 'payment_method_name', :with => 'Cheque payment method'
|
||||
|
||||
select @distributors[0].name, :from => 'payment_method_distributor_ids', visible: false
|
||||
check "payment_method_distributor_ids_#{@distributors[0].id}"
|
||||
click_button 'Create'
|
||||
|
||||
flash_message.should == 'Payment Method has been successfully created!'
|
||||
@@ -29,6 +29,28 @@ feature %q{
|
||||
payment_method = Spree::PaymentMethod.find_by_name('Cheque payment method')
|
||||
payment_method.distributors.should == [@distributors[0]]
|
||||
end
|
||||
|
||||
scenario "updating a payment method" do
|
||||
pm = create(:payment_method, distributors: [@distributors[0]])
|
||||
login_to_admin_section
|
||||
|
||||
visit spree.edit_admin_payment_method_path pm
|
||||
|
||||
fill_in 'payment_method_name', :with => 'New PM Name'
|
||||
|
||||
uncheck "payment_method_distributor_ids_#{@distributors[0].id}"
|
||||
check "payment_method_distributor_ids_#{@distributors[1].id}"
|
||||
check "payment_method_distributor_ids_#{@distributors[2].id}"
|
||||
select2_select "PayPal Express", from: "payment_method_type"
|
||||
click_button 'Update'
|
||||
|
||||
expect(flash_message).to eq 'Payment Method has been successfully updated!'
|
||||
|
||||
payment_method = Spree::PaymentMethod.find_by_name('New PM Name')
|
||||
expect(payment_method.distributors).to include @distributors[1], @distributors[2]
|
||||
expect(payment_method.distributors).not_to include @distributors[0]
|
||||
expect(payment_method.type).to eq "Spree::Gateway::PayPalExpress"
|
||||
end
|
||||
end
|
||||
|
||||
context "as an enterprise user" do
|
||||
@@ -46,14 +68,18 @@ feature %q{
|
||||
login_to_admin_as enterprise_user
|
||||
end
|
||||
|
||||
it "creates payment methods" do
|
||||
it "I can get to the new enterprise page" do
|
||||
click_link 'Enterprises'
|
||||
within(".enterprise-#{distributor1.id}") { click_link 'Payment Methods' }
|
||||
click_link 'New Payment Method'
|
||||
current_path.should == spree.new_admin_payment_method_path
|
||||
end
|
||||
|
||||
it "creates payment methods" do
|
||||
visit spree.new_admin_payment_method_path
|
||||
fill_in 'payment_method_name', :with => 'Cheque payment method'
|
||||
|
||||
select distributor1.name, :from => 'payment_method_distributor_ids'
|
||||
check "payment_method_distributor_ids_#{distributor1.id}"
|
||||
click_button 'Create'
|
||||
|
||||
flash_message.should == 'Payment Method has been successfully created!'
|
||||
|
||||
@@ -12,7 +12,8 @@ feature "As a consumer I want to check out my cart", js: true do
|
||||
let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise)) }
|
||||
let(:product) { create(:simple_product, supplier: supplier) }
|
||||
let(:order) { create(:order, order_cycle: order_cycle, distributor: distributor) }
|
||||
let(:user) { create_enterprise_user }
|
||||
let(:address) { create(:address, firstname: "Foo", lastname: "Bar") }
|
||||
let(:user) { create(:user, bill_address: address, ship_address: address) }
|
||||
after { Warden.test_reset! }
|
||||
|
||||
before do
|
||||
@@ -38,6 +39,19 @@ feature "As a consumer I want to check out my cart", js: true do
|
||||
page.should have_login_modal
|
||||
end
|
||||
|
||||
it "populates user details once logged in" do
|
||||
visit checkout_path
|
||||
within("section[role='main']") { click_button "Log in" }
|
||||
page.should have_login_modal
|
||||
fill_in "Email", with: user.email
|
||||
fill_in "Password", with: user.password
|
||||
within(".login-modal") { click_button 'Log in' }
|
||||
toggle_details
|
||||
|
||||
page.should have_field 'First Name', with: 'Foo'
|
||||
page.should have_field 'Last Name', with: 'Bar'
|
||||
end
|
||||
|
||||
it "allows user to checkout as guest" do
|
||||
visit checkout_path
|
||||
checkout_as_guest
|
||||
|
||||
@@ -72,8 +72,7 @@ feature "As a consumer I want to check out my cart", js: true do
|
||||
let!(:pm1) { create(:payment_method, distributors: [distributor], name: "Roger rabbit", type: "Spree::PaymentMethod::Check") }
|
||||
let!(:pm2) { create(:payment_method, distributors: [distributor]) }
|
||||
let!(:pm3) do
|
||||
Spree::Gateway::PayPalExpress.create!(name: "Paypal", environment: 'test').tap do |pm|
|
||||
pm.distributors << distributor
|
||||
Spree::Gateway::PayPalExpress.create!(name: "Paypal", environment: 'test', distributor_ids: [distributor.id]).tap do |pm|
|
||||
pm.preferred_login = 'devnull-facilitator_api1.rohanmitchell.com'
|
||||
pm.preferred_password = '1406163716'
|
||||
pm.preferred_signature = 'AFcWxV21C7fd0v3bYYYRCpSSRl31AaTntNJ-AjvUJkWf4dgJIvcLsf1V'
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
describe "ProvidersCtrl", ->
|
||||
ctrl = null
|
||||
scope = null
|
||||
paymentMethod = null
|
||||
|
||||
describe "initialising using a payment method without a type", ->
|
||||
beforeEach ->
|
||||
module 'ofn.admin'
|
||||
scope = {}
|
||||
paymentMethod =
|
||||
type: null
|
||||
|
||||
inject ($controller)->
|
||||
ctrl = $controller 'ProvidersCtrl', {$scope: scope, paymentMethod: paymentMethod }
|
||||
|
||||
it "sets the invlude_html porperty on scope to blank", ->
|
||||
expect(scope.include_html).toBe ""
|
||||
|
||||
describe "initialising using a payment method with a type", ->
|
||||
beforeEach ->
|
||||
module 'ofn.admin'
|
||||
scope = {}
|
||||
paymentMethod =
|
||||
type: "NOT NULL"
|
||||
|
||||
inject ($controller)->
|
||||
ctrl = $controller 'ProvidersCtrl', {$scope: scope, paymentMethod: paymentMethod }
|
||||
|
||||
it "sets the include_html porperty on scope to some address", ->
|
||||
expect(scope.include_html).toBe "/admin/payment_methods/show_provider_preferences?provider_type=NOT NULL;pm_id=#{paymentMethod.id};"
|
||||
32
spec/javascripts/unit/admin/services/taxons_spec.js.coffee
Normal file
32
spec/javascripts/unit/admin/services/taxons_spec.js.coffee
Normal file
@@ -0,0 +1,32 @@
|
||||
describe "Taxons service", ->
|
||||
Taxons = taxons = $httpBackend = $resource = null
|
||||
|
||||
beforeEach ->
|
||||
module "ofn.admin"
|
||||
module ($provide)->
|
||||
$provide.value "taxons", [{id: "1", name: "t1"}, {id: "2", name: "t2"}, {id: "12", name: "t12"}, {id: "31", name: "t31"}]
|
||||
null
|
||||
|
||||
beforeEach inject (_Taxons_, _$resource_, _$httpBackend_) ->
|
||||
Taxons = _Taxons_
|
||||
$resource = _$resource_
|
||||
$httpBackend = _$httpBackend_
|
||||
|
||||
describe "findByID", ->
|
||||
it "returns the taxon with exactly matching id, ignoring ids which do not exactly match", ->
|
||||
result = Taxons.findByID("1")
|
||||
expect(result).toEqual {id: "1", name: "t1"}
|
||||
|
||||
describe "findByIDs", ->
|
||||
it "returns taxons with exactly matching ids", ->
|
||||
result = Taxons.findByIDs("1,2")
|
||||
expect(result).toEqual [{id: "1", name: "t1"}, {id: "2", name: "t2"}]
|
||||
|
||||
it "ignores ids which do not exactly match", ->
|
||||
result = Taxons.findByIDs("1,3")
|
||||
expect(result).toEqual [{id: "1", name: "t1"}]
|
||||
|
||||
describe "findByTerm", ->
|
||||
it "returns taxons which match partially", ->
|
||||
result = Taxons.findByTerm("t1")
|
||||
expect(result).toEqual [{id: "1", name: "t1"}, {id: "12", name: "t12"}]
|
||||
@@ -184,10 +184,7 @@ describe "filtering products for submission to database", ->
|
||||
created_at: null
|
||||
updated_at: null
|
||||
count_on_hand: 0
|
||||
supplier_id: 5
|
||||
supplier:
|
||||
id: 5
|
||||
name: "Supplier 1"
|
||||
producer: 5
|
||||
|
||||
group_buy: null
|
||||
group_buy_unit_size: null
|
||||
@@ -238,6 +235,11 @@ describe "AdminProductEditCtrl", ->
|
||||
|
||||
beforeEach ->
|
||||
module "ofn.admin"
|
||||
module ($provide)->
|
||||
$provide.value "producers", []
|
||||
$provide.value "taxons", []
|
||||
null
|
||||
|
||||
beforeEach inject((_$controller_, _$timeout_, $rootScope, _$httpBackend_, _DirtyProducts_) ->
|
||||
$scope = $rootScope.$new()
|
||||
$ctrl = _$controller_
|
||||
@@ -249,38 +251,43 @@ describe "AdminProductEditCtrl", ->
|
||||
)
|
||||
|
||||
describe "loading data upon initialisation", ->
|
||||
it "gets a list of suppliers and then resets products with a list of data", ->
|
||||
it "gets a list of producers and then resets products with a list of data", ->
|
||||
$httpBackend.expectGET("/api/users/authorise_api?token=api_key").respond success: "Use of API Authorised"
|
||||
$httpBackend.expectGET("/api/enterprises/managed?template=bulk_index&q[is_primary_producer_eq]=true").respond "list of suppliers"
|
||||
spyOn($scope, "fetchProducts").andReturn "nothing"
|
||||
$scope.initialise "api_key"
|
||||
$httpBackend.flush()
|
||||
expect($scope.suppliers).toEqual "list of suppliers"
|
||||
expect($scope.fetchProducts.calls.length).toEqual 1
|
||||
expect($scope.spree_api_key_ok).toEqual true
|
||||
|
||||
|
||||
describe "fetching products", ->
|
||||
it "makes a standard call to dataFetcher when no filters exist", ->
|
||||
$httpBackend.expectGET("/api/products/managed?template=bulk_index;page=1;per_page=500;").respond "list of products"
|
||||
$httpBackend.expectGET("/api/products/bulk_products?page=1;per_page=20;").respond "list of products"
|
||||
$scope.fetchProducts()
|
||||
|
||||
it "calls resetProducts after data has been received", ->
|
||||
spyOn $scope, "resetProducts"
|
||||
$httpBackend.expectGET("/api/products/managed?template=bulk_index;page=1;per_page=500;").respond "list of products"
|
||||
$httpBackend.expectGET("/api/products/bulk_products?page=1;per_page=20;").respond { products: "list of products" }
|
||||
$scope.fetchProducts()
|
||||
$httpBackend.flush()
|
||||
expect($scope.resetProducts).toHaveBeenCalledWith "list of products"
|
||||
|
||||
it "calls 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"] }
|
||||
$scope.fetchProducts()
|
||||
$httpBackend.flush()
|
||||
|
||||
it "applies filters when they are present", ->
|
||||
filter = {property: $scope.filterableColumns[1], predicate:$scope.filterTypes[0], value:"Product1"}
|
||||
$scope.currentFilters.push filter # Don't use addFilter as that is not what we are testing
|
||||
expect($scope.currentFilters).toEqual [filter]
|
||||
$httpBackend.expectGET("/api/products/managed?template=bulk_index;page=1;per_page=500;q[name_eq]=Product1;").respond "list of products"
|
||||
$httpBackend.expectGET("/api/products/bulk_products?page=1;per_page=20;q[name_eq]=Product1;").respond "list of products"
|
||||
$scope.fetchProducts()
|
||||
$httpBackend.flush()
|
||||
|
||||
it "sets the loading property to true before fetching products and unsets it when loading is complete", ->
|
||||
$httpBackend.expectGET("/api/products/managed?template=bulk_index;page=1;per_page=500;").respond "list of products"
|
||||
$httpBackend.expectGET("/api/products/bulk_products?page=1;per_page=20;").respond "list of products"
|
||||
$scope.fetchProducts()
|
||||
expect($scope.loading).toEqual true
|
||||
$httpBackend.flush()
|
||||
@@ -324,7 +331,6 @@ describe "AdminProductEditCtrl", ->
|
||||
|
||||
describe "preparing products", ->
|
||||
beforeEach ->
|
||||
spyOn $scope, "matchSupplier"
|
||||
spyOn $scope, "loadVariantUnit"
|
||||
|
||||
it "initialises display properties for the product", ->
|
||||
@@ -333,12 +339,6 @@ describe "AdminProductEditCtrl", ->
|
||||
$scope.unpackProduct product
|
||||
expect($scope.displayProperties[123]).toEqual {showVariants: false}
|
||||
|
||||
it "calls matchSupplier for the product", ->
|
||||
product = {id: 123}
|
||||
$scope.displayProperties = {}
|
||||
$scope.unpackProduct product
|
||||
expect($scope.matchSupplier.calls.length).toEqual 1
|
||||
|
||||
it "calls loadVariantUnit for the product", ->
|
||||
product = {id: 123}
|
||||
$scope.displayProperties = {}
|
||||
@@ -346,33 +346,6 @@ describe "AdminProductEditCtrl", ->
|
||||
expect($scope.loadVariantUnit.calls.length).toEqual 1
|
||||
|
||||
|
||||
describe "matching supplier", ->
|
||||
it "changes the supplier of the product to the one which matches it from the suppliers list", ->
|
||||
s1_s =
|
||||
id: 1
|
||||
name: "S1"
|
||||
|
||||
s2_s =
|
||||
id: 2
|
||||
name: "S2"
|
||||
|
||||
s1_p =
|
||||
id: 1
|
||||
name: "S1"
|
||||
|
||||
expect(s1_s is s1_p).not.toEqual true
|
||||
$scope.suppliers = [
|
||||
s1_s
|
||||
s2_s
|
||||
]
|
||||
product =
|
||||
id: 10
|
||||
supplier: s1_p
|
||||
|
||||
$scope.matchSupplier product
|
||||
expect(product.supplier is s1_s).toEqual true
|
||||
|
||||
|
||||
describe "loading variant unit", ->
|
||||
describe "setting product variant_unit_with_scale field", ->
|
||||
it "sets by combining variant_unit and variant_unit_scale", ->
|
||||
@@ -397,8 +370,9 @@ describe "AdminProductEditCtrl", ->
|
||||
$scope.loadVariantUnit product
|
||||
expect(product.variant_unit_with_scale).toEqual "items"
|
||||
|
||||
it "loads data for variants (inc. master)", ->
|
||||
spyOn $scope, "loadVariantVariantUnit"
|
||||
it "loads data for variants (incl. master)", ->
|
||||
spyOn $scope, "loadVariantUnitValues"
|
||||
spyOn $scope, "loadVariantUnitValue"
|
||||
|
||||
product =
|
||||
variant_unit_scale: 1.0
|
||||
@@ -406,15 +380,27 @@ describe "AdminProductEditCtrl", ->
|
||||
variants: [{id: 2, unit_value: 2, unit_description: '(two)'}]
|
||||
$scope.loadVariantUnit product
|
||||
|
||||
expect($scope.loadVariantVariantUnit).toHaveBeenCalledWith product, product.variants[0]
|
||||
expect($scope.loadVariantVariantUnit).toHaveBeenCalledWith product, product.master
|
||||
expect($scope.loadVariantUnitValues).toHaveBeenCalledWith product
|
||||
expect($scope.loadVariantUnitValue).toHaveBeenCalledWith product, product.master
|
||||
|
||||
it "loads data for variants (excl. master)", ->
|
||||
spyOn $scope, "loadVariantUnitValue"
|
||||
|
||||
product =
|
||||
variant_unit_scale: 1.0
|
||||
master: {id: 1, unit_value: 1, unit_description: '(one)'}
|
||||
variants: [{id: 2, unit_value: 2, unit_description: '(two)'}]
|
||||
$scope.loadVariantUnitValues product
|
||||
|
||||
expect($scope.loadVariantUnitValue).toHaveBeenCalledWith product, product.variants[0]
|
||||
expect($scope.loadVariantUnitValue).not.toHaveBeenCalledWith product, product.master
|
||||
|
||||
describe "setting variant unit_value_with_description", ->
|
||||
it "sets by combining unit_value and unit_description", ->
|
||||
product =
|
||||
variant_unit_scale: 1.0
|
||||
variants: [{id: 1, unit_value: 1, unit_description: '(bottle)'}]
|
||||
$scope.loadVariantVariantUnit product, product.variants[0]
|
||||
$scope.loadVariantUnitValues product, product.variants[0]
|
||||
expect(product.variants[0]).toEqual
|
||||
id: 1
|
||||
unit_value: 1
|
||||
@@ -425,28 +411,28 @@ describe "AdminProductEditCtrl", ->
|
||||
product =
|
||||
variant_unit_scale: 1.0
|
||||
variants: [{id: 1, unit_value: 1}]
|
||||
$scope.loadVariantVariantUnit product, product.variants[0]
|
||||
$scope.loadVariantUnitValues product, product.variants[0]
|
||||
expect(product.variants[0].unit_value_with_description).toEqual '1'
|
||||
|
||||
it "uses unit_description when value is missing", ->
|
||||
product =
|
||||
variant_unit_scale: 1.0
|
||||
variants: [{id: 1, unit_description: 'Small'}]
|
||||
$scope.loadVariantVariantUnit product, product.variants[0]
|
||||
$scope.loadVariantUnitValues product, product.variants[0]
|
||||
expect(product.variants[0].unit_value_with_description).toEqual 'Small'
|
||||
|
||||
it "converts values from base value to chosen unit", ->
|
||||
product =
|
||||
variant_unit_scale: 1000.0
|
||||
variants: [{id: 1, unit_value: 2500}]
|
||||
$scope.loadVariantVariantUnit product, product.variants[0]
|
||||
$scope.loadVariantUnitValues product, product.variants[0]
|
||||
expect(product.variants[0].unit_value_with_description).toEqual '2.5'
|
||||
|
||||
it "displays a unit_value of zero", ->
|
||||
product =
|
||||
variant_unit_scale: 1.0
|
||||
variants: [{id: 1, unit_value: 0}]
|
||||
$scope.loadVariantVariantUnit product, product.variants[0]
|
||||
$scope.loadVariantUnitValues product, product.variants[0]
|
||||
expect(product.variants[0].unit_value_with_description).toEqual '0'
|
||||
|
||||
|
||||
@@ -827,6 +813,8 @@ describe "AdminProductEditCtrl", ->
|
||||
|
||||
it "runs displaySuccess() when post returns success", ->
|
||||
spyOn $scope, "displaySuccess"
|
||||
spyOn $scope, "updateVariantLists"
|
||||
spyOn DirtyProducts, "clear"
|
||||
$scope.products = [
|
||||
{
|
||||
id: 1
|
||||
@@ -851,104 +839,34 @@ describe "AdminProductEditCtrl", ->
|
||||
$httpBackend.flush()
|
||||
$timeout.flush()
|
||||
expect($scope.displaySuccess).toHaveBeenCalled()
|
||||
expect(DirtyProducts.clear).toHaveBeenCalled()
|
||||
expect($scope.updateVariantLists).toHaveBeenCalled()
|
||||
|
||||
it "runs displayFailure() when post return data does not match $scope.products", ->
|
||||
spyOn $scope, "displayFailure"
|
||||
$scope.products = "current list of products"
|
||||
$httpBackend.expectPOST("/admin/products/bulk_update").respond 200, "returned list of products"
|
||||
$scope.updateProducts "updated list of products"
|
||||
$httpBackend.flush()
|
||||
expect($scope.displayFailure).toHaveBeenCalled()
|
||||
|
||||
it "runs displayFailure() when post returns error", ->
|
||||
it "runs displayFailure() when post returns an error", ->
|
||||
spyOn $scope, "displayFailure"
|
||||
$scope.products = "updated list of products"
|
||||
$httpBackend.expectPOST("/admin/products/bulk_update").respond 404, "updated list of products"
|
||||
$httpBackend.expectPOST("/admin/products/bulk_update").respond 500, "updated list of products"
|
||||
$scope.updateProducts "updated list of products"
|
||||
$httpBackend.flush()
|
||||
expect($scope.displayFailure).toHaveBeenCalled()
|
||||
|
||||
|
||||
describe "copying new variant ids from server to client", ->
|
||||
it "copies server ids to the client where the client id is negative", ->
|
||||
clientProducts = [
|
||||
{
|
||||
id: 123
|
||||
variants: [{id: 1}, {id: -2}, {id: -3}]
|
||||
}
|
||||
]
|
||||
serverProducts = [
|
||||
{
|
||||
id: 123
|
||||
variants: [{id: 1}, {id: 4534}, {id: 3453}]
|
||||
}
|
||||
]
|
||||
$scope.copyNewVariantIds(clientProducts, serverProducts)
|
||||
expect(clientProducts).toEqual(serverProducts)
|
||||
|
||||
|
||||
describe "fetching products without derived attributes", ->
|
||||
it "returns products without the variant_unit_with_scale field", ->
|
||||
$scope.products = [{id: 123, variant_unit_with_scale: 'weight_1000'}]
|
||||
expect($scope.productsWithoutDerivedAttributes($scope.products)).toEqual([{id: 123}])
|
||||
|
||||
it "returns an empty array when products are undefined", ->
|
||||
expect($scope.productsWithoutDerivedAttributes($scope.products)).toEqual([])
|
||||
|
||||
it "does not alter original products", ->
|
||||
$scope.products = [{
|
||||
id: 123
|
||||
variant_unit_with_scale: 'weight_1000'
|
||||
variants: [{options_text: 'foo'}]
|
||||
}]
|
||||
$scope.productsWithoutDerivedAttributes($scope.products)
|
||||
expect($scope.products).toEqual [{
|
||||
id: 123
|
||||
variant_unit_with_scale: 'weight_1000'
|
||||
variants: [{options_text: 'foo'}]
|
||||
}]
|
||||
|
||||
describe "updating variants", ->
|
||||
it "returns variants without the unit_value_with_description field", ->
|
||||
$scope.products = [{id: 123, variants: [{id: 234, unit_value_with_description: 'foo'}]}]
|
||||
expect($scope.productsWithoutDerivedAttributes($scope.products)).toEqual [
|
||||
{
|
||||
id: 123
|
||||
variants: [{id: 234}]
|
||||
}
|
||||
]
|
||||
|
||||
it "removes the master variant", ->
|
||||
$scope.products = [{id: 123, master: {id: 234, unit_value_with_description: 'foo'}}]
|
||||
expect($scope.productsWithoutDerivedAttributes($scope.products)).toEqual [
|
||||
{
|
||||
id: 123
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
describe "deep copying products", ->
|
||||
it "copies products", ->
|
||||
product = {id: 123}
|
||||
copiedProducts = $scope.deepCopyProducts [product]
|
||||
expect(copiedProducts[0]).not.toBe(product)
|
||||
|
||||
it "copies variants", ->
|
||||
variant = {id: 1}
|
||||
product = {id: 123, variants: [variant]}
|
||||
copiedProducts = $scope.deepCopyProducts [product]
|
||||
expect(copiedProducts[0].variants[0]).not.toBe(variant)
|
||||
|
||||
it "shows an alert with error information when post returns 400 with an errors array", ->
|
||||
spyOn(window, "alert")
|
||||
$scope.products = "updated list of products"
|
||||
$httpBackend.expectPOST("/admin/products/bulk_update").respond 400, { "errors": ["an error"] }
|
||||
$scope.updateProducts "updated list of products"
|
||||
$httpBackend.flush()
|
||||
expect(window.alert).toHaveBeenCalledWith("Saving failed with the following error(s):\nan error\n")
|
||||
|
||||
describe "fetching a product by id", ->
|
||||
it "returns the product when it is present", ->
|
||||
product = {id: 123}
|
||||
$scope.products = [product]
|
||||
expect($scope.findProduct(123)).toEqual product
|
||||
expect($scope.findProduct(123, $scope.products)).toEqual product
|
||||
|
||||
it "returns null when the product is not present", ->
|
||||
$scope.products = []
|
||||
expect($scope.findProduct(123)).toBeNull()
|
||||
expect($scope.findProduct(123, $scope.products)).toBeNull()
|
||||
|
||||
|
||||
describe "adding variants", ->
|
||||
@@ -1122,7 +1040,7 @@ describe "AdminProductEditCtrl", ->
|
||||
$scope.cloneProduct $scope.products[0]
|
||||
$httpBackend.flush()
|
||||
|
||||
it "adds the newly created product to $scope.products and matches supplier", ->
|
||||
it "adds the newly created product to $scope.products and matches producer", ->
|
||||
spyOn($scope, "unpackProduct").andCallThrough()
|
||||
$scope.products = [
|
||||
id: 13
|
||||
@@ -1132,8 +1050,7 @@ describe "AdminProductEditCtrl", ->
|
||||
product:
|
||||
id: 17
|
||||
name: "new_product"
|
||||
supplier:
|
||||
id: 6
|
||||
producer: 6
|
||||
|
||||
variants: [
|
||||
id: 3
|
||||
@@ -1143,8 +1060,7 @@ describe "AdminProductEditCtrl", ->
|
||||
$httpBackend.expectGET("/api/products/17?template=bulk_show").respond 200,
|
||||
id: 17
|
||||
name: "new_product"
|
||||
supplier:
|
||||
id: 6
|
||||
producer: 6
|
||||
|
||||
variants: [
|
||||
id: 3
|
||||
@@ -1157,8 +1073,7 @@ describe "AdminProductEditCtrl", ->
|
||||
id: 17
|
||||
name: "new_product"
|
||||
variant_unit_with_scale: null
|
||||
supplier:
|
||||
id: 6
|
||||
producer: 6
|
||||
|
||||
variants: [
|
||||
id: 3
|
||||
@@ -1175,8 +1090,7 @@ describe "AdminProductEditCtrl", ->
|
||||
id: 17
|
||||
name: "new_product"
|
||||
variant_unit_with_scale: null
|
||||
supplier:
|
||||
id: 6
|
||||
producer: 6
|
||||
|
||||
variants: [
|
||||
id: 3
|
||||
@@ -1188,94 +1102,15 @@ describe "AdminProductEditCtrl", ->
|
||||
|
||||
|
||||
describe "filtering products", ->
|
||||
describe "adding a filter to the filter list", ->
|
||||
filterObject1 = filterObject2 = null
|
||||
|
||||
beforeEach ->
|
||||
spyOn($scope, "fetchProducts").andReturn "nothing"
|
||||
spyOn(DirtyProducts, "count").andReturn 0
|
||||
filterObject1 = {property: $scope.filterableColumns[0], predicate: $scope.filterTypes[0], value: "value1"}
|
||||
filterObject2 = {property: $scope.filterableColumns[1], predicate: $scope.filterTypes[1], value: "value2"}
|
||||
$scope.addFilter filterObject1
|
||||
$scope.addFilter filterObject2
|
||||
|
||||
it "adds objects sent to addFilter() to $scope.currentFilters", ->
|
||||
expect($scope.currentFilters).toEqual [filterObject1, filterObject2]
|
||||
|
||||
it "ignores objects sent to addFilter() which do not contain a 'property' with a corresponding key in filterableColumns", ->
|
||||
filterObject3 = {property: "some_random_property", predicate: $scope.filterTypes[0], value: "value3"}
|
||||
$scope.addFilter filterObject3
|
||||
expect($scope.currentFilters).toEqual [filterObject1, filterObject2]
|
||||
|
||||
it "ignores objects sent to addFilter() which do not contain a query with a corresponding key in filterTypes", ->
|
||||
filterObject3 = {property: $scope.filterableColumns[0], predicate: "something", value: "value3"}
|
||||
$scope.addFilter filterObject3
|
||||
expect($scope.currentFilters).toEqual [filterObject1, filterObject2]
|
||||
|
||||
it "ignores objects sent to addFilter() which have a blank 'value' property", ->
|
||||
filterObject3 = {property: $scope.filterableColumns[0], predicate: $scope.filterTypes[1], value: ""}
|
||||
$scope.addFilter filterObject3
|
||||
expect($scope.currentFilters).toEqual [filterObject1, filterObject2]
|
||||
|
||||
it "calls fetchProducts when adding a new filter", ->
|
||||
expect($scope.fetchProducts.calls.length).toEqual(2)
|
||||
|
||||
describe "when unsaved products exist", ->
|
||||
beforeEach ->
|
||||
filterObject3 = {property: $scope.filterableColumns[0], predicate: $scope.filterTypes[1], value: "value3"}
|
||||
spyOn(window, "confirm").andReturn false
|
||||
DirtyProducts.count.andReturn 1
|
||||
$scope.addFilter filterObject3
|
||||
|
||||
it "it does not call fetchProducts", ->
|
||||
expect($scope.fetchProducts.calls.length).toEqual(2)
|
||||
|
||||
it "does not add the filter to $scope.currentFilters", ->
|
||||
expect($scope.currentFilters).toEqual [filterObject1, filterObject2]
|
||||
|
||||
it "asks the user to save changes before proceeding", ->
|
||||
expect(window.confirm).toHaveBeenCalledWith "Unsaved changes will be lost. Continue anyway?"
|
||||
|
||||
describe "when a filter on the same property and predicate already exists", ->
|
||||
filterObject3 = null
|
||||
|
||||
beforeEach ->
|
||||
filterObject3 = { property: filterObject2.property, predicate: filterObject2.predicate, value: "new value" }
|
||||
|
||||
it "asks the user for permission before proceeding", ->
|
||||
spyOn(window, "confirm").andReturn true
|
||||
$scope.addFilter filterObject3
|
||||
expect(window.confirm).toHaveBeenCalledWith "'#{filterObject3.predicate.name}' filter already exists on column '#{filterObject3.property.name}'. Replace it?"
|
||||
|
||||
it "replaces the filter in $scope.currentFilters when user clicks OK", ->
|
||||
spyOn(window, "confirm").andReturn true
|
||||
$scope.addFilter filterObject3
|
||||
expect($scope.currentFilters).toEqual [filterObject1, filterObject3]
|
||||
|
||||
it "does not add the filter to $scope.currentFilters when user clicks cancel", ->
|
||||
spyOn(window, "confirm").andReturn false
|
||||
$scope.addFilter filterObject3
|
||||
expect($scope.currentFilters).toEqual [filterObject1, filterObject2]
|
||||
|
||||
describe "removing a filter from the filter list", ->
|
||||
filterObject1 = filterObject2 = null
|
||||
|
||||
beforeEach ->
|
||||
spyOn($scope, "fetchProducts").andReturn "nothing"
|
||||
filterObject1 = {property: $scope.filterableColumns[0], predicate: $scope.filterTypes[0], value: "Product1"}
|
||||
filterObject2 = {property: $scope.filterableColumns[0], predicate: $scope.filterTypes[0], value: "Product2"}
|
||||
$scope.currentFilters = [ filterObject1, filterObject2 ]
|
||||
|
||||
it "removes the specified filter from $scope.currentFilters and calls fetchProducts", ->
|
||||
$scope.removeFilter filterObject1
|
||||
expect($scope.currentFilters).toEqual [ filterObject2 ]
|
||||
expect($scope.fetchProducts.calls.length).toEqual 1
|
||||
|
||||
it "ignores filters which do not exist in currentFilters", ->
|
||||
filterObject3 = {property: $scope.filterableColumns[1], predicate: $scope.filterTypes[1], value: "SomethingElse"}
|
||||
$scope.removeFilter filterObject3
|
||||
expect($scope.currentFilters).toEqual [ filterObject1, filterObject2 ]
|
||||
expect($scope.fetchProducts.calls.length).toEqual 0
|
||||
describe "clearing filters", ->
|
||||
it "resets filter variables", ->
|
||||
$scope.query = "lala"
|
||||
$scope.producerFilter = "5"
|
||||
$scope.categoryFilter = "6"
|
||||
$scope.resetSelectFilters()
|
||||
expect($scope.query).toBe ""
|
||||
expect($scope.producerFilter).toBe "0"
|
||||
expect($scope.categoryFilter).toBe "0"
|
||||
|
||||
|
||||
describe "converting arrays of objects with ids to an object with ids as keys", ->
|
||||
@@ -1334,28 +1169,3 @@ describe "converting arrays of objects with ids to an object with ids as keys",
|
||||
|
||||
expect(toObjectWithIDKeys).toHaveBeenCalledWith [id: 17]
|
||||
expect(toObjectWithIDKeys).not.toHaveBeenCalledWith {12: {id: 12}}
|
||||
|
||||
describe "Taxons service", ->
|
||||
Taxons = $httpBackend = $resource = null
|
||||
|
||||
beforeEach ->
|
||||
module "ofn.admin"
|
||||
|
||||
beforeEach inject (_Taxons_, _$resource_, _$httpBackend_) ->
|
||||
Taxons = _Taxons_
|
||||
$resource = _$resource_
|
||||
$httpBackend = _$httpBackend_
|
||||
|
||||
it "calling findByIDs makes a http request", ->
|
||||
response = { taxons: "list of taxons by id" }
|
||||
$httpBackend.expectGET("/admin/taxons/search?ids=1,2").respond 200, response
|
||||
taxons = Taxons.findByIDs("1,2")
|
||||
$httpBackend.flush()
|
||||
expect(angular.equals(taxons,response)).toBe true
|
||||
|
||||
it "calling findByTerm makes a http request", ->
|
||||
response = { taxons: "list of taxons by term" }
|
||||
$httpBackend.expectGET("/admin/taxons/search?q=lala").respond 200, response
|
||||
taxons = Taxons.findByTerm("lala")
|
||||
$httpBackend.flush()
|
||||
expect(angular.equals(taxons,response)).toBe true
|
||||
|
||||
@@ -46,7 +46,7 @@ describe "CheckoutCtrl", ->
|
||||
|
||||
describe "Local storage", ->
|
||||
it "binds to localStorage when given a scope", ->
|
||||
prefix = "order_#{scope.order.id}#{scope.order.user_id}#{CurrentHubMock.hub.id}"
|
||||
prefix = "order_#{scope.order.id}#{CurrentUser?.id}#{CurrentHubMock.hub.id}"
|
||||
field = scope.fieldsToBind[0]
|
||||
expect(storage.bind).toHaveBeenCalledWith(scope, "Checkout.order.#{field}", {storeName: "#{prefix}_#{field}"})
|
||||
expect(storage.bind).toHaveBeenCalledWith(scope, "Checkout.ship_address_same_as_billing", {storeName: "#{prefix}_sameasbilling", defaultValue: true})
|
||||
|
||||
@@ -14,6 +14,7 @@ describe 'Products service', ->
|
||||
supplier:
|
||||
id: 9
|
||||
price: 11
|
||||
master: {}
|
||||
variants: []
|
||||
currentOrder =
|
||||
line_items: []
|
||||
@@ -39,7 +40,7 @@ describe 'Products service', ->
|
||||
it "dereferences suppliers", ->
|
||||
Enterprises.enterprises_by_id =
|
||||
{id: 9, name: "test"}
|
||||
$httpBackend.expectGET("/shop/products").respond([{supplier : {id: 9}}])
|
||||
$httpBackend.expectGET("/shop/products").respond([{supplier : {id: 9}, master: {}}])
|
||||
$httpBackend.flush()
|
||||
expect(Products.products[0].supplier).toBe Enterprises.enterprises_by_id["9"]
|
||||
|
||||
|
||||
@@ -37,19 +37,20 @@ module OpenFoodNetwork
|
||||
let!(:ef_sales) { create(:enterprise_fee, fee_type: 'sales', amount: 4.56) }
|
||||
let!(:ef_packing) { create(:enterprise_fee, fee_type: 'packing', amount: 7.89) }
|
||||
let!(:ef_transport) { create(:enterprise_fee, fee_type: 'transport', amount: 0.12) }
|
||||
let!(:ef_fundraising) { create(:enterprise_fee, fee_type: 'fundraising', amount: 3.45) }
|
||||
let!(:exchange) { create(:exchange, order_cycle: order_cycle,
|
||||
sender: coordinator, receiver: distributor, incoming: false,
|
||||
enterprise_fees: [ef_admin, ef_sales, ef_packing, ef_transport],
|
||||
enterprise_fees: [ef_admin, ef_sales, ef_packing, ef_transport, ef_fundraising],
|
||||
variants: [product.master]) }
|
||||
|
||||
it "returns a breakdown of fees" do
|
||||
EnterpriseFeeCalculator.new(distributor, order_cycle).fees_by_type_for(product.master).should == {admin: 1.23, sales: 4.56, packing: 7.89, transport: 0.12}
|
||||
EnterpriseFeeCalculator.new(distributor, order_cycle).fees_by_type_for(product.master).should == {admin: 1.23, sales: 4.56, packing: 7.89, transport: 0.12, fundraising: 3.45}
|
||||
end
|
||||
|
||||
it "filters out zero fees" do
|
||||
ef_admin.calculator.update_attribute :preferred_amount, 0
|
||||
|
||||
EnterpriseFeeCalculator.new(distributor, order_cycle).fees_by_type_for(product.master).should == {sales: 4.56, packing: 7.89, transport: 0.12}
|
||||
EnterpriseFeeCalculator.new(distributor, order_cycle).fees_by_type_for(product.master).should == {sales: 4.56, packing: 7.89, transport: 0.12, fundraising: 3.45}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -4,34 +4,34 @@ module OpenFoodNetwork
|
||||
describe OptionValueNamer do
|
||||
describe "generating option value name" do
|
||||
let(:v) { Spree::Variant.new }
|
||||
let(:subject) { OptionValueNamer.new v }
|
||||
let(:subject) { OptionValueNamer.new }
|
||||
|
||||
it "when description is blank" do
|
||||
v.stub(:unit_description) { nil }
|
||||
subject.stub(:value_scaled?) { true }
|
||||
subject.stub(:option_value_value_unit) { %w(value unit) }
|
||||
subject.name.should == "valueunit"
|
||||
subject.name(v).should == "valueunit"
|
||||
end
|
||||
|
||||
it "when description is present" do
|
||||
v.stub(:unit_description) { 'desc' }
|
||||
subject.stub(:option_value_value_unit) { %w(value unit) }
|
||||
subject.stub(:value_scaled?) { true }
|
||||
subject.name.should == "valueunit desc"
|
||||
subject.name(v).should == "valueunit desc"
|
||||
end
|
||||
|
||||
it "when value is blank and description is present" do
|
||||
v.stub(:unit_description) { 'desc' }
|
||||
subject.stub(:option_value_value_unit) { [nil, nil] }
|
||||
subject.stub(:value_scaled?) { true }
|
||||
subject.name.should == "desc"
|
||||
subject.name(v).should == "desc"
|
||||
end
|
||||
|
||||
it "spaces value and unit when value is unscaled" do
|
||||
v.stub(:unit_description) { nil }
|
||||
subject.stub(:option_value_value_unit) { %w(value unit) }
|
||||
subject.stub(:value_scaled?) { false }
|
||||
subject.name.should == "value unit"
|
||||
subject.name(v).should == "value unit"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -42,7 +42,7 @@ module OpenFoodNetwork
|
||||
v.stub(:product) { p }
|
||||
subject = OptionValueNamer.new v
|
||||
|
||||
subject.value_scaled?.should be_true
|
||||
expect(subject.send(:value_scaled?)).to be_true
|
||||
end
|
||||
|
||||
it "returns false otherwise" do
|
||||
@@ -51,7 +51,7 @@ module OpenFoodNetwork
|
||||
v.stub(:product) { p }
|
||||
subject = OptionValueNamer.new v
|
||||
|
||||
subject.value_scaled?.should be_false
|
||||
expect(subject.send(:value_scaled?)).to be_false
|
||||
end
|
||||
end
|
||||
|
||||
@@ -65,7 +65,7 @@ module OpenFoodNetwork
|
||||
v.stub(:unit_value) { 100 }
|
||||
|
||||
|
||||
subject.option_value_value_unit.should == [100, 'g']
|
||||
expect(subject.send(:option_value_value_unit)).to eq [100, 'g']
|
||||
end
|
||||
|
||||
it "generates values when unit value is non-integer" do
|
||||
@@ -73,7 +73,7 @@ module OpenFoodNetwork
|
||||
v.stub(:product) { p }
|
||||
v.stub(:unit_value) { 123.45 }
|
||||
|
||||
subject.option_value_value_unit.should == [123.45, 'g']
|
||||
expect(subject.send(:option_value_value_unit)).to eq [123.45, 'g']
|
||||
end
|
||||
|
||||
it "returns a value of 1 when unit value equals the scale" do
|
||||
@@ -81,7 +81,7 @@ module OpenFoodNetwork
|
||||
v.stub(:product) { p }
|
||||
v.stub(:unit_value) { 1000.0 }
|
||||
|
||||
subject.option_value_value_unit.should == [1, 'kg']
|
||||
expect(subject.send(:option_value_value_unit)).to eq [1, 'kg']
|
||||
end
|
||||
|
||||
it "generates values for all weight scales" do
|
||||
@@ -89,7 +89,7 @@ module OpenFoodNetwork
|
||||
p = double(:product, variant_unit: 'weight', variant_unit_scale: scale)
|
||||
v.stub(:product) { p }
|
||||
v.stub(:unit_value) { 100 * scale }
|
||||
subject.option_value_value_unit.should == [100, unit]
|
||||
expect(subject.send(:option_value_value_unit)).to eq [100, unit]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -98,7 +98,7 @@ module OpenFoodNetwork
|
||||
p = double(:product, variant_unit: 'volume', variant_unit_scale: scale)
|
||||
v.stub(:product) { p }
|
||||
v.stub(:unit_value) { 100 * scale }
|
||||
subject.option_value_value_unit.should == [100, unit]
|
||||
expect(subject.send(:option_value_value_unit)).to eq [100, unit]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -106,7 +106,7 @@ module OpenFoodNetwork
|
||||
p = double(:product, variant_unit: 'volume', variant_unit_scale: 0.001)
|
||||
v.stub(:product) { p }
|
||||
v.stub(:unit_value) { 0.0001 }
|
||||
subject.option_value_value_unit.should == [0.1, 'mL']
|
||||
expect(subject.send(:option_value_value_unit)).to eq [0.1, 'mL']
|
||||
end
|
||||
|
||||
it "generates values for item units" do
|
||||
@@ -114,7 +114,7 @@ module OpenFoodNetwork
|
||||
p = double(:product, variant_unit: 'items', variant_unit_scale: nil, variant_unit_name: unit)
|
||||
v.stub(:product) { p }
|
||||
v.stub(:unit_value) { 100 }
|
||||
subject.option_value_value_unit.should == [100, unit.pluralize]
|
||||
expect(subject.send(:option_value_value_unit)).to eq [100, unit.pluralize]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -122,14 +122,14 @@ module OpenFoodNetwork
|
||||
p = double(:product, variant_unit: 'items', variant_unit_scale: nil, variant_unit_name: 'packet')
|
||||
v.stub(:product) { p }
|
||||
v.stub(:unit_value) { 1 }
|
||||
subject.option_value_value_unit.should == [1, 'packet']
|
||||
expect(subject.send(:option_value_value_unit)).to eq [1, 'packet']
|
||||
end
|
||||
|
||||
it "returns [nil, nil] when unit value is not set" do
|
||||
p = double(:product, variant_unit: 'items', variant_unit_scale: nil, variant_unit_name: 'foo')
|
||||
v.stub(:product) { p }
|
||||
v.stub(:unit_value) { nil }
|
||||
subject.option_value_value_unit.should == [nil, nil]
|
||||
expect(subject.send(:option_value_value_unit)).to eq [nil, nil]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,6 +25,8 @@ describe ProductDistribution do
|
||||
describe "adjusting orders" do
|
||||
context "integration" do
|
||||
it "creates an adjustment for product distributions" do
|
||||
pending "Intermittently failing spec - we intend to remove product distributions soon"
|
||||
|
||||
# Given an order
|
||||
distributor = create(:distributor_enterprise)
|
||||
order = create(:order, distributor: distributor)
|
||||
|
||||
@@ -22,9 +22,11 @@ describe Spree::Order do
|
||||
end
|
||||
|
||||
describe "Payment methods" do
|
||||
let(:order) { build(:order, distributor: create(:distributor_enterprise)) }
|
||||
let(:pm1) { create(:payment_method, distributors: [order.distributor])}
|
||||
let(:pm2) { create(:payment_method, distributors: [])}
|
||||
let(:order_distributor) { create(:distributor_enterprise) }
|
||||
let(:some_other_distributor) { create(:distributor_enterprise) }
|
||||
let(:order) { build(:order, distributor: order_distributor) }
|
||||
let(:pm1) { create(:payment_method, distributors: [order_distributor])}
|
||||
let(:pm2) { create(:payment_method, distributors: [some_other_distributor])}
|
||||
|
||||
it "finds the correct payment methods" do
|
||||
Spree::PaymentMethod.stub(:available).and_return [pm1, pm2]
|
||||
|
||||
@@ -9,5 +9,20 @@ module Spree
|
||||
|
||||
PaymentMethod.by_name.should == [pm2, pm3, pm1]
|
||||
end
|
||||
|
||||
it "raises errors when required fields are missing" do
|
||||
pm = PaymentMethod.new()
|
||||
pm.save
|
||||
pm.errors.to_a.should == ["Name can't be blank", "At least one hub must be selected"]
|
||||
end
|
||||
|
||||
it "generates a clean name for known Payment Method types" do
|
||||
Spree::PaymentMethod::Check.clean_name.should == "Cash/EFT/etc. (payments for which automatic validation is not required)"
|
||||
Spree::Gateway::Migs.clean_name.should == "MasterCard Internet Gateway Service (MIGS)"
|
||||
Spree::Gateway::PayPalExpress.clean_name.should == "PayPal Express"
|
||||
|
||||
# Testing else condition
|
||||
Spree::Gateway::BogusSimple.clean_name.should == "BogusSimple"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
7
spec/serializers/spree/product_serializer_spec.rb
Normal file
7
spec/serializers/spree/product_serializer_spec.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
describe Spree::Api::ProductSerializer do
|
||||
let(:product) { create(:simple_product) }
|
||||
it "serializes a product" do
|
||||
serializer = Spree::Api::ProductSerializer.new product
|
||||
serializer.to_json.should match product.name
|
||||
end
|
||||
end
|
||||
7
spec/serializers/spree/variant_serializer_spec.rb
Normal file
7
spec/serializers/spree/variant_serializer_spec.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
describe Spree::Api::VariantSerializer do
|
||||
let(:variant) { create(:variant) }
|
||||
it "serializes a variant" do
|
||||
serializer = Spree::Api::VariantSerializer.new variant
|
||||
serializer.to_json.should match variant.options_text
|
||||
end
|
||||
end
|
||||
@@ -1,8 +0,0 @@
|
||||
/**
|
||||
* angular-timer - v1.1.6 - 2014-07-01 7:37 AM
|
||||
* https://github.com/siddii/angular-timer
|
||||
*
|
||||
* Copyright (c) 2014 Siddique Hameed
|
||||
* Licensed MIT <https://github.com/siddii/angular-timer/blob/master/LICENSE.txt>
|
||||
*/
|
||||
var timerModule=angular.module("timer",[]).directive("timer",["$compile",function(a){return{restrict:"EAC",replace:!1,scope:{interval:"=interval",startTimeAttr:"=startTime",endTimeAttr:"=endTime",countdownattr:"=countdown",finishCallback:"&finishCallback",autoStart:"&autoStart",maxTimeUnit:"="},controller:["$scope","$element","$attrs","$timeout",function(b,c,d,e){function f(){b.timeoutId&&clearTimeout(b.timeoutId)}function g(){b.maxTimeUnit&&"day"!==b.maxTimeUnit?"second"===b.maxTimeUnit?(b.seconds=Math.floor(b.millis/1e3),b.minutes=0,b.hours=0,b.days=0,b.months=0,b.years=0):"minute"===b.maxTimeUnit?(b.seconds=Math.floor(b.millis/1e3%60),b.minutes=Math.floor(b.millis/6e4),b.hours=0,b.days=0,b.months=0,b.years=0):"hour"===b.maxTimeUnit?(b.seconds=Math.floor(b.millis/1e3%60),b.minutes=Math.floor(b.millis/6e4%60),b.hours=Math.floor(b.millis/36e5),b.days=0,b.months=0,b.years=0):"month"===b.maxTimeUnit?(b.seconds=Math.floor(b.millis/1e3%60),b.minutes=Math.floor(b.millis/6e4%60),b.hours=Math.floor(b.millis/36e5%24),b.days=Math.floor(b.millis/36e5/24%30),b.months=Math.floor(b.millis/36e5/24/30),b.years=0):"year"===b.maxTimeUnit&&(b.seconds=Math.floor(b.millis/1e3%60),b.minutes=Math.floor(b.millis/6e4%60),b.hours=Math.floor(b.millis/36e5%24),b.days=Math.floor(b.millis/36e5/24%30),b.months=Math.floor(b.millis/36e5/24/30%12),b.years=Math.floor(b.millis/36e5/24/365)):(b.seconds=Math.floor(b.millis/1e3%60),b.minutes=Math.floor(b.millis/6e4%60),b.hours=Math.floor(b.millis/36e5%24),b.days=Math.floor(b.millis/36e5/24),b.months=0,b.years=0),b.secondsS=1==b.seconds?"":"s",b.minutesS=1==b.minutes?"":"s",b.hoursS=1==b.hours?"":"s",b.daysS=1==b.days?"":"s",b.monthsS=1==b.months?"":"s",b.yearsS=1==b.years?"":"s",b.sseconds=b.seconds<10?"0"+b.seconds:b.seconds,b.mminutes=b.minutes<10?"0"+b.minutes:b.minutes,b.hhours=b.hours<10?"0"+b.hours:b.hours,b.ddays=b.days<10?"0"+b.days:b.days,b.mmonths=b.months<10?"0"+b.months:b.months,b.yyears=b.years<10?"0"+b.years:b.years}"function"!=typeof String.prototype.trim&&(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")}),b.autoStart=d.autoStart||d.autostart,c.append(0===c.html().trim().length?a("<span>{{millis}}</span>")(b):a(c.contents())(b)),b.startTime=null,b.endTime=null,b.timeoutId=null,b.countdown=b.countdownattr&&parseInt(b.countdownattr,10)>=0?parseInt(b.countdownattr,10):void 0,b.isRunning=!1,b.$on("timer-start",function(){b.start()}),b.$on("timer-resume",function(){b.resume()}),b.$on("timer-stop",function(){b.stop()}),b.$on("timer-clear",function(){b.clear()}),b.$on("timer-set-countdown",function(a,c){b.countdown=c}),b.start=c[0].start=function(){b.startTime=b.startTimeAttr?new Date(b.startTimeAttr):new Date,b.endTime=b.endTimeAttr?new Date(b.endTimeAttr):null,b.countdown||(b.countdown=b.countdownattr&&parseInt(b.countdownattr,10)>0?parseInt(b.countdownattr,10):void 0),f(),h(),b.isRunning=!0},b.resume=c[0].resume=function(){f(),b.countdownattr&&(b.countdown+=1),b.startTime=new Date-(b.stoppedTime-b.startTime),h(),b.isRunning=!0},b.stop=b.pause=c[0].stop=c[0].pause=function(){var a=b.timeoutId;b.clear(),b.$emit("timer-stopped",{timeoutId:a,millis:b.millis,seconds:b.seconds,minutes:b.minutes,hours:b.hours,days:b.days})},b.clear=c[0].clear=function(){b.stoppedTime=new Date,f(),b.timeoutId=null,b.isRunning=!1},c.bind("$destroy",function(){f(),b.isRunning=!1}),b.countdownattr?(b.millis=1e3*b.countdownattr,b.addCDSeconds=c[0].addCDSeconds=function(a){b.countdown+=a,b.$digest(),b.isRunning||b.start()},b.$on("timer-add-cd-seconds",function(a,c){e(function(){b.addCDSeconds(c)})}),b.$on("timer-set-countdown-seconds",function(a,c){b.isRunning||b.clear(),b.countdown=c,b.millis=1e3*c,g()})):b.millis=0,g();var h=function(){b.millis=new Date-b.startTime;var a=b.millis%1e3;return b.endTimeAttr&&(b.millis=b.endTime-new Date,a=b.interval-b.millis%1e3),b.countdownattr&&(b.millis=1e3*b.countdown),b.millis<0?(b.stop(),b.millis=0,g(),void(b.finishCallback&&b.$eval(b.finishCallback))):(g(),b.timeoutId=setTimeout(function(){h(),b.$digest()},b.interval-a),b.$emit("timer-tick",{timeoutId:b.timeoutId,millis:b.millis}),void(b.countdown>0?b.countdown--:b.countdown<=0&&(b.stop(),b.finishCallback&&b.$eval(b.finishCallback))))};(void 0===b.autoStart||b.autoStart===!0)&&b.start()}]}}]);"undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports=timerModule);
|
||||
Reference in New Issue
Block a user