Merge branch 'master' into folklabs-producer-emails

Conflicts:
	Gemfile
This commit is contained in:
Rohan Mitchell
2015-06-11 17:02:34 +10:00
157 changed files with 2197 additions and 660 deletions

4
.rspec_parallel Normal file
View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
angular.module("admin.customers", ['ngResource', 'ngTagsInput', 'admin.indexUtils', 'admin.dropdown'])

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
angular.module("admin.dropdown").controller "DropDownCtrl", ($scope) ->
$scope.expanded = false

View File

@@ -0,0 +1,5 @@
angular.module("admin.dropdown").directive "ofnCloseOnClick", ($document) ->
link: (scope, element, attrs) ->
element.click (event) ->
event.stopPropagation()
scope.$emit "offClick"

View File

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

View File

@@ -0,0 +1 @@
angular.module("admin.dropdown", [])

View File

@@ -0,0 +1,4 @@
angular.module("admin.indexUtils").controller "ColumnsCtrl", ($scope, Columns) ->
$scope.columns = Columns.columns
$scope.predicate = ""
$scope.reverse = false

View File

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

View File

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

View File

@@ -0,0 +1 @@
angular.module("admin.indexUtils", ['ngResource']).config ($httpProvider) ->

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -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()" } } &times;
.small-12.large-8.columns

View File

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

View File

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

View File

@@ -38,9 +38,13 @@
%i.ofn-i_013-help
&nbsp;
%p Producers make yummy things to eat &amp;/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
&nbsp;
%p If youre not a producer, youre 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" }

View File

@@ -10,6 +10,7 @@
*= require shared/jquery-ui-timepicker-addon
*= require shared/textAngular.min
*= require shared/ng-tags-input.min
*= require_self
*= require_tree .

View File

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

View 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}

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
class GroupsController < BaseController
layout 'darkswarm'
before_filter :load_active_distributors
def index
@groups = EnterpriseGroup.on_front_page.by_position

View File

@@ -1,11 +1,9 @@
class HomeController < BaseController
layout 'darkswarm'
before_filter :load_active_distributors
def index
end
def about_us
end
end

View File

@@ -1,6 +1,5 @@
class MapController < BaseController
layout 'darkswarm'
before_filter :load_active_distributors
def index
end

View File

@@ -1,7 +1,6 @@
class ProducersController < BaseController
layout 'darkswarm'
before_filter :load_active_distributors
def index
end
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
/ insert_bottom "[data-hook='admin_footer_scripts']"
= render 'shared/analytics'

View 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

View File

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

View File

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

View 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 &nbsp;
.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 &nbsp;
-# %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "bulk_actions_dropdown", 'ofn-drop-down' => true }
-# %span{ :class => 'icon-check' } &nbsp; 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 &nbsp;
.three.columns.omega
%div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "columns_dropdown", 'ofn-drop-down' => true, :style => 'float:right;' }
%span{ :class => 'icon-reorder' } &nbsp; 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 && "&#10003;" || !column.visible && "&nbsp;" }}
%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?&nbsp;
%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()' }

View File

@@ -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 &nbsp;
%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 &nbsp;
%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'

View File

@@ -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 &nbsp;
-# %p.callout
-# %strong
-# Your enterprise details
-# %table{:width => "100%"}
-# %tr
-# %td{:align => "right"}
-# %strong
-# Shop URL
-# %td &nbsp;
-# %td
-# %a{:href => "#{ main_app.enterprise_shop_url(@enterprise) }", :target => "_blank"}
-# = main_app.enterprise_shop_url(@enterprise)
-# %tr
-# %td &nbsp;
-# %tr
-# %td{:align => "right"}
-# %strong
-# Email
-# %td &nbsp;
-# %td
-# %a{:href => "mailto:#{ @enterprise.email }", :target => "_blank"}
-# = @enterprise.email
%p &nbsp;
%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 &nbsp;
%td
%a{:href => "#{ spree.admin_url }", :target => "_blank"}
= spree.admin_url
%p &nbsp;
/ /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'

View File

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

View File

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

View File

@@ -1,6 +0,0 @@
object current_distributor
extends 'json/partials/enterprise'
child suppliers: :producers do
attributes :id
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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?&nbsp;
%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()' }

View File

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

View File

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

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

View File

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

View File

@@ -21,7 +21,9 @@
%hr/
%p &nbsp;
%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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

View 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

View File

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

View File

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

View File

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