Merge branch 'master' into enterprise-fee-naming-example

This commit is contained in:
Rohan Mitchell
2014-09-02 13:36:50 +10:00
251 changed files with 3500 additions and 2314 deletions

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="32px" y="32px" height="32px" width="32px"
viewBox="0 0 30.2 40" enable-background="new 0 0 30.2 40" xml:space="preserve">
<g id="Layer_1_1_">
<g>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="32px" y="32px" height="32px" width="32px"
viewBox="0 0 30.2 40" enable-background="new 0 0 30.2 40" xml:space="preserve">
<g id="Layer_1_1_">
<g>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="32px" y="32px" height="32px" width="32px"
viewBox="0 0 30.2 40" enable-background="new 0 0 30.2 40" xml:space="preserve">
<g id="Layer_1_1_">
<g>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -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, */*"

View File

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

View File

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

View File

@@ -1,9 +1,10 @@
angular.module("ofn.admin").controller "AdminEnterpriseRelationshipsCtrl", ($scope, EnterpriseRelationships, Enterprises) ->
$scope.EnterpriseRelationships = EnterpriseRelationships
$scope.Enterprises = Enterprises
$scope.permissions = {}
$scope.create = ->
$scope.EnterpriseRelationships.create($scope.parent_id, $scope.child_id)
$scope.EnterpriseRelationships.create($scope.parent_id, $scope.child_id, $scope.permissions)
$scope.delete = (enterprise_relationship) ->
if confirm("Are you sure?")

View File

@@ -0,0 +1,11 @@
angular.module("ofn.admin").controller "AdminEnterpriseRolesCtrl", ($scope, EnterpriseRoles, Users, Enterprises) ->
$scope.EnterpriseRoles = EnterpriseRoles
$scope.Users = Users
$scope.Enterprises = Enterprises
$scope.create = ->
$scope.EnterpriseRoles.create($scope.user_id, $scope.enterprise_id)
$scope.delete = (enterprise_role) ->
if confirm("Are you sure?")
$scope.EnterpriseRoles.delete enterprise_role

View File

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

View File

@@ -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};"

View File

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

View File

@@ -4,8 +4,8 @@ angular.module("admin.enterprises")
$scope.PaymentMethods = PaymentMethods.paymentMethods
$scope.ShippingMethods = ShippingMethods.shippingMethods
for PaymentMethod in $scope.PaymentMethods
PaymentMethod.selected = if PaymentMethod.id in $scope.Enterprise.payment_method_ids then true else false
for payment_method in $scope.PaymentMethods
payment_method.selected = payment_method.id in $scope.Enterprise.payment_method_ids
$scope.paymentMethodsColor = ->
if $scope.PaymentMethods.length > 0
@@ -14,13 +14,13 @@ angular.module("admin.enterprises")
"red"
$scope.selectedPaymentMethodsCount = ->
$scope.PaymentMethods.reduce (count, PaymentMethod) ->
count++ if PaymentMethod.selected
$scope.PaymentMethods.reduce (count, payment_method) ->
count++ if payment_method.selected
count
, 0
for ShippingMethod in $scope.ShippingMethods
ShippingMethod.selected = if ShippingMethod.id in $scope.Enterprise.shipping_method_ids then true else false
for shipping_method in $scope.ShippingMethods
shipping_method.selected = shipping_method.id in $scope.Enterprise.shipping_method_ids
$scope.shippingMethodsColor = ->
if $scope.ShippingMethods.length > 0
@@ -29,7 +29,7 @@ angular.module("admin.enterprises")
"red"
$scope.selectedShippingMethodsCount = ->
$scope.ShippingMethods.reduce (count, ShippingMethod) ->
count++ if ShippingMethod.selected
$scope.ShippingMethods.reduce (count, shipping_method) ->
count++ if shipping_method.selected
count
, 0

View File

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

View File

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

View File

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

View File

@@ -330,7 +330,7 @@ angular.module('order_cycle', ['ngResource'])
}])
.factory('Enterprise', ['$resource', ($resource) ->
Enterprise = $resource('/admin/enterprises/:enterprise_id.json', {}, {'index': {method: 'GET', isArray: true}})
Enterprise = $resource('/admin/enterprises/for_order_cycle/:enterprise_id.json', {}, {'index': {method: 'GET', isArray: true}})
{
Enterprise: Enterprise

View File

@@ -1,12 +1,17 @@
angular.module("ofn.admin").factory 'EnterpriseRelationships', ($http, enterprise_relationships) ->
new class EnterpriseRelationships
create_errors: ""
all_permissions: [
'add_to_order_cycle'
'manage_products'
]
constructor: ->
@enterprise_relationships = enterprise_relationships
create: (parent_id, child_id) ->
$http.post('/admin/enterprise_relationships', {enterprise_relationship: {parent_id: parent_id, child_id: child_id}}).success (data, status) =>
create: (parent_id, child_id, permissions) ->
permissions = (name for name, enabled of permissions when enabled)
$http.post('/admin/enterprise_relationships', {enterprise_relationship: {parent_id: parent_id, child_id: child_id, permissions_list: permissions}}).success (data, status) =>
@enterprise_relationships.unshift(data)
@create_errors = ""
@@ -16,3 +21,8 @@ angular.module("ofn.admin").factory 'EnterpriseRelationships', ($http, enterpris
delete: (er) ->
$http.delete('/admin/enterprise_relationships/' + er.id).success (data) =>
@enterprise_relationships.splice @enterprise_relationships.indexOf(er), 1
permission_presentation: (permission) ->
switch permission
when "add_to_order_cycle" then "can add to order cycle"
when "manage_products" then "can manage the products of"

View File

@@ -0,0 +1,18 @@
angular.module("ofn.admin").factory 'EnterpriseRoles', ($http, enterpriseRoles) ->
new class EnterpriseRoles
create_errors: ""
constructor: ->
@enterprise_roles = enterpriseRoles
create: (user_id, enterprise_id) ->
$http.post('/admin/enterprise_roles', {enterprise_role: {user_id: user_id, enterprise_id: enterprise_id}}).success (data, status) =>
@enterprise_roles.unshift(data)
@create_errors = ""
.error (response, status) =>
@create_errors = response.errors
delete: (er) ->
$http.delete('/admin/enterprise_roles/' + er.id).success (data) =>
@enterprise_roles.splice @enterprise_roles.indexOf(er), 1

View File

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

View File

@@ -0,0 +1,4 @@
angular.module("ofn.admin").factory 'Users', (users) ->
new class Users
constructor: ->
@users = users

View File

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

View File

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

View File

@@ -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}",

View File

@@ -6,7 +6,7 @@ Darkswarm.controller "PaymentCtrl", ($scope, $timeout) ->
{key: "January", value: "1"},
{key: "February", value: "2"},
{key: "March", value: "3"},
{key: "April", value: "4"},
{key: "April", value: "4"},
{key: "May", value: "5"},
{key: "June", value: "6"},
{key: "July", value: "7"},
@@ -20,4 +20,4 @@ Darkswarm.controller "PaymentCtrl", ($scope, $timeout) ->
$scope.years = [moment().year()..(moment().year()+15)]
$scope.secrets.card_month = "1"
$scope.secrets.card_year = moment().year()
$timeout $scope.onTimeout
$timeout $scope.onTimeout

View File

@@ -6,7 +6,8 @@ Darkswarm.controller "ProductsCtrl", ($scope, $rootScope, Products, OrderCycle,
$scope.filterText = FilterSelectorsService.filterText
$scope.FilterSelectorsService = FilterSelectorsService
$scope.limit = 3
$scope.ordering = {order: "name"}
$scope.ordering =
order: "primary_taxon.name"
$scope.order_cycle = OrderCycle.order_cycle
$scope.incrementLimit = ->

View File

@@ -5,7 +5,6 @@ window.Darkswarm = angular.module("Darkswarm", ["ngResource",
'infinite-scroll',
'angular-flash.service',
'templates',
'timer',
'ngSanitize',
'ngAnimate',
'google-maps',

View File

@@ -1,4 +1,6 @@
Darkswarm.directive "activeSelector", ->
# A generic selector that allows an object/scope to be toggled between active and inactive
# Used in the filters, but hypothetically useable anywhere
restrict: 'E'
transclude: true
replace: true
@@ -8,5 +10,6 @@ Darkswarm.directive "activeSelector", ->
elem.bind "click", ->
scope.$apply ->
scope.selector.active = !scope.selector.active
scope.emit()
# This function is a convention, e.g. a callback on the scope applied when active changes
scope.emit() if scope.emit

View File

@@ -1,11 +1,12 @@
Darkswarm.directive "activeTableHubLink", (CurrentHub, CurrentOrder) ->
# Change the text of the hub link based on CurrentHub
# To be used with ofnEmptiesCart
# Takes "change" and "shop" as text string attributes
restrict: "A"
scope:
hub: '=activeTableHubLink'
template: "{{action}}"
link: (scope, elm, attr)->
# Swap out the text of the hub link depending on whether it'll change current hub
# To be used with ofnEmptiesCart
if CurrentHub.hub?.id and CurrentHub.hub.id isnt scope.hub.id
scope.action = attr.change
else

View File

@@ -1,4 +1,5 @@
Darkswarm.directive "cart", ->
# Toggles visibility of the "cart" popover
restrict: 'A'
link: (scope, elem, attr)->
scope.open = false

View File

@@ -1,4 +1,6 @@
Darkswarm.directive "ngDebounce", ($timeout) ->
# Slows down ng-model updates, only triggering binding ngDebounce milliseconds
# after the last change. Used to prevent squirrely UI
restrict: "A"
require: "ngModel"
priority: 99

View File

@@ -1,4 +1,5 @@
Darkswarm.directive "ofnDisableEnter", ()->
# Stops enter from doing normal enter things
restrict: 'A'
link: (scope, element, attrs)->
element.bind "keydown keypress", (e)->

View File

@@ -1,6 +1,7 @@
Darkswarm.directive "ofnDisableScroll", ()->
# Stops scrolling from incrementing or decrementing input value
# Useful for number inputs
restrict: 'A'
link: (scope, element, attrs)->
element.bind 'focus', ->
element.bind 'mousewheel', (e)->

View File

@@ -1,12 +1,13 @@
Darkswarm.directive "ofnEmptiesCart", (CurrentHub, CurrentOrder, Navigation, storage) ->
Darkswarm.directive "ofnEmptiesCart", (CurrentHub, Cart, Navigation, storage) ->
# Compares scope.hub with CurrentHub. Will trigger an confirmation if they are different,
# and Cart isn't empty
restrict: "A"
scope:
hub: "=ofnEmptiesCart"
link: (scope, elm, attr)->
hub = scope.$eval(attr.ofnEmptiesCart)
# A hub is selected, we're changing to a different hub, and the cart isn't empty
if CurrentHub.hub?.id and CurrentHub.hub.id isnt hub.id
unless CurrentOrder.empty()
elm.bind 'click', (ev)->
ev.preventDefault()
if confirm "Are you sure? This will change your selected Hub and remove any items in you shopping cart."
storage.clearAll() # One day this will have to be moar GRANULAR
Navigation.go scope.hub.path
if CurrentHub.hub?.id and CurrentHub.hub.id isnt scope.hub.id and !Cart.empty()
elm.bind 'click', (ev)->
ev.preventDefault()
if confirm "Are you sure? This will change your selected Hub and remove any items in you shopping cart."
storage.clearAll() # One day this will have to be moar GRANULAR
Navigation.go scope.hub.path

View File

@@ -1,10 +1,9 @@
Darkswarm.directive "fillVertical", ($window)->
# Makes something fill the window vertically. Used on the Google Map.
restrict: 'A'
link: (scope, element, attrs)->
setSize = ->
element.css "height", ($window.innerHeight - element.offset().top)
setSize()
angular.element($window).bind "resize", ->
setSize()

View File

@@ -1,5 +1,6 @@
Darkswarm.directive "ofnFlash", (flash, $timeout, RailsFlashLoader)->
# Mappings between flash types (left) and Foundation classes
# Our own flash class. Uses the "flash" service (third party), and a directive
# called RailsFlashLoader to render
typePairings =
info: "info"
error: "alert"
@@ -13,6 +14,8 @@ Darkswarm.directive "ofnFlash", (flash, $timeout, RailsFlashLoader)->
link: ($scope, element, attr) ->
$scope.flashes = []
# Callback when a new flash message is pushed to flash service
show = (message, type)=>
if message
$scope.flashes.push({message: message, type: typePairings[type]})
@@ -21,5 +24,6 @@ Darkswarm.directive "ofnFlash", (flash, $timeout, RailsFlashLoader)->
$scope.delete = ->
$scope.flashes.shift()
# Register our callback (above) with flash service
flash.subscribe(show)
RailsFlashLoader.initFlash()

View File

@@ -1,9 +1,9 @@
Darkswarm.directive "ofnFocus", ->
# Takes an expression attrs.ofnFocus
# Watches value of expression, triggers element.focus() when value is truthy
# Used to automatically focus on specific inputs in various circumstances
restrict: "A"
link: (scope, element, attrs) ->
scope.$watch attrs.ofnFocus, ((focus) ->
focus and element.focus()
return
), true
return

View File

@@ -1,4 +1,5 @@
Darkswarm.directive "loading", (Loading)->
# Triggers a screen-wide "loading" thing when Ajaxy stuff is happening
scope: {}
restrict: 'E'
templateUrl: 'loading.html'
@@ -6,5 +7,3 @@ Darkswarm.directive "loading", (Loading)->
$scope.Loading = Loading
$scope.show = ->
$scope.Loading.message?
link: ($scope, element, attr)->

View File

@@ -3,7 +3,7 @@ Darkswarm.directive 'mapSearch', ($timeout)->
restrict: 'E'
require: '^googleMap'
replace: true
template: '<input id="pac-input"></input>'
template: '<input id="pac-input" placeholder="Type in a location..."></input>'
link: (scope, elem, attrs, ctrl)->
$timeout =>
map = ctrl.getMap()

View File

@@ -0,0 +1,6 @@
Darkswarm.directive "max", ->
restrict: 'A'
link: (scope, elem, attr)->
elem.bind 'input', ->
if elem.val() > +attr.max
elem.val attr.max

View File

@@ -1,16 +1,19 @@
Darkswarm.directive "ofnModal", ($modal)->
# Generic modal! Uses transclusion so designer-types can do stuff like:
# %ofn-modal
# CONTENT
# Only works for simple cases, so roll your own when necessary!
restrict: 'E'
replace: true
transclude: true
scope: {}
scope: true
template: "<a>{{title}}</a>"
# Instead of using ng-transclude we compile the transcluded template to a string
# This compiled template is sent to the $modal service! Such magic!
# In theory we could compile the template directly inside link rather than onclick, but it's performant so meh!
link: (scope, elem, attrs, ctrl, transclude)->
scope.title = attrs.title
contents = null
elem.on "click", =>
# We're using an isolate scope, which is a child of the original scope
# We have to compile the transclude against the original scope, not the isolate
transclude scope.$parent, (clone)->
contents = clone
scope.modalInstance = $modal.open(controller: ctrl, template: contents, scope: scope.$parent)
transclude scope, (clone)->
scope.modalInstance = $modal.open(controller: ctrl, template: clone, scope: scope)

View File

@@ -1,11 +1,20 @@
Darkswarm.directive "priceBreakdown", ($tooltip)->
tooltip = $tooltip 'priceBreakdown', 'priceBreakdown', 'click'
# We use the $tooltip service from Angular foundation to give us boilerplate
# Subsequently we patch the scope, template and restrictions
tooltip = $tooltip 'priceBreakdown', 'priceBreakdown', 'click'
tooltip.scope =
variant: "="
tooltip.templateUrl = "price_breakdown_button.html"
tooltip.replace = true
tooltip.restrict = 'E'
tooltip
# This is automatically referenced via naming convention in $tooltip
Darkswarm.directive 'priceBreakdownPopup', ->
restrict: 'EA'
replace: true
templateUrl: 'price_breakdown.html'
scope: true
scope: false
link: (scope, elem, attrs) ->
scope.expanded = false unless scope.expanded?

View File

@@ -0,0 +1,10 @@
Darkswarm.directive "pricePercentage", ->
restrict: 'E'
replace: true
templateUrl: 'price_percentage.html'
scope:
percentage: '='
link: (scope, elem, attrs) ->
elem.find(".meter").css
width: "#{scope.percentage}%"

View File

@@ -1,7 +1,11 @@
Darkswarm.directive "renderSvg", ()->
# Magical directive that'll render SVGs from URLs
# If only there were a neater way of doing this
restrict: 'E'
priority: 99
template: "<svg-wrapper></svg-wrapper>"
# Fetch SVG via ajax, inject into page using DOM
link: (scope, elem, attr)->
if /.svg/.test attr.path # Only do this if we've got an svg
$.ajax

View File

@@ -1,4 +1,5 @@
Darkswarm.directive 'scrollAfterLoad', ($timeout, $location, $document)->
# Scroll to an element on page load
restrict: "A"
link: (scope, element, attr) ->
if scope.$last is true

View File

@@ -1,4 +1,6 @@
Darkswarm.directive "ofnScrollTo", ($location, $anchorScroll)->
# Onclick sets $location.hash to attrs.ofnScrollTo
# Then triggers anchorScroll
restrict: 'A'
link: (scope, element, attrs)->
element.bind 'click', (ev)->

View File

@@ -1,4 +1,5 @@
Darkswarm.directive "shippingTypeSelector", (FilterSelectorsService)->
# Builds selector for shipping types
restrict: 'E'
replace: true
templateUrl: 'shipping_type_selector.html'

View File

@@ -0,0 +1,6 @@
Darkswarm.directive "shopVariant", ->
restrict: 'E'
replace: true
templateUrl: 'shop_variant.html'
scope:
variant: '='

View File

@@ -1,4 +1,6 @@
Darkswarm.directive "taxonSelector", (FilterSelectorsService)->
# Automatically builds activeSelectors for taxons
# Lots of magic here
restrict: 'E'
replace: true
scope:
@@ -8,7 +10,7 @@ Darkswarm.directive "taxonSelector", (FilterSelectorsService)->
link: (scope, elem, attr)->
selectors_by_id = {}
selectors = ["foo"]
selectors = null # To get scoping/closure right
scope.emit = ->
scope.results = selectors.filter (selector)->
@@ -16,6 +18,7 @@ Darkswarm.directive "taxonSelector", (FilterSelectorsService)->
.map (selector)->
selector.taxon.id
# Build hash of unique taxons, each of which gets an ActiveSelector
scope.selectors = ->
taxons = {}
selectors = []
@@ -25,7 +28,11 @@ Darkswarm.directive "taxonSelector", (FilterSelectorsService)->
if object.supplied_taxons
for taxon in object.supplied_taxons
taxons[taxon.id] = taxon
# Generate a selector for each taxon.
# NOTE: THESE ARE MEMOIZED to stop new selectors from being created constantly, otherwise function always returns non-identical results
# This means the $digest cycle can never close and times out
# See http://stackoverflow.com/questions/19306452/how-to-fix-10-digest-iterations-reached-aborting-error-in-angular-1-2-fil
for id, taxon of taxons
if selector = selectors_by_id[id]
selectors.push selector

View File

@@ -1,7 +1,7 @@
window.FieldsetMixin = ($scope)->
$scope.next = (event = false)->
event.preventDefault() if event
$scope.show $scope.nextPanel
$scope.show $scope.nextPanel
$scope.onTimeout = ->
if $scope[$scope.name].$valid
@@ -36,7 +36,6 @@ window.FieldsetMixin = ($scope)->
when "number" then "must be number"
when "email" then "must be email address"
#server_errors = $scope.Order.errors[path.replace('order.', '')]
#errors.push server_errors if server_errors?
(errors.filter (error) -> error?).join ", "
#server_errors = $scope.Order.errors[path.replace('order.', '')]
#errors.push server_errors if server_errors?
(errors.filter (error) -> error?).join ", "

View File

@@ -19,7 +19,7 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http)->
$http.post('/orders/populate', @data()).success (data, status)=>
@saved()
.error (response, status)=>
alert "There was an error on the server! Please refresh the page"
# TODO what shall we do here?
data: =>
variants = {}
@@ -43,6 +43,9 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http)->
@line_items.filter (li)->
li.quantity > 0
empty: =>
@line_items_present().length == 0
total: =>
@line_items_present().map (li)->
li.variant.getPrice()
@@ -57,6 +60,6 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http)->
create_line_item: (variant)->
variant.line_item =
variant: variant
quantity: 0
quantity: null
max_quantity: null
@line_items.push variant.line_item

View File

@@ -32,6 +32,10 @@ Darkswarm.factory 'Checkout', (CurrentOrder, ShippingMethods, PaymentMethods, $h
if @ship_address_same_as_billing
munged_order.ship_address_attributes = munged_order.bill_address_attributes
# If the order already has a ship and bill address (as with logged in users with
# past orders), and we don't remove id here, then this will set the wrong id for
# ship address, and Rails will error with a 404 for that address.
delete munged_order.ship_address_attributes.id
if @paymentMethod()?.method_type == 'gateway'
angular.extend munged_order.payments_attributes[0], {

View File

@@ -1,5 +1,3 @@
Darkswarm.factory 'CurrentOrder', (currentOrder) ->
new class CurrentOrder
order: currentOrder
empty: =>
@order.line_items.length == 0

View File

@@ -28,6 +28,8 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Car
for product in @products
if product.variants
product.variants = (Variants.register variant for variant in product.variants)
variant.product = product for variant in product.variants
product.master.product = product
product.master = Variants.register product.master if product.master
registerVariantsWithCart: ->
@@ -44,5 +46,6 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Car
product.price = Math.min.apply(null, prices)
product.hasVariants = product.variants?.length > 0
product.primaryImage = product.images[0]?.small_url
product.primaryImage = product.images[0]?.small_url if product.images
product.primaryImageOrMissing = product.primaryImage || "/assets/noimage/small.png"
product.largeImage = product.images[0]?.large_url if product.images

View File

@@ -4,5 +4,6 @@ Darkswarm.factory "ShippingMethods", (shippingMethods)->
shipping_methods_by_id: {}
constructor: ->
for method in @shipping_methods
method.price = parseFloat(method.price)
@shipping_methods_by_id[method.id] = method

View File

@@ -7,4 +7,5 @@ Darkswarm.factory 'Variants', ->
extend: (variant)->
variant.getPrice = ->
variant.price * variant.line_item.quantity
variant.basePricePercentage = Math.round(variant.base_price / variant.price * 100)
variant

View File

@@ -1,14 +1,14 @@
%div{bindonce: true}
%div.contact-container{bindonce: true}
%div.modal-centered{"bo-if" => "enterprise.email || enterprise.website || enterprise.phone"}
%p.modal-header Contact
%p{"ng-if" => "enterprise.phone"}
{{ enterprise.phone }}
%p{"ng-if" => "enterprise.email"}
%p.word-wrap{"ng-if" => "enterprise.email"}
%a{"ng-href" => "{{enterprise.email | stripUrl}}", target: "_blank", mailto: true}
%span.email
{{ enterprise.email | stripUrl }}
%p{"ng-if" => "enterprise.website"}
%p.word-wrap{"ng-if" => "enterprise.website"}
%a{"ng-href" => "http://{{enterprise.website | stripUrl}}", target: "_blank" }
{{ enterprise.website | stripUrl }}

View File

@@ -1,12 +1,12 @@
.highlight
.highlight-top
%p.right
{{ [enterprise.address.city, enterprise.address.state] | printArray}}
{{ [enterprise.address.city, enterprise.address.state_name] | printArray}}
%h3{"ng-if" => "enterprise.is_distributor"}
%a{"bo-href" => "enterprise.path", "ofn-empties-cart" => "enterprise", bindonce: true}
%i.ofn-i_040-hub
{{ enterprise.name }}
%span {{ enterprise.name }}
%h3{"ng-if" => "!enterprise.is_distributor"}
%i.ofn-i_036-producers
{{ enterprise.name }}
%span {{ enterprise.name }}
%img.hero-img{"ng-src" => "{{enterprise.promo_image}}"}

View File

@@ -11,6 +11,6 @@
%i.ofn-i_033-open-sign{"bo-if" => "hub.active"}
%i.ofn-i_032-closed-sign{"bo-if" => "!hub.active"}
{{hub.name}}
.button-address {{ hub.address.city }} , {{hub.address.state}}
.button-address {{ hub.address.city }} , {{hub.address.state_name}}
%i.ofn-i_007-caret-right

View File

@@ -1,10 +1,10 @@
.row.pad-top{bindonce: true}
.cta-container.small-12.columns
.row
.small-12.large-6.columns
%label{"active-table-hub-link" => "enterprise", change: "Change hub to", shop: "Shop now at"}
.small-12.large-6.columns.right
.right{"bo-if" => "enterprise.pickup || enterprise.delivery"}
.small-4.columns
%label{"active-table-hub-link" => "enterprise", change: "Change hub to:", shop: "Shop now at:"}
.small-8.columns.right
%label.right{"bo-if" => "enterprise.pickup || enterprise.delivery"}
Delivery options:
%span{"bo-if" => "enterprise.pickup"}
%i.ofn-i_038-takeaway
@@ -20,5 +20,5 @@
%i.ofn-i_033-open-sign{"bo-if" => "enterprise.active"}
%i.ofn-i_032-closed-sign{"bo-if" => "!enterprise.active"}
{{enterprise.name}}
.button-address {{ enterprise.address.city }} , {{enterprise.address.state}}
.button-address {{ enterprise.address.city }} , {{enterprise.address.state_name}}
%i.ofn-i_007-caret-right

View File

@@ -1,4 +1,38 @@
.joyride-tip-guide{"ng-class" => "{ in: tt_isOpen, fade: tt_animation }"}
%span.joyride-nub.bottom
.joyride-tip-guide.price_breakdown{bindonce: true, "ng-class" => "{ in: tt_isOpen, fade: tt_animation }"}
%span.joyride-nub.right
.joyride-content-wrapper
{{ variant.id }}
.collapsed{"ng-show" => "!expanded"}
%price-percentage{percentage: 'variant.basePricePercentage'}
%a{"ng-click" => "expanded = !expanded"}
Full price breakdown
%i.ofn-i_005-caret-down
.expanded{"ng-show" => "expanded"}
%ul
%li.cost
.right {{ variant.base_price | currency }}
Item cost
%li{"bo-if" => "variant.fees.admin"}
.right {{ variant.fees.admin | currency }}
Admin fee
%li{"bo-if" => "variant.fees.sales"}
.right {{ variant.fees.sales | currency }}
Sales fee
%li{"bo-if" => "variant.fees.packing"}
.right {{ variant.fees.packing | currency }}
Packing fee
%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 }}
&nbsp;
%a{"ng-click" => "expanded = !expanded"}
Price graph
%i.ofn-i_006-caret-up

View File

@@ -0,0 +1,2 @@
%button.graph-button{"ng-class" => "{open: tt_isOpen}"}
%i.ofn-i-058-graph

View File

@@ -0,0 +1,5 @@
.progress
.right Fees
.meter
Item cost

View File

@@ -1,9 +1,9 @@
.row
.columns.small-12.large-6
%img.product-img{"ng-src" => "{{product.primaryImage}}", "ng-if" => "product.primaryImage"}
%img.product-img{"ng-src" => "{{product.largeImage}}", "ng-if" => "product.largeImage"}
.columns.small-12.large-6.product-header
%h2
%render-svg{path: "{{product.primary_taxon.icon}}"}
/ %render-svg{path: "{{product.primary_taxon.icon}}"}
{{product.name}}
%p {{product.description}}
%ng-include{src: "'partials/close.html'"}

View File

@@ -1,16 +1,15 @@
.row.variants{bindonce: true,
"ng-repeat" => "variant in product.variants track by variant.id"}
.variants.row
.small-12.medium-4.large-4.columns.variant-name
.table-cell
.inline {{ variant.name_to_display }}
.bulk-buy.inline{"bo-if" => "product.group_buy"}
.bulk-buy.inline{"bo-if" => "variant.product.group_buy"}
%i.ofn-i_056-bulk><
%em><
\ Bulk
-# WITHOUT GROUP BUY
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "!product.group_buy"}
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "!variant.product.group_buy"}
%input{type: :number,
value: nil,
min: 0,
@@ -22,7 +21,7 @@
-# WITH GROUP BUY
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "product.group_buy"}
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "variant.product.group_buy"}
%span.bulk-input-container
%span.bulk-input
%input.bulk.first{type: :number,
@@ -33,7 +32,7 @@
"ofn-disable-scroll" => true,
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"}
%span.bulk-input{"bo-if" => "product.group_buy"}
%span.bulk-input{"bo-if" => "variant.product.group_buy"}
%input.bulk.second{type: :number,
min: 0,
"ng-model" => "variant.line_item.max_quantity",
@@ -51,8 +50,11 @@
%i.ofn-i_009-close
{{ variant.price | currency }}
/ %button.graph-button{popover: "This is the popover text", "popover-title" => "The title.", "popover-animation" => "true", "popover-trigger" =>"mouseenter", "popover-placement" => "top", "tabindex" => "-1"}
/ %i.ofn-i-058-graph
-# Now in a template in app/assets/javascripts/templates !
%price-breakdown{"price-breakdown" => "_", variant: "variant",
"price-breakdown-append-to-body" => "true",
"price-breakdown-placement" => "left",
"price-breakdown-animation" => true}
.small-12.medium-2.large-2.columns.total-price.text-right
.table-cell

View File

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

View File

@@ -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%;
}
}

View File

@@ -7,7 +7,10 @@
background-color: #fff
table#enterprise-relationships
table#enterprise-relationships, table#enterprise-roles
ul
list-style-type: none
th.actions, td.actions
width: 16%
.errors

View File

@@ -1,37 +1,117 @@
@import mixins
@import branding
.darkswarm
product
// Pop over
// .darkswarm
// product
// Foundation overrides
.joyride-tip-guide
// JS needs to be tweaked to adjust for left alignment - this is dynamic can't rewrite in CSS
background-color: #ebebeb
border: 1px solid #a5a5a5
color: #1f1f1f
h1, h2, h3, h4, h5, h6
color: #1f1f1f
.joyride-nub.bottom
border-color: #a5a5a5 !important
border-bottom-color: transparent !important
border-left-color: transparent !important
border-right-color: transparent !important
button.graph-button
padding: 0
ordercycle
.joyride-tip-guide
background-color: $clr-brick
.joyride-nub.right
border-color: $clr-brick !important
border-top-color: transparent !important
border-right-color: transparent !important
border-bottom-color: transparent !important
p
margin: 0
@include border-radius(0)
display: inline
background: none
font-weight: 700
// Pop over
// Foundation overrides
.joyride-tip-guide.price_breakdown
// JS needs to be tweaked to adjust for left alignment - this is dynamic can't rewrite in CSS
background-color: #999
color: #1f1f1f
margin-left: -8px
@include box-shadow(0 1px 2px 0 rgba(0,0,0,0.7))
.joyride-content-wrapper
padding: 1.125rem 1.25rem 1.5rem
padding: 1rem
margin: 1%
width: 98%
background-color: white
h1, h2, h3, h4, h5, h6
color: #1f1f1f
.joyride-nub.right
top: 38px
border-color: #999 !important
border-top-color: transparent !important
border-right-color: transparent !important
border-bottom-color: transparent !important
.progress
background-color: #13bf85
padding: 0
border: none
color: white
font-size: 0.75rem
font-style: oblique
line-height: 1
height: auto
.right
padding: 0.5rem 0.25rem 0 0
.meter
background-color: #0b8c61
padding: 0.5rem 0.25rem
border-right: 1px solid #539f92
.expanded
ul, li
list-style: none
margin: 0
font-size: 0.875rem
li
background-color: #13bf85
padding: 0 0.25rem
margin-bottom: 2px
color: white
li.cost
background-color: #0b8c61
li:last-child
margin-bottom: 0.75rem
button.graph-button
z-index: 9999999
border: 1px solid transparent
@include box-shadow(none)
padding: 0
margin: 0
@include border-radius(999rem)
display: inline
background-color: rgba(255,255,255,0.5)
padding: 5px
&:hover, &:active, &:focus
background-color: rgba(255,255,255,1)
i.ofn-i-058-graph
color: $clr-brick-bright
i.ofn-i-058-graph
color: #999
margin: 0
padding: 0
font-size: 1rem
@media all and (max-width: 640px)
padding: 3px
i.ofn-i-058-graph
font-size: 0.75rem
button.graph-button.open
@include box-shadow(inset 0 1px 1px 0 rgba(0,0,0,0.35))
border: 1px solid #999
&:hover, &:active, &:focus
background-color: rgba(255,255,255,1)
i.ofn-i-058-graph
color: $clr-brick-bright
i.ofn-i-058-graph
color: $clr-brick
i.ofn-i-058-graph
color: #999
margin: 0
padding: 0
font-size: 1rem
&:hover, &:focus, &:active, &.active
color: #444

View File

@@ -1,3 +1,5 @@
@import branding.css.sass
.darkswarm
products
product
@@ -25,9 +27,13 @@
.row.variants
margin-left: 0
margin-right: 0
background: url("/assets/gray_jean.png") top left repeat
background-color: #ECECEC
&:hover, &:focus, &:active
background-color: $clr-brick-light
&:nth-of-type(even)
background: url("/assets/gray_jean_light.png") top left repeat
background-color: #f9f9f9
&:hover, &:focus, &:active
background-color: $clr-brick-ultra-light
// Variant name
.variant-name
@@ -72,7 +78,6 @@
.table-cell
height: 27px
// ROW SUMMARY
.row.summary
margin-left: 0
@@ -100,12 +105,10 @@
h3
font-size: 1.5rem
margin: 0
a h3
color: black
h3 a
color: #222
&:hover, &:focus, &:active
color: black

View File

@@ -13,7 +13,7 @@
float: left
display: block
z-index: 999999
background-color: #999
background-color: white
overflow: hidden
@media all and (max-width: 768px)

View File

@@ -8,12 +8,13 @@
// $clr-turquoise: #097563
// $clr-turquoise-light: #cef2ec
// $clr-turquoise-ultra-light: #e6faf7
// $clr-turquoise-bright: #1d8f7c
// $clr-turquoise-bright: #1d8f7c
$clr-brick: #c1122b
$clr-brick-light: #f5e6e7
$clr-brick-ultra-light: #faf5f6
$clr-brick-bright: #eb4c46
$clr-brick-med-bright: #e5a2a0
$clr-brick-light-bright: #f5c4c9
$clr-turquoise: #0b8c61

View File

@@ -3,10 +3,19 @@
checkout
display: block
@media all and (max-width: 640px)
&.row .row
margin-left: 0
margin-right: 0
orderdetails
.button, table
width: 100%
@media all and (max-width: 640px)
form.edit_order
border: 1px solid $disabled-bright
margin-bottom: 2rem
#details, #billing, #shipping, #payment
border: 0

View File

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

View File

@@ -12,7 +12,7 @@
//Hub icon styline
i.ofn-i_040-hub
@include border-radius(9999em)
@include border-radius(99999rem)
font-size: 1.15rem
display: inline-block
padding: 0.2rem

View File

@@ -11,9 +11,9 @@
@include box-shadow(0 1px 2px 1px rgba(0,0,0,0.25))
.hero-img
background-color: #333
border-bottom: 1px solid $disabled-bright
width: 100%
min-height: 160px
min-height: 56px
height: inherit
max-height: 260px
overflow: hidden

View File

@@ -1,16 +1,27 @@
// Place all the styles related to the map controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
@import big-input
.map-container
width: 100%
map, .angular-google-map-container, google-map, .angular-google-map
display: block
height: 100%
width: 100%
img // https://github.com/zurb/foundation/issues/112
max-width: none
height: auto
#pac-input
padding: 4px
font-size: 2em
#pac-input
@include big-input(#888, #333, $clr-brick)
@include big-input-static
font-size: 1.5rem
background: rgba(255,255,255,0.85)
width: 50%
margin-top: 1.2rem
@media all and (max-width: 768px)
width: 80%
&:active, &:focus, &.active
background: rgba(255,255,255, 1)

View File

@@ -22,6 +22,9 @@
p
line-height: 2
h3 a:hover span
border-bottom: 1px solid $clr-brick-bright
// ABOUT Enterprise
@@ -41,6 +44,11 @@
max-width: 180px
max-height: 180px
// CONTACT Enterprise
.contact-container
a:hover
text-decoration: underline
// FOLLOW Enterprise
@@ -60,19 +68,26 @@
// CALL TO ACTION - hub click throughs
.cta-container
background: url("/assets/gray_jean.png") repeat
background-color: #ececec
padding-top: 0.75rem
label
text-transform: uppercase
font-size: 0.875rem
margin-bottom: 0.5rem
margin-bottom: 0
5rem
color: $dark-grey
label.right
color: $disabled-dark
span
text-transform: capitalize
.button.secondary
background-color: #999
.button.hub
margin-right: 1rem
margin-top: 0.25rem
margin-bottom: 1rem
padding-left: 1rem
padding-right: 1rem
@@ -114,4 +129,4 @@
border-bottom: 1px solid $disabled-dark
margin-top: 0.75rem
margin-bottom: 0.5rem

View File

@@ -4,46 +4,66 @@
dialog, .reveal-modal
border: none
outline: none
padding: 1rem
div
overflow: scroll
@media only screen and (min-width: 40.063em)
max-height: 580px
@media all and (max-width: 768px)
max-height: 440px
@media all and (max-width: 640px)
max-height: 400px
@media all and (max-width: 640px)
max-height: inherit
overflow: scroll
padding: 1rem
overflow-y: scroll
// Sets up max heights based on device height
@media all and (min-height: 1025px)
max-height: 80%
@media all and (min-height: 700px) and (max-height: 1024px)
max-height: 70%
@media all and (min-height: 600px) and (max-height: 699px)
max-height: 60%
@media all and (min-height: 481px) and (max-height: 599px)
max-height: 60%
@media only screen and (max-height: 480px) and (min-width: 641px)
max-height: 60%
@media all and (max-height: 480px)
overflow-y: scroll
.reveal-modal-bg
background-color: rgba(0,0,0,0.65)
dialog .close-reveal-modal.outside, .reveal-modal .close-reveal-modal.outside
top: -2.5rem
right: -2.5rem
font-size: 2rem
color: white
dialog .close-reveal-modal, .reveal-modal .close-reveal-modal
right: 0.4rem
background-color: rgba(235,235,235,0.85)
text-shadow: none
padding: 0.25rem
@include border-radius(999999)
border: 1px solid transparent
padding: 0.3rem
@include border-radius(999999rem)
&:hover, &:active, &:focus
text-shadow: 0 1px 3px #333
border: 1px solid white
background-color: rgba(235,235,235,1)
color: #333
@media all and (max-width: 640px)
top: 0.5rem
right: 0.5rem
font-size: 2rem
color: white
text-shadow: none
padding: 0.25rem
background-color: rgba(150,150,150,0.85)
@include border-radius(999999)
border: 1px solid transparent
&:hover, &:active, &:focus
text-shadow: 0 1px 3px #333
border: 1px solid white
// dialog .close-reveal-modal.outside, .reveal-modal .close-reveal-modal.outside
// top: -2.5rem
// right: -2.5rem
// font-size: 2rem
// color: white
// text-shadow: none
// padding: 0.25rem
// @include border-radius(999999)
// border: 1px solid transparent
// &:hover, &:active, &:focus
// text-shadow: 0 1px 3px #333
// border: 1px solid white
// @media all and (max-width: 640px)
// top: 0.5rem
// right: 0.5rem
// font-size: 2rem
// color: white
// text-shadow: none
// padding: 0.25rem
// background-color: rgba(150,150,150,0.85)
// @include border-radius(999999)
// border: 1px solid transparent
// &:hover, &:active, &:focus
// text-shadow: 0 1px 3px #333
// border: 1px solid white

View File

@@ -30,6 +30,7 @@
product
@include csstrans
border-bottom: 1px solid #e5e5e5
border-top: 1px solid #e5e5e5
padding-bottom: 1px
@@ -38,6 +39,10 @@
display: block
color: #444
&:hover, &:focus, &:active
border-bottom: 1px solid $clr-brick-med-bright
border-top: 1px solid $clr-brick-med-bright
// BULK
.bulk-buy
font-size: 0.875rem
@@ -55,6 +60,8 @@
i
font-size: 0.75em
padding-right: 0.9375rem
@media all and (max-width: 640px)
padding-right: 0.25rem
i.ofn-i_056-bulk, i.ofn-i_036-producers
font-size: 1rem

View File

@@ -12,14 +12,21 @@
display: block
right: 10px
top: 55px
width: 400px
width: 480px
@media screen and (max-width: 640px)
width: 96%
.joyride-nub
right: 22px !important
left: auto
ul, li
list-style: none
margin-left: 0
li
float: none
.row .columns
padding-left: 0.25rem
padding-right: 0.25rem

View File

@@ -34,6 +34,10 @@ small, .small
margin-bottom: 0.5rem
&, & *
font-size: 0.875rem
.word-wrap
word-wrap: break-word
.light
color: #999
display: inline

View File

@@ -46,6 +46,7 @@
.button, button
@include border-radius(0.5em)
outline: none // Turn off blue highlight on chrome
.button.primary, button.primary
font-family: 'Open Sans', Calibri, Candara, Segoe, "Segoe UI", Optima, Arial, sans-serif
@@ -57,10 +58,10 @@
text-shadow: 0 1px 0 $clr-brick
button.success, .button.success
background: $clr-turquoise
background: #0096ad
.button.success:hover, .button.success:active, .button.success:focus, button.success:hover, button.success:active, button.success:focus
background: $clr-turquoise-bright
background: #14b6cc
// Responsive
@media screen and (min-width: 768px)

View File

@@ -10,7 +10,7 @@ module Admin
@enterprise_relationship = EnterpriseRelationship.new params[:enterprise_relationship]
if @enterprise_relationship.save
render partial: "admin/json/enterprise_relationship", locals: {enterprise_relationship: @enterprise_relationship}
render text: Api::Admin::EnterpriseRelationshipSerializer.new(@enterprise_relationship).to_json
else
render status: 400, json: {errors: @enterprise_relationship.errors.full_messages.join(', ')}
end

View File

@@ -0,0 +1,26 @@
module Admin
class EnterpriseRolesController < ResourceController
def index
@enterprise_roles = EnterpriseRole.by_user_email
@users = Spree::User.order('spree_users.email')
@my_enterprises = @all_enterprises = Enterprise.by_name
end
def create
@enterprise_role = EnterpriseRole.new params[:enterprise_role]
if @enterprise_role.save
render text: Api::Admin::EnterpriseRoleSerializer.new(@enterprise_role).to_json
else
render status: 400, json: {errors: @enterprise_role.errors.full_messages.join(', ')}
end
end
def destroy
@enterprise_role = EnterpriseRole.find params[:id]
@enterprise_role.destroy
render nothing: true
end
end
end

View File

@@ -6,6 +6,11 @@ module Admin
create.after :grant_management
helper 'spree/products'
include OrderCyclesHelper
def for_order_cycle
@collection = order_cycle_permitted_enterprises
end
def bulk_update
@@ -53,7 +58,7 @@ module Admin
end
def collection_actions
[:index, :bulk_update]
[:index, :for_order_cycle, :bulk_update]
end
def load_methods_and_fees

View File

@@ -1,7 +1,10 @@
require 'open_food_network/permissions'
require 'open_food_network/order_cycle_form_applicator'
module Admin
class OrderCyclesController < ResourceController
include OrderCyclesHelper
before_filter :load_order_cycle_set, :only => :index
def show
@@ -23,7 +26,7 @@ module Admin
respond_to do |format|
if @order_cycle.save
OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle, managed_enterprises).go!
OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle, order_cycle_permitted_enterprises).go!
flash[:notice] = 'Your order cycle has been created.'
format.html { redirect_to admin_order_cycles_path }
@@ -40,7 +43,7 @@ module Admin
respond_to do |format|
if @order_cycle.update_attributes(params[:order_cycle])
OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle, managed_enterprises).go!
OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle, order_cycle_permitted_enterprises).go!
flash[:notice] = 'Your order cycle has been updated.'
format.html { redirect_to admin_order_cycles_path }

View File

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

View File

@@ -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
@@ -30,10 +30,17 @@ Spree::Admin::ProductsController.class_eval do
"#{string}q[#{filter[:property][:db_column]}_#{filter[:predicate][:predicate]}]=#{filter[:value]};"
end
# Ensure we're authorised to update all products
product_set.collection.each { |p| authorize! :update, p }
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 +85,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 = OpenFoodNetwork::Permissions.new(spree_current_user).managed_product_enterprises.is_primary_producer.by_name
@taxons = Spree::Taxon.order(:name)
end
end

View File

@@ -4,12 +4,9 @@ require 'open_food_network/products_and_inventory_report'
require 'open_food_network/group_buy_report'
require 'open_food_network/order_grouper'
require 'open_food_network/customers_report'
require 'open_food_network/model_class_from_controller_name'
Spree::Admin::ReportsController.class_eval do
include OpenFoodNetwork::ModelClassFromControllerName
# Fetches user's distributors, suppliers and order_cycles
# Fetches user's distributors, suppliers and order_cycles
before_filter :load_data, only: [:customers, :products_and_inventory]
# Render a partial for orders and fulfillment description
@@ -365,9 +362,9 @@ Spree::Admin::ReportsController.class_eval do
lis
end.flatten
#payments = orders.map { |o| o.payments.select { |payment| payment.completed? } }.flatten # Only select completed payments
# -- Prepare form options
my_distributors = Enterprise.is_distributor.managed_by(spree_current_user)
my_distributors = Enterprise.is_distributor.managed_by(spree_current_user)
my_suppliers = Enterprise.is_primary_producer.managed_by(spree_current_user)
# My distributors and any distributors distributing products I supply
@@ -386,12 +383,13 @@ 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"]
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| total_units(line_items) },
proc { |line_items| line_items.first.variant.price },
proc { |line_items| line_items.sum { |li| li.quantity * li.price } },
proc { |line_items| "" },
@@ -596,10 +594,10 @@ Spree::Admin::ReportsController.class_eval do
def load_data
my_distributors = Enterprise.is_distributor.managed_by(spree_current_user)
my_suppliers = Enterprise.is_primary_producer.managed_by(spree_current_user)
distributors_of_my_products = Enterprise.with_distributed_products_outer.merge(Spree::Product.in_any_supplier(my_suppliers))
@distributors = my_distributors | distributors_of_my_products
distributors_of_my_products = Enterprise.with_distributed_products_outer.merge(Spree::Product.in_any_supplier(my_suppliers))
@distributors = my_distributors | distributors_of_my_products
suppliers_of_products_I_distribute = my_distributors.map { |d| Spree::Product.in_distributor(d) }.flatten.map(&:supplier).uniq
@suppliers = my_suppliers | suppliers_of_products_I_distribute
@suppliers = my_suppliers | suppliers_of_products_I_distribute
@order_cycles = OrderCycle.active_or_complete.accessible_by(spree_current_user).order('orders_close_at DESC')
end
@@ -617,4 +615,13 @@ Spree::Admin::ReportsController.class_eval do
end
reports
end
def total_units(line_items)
return " " if line_items.map{ |li| li.variant.unit_value.nil? }.any?
total_units = line_items.sum do |li|
scale_factor = ( li.product.variant_unit == 'weight' ? 1000 : 1 )
li.quantity * li.variant.unit_value / scale_factor
end
total_units.round(3)
end
end

View File

@@ -1,3 +1,5 @@
require 'open_food_network/permissions'
Spree::Api::ProductsController.class_eval do
def managed
authorize! :admin, Spree::Product
@@ -7,6 +9,14 @@ Spree::Api::ProductsController.class_eval do
respond_with(@products, default_template: :index)
end
def bulk_products
@products = OpenFoodNetwork::Permissions.new(current_api_user).managed_products.
merge(product_scope).
ransack(params[:q]).result.
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

View File

@@ -4,6 +4,19 @@ module Admin
admin_inject_json_ams "admin.enterprises", "enterprise", @enterprise, Api::Admin::EnterpriseSerializer
end
def admin_inject_enterprises
admin_inject_json_ams_array("ofn.admin", "my_enterprises", @my_enterprises, Api::Admin::EnterpriseSerializer) +
admin_inject_json_ams_array("ofn.admin", "all_enterprises", @all_enterprises, Api::Admin::EnterpriseSerializer)
end
def admin_inject_enterprise_relationships
admin_inject_json_ams_array "ofn.admin", "enterprise_relationships", @enterprise_relationships, Api::Admin::EnterpriseRelationshipSerializer
end
def admin_inject_enterprise_roles
admin_inject_json_ams_array "ofn.admin", "enterpriseRoles", @enterprise_roles, Api::Admin::EnterpriseRoleSerializer
end
def admin_inject_payment_methods
admin_inject_json_ams_array "admin.payment_methods", "paymentMethods", @payment_methods, Api::Admin::IdNameSerializer
end
@@ -12,6 +25,21 @@ 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_users
admin_inject_json_ams_array "ofn.admin", "users", @users, Api::Admin::UserSerializer
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,4 +50,4 @@ module Admin
render partial: "admin/json/injection_ams", locals: {ngModule: ngModule, name: name, json: json}
end
end
end
end

View File

@@ -1,18 +1,31 @@
module CheckoutHelper
def checkout_adjustments_for_summary(order, opts={})
adjustments = order.adjustments.eligible
exclude = opts[:exclude] || {}
# Remove empty tax adjustments and (optionally) shipping fees
adjustments.reject! { |a| a.originator_type == 'Spree::TaxRate' && a.amount == 0 }
adjustments.reject! { |a| a.originator_type == 'Spree::ShippingMethod' } if opts[:exclude_shipping]
adjustments.reject! { |a| a.originator_type == 'Spree::ShippingMethod' } if exclude.include? :shipping
enterprise_fee_adjustments = adjustments.select { |a| a.originator_type == 'EnterpriseFee' }
adjustments.reject! { |a| a.originator_type == 'EnterpriseFee' }
adjustments << Spree::Adjustment.new(label: 'Distribution', amount: enterprise_fee_adjustments.sum(&:amount))
unless exclude.include? :distribution
adjustments << Spree::Adjustment.new(label: 'Distribution', amount: enterprise_fee_adjustments.sum(&:amount))
end
adjustments
end
def checkout_adjustments_total(order)
adjustments = checkout_adjustments_for_summary(order, exclude: [:shipping])
adjustments.sum &:display_amount
end
def checkout_cart_total_with_adjustments(order)
order.display_item_total.money.to_f + checkout_adjustments_total(order).money.to_f
end
def validated_input(name, path, args = {})
attributes = {
required: true,

View File

@@ -3,8 +3,20 @@ module OrderCyclesHelper
@current_order_cycle ||= current_order(false).andand.order_cycle
end
def order_cycle_permitted_enterprises
OpenFoodNetwork::Permissions.new(spree_current_user).order_cycle_enterprises
end
def order_cycle_producer_enterprises
order_cycle_permitted_enterprises.is_primary_producer.by_name
end
def coordinating_enterprises
Enterprise.is_distributor.managed_by(spree_current_user).order('name')
order_cycle_hub_enterprises
end
def order_cycle_hub_enterprises
order_cycle_permitted_enterprises.is_distributor.by_name
end
def order_cycle_local_remote_class(distributor, order_cycle)

View File

@@ -0,0 +1,16 @@
module Spree
module Admin
module NavigationHelper
# Make it so that the Reports admin tab can be enabled/disabled through the cancan
# :report resource, since it does not have a corresponding resource class (unlike
# eg. Spree::Product).
def klass_for_with_sym_fallback(name)
klass = klass_for_without_sym_fallback(name)
klass ||= name.singularize.to_sym
klass = :overview if klass == :dashboard
klass
end
alias_method_chain :klass_for, :sym_fallback
end
end
end

View File

@@ -1,6 +1,9 @@
class Enterprise < ActiveRecord::Base
TYPES = %w(full single profile)
ENTERPRISE_SEARCH_RADIUS = 100
self.inheritance_column = nil
acts_as_gmappable :process_geocoding => false
has_and_belongs_to_many :groups, class_name: 'EnterpriseGroup'
@@ -40,9 +43,9 @@ class Enterprise < ActiveRecord::Base
supports_s3 :promo_image
validates_presence_of :name
validates_presence_of :address
validates_associated :address
validates :name, presence: true
validates :type, presence: true, inclusion: {in: TYPES}
validates :address, presence: true, associated: true
before_validation :set_unused_address_fields
after_validation :geocode_address

View File

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

View File

@@ -1,6 +1,7 @@
class EnterpriseRelationship < ActiveRecord::Base
belongs_to :parent, class_name: 'Enterprise', touch: true
belongs_to :child, class_name: 'Enterprise', touch: true
has_many :permissions, class_name: 'EnterpriseRelationshipPermission'
validates_presence_of :parent_id, :child_id
validates_uniqueness_of :child_id, scope: :parent_id, message: "^That relationship is already established."
@@ -8,9 +9,22 @@ class EnterpriseRelationship < ActiveRecord::Base
scope :with_enterprises,
joins('LEFT JOIN enterprises AS parent_enterprises ON parent_enterprises.id = enterprise_relationships.parent_id').
joins('LEFT JOIN enterprises AS child_enterprises ON child_enterprises.id = enterprise_relationships.child_id')
scope :by_name, with_enterprises.order('parent_enterprises.name, child_enterprises.name')
scope :involving_enterprises, ->(enterprises) {
where('parent_id IN (?) OR child_id IN (?)', enterprises, enterprises)
}
scope :permitting, ->(enterprises) { where('child_id IN (?)', enterprises) }
scope :with_permission, ->(permission) {
joins(:permissions).
where('enterprise_relationship_permissions.name = ?', permission)
}
scope :by_name, with_enterprises.order('child_enterprises.name, parent_enterprises.name')
def permissions_list=(perms)
perms.andand.each { |name| permissions.build name: name }
end
end

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