mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-31 21:37:16 +00:00
Merge branch 'master' into folklabs-producer-emails
Conflicts: Gemfile
This commit is contained in:
4
.rspec_parallel
Normal file
4
.rspec_parallel
Normal file
@@ -0,0 +1,4 @@
|
||||
--format progress
|
||||
--format ParallelTests::RSpec::SummaryLogger --out tmp/spec_summary.log
|
||||
--format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log
|
||||
--tag ~performance
|
||||
3
Gemfile
3
Gemfile
@@ -51,6 +51,8 @@ gem 'custom_error_message', :github => 'jeremydurham/custom-err-msg'
|
||||
gem 'angularjs-file-upload-rails', '~> 1.1.0'
|
||||
gem 'roadie-rails', '~> 1.0.3'
|
||||
gem 'figaro'
|
||||
gem 'acts-as-taggable-on', '~> 3.4'
|
||||
|
||||
gem 'foreigner'
|
||||
gem 'immigrant'
|
||||
|
||||
@@ -113,4 +115,5 @@ group :development do
|
||||
gem 'guard-rails'
|
||||
gem 'guard-zeus'
|
||||
gem 'guard-rspec'
|
||||
gem 'parallel_tests'
|
||||
end
|
||||
|
||||
20
Gemfile.lock
20
Gemfile.lock
@@ -142,6 +142,8 @@ GEM
|
||||
activesupport (3.2.21)
|
||||
i18n (~> 0.6, >= 0.6.4)
|
||||
multi_json (~> 1.0)
|
||||
acts-as-taggable-on (3.5.0)
|
||||
activerecord (>= 3.2, < 5)
|
||||
acts_as_list (0.1.4)
|
||||
addressable (2.3.3)
|
||||
andand (1.3.3)
|
||||
@@ -249,8 +251,7 @@ GEM
|
||||
erubis (2.7.0)
|
||||
eventmachine (1.0.3)
|
||||
excon (0.25.3)
|
||||
execjs (1.4.0)
|
||||
multi_json (~> 1.0)
|
||||
execjs (2.5.2)
|
||||
factory_girl (3.3.0)
|
||||
activesupport (>= 3.0.0)
|
||||
factory_girl_rails (3.3.0)
|
||||
@@ -326,7 +327,7 @@ GEM
|
||||
kaminari (0.14.1)
|
||||
actionpack (>= 3.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
kgio (2.7.4)
|
||||
kgio (2.9.3)
|
||||
launchy (2.1.2)
|
||||
addressable (~> 2.3)
|
||||
letter_opener (1.0.0)
|
||||
@@ -362,6 +363,9 @@ GEM
|
||||
activesupport (>= 3.0.0)
|
||||
cocaine (~> 0.5.3)
|
||||
mime-types
|
||||
parallel (1.4.1)
|
||||
parallel_tests (1.3.7)
|
||||
parallel
|
||||
paypal-sdk-core (0.2.10)
|
||||
multi_json (~> 1.0)
|
||||
xml-simple
|
||||
@@ -413,7 +417,7 @@ GEM
|
||||
rake (>= 0.8.7)
|
||||
rdoc (~> 3.4)
|
||||
thor (>= 0.14.6, < 2.0)
|
||||
raindrops (0.9.0)
|
||||
raindrops (0.13.0)
|
||||
rake (10.4.2)
|
||||
ransack (0.7.2)
|
||||
actionpack (~> 3.0)
|
||||
@@ -497,10 +501,10 @@ GEM
|
||||
turn (0.8.3)
|
||||
ansi
|
||||
tzinfo (0.3.44)
|
||||
uglifier (1.2.4)
|
||||
uglifier (2.7.1)
|
||||
execjs (>= 0.3.0)
|
||||
multi_json (>= 1.0.2)
|
||||
unicorn (4.3.1)
|
||||
json (>= 1.8.0)
|
||||
unicorn (4.9.0)
|
||||
kgio (~> 2.6)
|
||||
rack
|
||||
raindrops (~> 0.7)
|
||||
@@ -532,6 +536,7 @@ PLATFORMS
|
||||
|
||||
DEPENDENCIES
|
||||
active_model_serializers
|
||||
acts-as-taggable-on (~> 3.4)
|
||||
andand
|
||||
angular-rails-templates
|
||||
angularjs-file-upload-rails (~> 1.1.0)
|
||||
@@ -576,6 +581,7 @@ DEPENDENCIES
|
||||
nokogiri
|
||||
oj
|
||||
paperclip
|
||||
parallel_tests
|
||||
pg
|
||||
poltergeist
|
||||
pry-debugger
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
angular.module("ofn.admin", ["ngResource", "ngAnimate", "ofn.dropdown", "admin.products", "admin.taxons", "infinite-scroll"]).config ($httpProvider) ->
|
||||
angular.module("ofn.admin", ["ngResource", "ngAnimate", "admin.indexUtils", "admin.dropdown", "admin.products", "admin.taxons", "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, */*"
|
||||
|
||||
@@ -17,9 +17,13 @@
|
||||
//= require admin/spree_promo
|
||||
//= require admin/spree_paypal_express
|
||||
//= require ../shared/ng-infinite-scroll.min.js
|
||||
//= require ../shared/ng-tags-input.min.js
|
||||
//= require ./admin
|
||||
//= require ./customers/customers
|
||||
//= require ./dropdown/dropdown
|
||||
//= require ./enterprises/enterprises
|
||||
//= require ./enterprise_groups/enterprise_groups
|
||||
//= require ./index_utils/index_utils
|
||||
//= require ./payment_methods/payment_methods
|
||||
//= require ./products/products
|
||||
//= require ./shipping_methods/shipping_methods
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [
|
||||
"$scope", "$http", "$filter", "dataFetcher", "blankOption", "pendingChanges", "VariantUnitManager", "OptionValueNamer", "SpreeApiKey"
|
||||
($scope, $http, $filter, dataFetcher, blankOption, pendingChanges, VariantUnitManager, OptionValueNamer, SpreeApiKey) ->
|
||||
"$scope", "$http", "$filter", "dataFetcher", "blankOption", "pendingChanges", "VariantUnitManager", "OptionValueNamer", "SpreeApiKey", "Columns"
|
||||
($scope, $http, $filter, dataFetcher, blankOption, pendingChanges, VariantUnitManager, OptionValueNamer, SpreeApiKey, Columns) ->
|
||||
$scope.loading = true
|
||||
|
||||
$scope.initialiseVariables = ->
|
||||
@@ -18,9 +18,7 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [
|
||||
$scope.selectedUnitsProduct = {};
|
||||
$scope.selectedUnitsVariant = {};
|
||||
$scope.sharedResource = false
|
||||
$scope.predicate = ""
|
||||
$scope.reverse = false
|
||||
$scope.columns =
|
||||
$scope.columns = Columns.setColumns
|
||||
order_no: { name: "Order No.", visible: false }
|
||||
full_name: { name: "Name", visible: true }
|
||||
email: { name: "Email", visible: false }
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $http, BulkProducts, DisplayProperties, dataFetcher, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth, tax_categories) ->
|
||||
angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $http, BulkProducts, DisplayProperties, dataFetcher, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth, Columns, tax_categories) ->
|
||||
$scope.loading = true
|
||||
|
||||
$scope.StatusMessage = StatusMessage
|
||||
|
||||
$scope.columns =
|
||||
$scope.columns = Columns.setColumns
|
||||
producer: {name: "Producer", visible: true}
|
||||
sku: {name: "SKU", visible: false}
|
||||
name: {name: "Name", visible: true}
|
||||
@@ -109,6 +109,12 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
window.location = "/admin/products/" + product.permalink_live + ((if variant then "/variants/" + variant.id else "")) + "/edit"
|
||||
|
||||
|
||||
$scope.toggleShowAllVariants = ->
|
||||
showVariants = !DisplayProperties.showVariants 0
|
||||
$scope.filteredProducts.forEach (product) ->
|
||||
DisplayProperties.setShowVariants product.id, showVariants
|
||||
DisplayProperties.setShowVariants 0, showVariants
|
||||
|
||||
$scope.addVariant = (product) ->
|
||||
product.variants.push
|
||||
id: $scope.nextVariantId()
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
angular.module("admin.customers").controller "customersCtrl", ($scope, Customers, Columns, pendingChanges, shops) ->
|
||||
$scope.shop = null
|
||||
$scope.shops = shops
|
||||
$scope.submitAll = pendingChanges.submitAll
|
||||
|
||||
$scope.columns = Columns.setColumns
|
||||
email: { name: "Email", visible: true }
|
||||
code: { name: "Code", visible: true }
|
||||
tags: { name: "Tags", visible: true }
|
||||
|
||||
$scope.$watch "shop", ->
|
||||
if $scope.shop?
|
||||
Customers.loaded = false
|
||||
$scope.customers = Customers.index(enterprise_id: $scope.shop.id)
|
||||
|
||||
$scope.loaded = ->
|
||||
Customers.loaded
|
||||
@@ -0,0 +1 @@
|
||||
angular.module("admin.customers", ['ngResource', 'ngTagsInput', 'admin.indexUtils', 'admin.dropdown'])
|
||||
@@ -0,0 +1,8 @@
|
||||
angular.module("admin.customers").directive "tagsWithTranslation", ->
|
||||
restrict: "E"
|
||||
template: "<tags-input ng-model='object.tags'>"
|
||||
scope:
|
||||
object: "="
|
||||
link: (scope, element, attrs) ->
|
||||
scope.$watchCollection "object.tags", ->
|
||||
scope.object.tag_list = (tag.text for tag in scope.object.tags).join(",")
|
||||
@@ -0,0 +1,8 @@
|
||||
angular.module("admin.customers").factory 'CustomerResource', ($resource) ->
|
||||
$resource('/admin/customers.json', {}, {
|
||||
'index':
|
||||
method: 'GET'
|
||||
isArray: true
|
||||
params:
|
||||
enterprise_id: '@enterprise_id'
|
||||
})
|
||||
@@ -0,0 +1,16 @@
|
||||
angular.module("admin.customers").factory 'Customers', (CustomerResource) ->
|
||||
new class Customers
|
||||
customers: []
|
||||
customers_by_id: {}
|
||||
loaded: false
|
||||
|
||||
index: (params={}, callback=null) ->
|
||||
CustomerResource.index params, (data) =>
|
||||
for customer in data
|
||||
@customers.push customer
|
||||
@customers_by_id[customer.id] = customer
|
||||
|
||||
@loaded = true
|
||||
(callback || angular.noop)(@customers)
|
||||
|
||||
@customers
|
||||
@@ -1,25 +0,0 @@
|
||||
angular.module("ofn.admin").directive "ofnLineItemUpdAttr", [
|
||||
"switchClass", "pendingChanges"
|
||||
(switchClass, pendingChanges) ->
|
||||
require: "ngModel"
|
||||
link: (scope, element, attrs, ngModel) ->
|
||||
attrName = attrs.ofnLineItemUpdAttr
|
||||
element.dbValue = scope.$eval(attrs.ngModel)
|
||||
scope.$watch ->
|
||||
scope.$eval(attrs.ngModel)
|
||||
, (value) ->
|
||||
#if ngModel.$dirty
|
||||
# i think i can take this out, this directive is still only called
|
||||
# on a change and only an updated value will create a db call.
|
||||
if value == element.dbValue
|
||||
pendingChanges.remove(scope.line_item.id, attrName)
|
||||
switchClass( element, "", ["update-pending", "update-error", "update-success"], false )
|
||||
else
|
||||
changeObj =
|
||||
lineItem: scope.line_item
|
||||
element: element
|
||||
attrName: attrName
|
||||
url: "/api/orders/#{scope.line_item.order.number}/line_items/#{scope.line_item.id}?line_item[#{attrName}]=#{value}"
|
||||
pendingChanges.add(scope.line_item.id, attrName, changeObj)
|
||||
switchClass( element, "update-pending", ["update-error", "update-success"], false )
|
||||
]
|
||||
@@ -1,10 +1,8 @@
|
||||
angular.module("ofn.admin").directive "ofnToggleVariants", (DisplayProperties) ->
|
||||
link: (scope, element, attrs) ->
|
||||
if DisplayProperties.showVariants scope.product.id
|
||||
element.removeClass "icon-chevron-right"
|
||||
element.addClass "icon-chevron-down"
|
||||
else
|
||||
element.removeClass "icon-chevron-down"
|
||||
element.addClass "icon-chevron-right"
|
||||
|
||||
element.on "click", ->
|
||||
@@ -16,4 +14,4 @@ angular.module("ofn.admin").directive "ofnToggleVariants", (DisplayProperties) -
|
||||
else
|
||||
DisplayProperties.setShowVariants scope.product.id, true
|
||||
element.removeClass "icon-chevron-right"
|
||||
element.addClass "icon-chevron-down"
|
||||
element.addClass "icon-chevron-down"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
angular.module("admin.dropdown").controller "DropDownCtrl", ($scope) ->
|
||||
$scope.expanded = false
|
||||
@@ -0,0 +1,5 @@
|
||||
angular.module("admin.dropdown").directive "ofnCloseOnClick", ($document) ->
|
||||
link: (scope, element, attrs) ->
|
||||
element.click (event) ->
|
||||
event.stopPropagation()
|
||||
scope.$emit "offClick"
|
||||
@@ -1,6 +1,4 @@
|
||||
dropDownModule = angular.module("ofn.dropdown", [])
|
||||
|
||||
dropDownModule.directive "ofnDropDown", ($document) ->
|
||||
angular.module("admin.dropdown").directive "ofnDropDown", ($document) ->
|
||||
link: (scope, element, attrs) ->
|
||||
outsideClickListener = (event) ->
|
||||
unless $(event.target).is("div.ofn_drop_down##{attrs.id} div.menu") ||
|
||||
@@ -20,12 +18,3 @@ dropDownModule.directive "ofnDropDown", ($document) ->
|
||||
scope.$apply ->
|
||||
scope.expanded = true
|
||||
element.addClass "expanded"
|
||||
|
||||
dropDownModule.directive "ofnCloseOnClick", ($document) ->
|
||||
link: (scope, element, attrs) ->
|
||||
element.click (event) ->
|
||||
event.stopPropagation()
|
||||
scope.$emit "offClick"
|
||||
|
||||
dropDownModule.controller "DropDownCtrl", ($scope) ->
|
||||
$scope.expanded = false
|
||||
1
app/assets/javascripts/admin/dropdown/dropdown.js.coffee
Normal file
1
app/assets/javascripts/admin/dropdown/dropdown.js.coffee
Normal file
@@ -0,0 +1 @@
|
||||
angular.module("admin.dropdown", [])
|
||||
@@ -0,0 +1,4 @@
|
||||
angular.module("admin.indexUtils").controller "ColumnsCtrl", ($scope, Columns) ->
|
||||
$scope.columns = Columns.columns
|
||||
$scope.predicate = ""
|
||||
$scope.reverse = false
|
||||
@@ -0,0 +1,36 @@
|
||||
angular.module("admin.indexUtils").directive "objForUpdate", (switchClass, pendingChanges) ->
|
||||
scope:
|
||||
object: "&objForUpdate"
|
||||
type: "@objForUpdate"
|
||||
attr: "@attrForUpdate"
|
||||
link: (scope, element, attrs) ->
|
||||
scope.savedValue = scope.object()[scope.attr]
|
||||
|
||||
scope.$watch "object().#{scope.attr}", (value) ->
|
||||
if value == scope.savedValue
|
||||
pendingChanges.remove(scope.object().id, scope.attr)
|
||||
scope.clear()
|
||||
else
|
||||
change =
|
||||
object: scope.object()
|
||||
type: scope.type
|
||||
attr: scope.attr
|
||||
value: value
|
||||
scope: scope
|
||||
scope.pending()
|
||||
pendingChanges.add(scope.object().id, scope.attr, change)
|
||||
|
||||
scope.reset = (value) ->
|
||||
scope.savedValue = value
|
||||
|
||||
scope.success = ->
|
||||
switchClass( element, "update-success", ["update-pending", "update-error"], 3000 )
|
||||
|
||||
scope.pending = ->
|
||||
switchClass( element, "update-pending", ["update-error", "update-success"], false )
|
||||
|
||||
scope.error = ->
|
||||
switchClass( element, "update-error", ["update-pending", "update-success"], false )
|
||||
|
||||
scope.clear = ->
|
||||
switchClass( element, "", ["update-pending", "update-error", "update-success"], false )
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module("ofn.admin").directive "ofnToggleColumn", ->
|
||||
angular.module("admin.indexUtils").directive "ofnToggleColumn", ->
|
||||
link: (scope, element, attrs) ->
|
||||
element.addClass "selected" if scope.column.visible
|
||||
element.click "click", ->
|
||||
@@ -8,4 +8,4 @@ angular.module("ofn.admin").directive "ofnToggleColumn", ->
|
||||
element.removeClass "selected"
|
||||
else
|
||||
scope.column.visible = true
|
||||
element.addClass "selected"
|
||||
element.addClass "selected"
|
||||
@@ -0,0 +1 @@
|
||||
angular.module("admin.indexUtils", ['ngResource']).config ($httpProvider) ->
|
||||
@@ -0,0 +1,8 @@
|
||||
angular.module("admin.indexUtils").factory 'Columns', ->
|
||||
new class Columns
|
||||
columns: {}
|
||||
|
||||
setColumns: (columns) ->
|
||||
@columns = {}
|
||||
@columns[name] = column for name, column of columns
|
||||
@columns
|
||||
@@ -0,0 +1,33 @@
|
||||
angular.module("admin.indexUtils").factory "pendingChanges", (resources) ->
|
||||
new class pendingChanges
|
||||
pendingChanges: {}
|
||||
|
||||
add: (id, attr, change) =>
|
||||
@pendingChanges["#{id}"] = {} unless @pendingChanges.hasOwnProperty("#{id}")
|
||||
@pendingChanges["#{id}"]["#{attr}"] = change
|
||||
|
||||
removeAll: =>
|
||||
@pendingChanges = {}
|
||||
|
||||
remove: (id, attr) =>
|
||||
if @pendingChanges.hasOwnProperty("#{id}")
|
||||
delete @pendingChanges["#{id}"]["#{attr}"]
|
||||
delete @pendingChanges["#{id}"] if @changeCount( @pendingChanges["#{id}"] ) < 1
|
||||
|
||||
submitAll: =>
|
||||
all = []
|
||||
for id, objectChanges of @pendingChanges
|
||||
for attrName, change of objectChanges
|
||||
all.push @submit(change)
|
||||
all
|
||||
|
||||
submit: (change) ->
|
||||
resources.update(change).$promise.then (data) =>
|
||||
@remove change.object.id, change.attr
|
||||
change.scope.reset( data["#{change.attr}"] )
|
||||
change.scope.success()
|
||||
, (error) ->
|
||||
change.scope.error()
|
||||
|
||||
changeCount: (objectChanges) ->
|
||||
Object.keys(objectChanges).length
|
||||
@@ -0,0 +1,30 @@
|
||||
angular.module("admin.indexUtils").factory "resources", ($resource) ->
|
||||
LineItem = $resource '/api/orders/:order_number/line_items/:line_item_id.json',
|
||||
{ order_number: '@order_number', line_item_id: '@line_item_id'},
|
||||
'update': { method: 'PUT' }
|
||||
Customer = $resource '/admin/customers/:customer_id.json',
|
||||
{ customer_id: '@customer_id'},
|
||||
'update': { method: 'PUT' }
|
||||
|
||||
return {
|
||||
update: (change) ->
|
||||
params = {}
|
||||
data = {}
|
||||
resource = null
|
||||
|
||||
switch change.type
|
||||
when "line_item"
|
||||
resource = LineItem
|
||||
params.order_number = change.object.order.number
|
||||
params.line_item_id = change.object.id
|
||||
data.line_item = {}
|
||||
data.line_item[change.attr] = change.value
|
||||
when "customer"
|
||||
resource = Customer
|
||||
params.customer_id = change.object.id
|
||||
data.customer = {}
|
||||
data.customer[change.attr] = change.value
|
||||
else ""
|
||||
|
||||
resource.update(params, data)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
angular.module("admin.indexUtils").factory "switchClass", ($timeout) ->
|
||||
return (element,classToAdd,removeClasses,timeout) ->
|
||||
$timeout.cancel element.timeout if element.timeout
|
||||
element.removeClass className for className in removeClasses
|
||||
element.addClass classToAdd
|
||||
intRegex = /^\d+$/
|
||||
if timeout && intRegex.test(timeout)
|
||||
element.timeout = $timeout(->
|
||||
element.removeClass classToAdd
|
||||
, timeout, true)
|
||||
@@ -1,13 +0,0 @@
|
||||
angular.module("ofn.admin").factory "dataSubmitter", [
|
||||
"$http", "$q", "switchClass"
|
||||
($http, $q, switchClass) ->
|
||||
return (changeObj) ->
|
||||
deferred = $q.defer()
|
||||
$http.put(changeObj.url).success((data) ->
|
||||
switchClass changeObj.element, "update-success", ["update-pending", "update-error"], 3000
|
||||
deferred.resolve data
|
||||
).error ->
|
||||
switchClass changeObj.element, "update-error", ["update-pending", "update-success"], false
|
||||
deferred.reject()
|
||||
deferred.promise
|
||||
]
|
||||
@@ -3,12 +3,10 @@ angular.module("ofn.admin").factory "DisplayProperties", ->
|
||||
displayProperties: {}
|
||||
|
||||
showVariants: (product_id) ->
|
||||
@initProduct product_id
|
||||
@displayProperties[product_id].showVariants
|
||||
@productProperties(product_id).showVariants
|
||||
|
||||
setShowVariants: (product_id, showVariants) ->
|
||||
@initProduct product_id
|
||||
@displayProperties[product_id].showVariants = showVariants
|
||||
@productProperties(product_id).showVariants = showVariants
|
||||
|
||||
initProduct: (product_id) ->
|
||||
productProperties: (product_id) ->
|
||||
@displayProperties[product_id] ||= {showVariants: false}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
angular.module("ofn.admin").factory "pendingChanges",[
|
||||
"dataSubmitter"
|
||||
(dataSubmitter) ->
|
||||
pendingChanges: {}
|
||||
|
||||
add: (id, attrName, changeObj) ->
|
||||
@pendingChanges["#{id}"] = {} unless @pendingChanges.hasOwnProperty("#{id}")
|
||||
@pendingChanges["#{id}"]["#{attrName}"] = changeObj
|
||||
|
||||
removeAll: ->
|
||||
@pendingChanges = {}
|
||||
|
||||
remove: (id, attrName) ->
|
||||
if @pendingChanges.hasOwnProperty("#{id}")
|
||||
delete @pendingChanges["#{id}"]["#{attrName}"]
|
||||
delete @pendingChanges["#{id}"] if @changeCount( @pendingChanges["#{id}"] ) < 1
|
||||
|
||||
submitAll: ->
|
||||
all = []
|
||||
for id,lineItem of @pendingChanges
|
||||
for attrName,changeObj of lineItem
|
||||
all.push @submit(id, attrName, changeObj)
|
||||
all
|
||||
|
||||
submit: (id, attrName, change) ->
|
||||
dataSubmitter(change).then (data) =>
|
||||
@remove id, attrName
|
||||
change.element.dbValue = data["#{attrName}"]
|
||||
|
||||
changeCount: (lineItem) ->
|
||||
Object.keys(lineItem).length
|
||||
]
|
||||
@@ -1,13 +0,0 @@
|
||||
angular.module("ofn.admin").factory "switchClass", [
|
||||
"$timeout"
|
||||
($timeout) ->
|
||||
return (element,classToAdd,removeClasses,timeout) ->
|
||||
$timeout.cancel element.timeout if element.timeout
|
||||
element.removeClass className for className in removeClasses
|
||||
element.addClass classToAdd
|
||||
intRegex = /^\d+$/
|
||||
if timeout && intRegex.test(timeout)
|
||||
element.timeout = $timeout(->
|
||||
element.removeClass classToAdd
|
||||
, timeout, true)
|
||||
]
|
||||
@@ -32,8 +32,9 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Pro
|
||||
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
|
||||
if product.master
|
||||
product.master.product = product
|
||||
product.master = Variants.register product.master
|
||||
|
||||
registerVariantsWithCart: ->
|
||||
for product in @products
|
||||
|
||||
1
app/assets/javascripts/shared/ng-tags-input.min.js
vendored
Executable file
1
app/assets/javascripts/shared/ng-tags-input.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
@@ -14,7 +14,7 @@
|
||||
.small-12.columns
|
||||
.alert-box.info{ "ofn-inline-alert" => true, ng: { show: "visible" } }
|
||||
%h6 Success! {{ enterprise.name }} added to the Open Food Network
|
||||
%span If you exit the wizard at any stage, login and go to admin to edit or update your enterprise details.
|
||||
%span If you exit this wizard at any stage, you need to click the confirmation link in the email you have received. This will take you to your admin interface where you can continue setting up your profile.
|
||||
%a.close{ ng: { click: "close()" } } ×
|
||||
|
||||
.small-12.large-8.columns
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
%p
|
||||
We've sent a confirmation email to
|
||||
%strong {{ enterprise.email }}.
|
||||
%strong {{ enterprise.email }} if it hasn't been activated before.
|
||||
%br Please follow the instructions there to make your enterprise visible on the Open Food Network.
|
||||
|
||||
%a.button.primary{ type: "button", href: "/" } Open Food Network home >
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%h4
|
||||
%small
|
||||
%i.ofn-i_040-hub
|
||||
Create your enterprise profile
|
||||
You can now create a profile for your Producer or Hub
|
||||
.hide-for-large-up
|
||||
%hr
|
||||
%input.button.small.primary{ type: "button", value: "Let's get started!", ng: { click: "select('details')" } }
|
||||
@@ -38,6 +38,7 @@
|
||||
%strong contact
|
||||
you on the Open Food Network.
|
||||
%p Use this space to tell the story of your enterprise, to help drive connections to your social and online presence.
|
||||
%p It's also the first step towards trading on the Open Food Network, or opening an online store.
|
||||
|
||||
.row.show-for-large-up
|
||||
.small-12.columns
|
||||
|
||||
@@ -38,9 +38,13 @@
|
||||
%i.ofn-i_013-help
|
||||
|
||||
%p Producers make yummy things to eat &/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it.
|
||||
/ %p Hubs connect the producer to the eater. Hubs can be co-ops, independent retailers, buying groups, wholesalers, CSA box schemes, farm-gate stalls, etc.
|
||||
.panel.callout
|
||||
.left
|
||||
%i.ofn-i_013-help
|
||||
|
||||
%p If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other.
|
||||
|
||||
.row.buttons
|
||||
.small-12.columns
|
||||
%input.button.secondary{ type: "button", value: "Back", ng: { click: "select('contact')" } }
|
||||
%input.button.primary.right{ type: "submit", value: "Continue" }
|
||||
%input.button.primary.right{ type: "submit", value: "Create Profile" }
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
*= require shared/jquery-ui-timepicker-addon
|
||||
*= require shared/textAngular.min
|
||||
*= require shared/ng-tags-input.min
|
||||
|
||||
*= require_self
|
||||
*= require_tree .
|
||||
|
||||
@@ -8,16 +8,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
input.update-pending {
|
||||
border: solid 1px orange;
|
||||
input, div {
|
||||
&.update-pending {
|
||||
border: solid 1px orange;
|
||||
}
|
||||
}
|
||||
|
||||
input.update-error {
|
||||
border: solid 1px red;
|
||||
input, div {
|
||||
&.update-error {
|
||||
border: solid 1px red;
|
||||
}
|
||||
}
|
||||
|
||||
input.update-success {
|
||||
border: solid 1px #9fc820;
|
||||
input, div {
|
||||
&.update-success {
|
||||
border: solid 1px #9fc820;
|
||||
}
|
||||
}
|
||||
|
||||
.no-close .ui-dialog-titlebar-close {
|
||||
@@ -42,4 +48,4 @@ div#group_buy_calculation {
|
||||
.row span {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
app/assets/stylesheets/shared/ng-tags-input.min.css
vendored
Executable file
1
app/assets/stylesheets/shared/ng-tags-input.min.css
vendored
Executable file
@@ -0,0 +1 @@
|
||||
tags-input{display:block}tags-input *,tags-input :after,tags-input :before{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}tags-input .host{position:relative;margin-top:5px;margin-bottom:5px;height:100%}tags-input .host:active{outline:0}tags-input .tags{-moz-appearance:textfield;-webkit-appearance:textfield;padding:1px;overflow:hidden;word-wrap:break-word;cursor:text;background-color:#fff;border:1px solid #a9a9a9;box-shadow:1px 1px 1px 0 #d3d3d3 inset;height:100%}tags-input .tags.focused{outline:0;-webkit-box-shadow:0 0 3px 1px rgba(5,139,242,.6);-moz-box-shadow:0 0 3px 1px rgba(5,139,242,.6);box-shadow:0 0 3px 1px rgba(5,139,242,.6)}tags-input .tags .tag-list{margin:0;padding:0;list-style-type:none}tags-input .tags .tag-item{margin:2px;padding:0 5px;display:inline-block;float:left;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif;height:26px;line-height:25px;border:1px solid #acacac;border-radius:3px;background:-webkit-linear-gradient(top,#f0f9ff 0,#cbebff 47%,#a1dbff 100%);background:linear-gradient(to bottom,#f0f9ff 0,#cbebff 47%,#a1dbff 100%)}tags-input .tags .tag-item.selected{background:-webkit-linear-gradient(top,#febbbb 0,#fe9090 45%,#ff5c5c 100%);background:linear-gradient(to bottom,#febbbb 0,#fe9090 45%,#ff5c5c 100%)}tags-input .tags .tag-item .remove-button{margin:0 0 0 5px;padding:0;border:none;background:0 0;cursor:pointer;vertical-align:middle;font:700 16px Arial,sans-serif;color:#585858}tags-input .tags .tag-item .remove-button:active{color:red}tags-input .tags .input{border:0;outline:0;margin:2px;padding:0;padding-left:5px;float:left;height:26px;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif}tags-input .tags .input.invalid-tag{color:red}tags-input .tags .input::-ms-clear{display:none}tags-input.ng-invalid .tags{-webkit-box-shadow:0 0 3px 1px rgba(255,0,0,.6);-moz-box-shadow:0 0 3px 1px rgba(255,0,0,.6);box-shadow:0 0 3px 1px rgba(255,0,0,.6)}tags-input[disabled] .host:focus{outline:0}tags-input[disabled] .tags{background-color:#eee;cursor:default}tags-input[disabled] .tags .tag-item{opacity:.65;background:-webkit-linear-gradient(top,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%);background:linear-gradient(to bottom,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%)}tags-input[disabled] .tags .tag-item .remove-button{cursor:default}tags-input[disabled] .tags .tag-item .remove-button:active{color:#585858}tags-input[disabled] .tags .input{background-color:#eee;cursor:default}tags-input .autocomplete{margin-top:5px;position:absolute;padding:5px 0;z-index:999;width:100%;background-color:#fff;border:1px solid rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}tags-input .autocomplete .suggestion-list{margin:0;padding:0;list-style-type:none;max-height:280px;overflow-y:auto;position:relative}tags-input .autocomplete .suggestion-item{padding:5px 10px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font:16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff}tags-input .autocomplete .suggestion-item.selected,tags-input .autocomplete .suggestion-item.selected em{color:#fff;background-color:#0097cf}tags-input .autocomplete .suggestion-item em{font:normal bold 16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff}
|
||||
29
app/controllers/admin/customers_controller.rb
Normal file
29
app/controllers/admin/customers_controller.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
module Admin
|
||||
class CustomersController < ResourceController
|
||||
before_filter :load_managed_shops, only: :index, if: :html_request?
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
render json: ActiveModel::ArraySerializer.new( @collection,
|
||||
each_serializer: Api::Admin::CustomerSerializer, spree_current_user: spree_current_user
|
||||
).to_json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def collection
|
||||
return Customer.where("1=0") if html_request? || params[:enterprise_id].nil?
|
||||
enterprise = Enterprise.managed_by(spree_current_user).find_by_id(params[:enterprise_id])
|
||||
Customer.of(enterprise)
|
||||
end
|
||||
|
||||
def load_managed_shops
|
||||
@shops = Enterprise.managed_by(spree_current_user).is_distributor
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,3 +1,5 @@
|
||||
require 'open_food_network/referer_parser'
|
||||
|
||||
module Admin
|
||||
class EnterprisesController < ResourceController
|
||||
before_filter :load_enterprise_set, :only => :index
|
||||
@@ -199,7 +201,8 @@ module Admin
|
||||
|
||||
# Overriding method on Spree's resource controller
|
||||
def location_after_save
|
||||
refered_from_edit = URI(request.referer).path == main_app.edit_admin_enterprise_path(@enterprise)
|
||||
referer_path = OpenFoodNetwork::RefererParser::path(request.referer)
|
||||
refered_from_edit = referer_path == main_app.edit_admin_enterprise_path(@enterprise)
|
||||
if params[:enterprise].key?(:producer_properties_attributes) && !refered_from_edit
|
||||
main_app.admin_enterprises_path
|
||||
else
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
require 'open_food_network/referer_parser'
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
protect_from_forgery
|
||||
|
||||
@@ -9,7 +11,8 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
|
||||
def set_checkout_redirect
|
||||
if request.referer and referer_path = URI(request.referer).path
|
||||
referer_path = OpenFoodNetwork::RefererParser::path(request.referer)
|
||||
if referer_path
|
||||
session["spree_user_return_to"] = [main_app.checkout_path].include?(referer_path) ? referer_path : root_path
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,9 +12,6 @@ class BaseController < ApplicationController
|
||||
|
||||
before_filter :check_order_cycle_expiry
|
||||
|
||||
def load_active_distributors
|
||||
@active_distributors ||= Enterprise.distributors_with_active_order_cycles
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
|
||||
@@ -12,9 +12,6 @@ class CheckoutController < Spree::CheckoutController
|
||||
include EnterprisesHelper
|
||||
|
||||
def edit
|
||||
# Because this controller doesn't inherit from our BaseController
|
||||
# We need to duplicate the code here
|
||||
@active_distributors ||= Enterprise.distributors_with_active_order_cycles
|
||||
end
|
||||
|
||||
def update
|
||||
|
||||
@@ -4,7 +4,7 @@ class EnterprisesController < BaseController
|
||||
include OrderCyclesHelper
|
||||
|
||||
# These prepended filters are in the reverse order of execution
|
||||
prepend_before_filter :load_active_distributors, :set_order_cycles, :require_distributor_chosen, :reset_order, only: :shop
|
||||
prepend_before_filter :set_order_cycles, :require_distributor_chosen, :reset_order, only: :shop
|
||||
before_filter :clean_permalink, only: :check_permalink
|
||||
|
||||
respond_to :js, only: :permalink_checker
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
class GroupsController < BaseController
|
||||
layout 'darkswarm'
|
||||
before_filter :load_active_distributors
|
||||
|
||||
def index
|
||||
@groups = EnterpriseGroup.on_front_page.by_position
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
class HomeController < BaseController
|
||||
layout 'darkswarm'
|
||||
before_filter :load_active_distributors
|
||||
|
||||
|
||||
def index
|
||||
end
|
||||
|
||||
def about_us
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
class MapController < BaseController
|
||||
layout 'darkswarm'
|
||||
before_filter :load_active_distributors
|
||||
|
||||
def index
|
||||
end
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
class ProducersController < BaseController
|
||||
layout 'darkswarm'
|
||||
before_filter :load_active_distributors
|
||||
|
||||
|
||||
def index
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,12 +10,16 @@ class ShopController < BaseController
|
||||
end
|
||||
|
||||
def products
|
||||
# Can we make this query less slow?
|
||||
#
|
||||
if @products = products_for_shop
|
||||
|
||||
render status: 200,
|
||||
json: ActiveModel::ArraySerializer.new(@products, each_serializer: Api::ProductSerializer,
|
||||
current_order_cycle: current_order_cycle, current_distributor: current_distributor).to_json
|
||||
json: ActiveModel::ArraySerializer.new(@products,
|
||||
each_serializer: Api::ProductSerializer,
|
||||
current_order_cycle: current_order_cycle,
|
||||
current_distributor: current_distributor,
|
||||
variants: variants_for_shop_by_id,
|
||||
master_variants: master_variants_for_shop_by_id).to_json
|
||||
|
||||
else
|
||||
render json: "", status: 404
|
||||
end
|
||||
@@ -56,4 +60,30 @@ class ShopController < BaseController
|
||||
"name ASC"
|
||||
end
|
||||
end
|
||||
|
||||
def all_variants_for_shop
|
||||
# We use the in_stock? method here instead of the in_stock scope because we need to
|
||||
# look up the stock as overridden by VariantOverrides, and the scope method is not affected
|
||||
# by them.
|
||||
Spree::Variant.
|
||||
for_distribution(current_order_cycle, current_distributor).
|
||||
each { |v| v.scope_to_hub current_distributor }.
|
||||
select(&:in_stock?)
|
||||
end
|
||||
|
||||
def variants_for_shop_by_id
|
||||
index_by_product_id all_variants_for_shop.reject(&:is_master)
|
||||
end
|
||||
|
||||
def master_variants_for_shop_by_id
|
||||
index_by_product_id all_variants_for_shop.select(&:is_master)
|
||||
end
|
||||
|
||||
def index_by_product_id(variants)
|
||||
variants.inject({}) do |vs, v|
|
||||
vs[v.product_id] ||= []
|
||||
vs[v.product_id] << v
|
||||
vs
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -58,4 +58,8 @@ Spree::Admin::BaseController.class_eval do
|
||||
"Until you set these up, customers will not be able to shop at this hub."
|
||||
end
|
||||
end
|
||||
|
||||
def html_request?
|
||||
request.format.html?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,7 +47,7 @@ Spree::Admin::OrdersController.class_eval do
|
||||
|
||||
def managed
|
||||
permissions = OpenFoodNetwork::Permissions.new(spree_current_user)
|
||||
@orders = permissions.editable_orders.ransack(params[:q]).result.page(params[:page]).per(params[:per_page])
|
||||
@orders = permissions.editable_orders.order(:id).ransack(params[:q]).result.page(params[:page]).per(params[:per_page])
|
||||
render json: @orders, each_serializer: Api::Admin::OrderSerializer
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
require 'open_food_network/spree_api_key_loader'
|
||||
require 'open_food_network/referer_parser'
|
||||
|
||||
Spree::Admin::ProductsController.class_eval do
|
||||
include OpenFoodNetwork::SpreeApiKeyLoader
|
||||
@@ -53,7 +54,8 @@ Spree::Admin::ProductsController.class_eval do
|
||||
|
||||
protected
|
||||
def location_after_save
|
||||
if URI(request.referer).path == '/admin/products/bulk_edit'
|
||||
referer_path = OpenFoodNetwork::RefererParser::path(request.referer)
|
||||
if referer_path == '/admin/products/bulk_edit'
|
||||
bulk_edit_admin_products_url
|
||||
else
|
||||
location_after_save_original
|
||||
|
||||
@@ -7,6 +7,7 @@ require 'open_food_network/customers_report'
|
||||
require 'open_food_network/users_and_enterprises_report'
|
||||
require 'open_food_network/order_cycle_management_report'
|
||||
require 'open_food_network/sales_tax_report'
|
||||
require 'open_food_network/xero_invoices_report'
|
||||
|
||||
Spree::Admin::ReportsController.class_eval do
|
||||
|
||||
@@ -679,7 +680,22 @@ Spree::Admin::ReportsController.class_eval do
|
||||
render_report(@report.header, @report.table, params[:csv], "users_and_enterprises_#{timestamp}.csv")
|
||||
end
|
||||
|
||||
def render_report (header, table, create_csv, csv_file_name)
|
||||
def xero_invoices
|
||||
if request.get?
|
||||
params[:q] ||= {}
|
||||
params[:q][:completed_at_gt] = Time.zone.now.beginning_of_month
|
||||
end
|
||||
@distributors = Enterprise.is_distributor.managed_by(spree_current_user)
|
||||
@order_cycles = OrderCycle.active_or_complete.accessible_by(spree_current_user).order('orders_close_at DESC')
|
||||
|
||||
@search = Spree::Order.complete.managed_by(spree_current_user).order('id DESC').search(params[:q])
|
||||
orders = @search.result
|
||||
@report = OpenFoodNetwork::XeroInvoicesReport.new orders, params
|
||||
render_report(@report.header, @report.table, params[:csv], "xero_invoices_#{timestamp}.csv")
|
||||
end
|
||||
|
||||
|
||||
def render_report(header, table, create_csv, csv_file_name)
|
||||
unless create_csv
|
||||
render :html => table
|
||||
else
|
||||
@@ -716,7 +732,9 @@ Spree::Admin::ReportsController.class_eval do
|
||||
:sales_total => { :name => "Sales Total", :description => "Sales Total For All Orders" },
|
||||
:users_and_enterprises => { :name => "Users & Enterprises", :description => "Enterprise Ownership & Status" },
|
||||
:order_cycle_management => {:name => "Order Cycle Management", :description => ''},
|
||||
:sales_tax => { :name => "Sales Tax", :description => "Sales Tax For Orders" }
|
||||
:sales_tax => { :name => "Sales Tax", :description => "Sales Tax For Orders" },
|
||||
:xero_invoices => { :name => "Xero Invoices", :description => 'Invoices for import into Xero' }
|
||||
|
||||
}
|
||||
# Return only reports the user is authorized to view.
|
||||
reports.select { |action| can? action, :report }
|
||||
|
||||
@@ -25,6 +25,10 @@ module Admin
|
||||
admin_inject_json_ams_array "admin.shipping_methods", "shippingMethods", @shipping_methods, Api::Admin::IdNameSerializer
|
||||
end
|
||||
|
||||
def admin_inject_shops
|
||||
admin_inject_json_ams_array "admin.customers", "shops", @shops, Api::Admin::IdNameSerializer
|
||||
end
|
||||
|
||||
def admin_inject_hubs
|
||||
admin_inject_json_ams_array "ofn.admin", "hubs", @hubs, Api::Admin::IdNameSerializer
|
||||
end
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
require 'open_food_network/enterprise_injection_data'
|
||||
|
||||
module InjectionHelper
|
||||
def inject_enterprises
|
||||
inject_json_ams "enterprises", Enterprise.activated.all, Api::EnterpriseSerializer, active_distributors: @active_distributors
|
||||
inject_json_ams "enterprises", Enterprise.activated.includes(:address).all, Api::EnterpriseSerializer, enterprise_injection_data
|
||||
end
|
||||
|
||||
def inject_group_enterprises
|
||||
inject_json_ams "group_enterprises", @group.enterprises, Api::EnterpriseSerializer, enterprise_injection_data
|
||||
end
|
||||
|
||||
def inject_current_hub
|
||||
inject_json_ams "currentHub", current_distributor, Api::EnterpriseSerializer, enterprise_injection_data
|
||||
end
|
||||
|
||||
def inject_current_order
|
||||
@@ -53,4 +63,13 @@ module InjectionHelper
|
||||
end
|
||||
render partial: "json/injection_ams", locals: {name: name, json: json}
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def enterprise_injection_data
|
||||
@enterprise_injection_data ||= OpenFoodNetwork::EnterpriseInjectionData.new
|
||||
{data: @enterprise_injection_data}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
class Customer < ActiveRecord::Base
|
||||
acts_as_taggable
|
||||
|
||||
belongs_to :enterprise
|
||||
belongs_to :user, :class_name => Spree.user_class
|
||||
|
||||
validates :code, presence: true, uniqueness: {scope: :enterprise_id}
|
||||
validates :email, presence: true
|
||||
validates :code, uniqueness: { scope: :enterprise_id, allow_blank: true, allow_nil: true }
|
||||
validates :email, presence: true, uniqueness: { scope: :enterprise_id, message: "is associated with an existing customer" }
|
||||
validates :enterprise_id, presence: true
|
||||
|
||||
scope :of, ->(enterprise) { where(enterprise_id: enterprise) }
|
||||
|
||||
before_create :associate_user
|
||||
|
||||
private
|
||||
|
||||
def associate_user
|
||||
self.user = user || Spree::User.find_by_email(email)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,6 +30,7 @@ class Enterprise < ActiveRecord::Base
|
||||
has_and_belongs_to_many :payment_methods, join_table: 'distributors_payment_methods', class_name: 'Spree::PaymentMethod', foreign_key: 'distributor_id'
|
||||
has_many :distributor_shipping_methods, foreign_key: :distributor_id
|
||||
has_many :shipping_methods, through: :distributor_shipping_methods
|
||||
has_many :customers
|
||||
|
||||
delegate :latitude, :longitude, :city, :state_name, :to => :address
|
||||
|
||||
@@ -178,6 +179,10 @@ class Enterprise < ActiveRecord::Base
|
||||
count(distinct: true)
|
||||
end
|
||||
|
||||
def activated?
|
||||
confirmed_at.present? && sells != 'unspecified'
|
||||
end
|
||||
|
||||
def set_producer_property(property_name, property_value)
|
||||
transaction do
|
||||
property = Spree::Property.where(name: property_name).first_or_create!(presentation: property_name)
|
||||
@@ -212,12 +217,16 @@ class Enterprise < ActiveRecord::Base
|
||||
", self.id, self.id)
|
||||
end
|
||||
|
||||
def relatives_including_self
|
||||
Enterprise.where(id: relatives.pluck(:id) | [id])
|
||||
end
|
||||
|
||||
def distributors
|
||||
self.relatives.is_distributor
|
||||
self.relatives_including_self.is_distributor
|
||||
end
|
||||
|
||||
def suppliers
|
||||
self.relatives.is_primary_producer
|
||||
self.relatives_including_self.is_primary_producer
|
||||
end
|
||||
|
||||
def website
|
||||
|
||||
@@ -25,6 +25,32 @@ class EnterpriseRelationship < ActiveRecord::Base
|
||||
scope :by_name, with_enterprises.order('child_enterprises.name, parent_enterprises.name')
|
||||
|
||||
|
||||
# Load an array of the relatives of each enterprise (ie. any enterprise related to it in
|
||||
# either direction). This array is split into distributors and producers, and has the format:
|
||||
# {enterprise_id => {distributors: [id, ...], producers: [id, ...]} }
|
||||
def self.relatives(activated_only=false)
|
||||
relationships = EnterpriseRelationship.includes(:child, :parent)
|
||||
relatives = {}
|
||||
|
||||
relationships.each do |r|
|
||||
relatives[r.parent_id] ||= {distributors: Set.new, producers: Set.new}
|
||||
relatives[r.child_id] ||= {distributors: Set.new, producers: Set.new}
|
||||
|
||||
if !activated_only || r.child.activated?
|
||||
relatives[r.parent_id][:producers] << r.child_id if r.child.is_primary_producer
|
||||
relatives[r.parent_id][:distributors] << r.child_id if r.child.is_distributor
|
||||
end
|
||||
|
||||
if !activated_only || r.parent.activated?
|
||||
relatives[r.child_id][:producers] << r.parent_id if r.parent.is_primary_producer
|
||||
relatives[r.child_id][:distributors] << r.parent_id if r.parent.is_distributor
|
||||
end
|
||||
end
|
||||
|
||||
relatives
|
||||
end
|
||||
|
||||
|
||||
def permissions_list=(perms)
|
||||
perms.andand.each { |name| permissions.build name: name }
|
||||
end
|
||||
|
||||
@@ -92,11 +92,25 @@ class OrderCycle < ActiveRecord::Base
|
||||
with_distributor(distributor).soonest_closing.first
|
||||
end
|
||||
|
||||
|
||||
def self.most_recently_closed_for(distributor)
|
||||
with_distributor(distributor).most_recently_closed.first
|
||||
end
|
||||
|
||||
# Find the earliest closing times for each distributor in an active order cycle, and return
|
||||
# them in the format {distributor_id => closing_time, ...}
|
||||
def self.earliest_closing_times
|
||||
Hash[
|
||||
Exchange.
|
||||
outgoing.
|
||||
joins(:order_cycle).
|
||||
merge(OrderCycle.active).
|
||||
group('exchanges.receiver_id').
|
||||
select('exchanges.receiver_id AS receiver_id, MIN(order_cycles.orders_close_at) AS earliest_close_at').
|
||||
map { |ex| [ex.receiver_id, ex.earliest_close_at.to_time] }
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
def clone!
|
||||
oc = self.dup
|
||||
oc.name = "COPY OF #{oc.name}"
|
||||
|
||||
@@ -184,6 +184,8 @@ class AbilityDecorator
|
||||
|
||||
# Reports page
|
||||
can [:admin, :index, :customers, :group_buys, :bulk_coop, :sales_tax, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management], :report
|
||||
|
||||
can [:admin, :index, :update], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id)
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
module Spree
|
||||
Adjustment.class_eval do
|
||||
has_one :metadata, class_name: 'AdjustmentMetadata', dependent: :destroy
|
||||
# Deletion of metadata is handled in the database.
|
||||
# So we don't need the option `dependent: :destroy` as long as
|
||||
# AdjustmentMetadata has no destroy logic itself.
|
||||
has_one :metadata, class_name: 'AdjustmentMetadata'
|
||||
|
||||
scope :enterprise_fee, where(originator_type: 'EnterpriseFee')
|
||||
scope :included_tax, where(originator_type: 'Spree::TaxRate', adjustable_type: 'Spree::LineItem')
|
||||
scope :with_tax, where('spree_adjustments.included_tax > 0')
|
||||
scope :without_tax, where('spree_adjustments.included_tax = 0')
|
||||
|
||||
attr_accessible :included_tax
|
||||
|
||||
|
||||
@@ -24,6 +24,15 @@ Spree::LineItem.class_eval do
|
||||
where('spree_products.supplier_id IN (?)', enterprises)
|
||||
}
|
||||
|
||||
scope :with_tax, joins(:adjustments).
|
||||
where('spree_adjustments.originator_type = ?', 'Spree::TaxRate').
|
||||
select('DISTINCT spree_line_items.*')
|
||||
|
||||
# Line items without a Spree::TaxRate-originated adjustment
|
||||
scope :without_tax, joins("LEFT OUTER JOIN spree_adjustments ON (spree_adjustments.adjustable_id=spree_line_items.id AND spree_adjustments.adjustable_type = 'Spree::LineItem' AND spree_adjustments.originator_type='Spree::TaxRate')").
|
||||
where('spree_adjustments.id IS NULL')
|
||||
|
||||
|
||||
def price_with_adjustments
|
||||
# EnterpriseFee#create_locked_adjustment applies adjustments on line items to their parent order,
|
||||
# so line_item.adjustments returns an empty array
|
||||
|
||||
@@ -10,11 +10,14 @@ Spree::Order.class_eval do
|
||||
belongs_to :order_cycle
|
||||
belongs_to :distributor, :class_name => 'Enterprise'
|
||||
belongs_to :cart
|
||||
belongs_to :customer
|
||||
|
||||
validates :customer, presence: true, if: :require_customer?
|
||||
validate :products_available_from_new_distribution, :if => lambda { distributor_id_changed? || order_cycle_id_changed? }
|
||||
attr_accessible :order_cycle_id, :distributor_id
|
||||
|
||||
before_validation :shipping_address_from_distributor
|
||||
before_validation :associate_customer, unless: :customer_is_valid?
|
||||
|
||||
checkout_flow do
|
||||
go_to_state :address
|
||||
@@ -261,4 +264,24 @@ Spree::Order.class_eval do
|
||||
def product_distribution_for(line_item)
|
||||
line_item.variant.product.product_distribution_for self.distributor
|
||||
end
|
||||
|
||||
def require_customer?
|
||||
return true unless new_record? or state == 'cart'
|
||||
end
|
||||
|
||||
def customer_is_valid?
|
||||
return true unless require_customer?
|
||||
customer.present? && customer.enterprise_id == distributor_id && customer.email == (user.andand.email || email)
|
||||
end
|
||||
|
||||
def associate_customer
|
||||
email_for_customer = user.andand.email || email
|
||||
existing_customer = Customer.of(distributor).find_by_email(email_for_customer)
|
||||
if existing_customer
|
||||
self.customer = existing_customer
|
||||
else
|
||||
new_customer = Customer.create(enterprise: distributor, email: email_for_customer, user: user)
|
||||
self.customer = new_customer
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,6 +25,22 @@ Spree::ShippingMethod.class_eval do
|
||||
|
||||
scope :by_name, order('spree_shipping_methods.name ASC')
|
||||
|
||||
|
||||
# Return the services (pickup, delivery) that different distributors provide, in the format:
|
||||
# {distributor_id => {pickup: true, delivery: false}, ...}
|
||||
def self.services
|
||||
Hash[
|
||||
Spree::ShippingMethod.
|
||||
joins(:distributor_shipping_methods).
|
||||
group('distributor_id').
|
||||
select("distributor_id").
|
||||
select("BOOL_OR(spree_shipping_methods.require_ship_address = 'f') AS pickup").
|
||||
select("BOOL_OR(spree_shipping_methods.require_ship_address = 't') AS delivery").
|
||||
map { |sm| [sm.distributor_id.to_i, {pickup: sm.pickup == 't', delivery: sm.delivery == 't'}] }
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
def available_to_order_with_distributor_check?(order, display_on=nil)
|
||||
available_to_order_without_distributor_check?(order, display_on) &&
|
||||
self.distributors.include?(order.distributor)
|
||||
|
||||
@@ -9,4 +9,40 @@ Spree::Taxon.class_eval do
|
||||
#fs << Spree::ProductFilters.distributor_filter if Spree::ProductFilters.respond_to? :distributor_filter
|
||||
fs
|
||||
end
|
||||
|
||||
# Find all the taxons of supplied products for each enterprise, indexed by enterprise.
|
||||
# Format: {enterprise_id => [taxon_id, ...]}
|
||||
def self.supplied_taxons
|
||||
taxons = {}
|
||||
|
||||
Spree::Taxon.
|
||||
joins(:products => :supplier).
|
||||
select('spree_taxons.*, enterprises.id AS enterprise_id').
|
||||
each do |t|
|
||||
|
||||
taxons[t.enterprise_id.to_i] ||= Set.new
|
||||
taxons[t.enterprise_id.to_i] << t.id
|
||||
end
|
||||
|
||||
taxons
|
||||
end
|
||||
|
||||
# Find all the taxons of distributed products for each enterprise, indexed by enterprise.
|
||||
# Format: {enterprise_id => [taxon_id, ...]}
|
||||
def self.distributed_taxons
|
||||
taxons = {}
|
||||
|
||||
Spree::Taxon.
|
||||
joins(:products).
|
||||
merge(Spree::Product.with_order_cycles_outer).
|
||||
where('o_exchanges.incoming = ?', false).
|
||||
select('spree_taxons.*, o_exchanges.receiver_id AS enterprise_id').
|
||||
each do |t|
|
||||
|
||||
taxons[t.enterprise_id.to_i] ||= Set.new
|
||||
taxons[t.enterprise_id.to_i] << t.id
|
||||
end
|
||||
|
||||
taxons
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
Spree.user_class.class_eval do
|
||||
if method_defined? :send_reset_password_instructions_with_delay
|
||||
Bugsnag.notify RuntimeError.new "send_reset_password_instructions already handled asyncronously - double-calling results in infinite job loop"
|
||||
else
|
||||
handle_asynchronously :send_reset_password_instructions
|
||||
end
|
||||
# handle_asynchronously will define send_reset_password_instructions_with_delay.
|
||||
# If handle_asynchronously is called twice, we get an infinite job loop.
|
||||
handle_asynchronously :send_reset_password_instructions unless method_defined? :send_reset_password_instructions_with_delay
|
||||
|
||||
has_many :enterprise_roles, :dependent => :destroy
|
||||
has_many :enterprises, through: :enterprise_roles
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
/ insert_bottom "[data-hook='admin_footer_scripts']"
|
||||
|
||||
= render 'shared/analytics'
|
||||
11
app/serializers/api/admin/customer_serializer.rb
Normal file
11
app/serializers/api/admin/customer_serializer.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class Api::Admin::CustomerSerializer < ActiveModel::Serializer
|
||||
attributes :id, :email, :enterprise_id, :user_id, :code, :tags, :tag_list
|
||||
|
||||
def tag_list
|
||||
object.tag_list.join(",")
|
||||
end
|
||||
|
||||
def tags
|
||||
object.tag_list.map{ |t| { text: t } }
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,7 @@
|
||||
class Api::EnterpriseSerializer < ActiveModel::Serializer
|
||||
# We reference this here because otherwise the serializer complains about its absence
|
||||
Api::IdSerializer
|
||||
|
||||
def serializable_hash
|
||||
cached_serializer_hash.merge uncached_serializer_hash
|
||||
end
|
||||
@@ -6,11 +9,11 @@ class Api::EnterpriseSerializer < ActiveModel::Serializer
|
||||
private
|
||||
|
||||
def cached_serializer_hash
|
||||
Api::CachedEnterpriseSerializer.new(object, @options).serializable_hash
|
||||
Api::CachedEnterpriseSerializer.new(object, @options).serializable_hash || {}
|
||||
end
|
||||
|
||||
def uncached_serializer_hash
|
||||
Api::UncachedEnterpriseSerializer.new(object, @options).serializable_hash
|
||||
Api::UncachedEnterpriseSerializer.new(object, @options).serializable_hash || {}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,19 +21,22 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer
|
||||
attributes :orders_close_at, :active
|
||||
|
||||
def orders_close_at
|
||||
OrderCycle.first_closing_for(object).andand.orders_close_at
|
||||
options[:data].earliest_closing_times[object.id]
|
||||
end
|
||||
|
||||
def active
|
||||
@options[:active_distributors].andand.include? object
|
||||
options[:data].active_distributors.andand.include? object
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
class Api::CachedEnterpriseSerializer < ActiveModel::Serializer
|
||||
cached
|
||||
delegate :cache_key, to: :object
|
||||
#delegate :cache_key, to: :object
|
||||
|
||||
def cache_key
|
||||
object.andand.cache_key
|
||||
end
|
||||
|
||||
|
||||
attributes :name, :id, :description, :latitude, :longitude,
|
||||
:long_description, :website, :instagram, :linkedin, :twitter,
|
||||
@@ -38,17 +44,27 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer
|
||||
:email, :hash, :logo, :promo_image, :path, :pickup, :delivery,
|
||||
:icon, :icon_font, :producer_icon_font, :category, :producers, :hubs
|
||||
|
||||
has_many :distributed_taxons, key: :taxons, serializer: Api::IdSerializer
|
||||
has_many :supplied_taxons, serializer: Api::IdSerializer
|
||||
attributes :taxons, :supplied_taxons
|
||||
|
||||
has_one :address, serializer: Api::AddressSerializer
|
||||
|
||||
|
||||
def taxons
|
||||
ids_to_objs options[:data].distributed_taxons[object.id]
|
||||
end
|
||||
|
||||
def supplied_taxons
|
||||
ids_to_objs options[:data].supplied_taxons[object.id]
|
||||
end
|
||||
|
||||
def pickup
|
||||
object.shipping_methods.where(:require_ship_address => false).present?
|
||||
services = options[:data].shipping_method_services[object.id]
|
||||
services ? services[:pickup] : false
|
||||
end
|
||||
|
||||
def delivery
|
||||
object.shipping_methods.where(:require_ship_address => true).present?
|
||||
services = options[:data].shipping_method_services[object.id]
|
||||
services ? services[:delivery] : false
|
||||
end
|
||||
|
||||
def email
|
||||
@@ -72,11 +88,13 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer
|
||||
end
|
||||
|
||||
def producers
|
||||
ActiveModel::ArraySerializer.new(object.suppliers.activated, {each_serializer: Api::IdSerializer})
|
||||
relatives = options[:data].relatives[object.id]
|
||||
relatives ? ids_to_objs(relatives[:producers]) : []
|
||||
end
|
||||
|
||||
def hubs
|
||||
ActiveModel::ArraySerializer.new(object.distributors.activated, {each_serializer: Api::IdSerializer})
|
||||
relatives = options[:data].relatives[object.id]
|
||||
relatives ? ids_to_objs(relatives[:distributors]) : []
|
||||
end
|
||||
|
||||
# Map svg icons.
|
||||
@@ -116,4 +134,11 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer
|
||||
}
|
||||
icon_fonts[object.category]
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def ids_to_objs(ids)
|
||||
ids.andand.map { |id| {id: id} }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,8 +30,9 @@ class Api::CachedProductSerializer < ActiveModel::Serializer
|
||||
#cached
|
||||
#delegate :cache_key, to: :object
|
||||
|
||||
attributes :id, :name, :permalink, :count_on_hand, :on_demand, :group_buy,
|
||||
:notes, :description, :properties_with_values
|
||||
attributes :id, :name, :permalink, :count_on_hand
|
||||
attributes :on_demand, :group_buy, :notes, :description
|
||||
attributes :properties_with_values
|
||||
|
||||
has_many :variants, serializer: Api::VariantSerializer
|
||||
has_many :taxons, serializer: Api::IdSerializer
|
||||
@@ -46,13 +47,11 @@ class Api::CachedProductSerializer < ActiveModel::Serializer
|
||||
end
|
||||
|
||||
def variants
|
||||
# We use the in_stock? method here instead of the in_stock scope because we need to
|
||||
# look up the stock as overridden by VariantOverrides, and the scope method is not affected
|
||||
# by them.
|
||||
|
||||
object.variants.
|
||||
for_distribution(options[:current_order_cycle], options[:current_distributor]).
|
||||
each { |v| v.scope_to_hub options[:current_distributor] }.
|
||||
select(&:in_stock?)
|
||||
options[:variants][object.id] || []
|
||||
end
|
||||
|
||||
def master
|
||||
options[:master_variants][object.id].andand.first
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
72
app/views/admin/customers/index.html.haml
Normal file
72
app/views/admin/customers/index.html.haml
Normal file
@@ -0,0 +1,72 @@
|
||||
- content_for :page_title do
|
||||
%h1.page-title Customers
|
||||
|
||||
= admin_inject_shops
|
||||
|
||||
%div{ ng: { app: 'admin.customers', controller: 'customersCtrl' } }
|
||||
.row{ ng: { hide: "loaded() && filteredCustomers.length > 0" } }
|
||||
.five.columns.alpha
|
||||
%h3 Please select a Hub:
|
||||
.four.columns
|
||||
%select.select2.fullwidth#shop_id{ 'ng-model' => 'shop.id', name: 'shop_id', 'ng-options' => 'shop.id as shop.name for shop in shops' }
|
||||
.seven.columns.omega
|
||||
|
||||
.row{ 'ng-hide' => '!loaded() || filteredCustomers.length == 0' }
|
||||
.controls{ :class => "sixteen columns alpha", :style => "margin-bottom: 15px;" }
|
||||
.five.columns.alpha
|
||||
%input{ :class => "fullwidth", :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' }
|
||||
.five.columns
|
||||
-# %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "bulk_actions_dropdown", 'ofn-drop-down' => true }
|
||||
-# %span{ :class => 'icon-check' } Actions
|
||||
-# %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' => "action in bulkActions", 'ng-click' => "selectedBulkAction.callback(filteredCustomers)", 'ofn-close-on-click' => true }
|
||||
-# %span{ :class => 'three columns omega' } {{action.name }}
|
||||
.three.columns
|
||||
.three.columns.omega
|
||||
%div.ofn_drop_down{ '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 }}
|
||||
.row{ 'ng-if' => 'shop && !loaded()' }
|
||||
.sixteen.columns.alpha#loading
|
||||
%img.spinner{ src: "/assets/spinning-circles.svg" }
|
||||
%h1 LOADING CUSTOMERS
|
||||
.row{ :class => "sixteen columns alpha", 'ng-show' => 'loaded() && filteredCustomers.length == 0'}
|
||||
%h1#no_results No customers found.
|
||||
|
||||
|
||||
.row{ ng: { show: "loaded() && filteredCustomers.length > 0" } }
|
||||
%form{ name: "customers" }
|
||||
%table.index#customers
|
||||
%col.email{ width: "20%"}
|
||||
%col.code{ width: "20%"}
|
||||
%col.tags{ width: "50%"}
|
||||
%col.actions{ width: "10%"}
|
||||
%thead
|
||||
%tr{ ng: { controller: "ColumnsCtrl" } }
|
||||
-# %th.bulk
|
||||
-# %input{ :type => "checkbox", :name => 'toggle_bulk', 'ng-click' => 'toggleAllCheckboxes()', 'ng-checked' => "allBoxesChecked()" }
|
||||
%th.email{ 'ng-show' => 'columns.email.visible' }
|
||||
%a{ :href => '', 'ng-click' => "predicate = 'customer.email'; reverse = !reverse" } Email
|
||||
%th.code{ 'ng-show' => 'columns.code.visible' }
|
||||
%a{ :href => '', 'ng-click' => "predicate = 'customer.code'; reverse = !reverse" } Code
|
||||
%th.tags{ 'ng-show' => 'columns.tags.visible' } Tags
|
||||
%th.actions
|
||||
Ask?
|
||||
%input{ :type => 'checkbox', 'ng-model' => "confirmDelete" }
|
||||
%tr.customer{ 'ng-repeat' => "customer in filteredCustomers = ( customers | filter:quickSearch | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "c_{{customer.id}}" }
|
||||
-# %td.bulk
|
||||
-# %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'customer.checked' }
|
||||
%td.email{ 'ng-show' => 'columns.email.visible' } {{ customer.email }}
|
||||
%td.code{ 'ng-show' => 'columns.code.visible' }
|
||||
%input{ :type => 'text', :name => 'code', :id => 'code', 'ng-model' => 'customer.code', 'obj-for-update' => "customer", "attr-for-update" => "code" }
|
||||
%td.tags{ 'ng-show' => 'columns.tags.visible' }
|
||||
.tag_watcher{ 'obj-for-update' => "customer", "attr-for-update" => "tag_list"}
|
||||
%tags_with_translation{ object: 'customer' }
|
||||
%td.actions
|
||||
%a{ 'ng-click' => "deleteCustomer(customer)", :class => "delete-customer icon-trash no-text" }
|
||||
%input{ :type => "button", 'value' => 'Update', 'ng-click' => 'submitAll()' }
|
||||
@@ -1,20 +1,22 @@
|
||||
%h3
|
||||
= "Hi, #{@resource.contact}!"
|
||||
%p.lead
|
||||
= "Please confirm your email address for "
|
||||
%strong
|
||||
= "#{@resource.name}."
|
||||
= "A profile for #{@resource.name} has been successfully created!"
|
||||
To activate your Profile we need to confirm this email address.
|
||||
%p
|
||||
|
||||
%p.callout
|
||||
Click the link below to confirm your email and to activate your enterprise. This link can be used only once:
|
||||
Please click the link below to confirm your email and to continue setting up your profile.
|
||||
%br
|
||||
%strong
|
||||
= link_to 'Confirm this email address »', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token)
|
||||
|
||||
%p
|
||||
%p
|
||||
= "We're so excited that you're joining the #{ Spree::Config[:site_name] }! Don't hestitate to get in touch if you have any questions."
|
||||
After confirming your email you can access your administration account for this enterprise.
|
||||
See the
|
||||
= link_to 'User Guide', 'http://global.openfoodnetwork.org/platform/user-guide/'
|
||||
= "to find out more about #{ Spree::Config[:site_name] }'s features and to start using your profile or online store."
|
||||
|
||||
= render 'shared/mailers/signoff'
|
||||
|
||||
|
||||
@@ -1,67 +1,27 @@
|
||||
%h3
|
||||
= "Welcome, #{@enterprise.contact}!"
|
||||
%p.lead
|
||||
Congratulations,
|
||||
Thank you for confirming your email address.
|
||||
%strong
|
||||
%strong= @enterprise.name
|
||||
= @enterprise.name
|
||||
= "is now part of #{ Spree::Config.site_name }!"
|
||||
/ Heading Panel
|
||||
|
||||
%p
|
||||
Please find below all the details for viewing and editing your enterprise on
|
||||
%strong= "#{ Spree::Config.site_name }."
|
||||
We suggest keeping this email and information somewhere safe. Logging in with the account details below will allow complete access to your products and services.
|
||||
The User Guide with detailed support for setting up your Producer or Hub is here:
|
||||
= link_to 'Open Food Network User Guide', 'http://global.openfoodnetwork.org/platform/user-guide/'
|
||||
|
||||
-#%p
|
||||
|
||||
-# %p.callout
|
||||
-# %strong
|
||||
-# Your enterprise details
|
||||
-# %table{:width => "100%"}
|
||||
-# %tr
|
||||
-# %td{:align => "right"}
|
||||
-# %strong
|
||||
-# Shop URL
|
||||
-# %td
|
||||
-# %td
|
||||
-# %a{:href => "#{ main_app.enterprise_shop_url(@enterprise) }", :target => "_blank"}
|
||||
-# = main_app.enterprise_shop_url(@enterprise)
|
||||
-# %tr
|
||||
-# %td
|
||||
-# %tr
|
||||
-# %td{:align => "right"}
|
||||
-# %strong
|
||||
-# Email
|
||||
-# %td
|
||||
-# %td
|
||||
-# %a{:href => "mailto:#{ @enterprise.email }", :target => "_blank"}
|
||||
-# = @enterprise.email
|
||||
|
||||
%p
|
||||
%p
|
||||
Log into
|
||||
%strong= "#{ Spree::Config.site_name } Admin"
|
||||
in order to edit your enterprise details such as website and social media links, or to start adding products to your enterprise!
|
||||
You can manage your account by logging into the
|
||||
= link_to 'Admin Panel', spree.admin_url
|
||||
or by clicking on the cog in the top right hand side of the homepage, and selecting Administration.
|
||||
|
||||
%p.callout
|
||||
%strong
|
||||
OFN Admin
|
||||
%table{ :width => "100%"}
|
||||
%tr
|
||||
%td{:align => "right"}
|
||||
%strong
|
||||
Admin
|
||||
%td
|
||||
%td
|
||||
%a{:href => "#{ spree.admin_url }", :target => "_blank"}
|
||||
= spree.admin_url
|
||||
|
||||
%p
|
||||
/ /Heading Panel
|
||||
%p
|
||||
We're so pleased to have you as a valued member of
|
||||
%strong= "#{Spree::Config.site_name}!"
|
||||
Don't hestitate to get in touch if you have any questions.
|
||||
We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next.
|
||||
= link_to 'Join the community.', 'http://community.openfoodnetwork.org/'
|
||||
|
||||
%p
|
||||
If you have any difficulties, check out our FAQs, browse the forum or post a 'Support' topic and someone will help you out!
|
||||
|
||||
= render 'shared/mailers/signoff'
|
||||
|
||||
= render 'shared/mailers/social_and_contact'
|
||||
= render 'shared/mailers/social_and_contact'
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
%shop.darkswarm
|
||||
- content_for :order_cycle_form do
|
||||
|
||||
%div{"ng-controller" => "OrderCycleChangeCtrl"}
|
||||
%div{"ng-controller" => "OrderCycleChangeCtrl", "ng-cloak" => true}
|
||||
%closing{"ng-if" => "OrderCycle.selected()"}
|
||||
Next order closing
|
||||
%strong {{ OrderCycle.orders_close_at() | date_in_words }}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
= inject_enterprises
|
||||
|
||||
-# inject enterprises in this group
|
||||
-# further hubs and producers of these enterprises can't be resoleved within this small subset
|
||||
= inject_json_ams "group_enterprises", @group.enterprises, Api::EnterpriseSerializer, active_distributors: @active_distributors
|
||||
-# further hubs and producers of these enterprises can't be resolved within this small subset
|
||||
= inject_group_enterprises
|
||||
|
||||
#group-page.row.pad-top{"ng-controller" => "GroupPageCtrl"}
|
||||
.small-12.columns.pad-top
|
||||
@@ -95,7 +95,7 @@
|
||||
= render partial: 'home/fat'
|
||||
|
||||
= render partial: 'shared/components/enterprise_no_results'
|
||||
|
||||
|
||||
.small-12.medium-12.large-3.columns
|
||||
= render partial: 'contact'
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
object current_distributor
|
||||
extends 'json/partials/enterprise'
|
||||
|
||||
child suppliers: :producers do
|
||||
attributes :id
|
||||
end
|
||||
@@ -24,7 +24,7 @@
|
||||
= render partial: "shared/ie_warning"
|
||||
= javascript_include_tag "iehack"
|
||||
|
||||
= inject_json "currentHub", "current_hub"
|
||||
= inject_current_hub
|
||||
= inject_json "user", "current_user"
|
||||
= inject_json "railsFlash", "flash"
|
||||
= inject_taxons
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
= inject_enterprises
|
||||
.producers.pad-top{"ng-controller" => "EnterprisesCtrl"}
|
||||
= inject_enterprises
|
||||
|
||||
.producers.pad-top{"ng-controller" => "EnterprisesCtrl", "ng-cloak" => true}
|
||||
.row
|
||||
.small-12.columns.pad-top
|
||||
%h1 Find local producers
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
:javascript
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
- if Rails.env.production?
|
||||
:javascript
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-62912229-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
ga('create', 'UA-62912229-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
%section.left
|
||||
%a.left-off-canvas-toggle.menu-icon
|
||||
%span
|
||||
%section.right
|
||||
%section.right{"ng-cloak" => true}
|
||||
.cart
|
||||
= render partial: "shared/menu/cart"
|
||||
%a{href: main_app.shop_path}
|
||||
@@ -11,34 +11,34 @@
|
||||
%aside.left-off-canvas-menu.show-for-medium-down
|
||||
%ul.off-canvas-list
|
||||
%li.ofn-logo
|
||||
%a{href: root_path}
|
||||
%a{href: root_path}
|
||||
%img{src: "/assets/open-food-network-beta.png", srcset: "/assets/open-food-network-beta.svg", width: "110", height: "26"}
|
||||
|
||||
|
||||
- if current_page? root_path
|
||||
%li.li-menu
|
||||
%a{"ofn-scroll-to" => "hubs"}
|
||||
%span.nav-primary
|
||||
%span.nav-primary
|
||||
%i.ofn-i_040-hub
|
||||
Hubs
|
||||
- else
|
||||
%li.li-menu
|
||||
%a{href: root_path + "#/#hubs"}
|
||||
%span.nav-primary
|
||||
%span.nav-primary
|
||||
%i.ofn-i_040-hub
|
||||
Hubs
|
||||
%li.li-menu
|
||||
%a{href: main_app.map_path}
|
||||
%span.nav-primary
|
||||
%span.nav-primary
|
||||
%i.ofn-i_037-map
|
||||
Map
|
||||
%li.li-menu
|
||||
%a{href: main_app.producers_path}
|
||||
%span.nav-primary
|
||||
%span.nav-primary
|
||||
%i.ofn-i_036-producers
|
||||
Producers
|
||||
%li.li-menu
|
||||
%a{href: main_app.groups_path}
|
||||
%span.nav-primary
|
||||
%span.nav-primary
|
||||
%i.ofn-i_035-groups
|
||||
Groups
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
%products.small-12.columns{"ng-controller" => "ProductsCtrl", "ng-show" => "order_cycle.order_cycle_id != null",
|
||||
%products.small-12.columns{"ng-controller" => "ProductsCtrl", "ng-show" => "order_cycle.order_cycle_id != null", "ng-cloak" => true,
|
||||
"infinite-scroll" => "incrementLimit()", "infinite-scroll-distance" => "1"}
|
||||
|
||||
// TODO: Needs an ng-show to slide content down
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#tabs{"ng-controller" => "TabsCtrl"}
|
||||
#tabs{"ng-controller" => "TabsCtrl", "ng-cloak" => true}
|
||||
.row
|
||||
%tabset
|
||||
-# Build all tabs.
|
||||
- for name, heading_cols in { about: ["About #{current_distributor.name}", 6],
|
||||
producers: ["Producers",2],
|
||||
- for name, heading_cols in { about: ["About #{current_distributor.name}", 6],
|
||||
producers: ["Producers",2],
|
||||
contact: ["Contact",2],
|
||||
groups: ["Groups",2]}
|
||||
groups: ["Groups",2]}
|
||||
-# tabs take tab path in 'active' and 'select' functions defined in TabsCtrl.
|
||||
- heading, cols = heading_cols
|
||||
%tab.columns{heading: heading,
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
%form{ 'ng-model' => "bulk_order_form" }
|
||||
%table.index#listing_orders.bulk{ :class => "sixteen columns alpha" }
|
||||
%thead
|
||||
%tr
|
||||
%tr{ ng: { controller: "ColumnsCtrl" } }
|
||||
%th.bulk
|
||||
%input{ :type => "checkbox", :name => 'toggle_bulk', 'ng-click' => 'toggleAllCheckboxes()', 'ng-checked' => "allBoxesChecked()" }
|
||||
%th.order_no{ 'ng-show' => 'columns.order_no.visible' }
|
||||
@@ -132,28 +132,28 @@
|
||||
%th.actions
|
||||
Ask?
|
||||
%input{ :type => 'checkbox', 'ng-model' => "confirmDelete" }
|
||||
%tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" }
|
||||
%td.bulk
|
||||
%input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked' }
|
||||
%td.order_no{ 'ng-show' => 'columns.order_no.visible' } {{ line_item.order.number }}
|
||||
%td.full_name{ 'ng-show' => 'columns.full_name.visible' } {{ line_item.order.full_name }}
|
||||
%td.email{ 'ng-show' => 'columns.email.visible' } {{ line_item.order.email }}
|
||||
%td.phone{ 'ng-show' => 'columns.phone.visible' } {{ line_item.order.phone }}
|
||||
%td.date{ 'ng-show' => 'columns.order_date.visible' } {{ line_item.order.completed_at }}
|
||||
%td.producer{ 'ng-show' => 'columns.producer.visible' } {{ line_item.supplier.name }}
|
||||
%td.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } {{ line_item.order.order_cycle.name }}
|
||||
%td.hub{ 'ng-show' => 'columns.hub.visible' } {{ line_item.order.distributor.name }}
|
||||
%td.variant{ 'ng-show' => 'columns.variant.visible' }
|
||||
%a{ :href => '#', 'ng-click' => "setSelectedUnitsVariant(line_item.units_product,line_item.units_variant)" } {{ line_item.units_variant.full_name }}
|
||||
%td.quantity{ 'ng-show' => 'columns.quantity.visible' }
|
||||
%input{ :type => 'number', :name => 'quantity', 'ng-model' => "line_item.quantity", 'ofn-line-item-upd-attr' => "quantity" }
|
||||
%td.max{ 'ng-show' => 'columns.max.visible' } {{ line_item.max_quantity }}
|
||||
%td.unit_value{ 'ng-show' => 'columns.unit_value.visible' }
|
||||
%input{ :type => 'number', :name => 'unit_value', :id => 'unit_value', 'ng-model' => "line_item.unit_value", 'ng-readonly' => "unitValueLessThanZero(line_item)", 'ng-change' => "weightAdjustedPrice(line_item, {{ line_item.unit_value }})", 'ofn-line-item-upd-attr' => "unit_value" }
|
||||
%td.price{ 'ng-show' => 'columns.price.visible' }
|
||||
%input{ :type => 'text', :name => 'price', :id => 'price', :value => '{{ line_item.price | currency }}', 'ng-model' => "line_item.price", 'ng-readonly' => "true", 'ofn-line-item-upd-attr' => "price" }
|
||||
%td.actions
|
||||
%a{ :class => "edit-order icon-edit no-text", 'ofn-confirm-link-path' => "/admin/orders/{{line_item.order.number}}/edit" }
|
||||
%td.actions
|
||||
%a{ 'ng-click' => "deleteLineItem(line_item)", :class => "delete-line-item icon-trash no-text" }
|
||||
%tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" }
|
||||
%td.bulk
|
||||
%input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked' }
|
||||
%td.order_no{ 'ng-show' => 'columns.order_no.visible' } {{ line_item.order.number }}
|
||||
%td.full_name{ 'ng-show' => 'columns.full_name.visible' } {{ line_item.order.full_name }}
|
||||
%td.email{ 'ng-show' => 'columns.email.visible' } {{ line_item.order.email }}
|
||||
%td.phone{ 'ng-show' => 'columns.phone.visible' } {{ line_item.order.phone }}
|
||||
%td.date{ 'ng-show' => 'columns.order_date.visible' } {{ line_item.order.completed_at }}
|
||||
%td.producer{ 'ng-show' => 'columns.producer.visible' } {{ line_item.supplier.name }}
|
||||
%td.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } {{ line_item.order.order_cycle.name }}
|
||||
%td.hub{ 'ng-show' => 'columns.hub.visible' } {{ line_item.order.distributor.name }}
|
||||
%td.variant{ 'ng-show' => 'columns.variant.visible' }
|
||||
%a{ :href => '#', 'ng-click' => "setSelectedUnitsVariant(line_item.units_product,line_item.units_variant)" } {{ line_item.units_variant.full_name }}
|
||||
%td.quantity{ 'ng-show' => 'columns.quantity.visible' }
|
||||
%input{ :type => 'number', :name => 'quantity', 'ng-model' => "line_item.quantity", 'obj-for-update' => "line_item", "attr-for-update" => "quantity" }
|
||||
%td.max{ 'ng-show' => 'columns.max.visible' } {{ line_item.max_quantity }}
|
||||
%td.unit_value{ 'ng-show' => 'columns.unit_value.visible' }
|
||||
%input{ :type => 'number', :name => 'unit_value', :id => 'unit_value', 'ng-model' => "line_item.unit_value", 'ng-readonly' => "unitValueLessThanZero(line_item)", 'ng-change' => "weightAdjustedPrice(line_item, {{ line_item.unit_value }})", 'obj-for-update' => "line_item", "attr-for-update" => "unit_value" }
|
||||
%td.price{ 'ng-show' => 'columns.price.visible' }
|
||||
%input{ :type => 'text', :name => 'price', :id => 'price', :value => '{{ line_item.price | currency }}', 'ng-readonly' => "true", 'obj-for-update' => "line_item", "attr-for-update" => "price" }
|
||||
%td.actions
|
||||
%a{ :class => "edit-order icon-edit no-text", 'ofn-confirm-link-path' => "/admin/orders/{{line_item.order.number}}/edit" }
|
||||
%td.actions
|
||||
%a{ 'ng-click' => "deleteLineItem(line_item)", :class => "delete-line-item icon-trash no-text" }
|
||||
%input{ :type => "button", 'value' => 'Update', 'ng-click' => 'pendingChanges.submitAll()' }
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
%col.actions
|
||||
|
||||
%thead
|
||||
%tr
|
||||
%tr{ ng: { controller: "ColumnsCtrl" } }
|
||||
%th.left-actions
|
||||
%a{ 'ng-click' => 'toggleShowAllVariants()', :style => 'color: red' }
|
||||
Expand All
|
||||
%th.producer{ 'ng-show' => 'columns.producer.visible' } Producer
|
||||
%th.sku{ 'ng-show' => 'columns.sku.visible' } SKU
|
||||
%th.name{ 'ng-show' => 'columns.name.visible' } Name
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%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{ 'ofn-toggle-variants' => 'true', :class => "view-variants", 'ng-show' => 'hasVariants(product)' }
|
||||
%a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "!hasVariants(product) && hasUnit(product)" }
|
||||
%td.producer{ 'ng-show' => 'columns.producer.visible' }
|
||||
%select.select2.fullwidth{ 'ng-model' => 'product.producer_id', :name => 'producer_id', 'ofn-track-product' => 'producer_id', 'ng-options' => 'producer.id as producer.name for producer in producers' }
|
||||
|
||||
44
app/views/spree/admin/reports/xero_invoices.html.haml
Normal file
44
app/views/spree/admin/reports/xero_invoices.html.haml
Normal file
@@ -0,0 +1,44 @@
|
||||
= form_for @search, url: spree.xero_invoices_admin_reports_path do |f|
|
||||
= render 'date_range_form', f: f
|
||||
|
||||
.row
|
||||
.four.columns.alpha= label_tag nil, "Hub: "
|
||||
.four.columns.omega= f.collection_select(:distributor_id_eq, @distributors, :id, :name, {:include_blank => 'All'}, {:class => "select2 fullwidth"})
|
||||
.row
|
||||
.four.columns.alpha= label_tag nil, "Order Cycle: "
|
||||
.four.columns.omega= f.select(:order_cycle_id_eq,
|
||||
options_for_select(report_order_cycle_options(@order_cycles), params[:q][:order_cycle_id_eq]),
|
||||
{:include_blank => true}, {:class => "select2 fullwidth"})
|
||||
|
||||
.row
|
||||
.four.columns.alpha= label_tag :initial_invoice_number, "Initial invoice number:"
|
||||
.twelve.columns.omega= text_field_tag :initial_invoice_number, params[:initial_invoice_number]
|
||||
.row
|
||||
.four.columns.alpha= label_tag :invoice_date, "Invoice date:"
|
||||
.twelve.columns.omega= text_field_tag :invoice_date, params[:invoice_date], class: 'datetimepicker'
|
||||
.row
|
||||
.four.columns.alpha= label_tag :due_date, "Due date:"
|
||||
.twelve.columns.omega= text_field_tag :due_date, params[:due_date], class: 'datetimepicker'
|
||||
.row
|
||||
.four.columns.alpha= label_tag :account_code, "Account code:"
|
||||
.twelve.columns.omega= text_field_tag :account_code, params[:account_code]
|
||||
.row
|
||||
.four.columns.alpha= label_tag :csv, "Download as CSV:"
|
||||
.twelve.columns.omega= check_box_tag :csv
|
||||
.row
|
||||
.four.columns.alpha= button t(:search)
|
||||
|
||||
|
||||
%table#listing_invoices.index
|
||||
%thead
|
||||
%tr
|
||||
- @report.header.each do |header|
|
||||
%th= header
|
||||
%tbody
|
||||
- @report.table.each do |row|
|
||||
%tr
|
||||
- row.each do |column|
|
||||
%td= column
|
||||
- if @report.table.empty?
|
||||
%tr
|
||||
%td{:colspan => "2"}= t(:none)
|
||||
@@ -9,7 +9,7 @@
|
||||
- else
|
||||
= @order.distributor.next_collection_at
|
||||
|
||||
= render "shopping_shared/details"
|
||||
= render "shopping_shared/details" if current_distributor.present?
|
||||
|
||||
%fieldset#order_summary{"data-hook" => ""}
|
||||
.row
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
%hr/
|
||||
%p
|
||||
%p.lead
|
||||
Thanks for joining the network. We look forward to introducing you to many fantastic farmers, wonderful food hubs and delicious food!
|
||||
Thanks for joining the network.
|
||||
If you are a customer, we look forward to introducing you to many fantastic farmers, wonderful food hubs and delicious food!
|
||||
If you are a producer or food enterprise, we are excited to have you as a part of the network.
|
||||
%p
|
||||
We welcome all your questions and feedback; you can use the
|
||||
%em
|
||||
|
||||
@@ -82,7 +82,7 @@ module Openfoodnetwork
|
||||
config.assets.enabled = true
|
||||
|
||||
# Version of your assets, change this if you want to expire all your assets
|
||||
config.assets.version = '1.0'
|
||||
config.assets.version = '1.1'
|
||||
|
||||
config.sass.load_paths += [
|
||||
"#{Gem.loaded_specs['foundation-rails'].full_gem_path}/vendor/assets/stylesheets/foundation/components",
|
||||
|
||||
@@ -10,7 +10,7 @@ development:
|
||||
test:
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
database: open_food_network_test
|
||||
database: open_food_network_test<%= ENV['TEST_ENV_NUMBER'] %>
|
||||
pool: 5
|
||||
host: localhost
|
||||
username: ofn
|
||||
|
||||
@@ -31,7 +31,7 @@ Openfoodnetwork::Application.configure do
|
||||
|
||||
# Show emails using Letter Opener
|
||||
config.action_mailer.delivery_method = :letter_opener
|
||||
config.action_mailer.default_url_options = { host: "test.com" }
|
||||
config.action_mailer.default_url_options = { host: "0.0.0.0:3000" }
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@ Delayed::Worker.logger = Logger.new(Rails.root.join('log', 'delayed_job.log'))
|
||||
Delayed::Worker.destroy_failed_jobs = false
|
||||
Delayed::Worker.max_run_time = 15.minutes
|
||||
|
||||
# Uncomment the next line if you want jobs to be executed straight away.
|
||||
# For example you want emails to be opened in your browser while testing.
|
||||
#Delayed::Worker.delay_jobs = false
|
||||
|
||||
# Notify bugsnag when a job fails
|
||||
# Code adapted from http://trevorturk.com/2011/01/25/notify-hoptoad-if-theres-an-exception-in-delayedjob/
|
||||
class Delayed::Worker
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
Openfoodnetwork::Application.config.session_store :cookie_store, key: '_openfoodnetwork_session'
|
||||
# The cookie_store can be too small for very long URLs stored by Devise.
|
||||
# The maximum size of cookies is 4096 bytes.
|
||||
#Openfoodnetwork::Application.config.session_store :cookie_store, key: '_openfoodnetwork_session'
|
||||
|
||||
# Use the database for sessions instead of the cookie-based default,
|
||||
# which shouldn't be used to store highly confidential information
|
||||
# (create the session table with "rails generate session_migration")
|
||||
# Openfoodnetwork::Application.config.session_store :active_record_store
|
||||
Openfoodnetwork::Application.config.session_store :active_record_store
|
||||
|
||||
@@ -83,6 +83,8 @@ Openfoodnetwork::Application.routes.draw do
|
||||
resources :variant_overrides do
|
||||
post :bulk_update, on: :collection
|
||||
end
|
||||
|
||||
resources :customers, only: [:index, :update]
|
||||
end
|
||||
|
||||
namespace :api do
|
||||
@@ -138,6 +140,7 @@ Spree::Core::Engine.routes.prepend do
|
||||
match '/admin/orders/bulk_management' => 'admin/orders#bulk_management', :as => "admin_bulk_order_management"
|
||||
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/reports/xero_invoices' => 'admin/reports#xero_invoices', :as => "xero_invoices_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
|
||||
|
||||
|
||||
0
db/migrate/20120327000593_add_addresses_checkouts_indexes.rb
Executable file → Normal file
0
db/migrate/20120327000593_add_addresses_checkouts_indexes.rb
Executable file → Normal file
@@ -0,0 +1,31 @@
|
||||
# This migration comes from acts_as_taggable_on_engine (originally 1)
|
||||
class ActsAsTaggableOnMigration < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :tags do |t|
|
||||
t.string :name
|
||||
end
|
||||
|
||||
create_table :taggings do |t|
|
||||
t.references :tag
|
||||
|
||||
# You should make sure that the column created is
|
||||
# long enough to store the required class names.
|
||||
t.references :taggable, polymorphic: true
|
||||
t.references :tagger, polymorphic: true
|
||||
|
||||
# Limit is created to prevent MySQL error on index
|
||||
# length for MyISAM table type: http://bit.ly/vgW2Ql
|
||||
t.string :context, limit: 128
|
||||
|
||||
t.datetime :created_at
|
||||
end
|
||||
|
||||
add_index :taggings, :tag_id
|
||||
add_index :taggings, [:taggable_id, :taggable_type, :context]
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :taggings
|
||||
drop_table :tags
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,20 @@
|
||||
# This migration comes from acts_as_taggable_on_engine (originally 2)
|
||||
class AddMissingUniqueIndices < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_index :tags, :name, unique: true
|
||||
|
||||
remove_index :taggings, :tag_id
|
||||
remove_index :taggings, [:taggable_id, :taggable_type, :context]
|
||||
add_index :taggings,
|
||||
[:tag_id, :taggable_id, :taggable_type, :context, :tagger_id, :tagger_type],
|
||||
unique: true, name: 'taggings_idx'
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_index :tags, :name
|
||||
|
||||
remove_index :taggings, name: 'taggings_idx'
|
||||
add_index :taggings, :tag_id
|
||||
add_index :taggings, [:taggable_id, :taggable_type, :context]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
# This migration comes from acts_as_taggable_on_engine (originally 3)
|
||||
class AddTaggingsCounterCacheToTags < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column :tags, :taggings_count, :integer, default: 0
|
||||
|
||||
ActsAsTaggableOn::Tag.reset_column_information
|
||||
ActsAsTaggableOn::Tag.find_each do |tag|
|
||||
ActsAsTaggableOn::Tag.reset_counters(tag.id, :taggings)
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :tags, :taggings_count
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
# This migration comes from acts_as_taggable_on_engine (originally 4)
|
||||
class AddMissingTaggableIndex < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_index :taggings, [:taggable_id, :taggable_type, :context]
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_index :taggings, [:taggable_id, :taggable_type, :context]
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user