mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-13 18:46:49 +00:00
Compare commits
216 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51064f31a3 | ||
|
|
e25805aa78 | ||
|
|
1e55e8a907 | ||
|
|
47d239ee3a | ||
|
|
2e2e767564 | ||
|
|
f0709d5e46 | ||
|
|
e8ce9feb52 | ||
|
|
a3074fa51e | ||
|
|
c1cbf9a35c | ||
|
|
8dffb772dc | ||
|
|
60b2596774 | ||
|
|
6a2af09006 | ||
|
|
d9b1215a01 | ||
|
|
0895bd8647 | ||
|
|
2e3ff8f5c6 | ||
|
|
93f6667c4b | ||
|
|
23355d1a25 | ||
|
|
0fdd000589 | ||
|
|
57caeb64c5 | ||
|
|
d3e66bcfa5 | ||
|
|
1a96ef88bc | ||
|
|
d682a29a65 | ||
|
|
ced254919a | ||
|
|
64834dd50a | ||
|
|
5d34b711e9 | ||
|
|
4f7f2e2035 | ||
|
|
d595e1558a | ||
|
|
5ccae9cfab | ||
|
|
adc20e9e4e | ||
|
|
03627e2ef2 | ||
|
|
3e5028b6b9 | ||
|
|
e843beaf18 | ||
|
|
170799f65f | ||
|
|
9e1788a6b0 | ||
|
|
719ddea789 | ||
|
|
3eea002a0c | ||
|
|
6f4dc6943e | ||
|
|
e75b595b97 | ||
|
|
af04c61528 | ||
|
|
14900655df | ||
|
|
be66769999 | ||
|
|
8639109372 | ||
|
|
eca25a2564 | ||
|
|
012b0517a5 | ||
|
|
faa1d0d1c5 | ||
|
|
b97bbae00e | ||
|
|
679531fe2a | ||
|
|
eb6dcba396 | ||
|
|
9c9051498b | ||
|
|
5688cfd1dd | ||
|
|
fc409c97bf | ||
|
|
09b4b3e659 | ||
|
|
bf2bd403a4 | ||
|
|
012a210782 | ||
|
|
7caebb11e2 | ||
|
|
ee65452de3 | ||
|
|
3a9b13b55e | ||
|
|
734b2fc766 | ||
|
|
5559231245 | ||
|
|
3345f54e0a | ||
|
|
ec908fce92 | ||
|
|
d8f4061030 | ||
|
|
759c53ec34 | ||
|
|
8d041f5e7a | ||
|
|
32d3d0f912 | ||
|
|
917c17af59 | ||
|
|
21a9681205 | ||
|
|
54fd298e3a | ||
|
|
9ffc97f8b6 | ||
|
|
211171aed4 | ||
|
|
f43eacb75d | ||
|
|
fffc35d9ed | ||
|
|
17fb4a7247 | ||
|
|
3abce54115 | ||
|
|
28de125b59 | ||
|
|
4d71a56e38 | ||
|
|
ae968cd0eb | ||
|
|
1a89c3caf4 | ||
|
|
bdd792a3ea | ||
|
|
f5c2386296 | ||
|
|
8665b35f1d | ||
|
|
d68cfbff18 | ||
|
|
e8f7ab5425 | ||
|
|
b0dd765181 | ||
|
|
8ee467d2b9 | ||
|
|
5874ecbbef | ||
|
|
dcae584673 | ||
|
|
a90666467a | ||
|
|
cc9d0defca | ||
|
|
142e1d6d9a | ||
|
|
1d39fb4438 | ||
|
|
07eb857a8d | ||
|
|
66f64fc413 | ||
|
|
c7a1ca29f4 | ||
|
|
0a5e8fe629 | ||
|
|
61a39ea82f | ||
|
|
eabf792238 | ||
|
|
c15e281286 | ||
|
|
5f0766cb75 | ||
|
|
3a0c5bf0c3 | ||
|
|
61435b2dea | ||
|
|
b6892c82f1 | ||
|
|
5fd3dc1d28 | ||
|
|
bb3a4170f5 | ||
|
|
74f90dada3 | ||
|
|
e4a4cdd915 | ||
|
|
66b4eb4c5d | ||
|
|
1d67e3b2b8 | ||
|
|
0e67a116b6 | ||
|
|
1234b35199 | ||
|
|
1b17a7fb35 | ||
|
|
4887871474 | ||
|
|
372b17703a | ||
|
|
5de9a5eb54 | ||
|
|
1f72e4001c | ||
|
|
bd83dde89d | ||
|
|
a490d9696b | ||
|
|
d353906bb8 | ||
|
|
858a613ba2 | ||
|
|
2ef0196200 | ||
|
|
f3ae812f2b | ||
|
|
904a3a5bd4 | ||
|
|
de7f3a9e5c | ||
|
|
7424e93133 | ||
|
|
f21f57a42c | ||
|
|
dc95167f15 | ||
|
|
c168dec14b | ||
|
|
28b8e0b0c8 | ||
|
|
0ab75fe2ea | ||
|
|
669c9911fe | ||
|
|
6e6d2566d9 | ||
|
|
6816df5f72 | ||
|
|
5b93ac2ae1 | ||
|
|
dcf98ee29f | ||
|
|
7d340d5084 | ||
|
|
0eb6d9aaed | ||
|
|
d8dfb5b5ee | ||
|
|
07e6a204f3 | ||
|
|
06c5ffb427 | ||
|
|
673635fdcb | ||
|
|
64bc7404dc | ||
|
|
651afc34cb | ||
|
|
e5c42c0e54 | ||
|
|
5f8826533d | ||
|
|
f154a02c86 | ||
|
|
46792a4111 | ||
|
|
5b5c56064e | ||
|
|
794c9558bb | ||
|
|
eba0a12d29 | ||
|
|
7fd4815904 | ||
|
|
97c9504344 | ||
|
|
7f2508eeaa | ||
|
|
6e3ca3f90f | ||
|
|
744beaa26a | ||
|
|
d0d9e9e367 | ||
|
|
befcc37456 | ||
|
|
9fb7c47c73 | ||
|
|
80bb6c36e3 | ||
|
|
a61e96c316 | ||
|
|
ddf1bb90ea | ||
|
|
a509747ba7 | ||
|
|
3527ae6ea2 | ||
|
|
e679f1175c | ||
|
|
197c99349d | ||
|
|
73b90dba10 | ||
|
|
4cc8eb90fc | ||
|
|
8deb4ef9d4 | ||
|
|
d0b7a0795d | ||
|
|
d67b34c2bd | ||
|
|
3f8235593a | ||
|
|
c5c3051f98 | ||
|
|
4f2389e257 | ||
|
|
fc4cd517fd | ||
|
|
361f7e3432 | ||
|
|
213242627e | ||
|
|
960e4d3015 | ||
|
|
c3097cac70 | ||
|
|
b991f6c228 | ||
|
|
918889a572 | ||
|
|
7e685b646f | ||
|
|
eef302635b | ||
|
|
6105d008df | ||
|
|
dd04afe8f8 | ||
|
|
5a9101e303 | ||
|
|
c97ed026d0 | ||
|
|
ce46cb0956 | ||
|
|
9f8fa575b3 | ||
|
|
4da367a94b | ||
|
|
6197dfe403 | ||
|
|
1363daae3c | ||
|
|
d9f4a92648 | ||
|
|
bc11140a40 | ||
|
|
7a36e92592 | ||
|
|
2d79177bb5 | ||
|
|
f51a9679f2 | ||
|
|
173f4c0f03 | ||
|
|
116565fa0f | ||
|
|
52887dc699 | ||
|
|
89a571d497 | ||
|
|
ff4ee16f06 | ||
|
|
ae3a69c1af | ||
|
|
6d423ac990 | ||
|
|
aa0a031fa0 | ||
|
|
dd2f6d6430 | ||
|
|
ce0de6e1dc | ||
|
|
7cc2bc4fde | ||
|
|
6ed9a2620c | ||
|
|
860183e675 | ||
|
|
1cd9ee399f | ||
|
|
b5d841562d | ||
|
|
9f883db25b | ||
|
|
389ac07bd7 | ||
|
|
4da69f78cb | ||
|
|
c5526c78d9 | ||
|
|
470f10a828 | ||
|
|
0dbecce65d |
@@ -1,4 +1,4 @@
|
||||
--format progress
|
||||
--format Fuubar
|
||||
--format ParallelTests::RSpec::SummaryLogger --out tmp/spec_summary.log
|
||||
--format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log
|
||||
--tag ~performance
|
||||
|
||||
2
Gemfile
2
Gemfile
@@ -50,6 +50,7 @@ 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 'blockenspiel'
|
||||
gem 'acts-as-taggable-on', '~> 3.4'
|
||||
|
||||
gem 'foreigner'
|
||||
@@ -95,6 +96,7 @@ group :test, :development do
|
||||
gem 'letter_opener'
|
||||
gem 'timecop'
|
||||
gem 'poltergeist'
|
||||
gem 'rspec-retry'
|
||||
gem 'json_spec'
|
||||
gem 'unicorn-rails'
|
||||
end
|
||||
|
||||
@@ -165,6 +165,7 @@ GEM
|
||||
bcrypt (3.1.7)
|
||||
bcrypt-ruby (3.1.5)
|
||||
bcrypt (>= 3.1.3)
|
||||
blockenspiel (0.4.5)
|
||||
bugsnag (1.5.2)
|
||||
httparty (>= 0.6, < 1.0)
|
||||
multi_json (~> 1.0)
|
||||
@@ -461,6 +462,8 @@ GEM
|
||||
rspec-core (~> 2.14.0)
|
||||
rspec-expectations (~> 2.14.0)
|
||||
rspec-mocks (~> 2.14.0)
|
||||
rspec-retry (0.4.2)
|
||||
rspec-core
|
||||
ruby-hmac (0.4.0)
|
||||
ruby-progressbar (1.7.1)
|
||||
safe_yaml (0.9.5)
|
||||
@@ -546,6 +549,7 @@ DEPENDENCIES
|
||||
angularjs-rails (= 1.2.13)
|
||||
awesome_print
|
||||
aws-sdk
|
||||
blockenspiel
|
||||
bugsnag
|
||||
capybara
|
||||
coffee-rails (~> 3.2.1)
|
||||
@@ -597,6 +601,7 @@ DEPENDENCIES
|
||||
representative_view
|
||||
roadie-rails (~> 1.0.3)
|
||||
rspec-rails
|
||||
rspec-retry
|
||||
sass (~> 3.3)
|
||||
sass-rails (~> 3.2.3)
|
||||
shoulda-matchers
|
||||
|
||||
@@ -12,10 +12,15 @@ We're part of global movement - get involved!
|
||||
* Find out more and join in the conversation - http://openfoodnetwork.org
|
||||
|
||||
|
||||
## Getting started
|
||||
|
||||
Below are instructions for setting up a development environment for Open Food Network. If you're interested in provisioning a server, see [the project's Ansible playbooks](https://github.com/openfoodfoundation/ofn_deployment).
|
||||
|
||||
|
||||
## Dependencies
|
||||
|
||||
* Rails 3.2.x
|
||||
* Ruby >= 1.9.3
|
||||
* Ruby 1.9.3
|
||||
* PostgreSQL database
|
||||
* PhantomJS (for testing)
|
||||
* See Gemfile for a list of gems required
|
||||
@@ -32,19 +37,20 @@ You can view the code at:
|
||||
|
||||
You can download the source with the command:
|
||||
|
||||
git clone git@github.com:openfoodfoundation/openfoodnetwork
|
||||
git clone https://github.com/openfoodfoundation/openfoodnetwork.git
|
||||
|
||||
|
||||
## Get it running
|
||||
|
||||
For those new to Rails, the following tutorial will help get you up to speed with configuring a Rails environment: http://guides.rubyonrails.org/getting_started.html .
|
||||
|
||||
First, check your dependencies: Ensure that you have Ruby 1.9.x installed:
|
||||
First, check your dependencies: Ensure that you have Ruby >= 1.9.3 installed:
|
||||
|
||||
ruby --version
|
||||
|
||||
Install the project's gem dependencies:
|
||||
|
||||
cd openfoodnetwork
|
||||
bundle install
|
||||
|
||||
Configure the site:
|
||||
|
||||
@@ -30,7 +30,7 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [
|
||||
variant: { name: "Variant", visible: true }
|
||||
quantity: { name: "Quantity", visible: true }
|
||||
max: { name: "Max", visible: true }
|
||||
unit_value: { name: "Weight/Volume", visible: false }
|
||||
final_weight_volume: { name: "Weight/Volume", visible: false }
|
||||
price: { name: "Price", visible: false }
|
||||
$scope.initialise = ->
|
||||
$scope.initialiseVariables()
|
||||
@@ -166,10 +166,10 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [
|
||||
|
||||
$scope.weightAdjustedPrice = (lineItem, oldValue) ->
|
||||
if oldValue <= 0
|
||||
oldValue = lineItem.units_variant.unit_value
|
||||
if lineItem.unit_value <= 0
|
||||
lineItem.unit_value = lineItem.units_variant.unit_value
|
||||
lineItem.price = lineItem.price * lineItem.unit_value / oldValue
|
||||
oldValue = lineItem.units_variant.unit_value * line_item.quantity
|
||||
if lineItem.final_weight_volume <= 0
|
||||
lineItem.final_weight_volume = lineItem.units_variant.unit_value * lineItem.quantity
|
||||
lineItem.price = lineItem.price * lineItem.final_weight_volume / oldValue
|
||||
#$scope.bulk_order_form.line_item.price.$setViewValue($scope.bulk_order_form.line_item.price.$viewValue)
|
||||
|
||||
$scope.unitValueLessThanZero = (lineItem) ->
|
||||
@@ -178,6 +178,13 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [
|
||||
else
|
||||
false
|
||||
|
||||
$scope.updateOnQuantity = (lineItem, oldQuantity) ->
|
||||
if lineItem.quantity <= 0
|
||||
lineItem.quantity = 1
|
||||
# reset price to original unit value
|
||||
lineItem.price = lineItem.price * (oldQuantity * lineItem.units_variant.unit_value) / lineItem.final_weight_volume
|
||||
lineItem.final_weight_volume = lineItem.units_variant.unit_value * lineItem.quantity
|
||||
|
||||
$scope.$watch "orderCycleFilter", (newVal, oldVal) ->
|
||||
unless $scope.orderCycleFilter == "0" || angular.equals(newVal, oldVal)
|
||||
$scope.startDate = $scope.orderCyclesByID[$scope.orderCycleFilter].first_order
|
||||
|
||||
@@ -2,6 +2,7 @@ angular.module("admin.enterprises").factory 'PermalinkChecker', ($q, $http) ->
|
||||
new class PermalinkChecker
|
||||
deferredRequest: null
|
||||
deferredAbort: null
|
||||
MAX_PERMALINK_LENGTH: 255
|
||||
|
||||
check: (permalink) =>
|
||||
@abort(@deferredAbort) if @deferredRequest && @deferredRequest.promise
|
||||
@@ -15,9 +16,14 @@ angular.module("admin.enterprises").factory 'PermalinkChecker', ($q, $http) ->
|
||||
timeout: deferredAbort.promise
|
||||
)
|
||||
.success( (data) =>
|
||||
deferredRequest.resolve
|
||||
permalink: data
|
||||
available: "Available"
|
||||
if data.length > @MAX_PERMALINK_LENGTH || !data.match(/^[\w-]+$/)
|
||||
deferredRequest.resolve
|
||||
permalink: permalink
|
||||
available: "Error"
|
||||
else
|
||||
deferredRequest.resolve
|
||||
permalink: data
|
||||
available: "Available"
|
||||
).error (data,status) =>
|
||||
if status == 409
|
||||
deferredRequest.resolve
|
||||
|
||||
@@ -19,6 +19,7 @@ angular.module("ofn.admin").factory "BulkProducts", (PagedFetcher, dataFetcher)
|
||||
# when a respond_overrride for the clone action is used.
|
||||
id = data.product.id
|
||||
dataFetcher("/api/products/" + id + "?template=bulk_show").then (newProduct) =>
|
||||
@unpackProduct newProduct
|
||||
@insertProductAfter(product, newProduct)
|
||||
|
||||
updateVariantLists: (serverProducts, productsWithUnsavedVariants) ->
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#= require ../shared/bindonce.min.js
|
||||
#= require ../shared/ng-infinite-scroll.min.js
|
||||
#= require ../shared/angular-local-storage.js
|
||||
#= require ../shared/angular-slideables.js
|
||||
#= require angularjs-file-upload
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +1,69 @@
|
||||
Darkswarm.controller "EnterprisesCtrl", ($scope, Enterprises, Search, $document, $rootScope, HashNavigation, FilterSelectorsService, EnterpriseModal) ->
|
||||
Darkswarm.controller "EnterprisesCtrl", ($scope, $rootScope, $timeout, Enterprises, Search, $document, HashNavigation, FilterSelectorsService, EnterpriseModal, enterpriseMatchesNameQueryFilter, distanceWithinKmFilter) ->
|
||||
$scope.Enterprises = Enterprises
|
||||
$scope.totalActive = FilterSelectorsService.totalActive
|
||||
$scope.clearAll = FilterSelectorsService.clearAll
|
||||
$scope.filterText = FilterSelectorsService.filterText
|
||||
$scope.FilterSelectorsService = FilterSelectorsService
|
||||
$scope.totalActive = FilterSelectorsService.totalActive
|
||||
$scope.clearAll = FilterSelectorsService.clearAll
|
||||
$scope.filterText = FilterSelectorsService.filterText
|
||||
$scope.FilterSelectorsService = FilterSelectorsService
|
||||
$scope.query = Search.search()
|
||||
$scope.openModal = EnterpriseModal.open
|
||||
$scope.activeTaxons = []
|
||||
$scope.show_profiles = false
|
||||
$scope.filtersActive = false
|
||||
$scope.distanceMatchesShown = false
|
||||
|
||||
|
||||
$scope.$watch "query", (query)->
|
||||
Enterprises.flagMatching query
|
||||
Search.search query
|
||||
$rootScope.$broadcast 'enterprisesChanged'
|
||||
$scope.distanceMatchesShown = false
|
||||
|
||||
$timeout ->
|
||||
Enterprises.calculateDistance query, $scope.firstNameMatch()
|
||||
$rootScope.$broadcast 'enterprisesChanged'
|
||||
|
||||
|
||||
$rootScope.$on "enterprisesChanged", ->
|
||||
$scope.filterEnterprises()
|
||||
$scope.updateVisibleMatches()
|
||||
|
||||
|
||||
# When filter settings change, this could change which name match is at the top, or even
|
||||
# result in no matches. This affects the reference point that the distance matches are
|
||||
# calculated from, so we need to recalculate distances.
|
||||
$scope.$watch '[activeTaxons, shippingTypes, show_profiles]', ->
|
||||
$timeout ->
|
||||
Enterprises.calculateDistance $scope.query, $scope.firstNameMatch()
|
||||
$rootScope.$broadcast 'enterprisesChanged'
|
||||
, true
|
||||
|
||||
|
||||
$rootScope.$on "$locationChangeSuccess", (newRoute, oldRoute) ->
|
||||
if HashNavigation.active "hubs"
|
||||
$document.scrollTo $("#hubs"), 100, 200
|
||||
|
||||
|
||||
$scope.filterEnterprises = ->
|
||||
es = Enterprises.hubs
|
||||
$scope.nameMatches = enterpriseMatchesNameQueryFilter(es, true)
|
||||
$scope.distanceMatches = enterpriseMatchesNameQueryFilter(es, false)
|
||||
$scope.distanceMatches = distanceWithinKmFilter($scope.distanceMatches, 50)
|
||||
|
||||
|
||||
$scope.updateVisibleMatches = ->
|
||||
$scope.visibleMatches = if $scope.nameMatches.length == 0 || $scope.distanceMatchesShown
|
||||
$scope.nameMatches.concat $scope.distanceMatches
|
||||
else
|
||||
$scope.nameMatches
|
||||
|
||||
|
||||
$scope.showDistanceMatches = ->
|
||||
$scope.distanceMatchesShown = true
|
||||
$scope.updateVisibleMatches()
|
||||
|
||||
|
||||
$scope.firstNameMatch = ->
|
||||
if $scope.nameMatchesFiltered?
|
||||
$scope.nameMatchesFiltered[0]
|
||||
else
|
||||
undefined
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
Darkswarm.controller "LineItemCtrl", ($scope)->
|
||||
$scope.$watch "line_item.quantity", (newValue, oldValue)->
|
||||
$scope.$watch '[line_item.quantity, line_item.max_quantity]', (newValue, oldValue)->
|
||||
if newValue != oldValue
|
||||
$scope.Cart.orderChanged()
|
||||
, true
|
||||
@@ -1,6 +1,4 @@
|
||||
# TODO this SUCKS. Fix it
|
||||
|
||||
Darkswarm.controller "OrderCycleCtrl", ($scope, OrderCycle, $timeout) ->
|
||||
Darkswarm.controller "OrderCycleCtrl", ($scope, $timeout, OrderCycle) ->
|
||||
$scope.order_cycle = OrderCycle.order_cycle
|
||||
$scope.OrderCycle = OrderCycle
|
||||
|
||||
@@ -9,11 +7,26 @@ Darkswarm.controller "OrderCycleCtrl", ($scope, OrderCycle, $timeout) ->
|
||||
# That takes an expression instead of a trigger, and binds to that
|
||||
$timeout =>
|
||||
if !$scope.OrderCycle.selected()
|
||||
$("#order_cycle_id").trigger("openTrigger")
|
||||
$("#order_cycle_id").trigger("openTrigger")
|
||||
|
||||
|
||||
Darkswarm.controller "OrderCycleChangeCtrl", ($scope, OrderCycle, Products, $timeout) ->
|
||||
Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $timeout, OrderCycle, Products, Variants, Cart) ->
|
||||
# Track previous order cycle id for use with revertOrderCycle()
|
||||
$scope.previous_order_cycle_id = OrderCycle.order_cycle.order_cycle_id
|
||||
$scope.$watch 'order_cycle.order_cycle_id', (newValue, oldValue)->
|
||||
$scope.previous_order_cycle_id = oldValue
|
||||
|
||||
$scope.changeOrderCycle = ->
|
||||
OrderCycle.push_order_cycle Products.update
|
||||
OrderCycle.push_order_cycle $scope.orderCycleChanged
|
||||
$timeout ->
|
||||
$("#order_cycle_id").trigger("closeTrigger")
|
||||
$("#order_cycle_id").trigger("closeTrigger")
|
||||
|
||||
$scope.revertOrderCycle = ->
|
||||
$scope.order_cycle.order_cycle_id = $scope.previous_order_cycle_id
|
||||
|
||||
$scope.orderCycleChanged = ->
|
||||
# push_order_cycle clears the cart server-side. Here we call Cart.clear() to clear the
|
||||
# client-side cart.
|
||||
Variants.clear()
|
||||
Cart.clear()
|
||||
Products.update()
|
||||
|
||||
@@ -10,6 +10,7 @@ window.Darkswarm = angular.module("Darkswarm", ["ngResource",
|
||||
'google-maps',
|
||||
'duScroll',
|
||||
'angularFileUpload',
|
||||
'angularSlideables'
|
||||
]).config ($httpProvider, $tooltipProvider, $locationProvider, $anchorScrollProvider) ->
|
||||
$httpProvider.defaults.headers.post['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content')
|
||||
$httpProvider.defaults.headers.put['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content')
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
Darkswarm.directive "ofnChangeHub", (CurrentHub, Cart) ->
|
||||
# Compares scope.hub with CurrentHub. Will trigger an confirmation if they are different,
|
||||
# and Cart isn't empty
|
||||
restrict: "A"
|
||||
scope:
|
||||
hub: "=ofnChangeHub"
|
||||
link: (scope, elm, attr)->
|
||||
cart_will_need_emptying = ->
|
||||
CurrentHub.hub?.id and CurrentHub.hub.id isnt scope.hub.id and !Cart.empty()
|
||||
|
||||
if cart_will_need_emptying()
|
||||
elm.bind 'click', (ev)->
|
||||
if confirm "Are you sure? This will change your selected hub and remove any items in your shopping cart."
|
||||
Cart.clear()
|
||||
else
|
||||
ev.preventDefault()
|
||||
@@ -0,0 +1,22 @@
|
||||
Darkswarm.directive "ofnChangeOrderCycle", (OrderCycle, Cart, storage) ->
|
||||
# Compares chosen order cycle with pre-set OrderCycle. Will trigger
|
||||
# a confirmation if they are different, and Cart isn't empty
|
||||
restrict: "A"
|
||||
scope: true
|
||||
link: (scope, elm, attr)->
|
||||
order_cycle_id = ->
|
||||
parseInt elm.val()
|
||||
|
||||
cart_needs_emptying = ->
|
||||
OrderCycle.order_cycle?.order_cycle_id && OrderCycle.order_cycle.order_cycle_id != order_cycle_id() && !Cart.empty()
|
||||
|
||||
elm.bind 'change', (ev)->
|
||||
if cart_needs_emptying()
|
||||
if confirm "Are you sure? This will change your selected order cycle and remove any items in your shopping cart."
|
||||
Cart.clear()
|
||||
scope.changeOrderCycle()
|
||||
else
|
||||
scope.$apply ->
|
||||
scope.revertOrderCycle()
|
||||
else
|
||||
scope.changeOrderCycle()
|
||||
@@ -1,13 +0,0 @@
|
||||
Darkswarm.directive "ofnEmptiesCart", (CurrentHub, Cart, Navigation, storage) ->
|
||||
# Compares scope.hub with CurrentHub. Will trigger an confirmation if they are different,
|
||||
# and Cart isn't empty
|
||||
restrict: "A"
|
||||
scope:
|
||||
hub: "=ofnEmptiesCart"
|
||||
link: (scope, elm, attr)->
|
||||
if CurrentHub.hub?.id and CurrentHub.hub.id isnt scope.hub.id and !Cart.empty()
|
||||
elm.bind 'click', (ev)->
|
||||
ev.preventDefault()
|
||||
if confirm "Are you sure? This will change your selected Hub and remove any items in your shopping cart."
|
||||
storage.clearAll() # One day this will have to be moar GRANULAR
|
||||
Navigation.go scope.hub.path
|
||||
@@ -0,0 +1,5 @@
|
||||
Darkswarm.directive "integer", ->
|
||||
restrict: 'A'
|
||||
link: (scope, elem, attr) ->
|
||||
elem.bind 'input', ->
|
||||
elem.val Math.round(elem.val())
|
||||
@@ -2,10 +2,9 @@ Darkswarm.directive 'scrollAfterLoad', ($timeout, $location, $document)->
|
||||
# Scroll to an element on page load
|
||||
restrict: "A"
|
||||
link: (scope, element, attr) ->
|
||||
if scope.$last is true
|
||||
$(window).load ->
|
||||
$timeout ->
|
||||
elem = $("##{$location.hash()}")
|
||||
if elem.length > 0
|
||||
$document.scrollTo elem , 100, 200, (x)->
|
||||
x * (2 - x)
|
||||
elem = element
|
||||
$(window).load ->
|
||||
$timeout ->
|
||||
if elem?
|
||||
$document.scrollTo elem, 100, 200, (x) ->
|
||||
x * (2 - x)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
Darkswarm.filter 'distanceWithinKm', ->
|
||||
(enterprises, range) ->
|
||||
enterprises ||= []
|
||||
enterprises.filter (enterprise) ->
|
||||
enterprise.distance / 1000 <= range
|
||||
@@ -0,0 +1,4 @@
|
||||
Darkswarm.filter 'enterpriseMatchesNameQuery', ->
|
||||
(enterprises, matches_name_query) ->
|
||||
enterprises.filter (enterprise) ->
|
||||
enterprise.matches_name_query == matches_name_query
|
||||
@@ -1,4 +1,4 @@
|
||||
Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http)->
|
||||
Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, storage)->
|
||||
# Handles syncing of current cart/order state to server
|
||||
new class Cart
|
||||
dirty: false
|
||||
@@ -20,7 +20,7 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http)->
|
||||
$http.post('/orders/populate', @data()).success (data, status)=>
|
||||
@saved()
|
||||
.error (response, status)=>
|
||||
# TODO what shall we do here?
|
||||
@scheduleRetry()
|
||||
|
||||
data: =>
|
||||
variants = {}
|
||||
@@ -30,6 +30,12 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http)->
|
||||
max_quantity: li.max_quantity
|
||||
{variants: variants}
|
||||
|
||||
scheduleRetry: =>
|
||||
console.log "Error updating cart: #{status}. Retrying in 3 seconds..."
|
||||
$timeout =>
|
||||
console.log "Retrying cart update"
|
||||
@orderChanged()
|
||||
, 3000
|
||||
|
||||
saved: =>
|
||||
@dirty = false
|
||||
@@ -63,6 +69,10 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http)->
|
||||
exists = @line_items.some (li)-> li.variant == variant
|
||||
@create_line_item(variant) unless exists
|
||||
|
||||
clear: ->
|
||||
@line_items = []
|
||||
storage.clearAll() # One day this will have to be moar GRANULAR
|
||||
|
||||
create_line_item: (variant)->
|
||||
variant.extended_name = @extendedVariantName(variant)
|
||||
variant.line_item =
|
||||
|
||||
@@ -26,7 +26,11 @@ Darkswarm.factory "EnterpriseRegistrationService", ($http, RegistrationService,
|
||||
RegistrationService.select('about')
|
||||
).error((data) =>
|
||||
Loading.clear()
|
||||
alert('Failed to create your enterprise.\nPlease ensure all fields are completely filled out.')
|
||||
if data?.errors?
|
||||
errors = ("#{k.capitalize()} #{v[0]}" for k, v of data.errors when v.length > 0)
|
||||
alert "Failed to create your enterprise.\n" + errors.join('\n')
|
||||
else
|
||||
alert('Failed to create your enterprise.\nPlease ensure all fields are completely filled out.')
|
||||
)
|
||||
# RegistrationService.select('about')
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Taxons, Dereferencer, visibleFilter)->
|
||||
Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Taxons, Dereferencer, visibleFilter, Matcher, Geo, $rootScope)->
|
||||
new class Enterprises
|
||||
enterprises_by_id: {}
|
||||
constructor: ->
|
||||
@@ -28,3 +28,36 @@ Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Taxons, Dereferencer,
|
||||
Dereferencer.dereference enterprise.taxons, Taxons.taxons_by_id
|
||||
Dereferencer.dereference enterprise.supplied_taxons, Taxons.taxons_by_id
|
||||
|
||||
flagMatching: (query) ->
|
||||
for enterprise in @enterprises
|
||||
enterprise.matches_name_query = if query? && query.length > 0
|
||||
Matcher.match([enterprise.name], query)
|
||||
else
|
||||
false
|
||||
|
||||
calculateDistance: (query, firstMatching) ->
|
||||
if query?.length > 0
|
||||
if firstMatching?
|
||||
@setDistanceFrom firstMatching
|
||||
else
|
||||
@calculateDistanceGeo query
|
||||
else
|
||||
@resetDistance()
|
||||
|
||||
calculateDistanceGeo: (query) ->
|
||||
Geo.geocode query, (results, status) =>
|
||||
$rootScope.$apply =>
|
||||
if status == Geo.OK
|
||||
#console.log "Geocoded #{query} -> #{results[0].geometry.location}."
|
||||
@setDistanceFrom results[0].geometry.location
|
||||
else
|
||||
console.log "Geocoding failed for the following reason: #{status}"
|
||||
@resetDistance()
|
||||
|
||||
setDistanceFrom: (locatable) ->
|
||||
for enterprise in @enterprises
|
||||
enterprise.distance = Geo.distanceBetween enterprise, locatable
|
||||
$rootScope.$broadcast 'enterprisesChanged'
|
||||
|
||||
resetDistance: ->
|
||||
enterprise.distance = null for enterprise in @enterprises
|
||||
|
||||
23
app/assets/javascripts/darkswarm/services/geo.js.erb.coffee
Normal file
23
app/assets/javascripts/darkswarm/services/geo.js.erb.coffee
Normal file
@@ -0,0 +1,23 @@
|
||||
Darkswarm.service "Geo", ->
|
||||
new class Geo
|
||||
OK: google.maps.GeocoderStatus.OK
|
||||
|
||||
# Usage:
|
||||
# Geo.geocode address, (results, status) ->
|
||||
# if status == Geo.OK
|
||||
# console.log results[0].geometry.location
|
||||
# else
|
||||
# console.log "Error: #{status}"
|
||||
geocode: (address, callback) ->
|
||||
geocoder = new google.maps.Geocoder()
|
||||
geocoder.geocode {'address': address, 'region': "<%= Spree::Country.find_by_id(Spree::Config[:default_country_id]).iso %>"}, callback
|
||||
|
||||
distanceBetween: (src, dst) ->
|
||||
google.maps.geometry.spherical.computeDistanceBetween @toLatLng(src), @toLatLng(dst)
|
||||
|
||||
# Wrap an object in a google.maps.LatLng if it has not been already
|
||||
toLatLng: (locatable) ->
|
||||
if locatable.lat?
|
||||
locatable
|
||||
else
|
||||
new google.maps.LatLng locatable.latitude, locatable.longitude
|
||||
@@ -9,8 +9,8 @@ Darkswarm.factory "OfnMap", (Enterprises, EnterpriseModal, visibleFilter) ->
|
||||
# Adding methods to each enterprise
|
||||
extend: (enterprise) ->
|
||||
new class MapMarker
|
||||
# We're whitelisting attributes because GMaps tries to crawl
|
||||
# our data, and our data is recursive, so it breaks
|
||||
# We cherry-pick attributes because GMaps tries to crawl
|
||||
# our data, and our data is cyclic, so it breaks
|
||||
latitude: enterprise.latitude
|
||||
longitude: enterprise.longitude
|
||||
icon: enterprise.icon
|
||||
|
||||
@@ -10,12 +10,26 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Pro
|
||||
|
||||
update: =>
|
||||
@loading = true
|
||||
@products = $resource("/shop/products").query (products)=>
|
||||
@extend() && @dereference()
|
||||
@products = []
|
||||
$resource("/shop/products").query (products)=>
|
||||
@products = products
|
||||
|
||||
@extend()
|
||||
@dereference()
|
||||
@registerVariants()
|
||||
@registerVariantsWithCart()
|
||||
@loading = false
|
||||
@
|
||||
|
||||
extend: ->
|
||||
for product in @products
|
||||
if product.variants?.length > 0
|
||||
prices = (v.price for v in product.variants)
|
||||
product.price = Math.min.apply(null, prices)
|
||||
product.hasVariants = product.variants?.length > 0
|
||||
|
||||
product.primaryImage = product.images[0]?.small_url if product.images
|
||||
product.primaryImageOrMissing = product.primaryImage || "/assets/noimage/small.png"
|
||||
product.largeImage = product.images[0]?.large_url if product.images
|
||||
|
||||
dereference: ->
|
||||
for product in @products
|
||||
@@ -42,14 +56,3 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Pro
|
||||
for variant in product.variants
|
||||
Cart.register_variant variant
|
||||
Cart.register_variant product.master if product.master
|
||||
|
||||
extend: ->
|
||||
for product in @products
|
||||
if product.variants?.length > 0
|
||||
prices = (v.price for v in product.variants)
|
||||
product.price = Math.min.apply(null, prices)
|
||||
product.hasVariants = product.variants?.length > 0
|
||||
|
||||
product.primaryImage = product.images[0]?.small_url if product.images
|
||||
product.primaryImageOrMissing = product.primaryImage || "/assets/noimage/small.png"
|
||||
product.largeImage = product.images[0]?.large_url if product.images
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
Darkswarm.factory 'Variants', ->
|
||||
new class Variants
|
||||
variants: {}
|
||||
|
||||
clear: ->
|
||||
@variants = {}
|
||||
|
||||
register: (variant)->
|
||||
@variants[variant.id] ||= @extend variant
|
||||
|
||||
|
||||
2
app/assets/javascripts/darkswarm/util.js.coffee
Normal file
2
app/assets/javascripts/darkswarm/util.js.coffee
Normal file
@@ -0,0 +1,2 @@
|
||||
String.prototype.capitalize = ->
|
||||
this.charAt(0).toUpperCase() + this.slice(1)
|
||||
55
app/assets/javascripts/shared/angular-slideables.js
vendored
Normal file
55
app/assets/javascripts/shared/angular-slideables.js
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Angular Slideables - A "pure" Angular implementation of jQuery-style slideToggle()
|
||||
* Source: https://github.com/EricWVGG/AngularSlideables
|
||||
* By Eric Jacobsen, used under MIT licence
|
||||
*/
|
||||
|
||||
angular.module('angularSlideables', [])
|
||||
.directive('slideable', function () {
|
||||
return {
|
||||
restrict:'C',
|
||||
compile: function (element, attr) {
|
||||
// wrap tag
|
||||
var contents = element.html();
|
||||
element.html('<div class="slideable_content" style="margin:0 !important; padding:0 !important" >' + contents + '</div>');
|
||||
|
||||
return function postLink(scope, element, attrs) {
|
||||
// default properties
|
||||
attrs.duration = (!attrs.duration) ? '1s' : attrs.duration;
|
||||
attrs.easing = (!attrs.easing) ? 'ease-in-out' : attrs.easing;
|
||||
element.css({
|
||||
'overflow': 'hidden',
|
||||
'height': '0px',
|
||||
'transitionProperty': 'height',
|
||||
'transitionDuration': attrs.duration,
|
||||
'transitionTimingFunction': attrs.easing
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
})
|
||||
.directive('slideToggle', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attrs) {
|
||||
var target, content;
|
||||
|
||||
attrs.expanded = false;
|
||||
|
||||
element.bind('click', function() {
|
||||
if (!target) target = document.querySelector(attrs.slideToggle);
|
||||
if (!content) content = target.querySelector('.slideable_content');
|
||||
|
||||
if(!attrs.expanded) {
|
||||
content.style.border = '1px solid rgba(0,0,0,0)';
|
||||
var y = content.clientHeight;
|
||||
content.style.border = 0;
|
||||
target.style.height = y + 'px';
|
||||
} else {
|
||||
target.style.height = '0px';
|
||||
}
|
||||
attrs.expanded = !attrs.expanded;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
0
app/assets/javascripts/shared/ng-tags-input.min.js
vendored
Executable file → Normal file
0
app/assets/javascripts/shared/ng-tags-input.min.js
vendored
Executable file → Normal file
@@ -1,4 +1,5 @@
|
||||
%ng-include{src: "'partials/enterprise_header.html'"}
|
||||
%ng-include{src: "'partials/enterprise_details.html'"}
|
||||
%ng-include{src: "'partials/hub_details.html'"}
|
||||
%ng-include{src: "'partials/producer_details.html'"}
|
||||
%ng-include{src: "'partials/close.html'"}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.highlight-top.row
|
||||
.small-12.medium-7.large-8.columns
|
||||
%h3{"ng-if" => "enterprise.is_distributor"}
|
||||
%a{"bo-href" => "enterprise.path", "ofn-empties-cart" => "enterprise"}
|
||||
%a{"bo-href" => "enterprise.path", "ofn-change-hub" => "enterprise"}
|
||||
%i{"ng-class" => "enterprise.icon_font"}
|
||||
%span{"bo-text" => "enterprise.name"}
|
||||
%h3{"ng-if" => "!enterprise.is_distributor", "ng-class" => "{'is_producer' : enterprise.is_primary_producer}"}
|
||||
@@ -10,4 +10,4 @@
|
||||
%span{"bo-text" => "enterprise.name"}
|
||||
.small-12.medium-5.large-4.columns.text-right.small-only-text-left
|
||||
%p{"bo-bind" => "[enterprise.address.city, enterprise.address.state_name] | printArray"}
|
||||
%img.hero-img{"bo-src" => "enterprise.promo_image"}
|
||||
%img.hero-img{"bo-src" => "enterprise.promo_image"}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
.row.pad-top{bindonce: true, "ng-if" => "enterprise.hubs.length > 0 && enterprise.is_distributor"}
|
||||
.cta-container.small-12.columns
|
||||
%label
|
||||
Shop for
|
||||
%strong{"bo-text" => "enterprise.name"}
|
||||
%label
|
||||
Shop for
|
||||
%strong{"bo-text" => "enterprise.name"}
|
||||
products at:
|
||||
%a.cta-hub{"ng-repeat" => "hub in enterprise.hubs",
|
||||
"bo-href" => "hub.path",
|
||||
%a.cta-hub{"ng-repeat" => "hub in enterprise.hubs",
|
||||
"bo-href" => "hub.path",
|
||||
"bo-class" => "{primary: hub.active, secondary: !hub.active}",
|
||||
"ofn-empties-cart" => "hub"}
|
||||
"ofn-change-hub" => "hub"}
|
||||
%i.ofn-i_033-open-sign{"bo-if" => "hub.active"}
|
||||
%i.ofn-i_032-closed-sign{"bo-if" => "!hub.active"}
|
||||
.hub-name{"bo-text" => "hub.name"}
|
||||
.hub-name{"bo-text" => "hub.name"}
|
||||
.button-address{"bo-bind" => "[hub.address.city, hub.address.state_name] | printArray"}
|
||||
/ %i.ofn-i_007-caret-right
|
||||
|
||||
|
||||
@@ -5,18 +5,18 @@
|
||||
%label{"active-table-hub-link" => "enterprise", change: "Change shop to:", shop: "Shop now at:"}
|
||||
.small-8.columns.right
|
||||
%label.right{"bo-if" => "enterprise.pickup || enterprise.delivery"}
|
||||
Delivery options:
|
||||
%span{"bo-if" => "enterprise.pickup"}
|
||||
Delivery options:
|
||||
%span{"bo-if" => "enterprise.pickup"}
|
||||
%i.ofn-i_038-takeaway
|
||||
Pickup
|
||||
%span{"bo-if" => "enterprise.delivery"}
|
||||
%span{"bo-if" => "enterprise.delivery"}
|
||||
%i.ofn-i_039-delivery
|
||||
Delivery
|
||||
.row
|
||||
.columns.small-12
|
||||
%a.cta-hub{"bo-href" => "enterprise.path",
|
||||
%a.cta-hub{"bo-href" => "enterprise.path",
|
||||
"ng-class" => "{primary: enterprise.active, secondary: !enterprise.active}",
|
||||
"ofn-empties-cart" => "enterprise"}
|
||||
"ofn-change-hub" => "enterprise"}
|
||||
%i.ofn-i_033-open-sign{"bo-if" => "enterprise.active"}
|
||||
%i.ofn-i_032-closed-sign{"bo-if" => "!enterprise.active"}
|
||||
.hub-name{"bo-text" => "enterprise.name"}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
-# Show places to buy products from this producer, when there are any
|
||||
-# Do not show this for producer shops selling only their own produce,
|
||||
-# Since a shopping link will already have been displayed in hub_details.html.haml
|
||||
.row.active_table_row.pad-top{bindonce: true, "ng-if" => "enterprise.is_primary_producer && enterprise.hubs.length > 0 && !(enterprise.hubs.length == 1 && enterprise.hubs[0] == enterprise)"}
|
||||
.columns.small-12
|
||||
.row
|
||||
.columns.small-12.fat
|
||||
%div{"bo-if" => "enterprise.name"}
|
||||
%label
|
||||
Shop for
|
||||
%span.turquoise{"bo-text" => "enterprise.name"}
|
||||
products at:
|
||||
%div.show-for-medium-up{"bo-if" => "!enterprise.name"}
|
||||
|
||||
.row.cta-container
|
||||
.columns.small-12
|
||||
%a.cta-hub{"ng-repeat" => "hub in enterprise.hubs | filter:{id: '!'+enterprise.id} | orderBy:'-active'",
|
||||
"bo-href" => "hub.path", "ofn-empties-cart" => "hub",
|
||||
"bo-class" => "{primary: hub.active, secondary: !hub.active}"}
|
||||
%i.ofn-i_033-open-sign{"bo-if" => "hub.active"}
|
||||
%i.ofn-i_032-closed-sign{"bo-if" => "!hub.active"}
|
||||
.hub-name{"bo-text" => "hub.name"}
|
||||
.button-address{"bo-bind" => "[hub.address.city, hub.address.state_name] | printArray"}
|
||||
@@ -11,6 +11,7 @@
|
||||
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "!variant.product.group_buy"}
|
||||
|
||||
%input{type: :number,
|
||||
integer: true,
|
||||
value: nil,
|
||||
min: 0,
|
||||
placeholder: "0",
|
||||
@@ -26,14 +27,17 @@
|
||||
%span.bulk-input
|
||||
%input.bulk.first{type: :number,
|
||||
value: nil,
|
||||
integer: true,
|
||||
min: 0,
|
||||
"ng-model" => "variant.line_item.quantity",
|
||||
placeholder: "min",
|
||||
"ofn-disable-scroll" => true,
|
||||
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
|
||||
name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"}
|
||||
%span.bulk-input{"bo-if" => "variant.product.group_buy"}
|
||||
%span.bulk-input
|
||||
%input.bulk.second{type: :number,
|
||||
"ng-disabled" => "!variant.line_item.quantity",
|
||||
integer: true,
|
||||
min: 0,
|
||||
"ng-model" => "variant.line_item.max_quantity",
|
||||
placeholder: "max",
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
100%
|
||||
opacity: 1
|
||||
|
||||
@-webkit-keyframes spin
|
||||
@-webkit-keyframes spin
|
||||
0%
|
||||
-webkit-transform: rotate(0deg)
|
||||
transform: rotate(0deg)
|
||||
@@ -104,12 +104,10 @@
|
||||
|
||||
|
||||
.animate-repeat
|
||||
-webkit-transform: translateZ(0)
|
||||
transform: translateZ(0)
|
||||
&.ng-move, &.ng-enter, &.ng-leave
|
||||
-webkit-transition: all 300ms linear
|
||||
transition: all 300ms linear
|
||||
|
||||
-webkit-transition: all 300ms linear
|
||||
transition: all 300ms linear
|
||||
|
||||
&.ng-leave
|
||||
opacity: 1
|
||||
&.ng-leave-active
|
||||
@@ -178,7 +176,7 @@ product.animate-repeat
|
||||
overflow: hidden
|
||||
max-height: 0
|
||||
opacity: 0 !important
|
||||
|
||||
|
||||
// &.ng-hide-add-active, &.ng-hide-remove-active
|
||||
|
||||
&.ng-hide-add, &.ng-hide-remove
|
||||
@@ -197,7 +195,7 @@ product.animate-repeat
|
||||
|
||||
&.ng-hide
|
||||
opacity: 0 !important
|
||||
|
||||
|
||||
// &.ng-hide-add-active, &.ng-hide-remove-active
|
||||
|
||||
&.ng-hide-add, &.ng-hide-remove
|
||||
@@ -206,8 +204,8 @@ product.animate-repeat
|
||||
it as hidden. */
|
||||
display: block !important
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@mixin csstrans
|
||||
@@ -217,7 +215,3 @@ product.animate-repeat
|
||||
-o-transition: all 300ms ease
|
||||
transition: all 300ms ease
|
||||
-webkit-transform-style: preserve-3d
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -36,11 +36,14 @@ footer
|
||||
border: 1px solid rgba($dark-grey, 0.35)
|
||||
background-image: url("/assets/tile-wide.png")
|
||||
background-position: center center
|
||||
background-color: #bbb
|
||||
padding: 12px 0 8px 0
|
||||
display: block
|
||||
&, & *
|
||||
@include csstrans
|
||||
color: #333
|
||||
strong
|
||||
letter-spacing: 0.5px
|
||||
&:hover, &:active, &:focus
|
||||
text-decoration: none
|
||||
border-color: white
|
||||
|
||||
@@ -43,6 +43,11 @@
|
||||
@include bodyFont
|
||||
font-size: 1.5rem
|
||||
font-weight: 300
|
||||
@media all and (max-width: 768px)
|
||||
h2
|
||||
font-size: 52px
|
||||
p
|
||||
font-size: 1.3rem
|
||||
|
||||
a.text-vbig i
|
||||
font-size: 75px
|
||||
@@ -71,7 +76,7 @@
|
||||
width: 64px
|
||||
height: 64px
|
||||
margin: 0 auto
|
||||
background-color: $australia-orange
|
||||
background-color: $brand-colour
|
||||
background-position: center center
|
||||
background-repeat: no-repeat
|
||||
background-size: auto 100%
|
||||
@@ -85,10 +90,13 @@
|
||||
h2
|
||||
font-size: 70px
|
||||
font-weight: 300
|
||||
color: $australia-orange
|
||||
color: $brand-colour
|
||||
@media all and (max-width: 640px)
|
||||
font-size: 45px
|
||||
|
||||
|
||||
a
|
||||
color: $australia-orange
|
||||
color: $brand-colour
|
||||
|
||||
.home-icon-box-bottom
|
||||
margin-top: 1rem
|
||||
@@ -102,8 +110,8 @@
|
||||
padding-left: 1rem
|
||||
padding-right: 1rem
|
||||
h4
|
||||
color: $australia-orange
|
||||
border-bottom: 2px solid lighten($australia-orange, 20%)
|
||||
color: $brand-colour
|
||||
border-bottom: 2px solid lighten($brand-colour, 20%)
|
||||
text-align: center
|
||||
padding: 1rem 0
|
||||
margin: 1.5rem 0
|
||||
@@ -113,6 +121,8 @@
|
||||
font-weight: 300
|
||||
font-size: 45px
|
||||
margin-bottom: 2rem
|
||||
@media all and (max-width: 830px)
|
||||
font-size: 35px
|
||||
|
||||
#stats.pane
|
||||
.row.header
|
||||
@@ -136,4 +146,4 @@
|
||||
font-weight: 300
|
||||
|
||||
#shops-signup.pane
|
||||
background-color: $australia-orange
|
||||
background-color: $brand-colour
|
||||
|
||||
@@ -24,5 +24,10 @@
|
||||
@media all and (min-width: 768px)
|
||||
margin-top: 10rem
|
||||
img
|
||||
max-width: 80%
|
||||
margin-bottom: 5rem
|
||||
max-width: 45%
|
||||
@media all and (min-height: 500px)
|
||||
max-width: 80%
|
||||
|
||||
margin-bottom: 2rem
|
||||
@media all and (min-width: 768px)
|
||||
margin-bottom: 5rem
|
||||
|
||||
@@ -5,3 +5,6 @@
|
||||
background-color: lighten($ofn-grey, 43%)
|
||||
@include panepadding
|
||||
@include sidepaddingSm
|
||||
|
||||
.name-matches, .distance-matches
|
||||
margin-top: 4em
|
||||
@@ -28,7 +28,7 @@ nav
|
||||
|
||||
// Default overrides - big menu
|
||||
.top-bar-section .has-dropdown > a
|
||||
padding-right: $topbar-height / 2 !important
|
||||
padding-right: $topbar-height / 3 !important
|
||||
|
||||
i.ofn-i_022-cog
|
||||
font-size: 24px
|
||||
@@ -51,7 +51,7 @@ nav
|
||||
opacity: 1
|
||||
|
||||
.nav-branded
|
||||
color: $australia-orange
|
||||
color: $brand-colour
|
||||
span
|
||||
font-size: 13px
|
||||
.nav-primary
|
||||
@@ -84,6 +84,9 @@ nav
|
||||
.tab-bar .menu-icon span::after
|
||||
box-shadow: 0 0 0 1px black, 0 7px 0 1px black, 0 14px 0 1px black
|
||||
|
||||
.tab-bar .ofn-logo
|
||||
padding: 9px 0 0 9px
|
||||
|
||||
.left-off-canvas-menu
|
||||
background-color: white
|
||||
|
||||
@@ -99,7 +102,7 @@ nav
|
||||
color: rgba(0, 0, 0, 0.9)
|
||||
&:hover
|
||||
background-color: transparent
|
||||
color: $australia-orange
|
||||
color: $brand-colour
|
||||
@include transition(all 0.3s ease-in-out)
|
||||
|
||||
.off-canvas-wrap.move-right ul.off-canvas-list i
|
||||
@@ -110,10 +113,10 @@ nav
|
||||
|
||||
// Responsive
|
||||
|
||||
@media screen and (max-width: 1350px)
|
||||
@media screen and (max-width: 1450px)
|
||||
nav .top-bar-section
|
||||
ul li a, .has-dropdown > a
|
||||
padding: 0 $topbar-height / 4 !important
|
||||
padding: 0 $topbar-height / 8 !important
|
||||
|
||||
ul.center
|
||||
margin-left: -24px
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
@mixin tiledPane
|
||||
background-image: url("/assets/tile-wide.png")
|
||||
background-color: $australia-orange
|
||||
background-color: $brand-colour
|
||||
background-position: center center
|
||||
@include paneWhiteText
|
||||
|
||||
|
||||
@@ -10,15 +10,23 @@
|
||||
border: 1px solid rgba($dark-grey, 0.35)
|
||||
border-left: none
|
||||
border-right: none
|
||||
background-color: lighten($ofn-grey, 15%)
|
||||
background-color: #bbb
|
||||
background-image: url("/assets/tile-wide.png")
|
||||
background-position: center center
|
||||
padding: 12px 0 8px 0
|
||||
margin: 0
|
||||
|
||||
h6
|
||||
@media all and (max-width: 480px)
|
||||
font-size: 10px
|
||||
line-height: 24px
|
||||
|
||||
a.alert-cta
|
||||
&, & *
|
||||
@include csstrans
|
||||
color: #333
|
||||
strong
|
||||
letter-spacing: 0.5px
|
||||
&:hover, &:active, &:focus
|
||||
&, & *
|
||||
text-decoration: none
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
padding: 4px 12px
|
||||
color: #fff
|
||||
.buttons
|
||||
margin-bottom: 0.1em
|
||||
.button
|
||||
height: auto
|
||||
top: 0px
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
#producer-case-studies, #shops-case-studies
|
||||
padding-top: 100px
|
||||
padding-bottom: 100px
|
||||
background-color: $australia-orange
|
||||
background-color: $brand-colour
|
||||
background-image: url("/assets/hubs-bg.jpg")
|
||||
background-position: center center
|
||||
-webkit-filter: brightness(1.1)
|
||||
filter: brightness(1.1)
|
||||
h2
|
||||
color: $australia-orange
|
||||
color: $brand-colour
|
||||
font-size: 3rem
|
||||
.case-study
|
||||
background-color: rgba(255, 255, 255, 0.5)
|
||||
@@ -42,7 +42,7 @@
|
||||
@media all and (min-width: 640px)
|
||||
text-align: left
|
||||
h4, a
|
||||
color: $australia-orange
|
||||
color: $brand-colour
|
||||
a
|
||||
&, & *
|
||||
@include csstrans
|
||||
@@ -68,9 +68,9 @@ table.signup-table.hubs-table, table.signup-table.producers-table
|
||||
background-color: lighten($ofn-grey, 41%)
|
||||
td:last-child
|
||||
&, & i
|
||||
color: $australia-orange
|
||||
border-bottom: 1px solid rgba($australia-orange, 0.3)
|
||||
background-color: lighten($australia-orange, 48%)
|
||||
color: $brand-colour
|
||||
border-bottom: 1px solid rgba($brand-colour, 0.3)
|
||||
background-color: lighten($brand-colour, 48%)
|
||||
thead
|
||||
background-color: transparent
|
||||
tr
|
||||
@@ -85,7 +85,7 @@ table.signup-table.hubs-table, table.signup-table.producers-table
|
||||
td:last-child
|
||||
&, & *
|
||||
color: white
|
||||
background: $australia-orange
|
||||
background: $brand-colour
|
||||
h5
|
||||
text-transform: uppercase
|
||||
color: $ofn-grey
|
||||
@@ -107,7 +107,7 @@ table.signup-table.hubs-table, table.signup-table.producers-table
|
||||
td:last-child
|
||||
&, & *
|
||||
color: white
|
||||
background: $australia-orange
|
||||
background: $brand-colour
|
||||
h2
|
||||
.text-small
|
||||
text-transform: uppercase
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
@import "foundation/functions"
|
||||
@import "foundation/components/global"
|
||||
|
||||
$australia-orange: rgba(242, 112, 82, 1)
|
||||
// Brand guide colours:
|
||||
// International: #81c26e
|
||||
// Australia: #f35746
|
||||
// Africa: #f35e32
|
||||
// South Africa: #f9a72b
|
||||
// Norway: #4b83cc
|
||||
// Scandanavia: #0c8bbc
|
||||
// UK: #e6373f
|
||||
|
||||
$brand-colour: #f27052
|
||||
|
||||
|
||||
// Topbar
|
||||
$topbar-height: rem-calc(75)
|
||||
$topbar-link-padding: $topbar-height / 2
|
||||
$topbar-link-padding: $topbar-height / 3
|
||||
|
||||
$topbar-bg: $white
|
||||
$topbar-bg-color: $topbar-bg
|
||||
|
||||
$topbar-link-color: $black
|
||||
$topbar-link-color-hover: $australia-orange
|
||||
$topbar-link-color-hover: $brand-colour
|
||||
$topbar-link-color-active: $black
|
||||
$topbar-link-color-active-hover: $white
|
||||
$topbar-link-bg-hover: $white
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
module Admin
|
||||
class ContentsController < Spree::Admin::BaseController
|
||||
def edit
|
||||
@preferences_home = [:home_tagline_cta, :home_whats_happening]
|
||||
@preferences_footer = [:footer_facebook_url, :footer_twitter_url, :footer_instagram_url, :footer_linkedin_url, :footer_googleplus_url, :footer_pinterest_url, :footer_email, :footer_links_md, :footer_about_url]
|
||||
@preference_sections = [{name: 'Header', preferences: [:logo, :logo_mobile, :logo_mobile_svg]},
|
||||
{name: 'Home page', preferences: [:home_hero, :home_show_stats]},
|
||||
{name: 'Producer signup page', preferences: [:producer_signup_pricing_table_html, :producer_signup_case_studies_html, :producer_signup_detail_html]},
|
||||
{name: 'Hub signup page', preferences: [:hub_signup_pricing_table_html, :hub_signup_case_studies_html, :hub_signup_detail_html]},
|
||||
{name: 'Group signup page', preferences: [:group_signup_pricing_table_html, :group_signup_case_studies_html, :group_signup_detail_html]},
|
||||
{name: 'Footer', preferences: [:footer_logo,
|
||||
:footer_facebook_url, :footer_twitter_url, :footer_instagram_url, :footer_linkedin_url, :footer_googleplus_url, :footer_pinterest_url,
|
||||
:footer_email, :footer_links_md, :footer_about_url, :footer_tos_url]}]
|
||||
end
|
||||
|
||||
def update
|
||||
params.each do |name, value|
|
||||
next unless ContentConfig.has_preference? name
|
||||
ContentConfig[name] = value
|
||||
if ContentConfig.has_preference?(name) || ContentConfig.has_attachment?(name)
|
||||
ContentConfig.send("#{name}=", value)
|
||||
end
|
||||
end
|
||||
|
||||
# Save any uploaded images
|
||||
ContentConfig.save
|
||||
|
||||
flash[:success] = t(:successfully_updated, :resource => "Your content")
|
||||
|
||||
redirect_to main_app.edit_admin_content_path
|
||||
|
||||
@@ -241,7 +241,7 @@ module Admin
|
||||
# Overriding method on Spree's resource controller
|
||||
def location_after_save
|
||||
referer_path = OpenFoodNetwork::RefererParser::path(request.referer)
|
||||
refered_from_edit = referer_path == main_app.edit_admin_enterprise_path(@enterprise)
|
||||
refered_from_edit = referer_path =~ /\/edit$/
|
||||
if params[:enterprise].key?(:producer_properties_attributes) && !refered_from_edit
|
||||
main_app.admin_enterprises_path
|
||||
else
|
||||
|
||||
@@ -65,8 +65,8 @@ module Admin
|
||||
end
|
||||
|
||||
def bulk_update
|
||||
@order_cycle_set = OrderCycleSet.new(params[:order_cycle_set])
|
||||
if @order_cycle_set.save
|
||||
@order_cycle_set = params[:order_cycle_set] && OrderCycleSet.new(params[:order_cycle_set])
|
||||
if @order_cycle_set.andand.save
|
||||
redirect_to main_app.admin_order_cycles_path, :notice => 'Order cycles have been updated.'
|
||||
else
|
||||
render :index
|
||||
@@ -132,10 +132,12 @@ module Admin
|
||||
end
|
||||
|
||||
def remove_unauthorized_bulk_attrs
|
||||
params[:order_cycle_set][:collection_attributes].each do |i, hash|
|
||||
order_cycle = OrderCycle.find(hash[:id])
|
||||
unless Enterprise.managed_by(spree_current_user).include?(order_cycle.andand.coordinator)
|
||||
params[:order_cycle_set][:collection_attributes].delete i
|
||||
if params.key? :order_cycle_set
|
||||
params[:order_cycle_set][:collection_attributes].each do |i, hash|
|
||||
order_cycle = OrderCycle.find(hash[:id])
|
||||
unless Enterprise.managed_by(spree_current_user).include?(order_cycle.andand.coordinator)
|
||||
params[:order_cycle_set][:collection_attributes].delete i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,7 +23,7 @@ class CheckoutController < Spree::CheckoutController
|
||||
return if redirect_to_paypal_express_form_if_needed
|
||||
end
|
||||
|
||||
if @order.next
|
||||
if advance_order_state(@order)
|
||||
state_callback(:after)
|
||||
else
|
||||
if @order.errors.present?
|
||||
@@ -83,6 +83,16 @@ class CheckoutController < Spree::CheckoutController
|
||||
params[:order]
|
||||
end
|
||||
|
||||
# Perform order.next, guarding against StaleObjectErrors
|
||||
def advance_order_state(order)
|
||||
tries ||= 3
|
||||
order.next
|
||||
|
||||
rescue ActiveRecord::StaleObjectError
|
||||
retry unless (tries -= 1).zero?
|
||||
false
|
||||
end
|
||||
|
||||
|
||||
def update_failed
|
||||
clear_ship_address
|
||||
|
||||
@@ -9,55 +9,6 @@ class EnterprisesController < BaseController
|
||||
|
||||
respond_to :js, only: :permalink_checker
|
||||
|
||||
def index
|
||||
@enterprises = Enterprise.all
|
||||
end
|
||||
|
||||
def suppliers
|
||||
@suppliers = Enterprise.is_primary_producer
|
||||
end
|
||||
|
||||
def distributors
|
||||
@distributors = Enterprise.is_distributor
|
||||
|
||||
respond_to do |format|
|
||||
format.js do
|
||||
@distributor_details = Hash[@distributors.map { |d| [d.id, render_to_string(:partial => 'enterprises/distributor_details', :locals => {:distributor => d})] }]
|
||||
end
|
||||
format.html do
|
||||
@distributors
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@enterprise = Enterprise.find_by_permalink(params[:id]) || Enterprise.find(params[:id])
|
||||
|
||||
# User can view this page if they've already chosen their distributor, or if this page
|
||||
# is for a supplier, they may use it to select a distributor that sells this supplier's
|
||||
# products.
|
||||
unless current_distributor || @enterprise.is_primary_producer
|
||||
redirect_to spree.root_path and return
|
||||
end
|
||||
|
||||
|
||||
options = {:enterprise_id => params[:id]}
|
||||
options.merge(params.reject { |k,v| k == :id })
|
||||
|
||||
@products = []
|
||||
|
||||
if @enterprise.is_primary_producer
|
||||
@distributors = Enterprise.distributing_any_product_of(@enterprise.supplied_products).by_name.all
|
||||
end
|
||||
|
||||
if current_order_cycle
|
||||
@searcher = Spree::Config.searcher_class.new(options)
|
||||
@products = @searcher.retrieve_products
|
||||
|
||||
order_cycle_products = current_order_cycle.products_distributed_by(current_distributor)
|
||||
@products = @products & order_cycle_products
|
||||
end
|
||||
end
|
||||
|
||||
def check_permalink
|
||||
return render text: params[:permalink], status: 409 if Enterprise.find_by_permalink params[:permalink]
|
||||
|
||||
@@ -2,12 +2,11 @@ class HomeController < BaseController
|
||||
layout 'darkswarm'
|
||||
|
||||
def index
|
||||
@num_distributors = Enterprise.is_distributor.activated.visible.count
|
||||
@num_producers = Enterprise.is_primary_producer.activated.visible.count
|
||||
@num_users = Spree::User.joins(:orders).merge(Spree::Order.complete).count('DISTINCT spree_users.*')
|
||||
@num_orders = Spree::Order.complete.count
|
||||
end
|
||||
|
||||
def about_us
|
||||
if ContentConfig.home_show_stats
|
||||
@num_distributors = Enterprise.is_distributor.activated.visible.count
|
||||
@num_producers = Enterprise.is_primary_producer.activated.visible.count
|
||||
@num_users = Spree::User.joins(:orders).merge(Spree::Order.complete).count('DISTINCT spree_users.*')
|
||||
@num_orders = Spree::Order.complete.count
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -46,10 +46,12 @@ class ShopController < BaseController
|
||||
|
||||
def products_for_shop
|
||||
if current_order_cycle
|
||||
scoper = OpenFoodNetwork::ScopeProductToHub.new(current_distributor)
|
||||
|
||||
current_order_cycle.
|
||||
valid_products_distributed_by(current_distributor).
|
||||
order(taxon_order).
|
||||
each { |p| p.scope_to_hub current_distributor }.
|
||||
each { |p| scoper.scope(p) }.
|
||||
select { |p| !p.deleted? && p.has_stock_for_distribution?(current_order_cycle, current_distributor) }
|
||||
end
|
||||
end
|
||||
@@ -69,9 +71,10 @@ class ShopController < BaseController
|
||||
# We use the in_stock? method here instead of the in_stock scope because we need to
|
||||
# look up the stock as overridden by VariantOverrides, and the scope method is not affected
|
||||
# by them.
|
||||
scoper = OpenFoodNetwork::ScopeVariantToHub.new(current_distributor)
|
||||
Spree::Variant.
|
||||
for_distribution(current_order_cycle, current_distributor).
|
||||
each { |v| v.scope_to_hub current_distributor }.
|
||||
each { |v| scoper.scope(v) }.
|
||||
select(&:in_stock?)
|
||||
end
|
||||
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
require 'csv'
|
||||
require 'open_food_network/order_and_distributor_report'
|
||||
require 'open_food_network/products_and_inventory_report'
|
||||
require 'open_food_network/lettuce_share_report'
|
||||
require 'open_food_network/group_buy_report'
|
||||
require 'open_food_network/order_grouper'
|
||||
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/packing_report'
|
||||
require 'open_food_network/sales_tax_report'
|
||||
require 'open_food_network/xero_invoices_report'
|
||||
require 'open_food_network/bulk_coop_report'
|
||||
require 'open_food_network/payments_report'
|
||||
require 'open_food_network/orders_and_fulfillments_report'
|
||||
|
||||
Spree::Admin::ReportsController.class_eval do
|
||||
|
||||
@@ -22,7 +27,8 @@ Spree::Admin::ReportsController.class_eval do
|
||||
],
|
||||
products_and_inventory: [
|
||||
['All products', :all_products],
|
||||
['Inventory (on hand)', :inventory]
|
||||
['Inventory (on hand)', :inventory],
|
||||
['LettuceShare', :lettuce_share]
|
||||
],
|
||||
customers: [
|
||||
["Mailing List", :mailing_list],
|
||||
@@ -31,11 +37,15 @@ Spree::Admin::ReportsController.class_eval do
|
||||
order_cycle_management: [
|
||||
["Payment Methods Report", :payment_methods],
|
||||
["Delivery Report", :delivery]
|
||||
],
|
||||
packing: [
|
||||
["Pack By Customer", :pack_by_customer],
|
||||
["Pack By Supplier", :pack_by_supplier]
|
||||
]
|
||||
}
|
||||
|
||||
# Fetches user's distributors, suppliers and order_cycles
|
||||
before_filter :load_data, only: [:customers, :products_and_inventory, :order_cycle_management]
|
||||
before_filter :load_data, only: [:customers, :products_and_inventory, :order_cycle_management, :packing]
|
||||
|
||||
# Render a partial for orders and fulfillment description
|
||||
respond_override :index => { :html => { :success => lambda {
|
||||
@@ -47,7 +57,9 @@ Spree::Admin::ReportsController.class_eval do
|
||||
render_to_string(partial: 'customers_description', layout: false, locals: {report_types: REPORT_TYPES[:customers]}).html_safe
|
||||
@reports[:order_cycle_management][:description] =
|
||||
render_to_string(partial: 'order_cycle_management_description', layout: false, locals: {report_types: REPORT_TYPES[:order_cycle_management]}).html_safe
|
||||
} } }
|
||||
@reports[:packing][:description] =
|
||||
render_to_string(partial: 'packing_description', layout: false, locals: {report_types: REPORT_TYPES[:packing]}).html_safe
|
||||
} } }
|
||||
|
||||
|
||||
# Overide spree reports list.
|
||||
@@ -75,19 +87,33 @@ Spree::Admin::ReportsController.class_eval do
|
||||
render_report(@report.header, @report.table, params[:csv], "order_cycle_management_#{timestamp}.csv")
|
||||
end
|
||||
|
||||
def packing
|
||||
# -- Prepare date parameters
|
||||
prepare_date_params params
|
||||
|
||||
# -- Prepare form options
|
||||
my_distributors = Enterprise.is_distributor.managed_by(spree_current_user)
|
||||
my_suppliers = Enterprise.is_primary_producer.managed_by(spree_current_user)
|
||||
|
||||
# My distributors and any distributors distributing products I supply
|
||||
@distributors = my_distributors | Enterprise.with_distributed_products_outer.merge(Spree::Product.in_any_supplier(my_suppliers))
|
||||
# My suppliers and any suppliers supplying products I distribute
|
||||
@suppliers = my_suppliers | my_distributors.map { |d| Spree::Product.in_distributor(d) }.flatten.map(&:supplier).uniq
|
||||
@order_cycles = OrderCycle.active_or_complete.accessible_by(spree_current_user).order('orders_close_at DESC')
|
||||
@report_types = REPORT_TYPES[:packing]
|
||||
@report_type = params[:report_type]
|
||||
|
||||
# -- Build Report with Order Grouper
|
||||
@report = OpenFoodNetwork::PackingReport.new spree_current_user, params
|
||||
order_grouper = OpenFoodNetwork::OrderGrouper.new @report.rules, @report.columns
|
||||
@table = order_grouper.table(@report.table_items)
|
||||
csv_file_name = "#{params[:report_type]}_#{timestamp}.csv"
|
||||
|
||||
render_report(@report.header, @table, params[:csv], "packing_#{timestamp}.csv")
|
||||
end
|
||||
|
||||
def orders_and_distributors
|
||||
params[:q] ||= {}
|
||||
|
||||
if params[:q][:completed_at_gt].blank?
|
||||
params[:q][:completed_at_gt] = Time.zone.now.beginning_of_month
|
||||
else
|
||||
params[:q][:completed_at_gt] = Time.zone.parse(params[:q][:completed_at_gt]).beginning_of_day rescue Time.zone.now.beginning_of_month
|
||||
end
|
||||
|
||||
if params[:q] && !params[:q][:completed_at_lt].blank?
|
||||
params[:q][:completed_at_lt] = Time.zone.parse(params[:q][:completed_at_lt]).end_of_day rescue ""
|
||||
end
|
||||
params[:q][:meta_sort] ||= "completed_at.desc"
|
||||
prepare_date_params params
|
||||
|
||||
@search = Spree::Order.complete.not_state(:canceled).managed_by(spree_current_user).search(params[:q])
|
||||
orders = @search.result
|
||||
@@ -105,18 +131,7 @@ Spree::Admin::ReportsController.class_eval do
|
||||
end
|
||||
|
||||
def sales_tax
|
||||
params[:q] ||= {}
|
||||
|
||||
if params[:q][:completed_at_gt].blank?
|
||||
params[:q][:completed_at_gt] = Time.zone.now.beginning_of_month
|
||||
else
|
||||
params[:q][:completed_at_gt] = Time.zone.parse(params[:q][:completed_at_gt]).beginning_of_day rescue Time.zone.now.beginning_of_month
|
||||
end
|
||||
|
||||
if params[:q] && !params[:q][:completed_at_lt].blank?
|
||||
params[:q][:completed_at_lt] = Time.zone.parse(params[:q][:completed_at_lt]).end_of_day rescue ""
|
||||
end
|
||||
params[:q][:meta_sort] ||= "completed_at.desc"
|
||||
prepare_date_params params
|
||||
|
||||
@search = Spree::Order.complete.not_state(:canceled).managed_by(spree_current_user).search(params[:q])
|
||||
orders = @search.result
|
||||
@@ -135,300 +150,47 @@ Spree::Admin::ReportsController.class_eval do
|
||||
end
|
||||
|
||||
def bulk_coop
|
||||
params[:q] ||= {}
|
||||
|
||||
if params[:q][:completed_at_gt].blank?
|
||||
params[:q][:completed_at_gt] = Time.zone.now.beginning_of_month
|
||||
else
|
||||
params[:q][:completed_at_gt] = Time.zone.parse(params[:q][:completed_at_gt]).beginning_of_day rescue Time.zone.now.beginning_of_month
|
||||
end
|
||||
|
||||
if params[:q] && !params[:q][:completed_at_lt].blank?
|
||||
params[:q][:completed_at_lt] = Time.zone.parse(params[:q][:completed_at_lt]).end_of_day rescue ""
|
||||
end
|
||||
params[:q][:meta_sort] ||= "completed_at.desc"
|
||||
|
||||
@search = Spree::Order.complete.not_state(:canceled).managed_by(spree_current_user).search(params[:q])
|
||||
|
||||
orders = @search.result
|
||||
@line_items = orders.map { |o| o.line_items.managed_by(spree_current_user) }.flatten
|
||||
# -- Prepare date parameters
|
||||
prepare_date_params params
|
||||
|
||||
# -- Prepare form options
|
||||
@distributors = Enterprise.is_distributor.managed_by(spree_current_user)
|
||||
@report_type = params[:report_type]
|
||||
|
||||
case params[:report_type]
|
||||
when "bulk_coop_supplier_report"
|
||||
# -- Build Report with Order Grouper
|
||||
@report = OpenFoodNetwork::BulkCoopReport.new spree_current_user, params
|
||||
order_grouper = OpenFoodNetwork::OrderGrouper.new @report.rules, @report.columns
|
||||
@table = order_grouper.table(@report.table_items)
|
||||
csv_file_name = "bulk_coop_#{params[:report_type]}_#{timestamp}.csv"
|
||||
|
||||
header = ["Supplier", "Product", "Unit Size", "Variant", "Weight", "Sum Total", "Sum Max Total", "Units Required", "Remainder"]
|
||||
|
||||
columns = [ proc { |lis| lis.first.variant.product.supplier.name },
|
||||
proc { |lis| lis.first.variant.product.name },
|
||||
proc { |lis| lis.first.variant.product.group_buy ? (lis.first.variant.product.group_buy_unit_size || 0.0) : "" },
|
||||
proc { |lis| lis.first.variant.full_name },
|
||||
proc { |lis| lis.first.variant.weight || 0 },
|
||||
proc { |lis| lis.sum { |li| li.quantity } },
|
||||
proc { |lis| lis.sum { |li| li.max_quantity || 0 } },
|
||||
proc { |lis| "" },
|
||||
proc { |lis| "" } ]
|
||||
|
||||
rules = [ { group_by: proc { |li| li.variant.product.supplier },
|
||||
sort_by: proc { |supplier| supplier.name } },
|
||||
{ group_by: proc { |li| li.variant.product },
|
||||
sort_by: proc { |product| product.name },
|
||||
summary_columns: [ proc { |lis| lis.first.variant.product.supplier.name },
|
||||
proc { |lis| lis.first.variant.product.name },
|
||||
proc { |lis| lis.first.variant.product.group_buy ? (lis.first.variant.product.group_buy_unit_size || 0.0) : "" },
|
||||
proc { |lis| "" },
|
||||
proc { |lis| "" },
|
||||
proc { |lis| lis.sum { |li| (li.quantity || 0) * (li.variant.weight || 0) } },
|
||||
proc { |lis| lis.sum { |li| (li.max_quantity || 0) * (li.variant.weight || 0) } },
|
||||
proc { |lis| ( (lis.first.variant.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max ) * (li.variant.weight || 0) } / lis.first.variant.product.group_buy_unit_size ) ).floor },
|
||||
proc { |lis| lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max) * (li.variant.weight || 0) } - ( ( (lis.first.variant.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max) * (li.variant.weight || 0) } / lis.first.variant.product.group_buy_unit_size ) ).floor * (lis.first.variant.product.group_buy_unit_size || 0) ) } ] },
|
||||
{ group_by: proc { |li| li.variant },
|
||||
sort_by: proc { |variant| variant.full_name } } ]
|
||||
|
||||
when "bulk_coop_allocation"
|
||||
|
||||
header = ["Customer", "Product", "Unit Size", "Variant", "Weight", "Sum Total", "Sum Max Total", "Total Allocated", "Remainder"]
|
||||
|
||||
columns = [ proc { |lis| lis.first.order.bill_address.firstname + " " + lis.first.order.bill_address.lastname },
|
||||
proc { |lis| lis.first.variant.product.name },
|
||||
proc { |lis| lis.first.variant.product.group_buy ? (lis.first.variant.product.group_buy_unit_size || 0.0) : "" },
|
||||
proc { |lis| lis.first.variant.full_name },
|
||||
proc { |lis| lis.first.variant.weight || 0 },
|
||||
proc { |lis| lis.sum { |li| li.quantity } },
|
||||
proc { |lis| lis.sum { |li| li.max_quantity || 0 } },
|
||||
proc { |lis| "" },
|
||||
proc { |lis| "" } ]
|
||||
|
||||
rules = [ { group_by: proc { |li| li.variant.product },
|
||||
sort_by: proc { |product| product.name },
|
||||
summary_columns: [ proc { |lis| "TOTAL" },
|
||||
proc { |lis| lis.first.variant.product.name },
|
||||
proc { |lis| lis.first.variant.product.group_buy ? (lis.first.variant.product.group_buy_unit_size || 0.0) : "" },
|
||||
proc { |lis| "" },
|
||||
proc { |lis| "" },
|
||||
proc { |lis| lis.sum { |li| li.quantity * (li.variant.weight || 0) } },
|
||||
proc { |lis| lis.sum { |li| (li.max_quantity || 0) * (li.variant.weight || 0) } },
|
||||
proc { |lis| ( (lis.first.variant.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max ) * (li.variant.weight || 0) } / lis.first.variant.product.group_buy_unit_size ) ).floor * (lis.first.variant.product.group_buy_unit_size || 0) },
|
||||
proc { |lis| lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max ) * (li.variant.weight || 0) } - ( ( (lis.first.variant.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max ) * (li.variant.weight || 0) } / lis.first.variant.product.group_buy_unit_size ) ).floor * (lis.first.variant.product.group_buy_unit_size || 0) ) } ] },
|
||||
{ group_by: proc { |li| li.variant },
|
||||
sort_by: proc { |variant| variant.full_name } },
|
||||
{ group_by: proc { |li| li.order },
|
||||
sort_by: proc { |order| order.to_s } } ]
|
||||
|
||||
when "bulk_coop_packing_sheets"
|
||||
|
||||
header = ["Customer", "Product", "Variant", "Sum Total"]
|
||||
|
||||
columns = [ proc { |lis| lis.first.order.bill_address.firstname + " " + lis.first.order.bill_address.lastname },
|
||||
proc { |lis| lis.first.variant.product.name },
|
||||
proc { |lis| lis.first.variant.full_name },
|
||||
proc { |lis| lis.sum { |li| li.quantity } } ]
|
||||
|
||||
rules = [ { group_by: proc { |li| li.variant.product },
|
||||
sort_by: proc { |product| product.name } },
|
||||
{ group_by: proc { |li| li.variant },
|
||||
sort_by: proc { |variant| variant.full_name } },
|
||||
{ group_by: proc { |li| li.order },
|
||||
sort_by: proc { |order| order.to_s } } ]
|
||||
|
||||
when "bulk_coop_customer_payments"
|
||||
|
||||
header = ["Customer", "Date of Order", "Total Cost", "Amount Owing", "Amount Paid"]
|
||||
|
||||
columns = [ proc { |lis| lis.first.order.bill_address.firstname + " " + lis.first.order.bill_address.lastname },
|
||||
proc { |lis| lis.first.order.completed_at.to_s },
|
||||
proc { |lis| lis.map { |li| li.order }.uniq.sum { |o| o.total } },
|
||||
proc { |lis| lis.map { |li| li.order }.uniq.sum { |o| o.outstanding_balance } },
|
||||
proc { |lis| lis.map { |li| li.order }.uniq.sum { |o| o.payment_total } } ]
|
||||
|
||||
rules = [ { group_by: proc { |li| li.order },
|
||||
sort_by: proc { |order| order.completed_at } } ]
|
||||
|
||||
else # List all line items
|
||||
|
||||
header = ["Supplier", "Product", "Unit Size", "Variant", "Weight", "Sum Total", "Sum Max Total", "Units Required", "Remainder"]
|
||||
|
||||
columns = [ proc { |lis| lis.first.variant.product.supplier.name },
|
||||
proc { |lis| lis.first.variant.product.name },
|
||||
proc { |lis| lis.first.variant.product.group_buy ? (lis.first.variant.product.group_buy_unit_size || 0.0) : "" },
|
||||
proc { |lis| lis.first.variant.full_name },
|
||||
proc { |lis| lis.first.variant.weight || 0 },
|
||||
proc { |lis| lis.sum { |li| li.quantity } },
|
||||
proc { |lis| lis.sum { |li| li.max_quantity || 0 } },
|
||||
proc { |lis| "" },
|
||||
proc { |lis| "" } ]
|
||||
|
||||
rules = [ { group_by: proc { |li| li.variant.product.supplier },
|
||||
sort_by: proc { |supplier| supplier.name } },
|
||||
{ group_by: proc { |li| li.variant.product },
|
||||
sort_by: proc { |product| product.name },
|
||||
summary_columns: [ proc { |lis| lis.first.variant.product.supplier.name },
|
||||
proc { |lis| lis.first.variant.product.name },
|
||||
proc { |lis| lis.first.variant.product.group_buy ? (lis.first.variant.product.group_buy_unit_size || 0.0) : "" },
|
||||
proc { |lis| "" },
|
||||
proc { |lis| "" },
|
||||
proc { |lis| lis.sum { |li| li.quantity * (li.variant.weight || 0) } },
|
||||
proc { |lis| lis.sum { |li| (li.max_quantity || 0) * (li.variant.weight || 0) } },
|
||||
proc { |lis| ( (lis.first.variant.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max ) * (li.variant.weight || 0) } / lis.first.variant.product.group_buy_unit_size ) ).floor },
|
||||
proc { |lis| lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max ) * (li.variant.weight || 0) } - ( ( (lis.first.variant.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max ) * (li.variant.weight || 0) } / lis.first.variant.product.group_buy_unit_size ) ).floor * (lis.first.variant.product.group_buy_unit_size || 0) ) } ] },
|
||||
{ group_by: proc { |li| li.variant },
|
||||
sort_by: proc { |variant| variant.full_name } } ]
|
||||
|
||||
end
|
||||
|
||||
order_grouper = OpenFoodNetwork::OrderGrouper.new rules, columns
|
||||
|
||||
@header = header
|
||||
@table = order_grouper.table(@line_items)
|
||||
csv_file_name = "bulk_coop_#{timestamp}.csv"
|
||||
|
||||
render_report(@header, @table, params[:csv], csv_file_name)
|
||||
render_report(@report.header, @table, params[:csv], csv_file_name)
|
||||
end
|
||||
|
||||
def payments
|
||||
params[:q] ||= {}
|
||||
|
||||
if params[:q][:completed_at_gt].blank?
|
||||
params[:q][:completed_at_gt] = Time.zone.now.beginning_of_month
|
||||
else
|
||||
params[:q][:completed_at_gt] = Time.zone.parse(params[:q][:completed_at_gt]).beginning_of_day rescue Time.zone.now.beginning_of_month
|
||||
end
|
||||
|
||||
if params[:q] && !params[:q][:completed_at_lt].blank?
|
||||
params[:q][:completed_at_lt] = Time.zone.parse(params[:q][:completed_at_lt]).end_of_day rescue ""
|
||||
end
|
||||
params[:q][:meta_sort] ||= "completed_at.desc"
|
||||
|
||||
@search = Spree::Order.complete.not_state(:canceled).managed_by(spree_current_user).search(params[:q])
|
||||
|
||||
orders = @search.result
|
||||
payments = orders.map { |o| o.payments.select { |payment| payment.completed? } }.flatten # Only select completed payments
|
||||
# -- Prepare Date Params
|
||||
prepare_date_params params
|
||||
|
||||
# -- Prepare Form Options
|
||||
@distributors = Enterprise.is_distributor.managed_by(spree_current_user)
|
||||
@report_type = params[:report_type]
|
||||
|
||||
case params[:report_type]
|
||||
when "payments_by_payment_type"
|
||||
table_items = payments
|
||||
|
||||
header = ["Payment State", "Distributor", "Payment Type", "Total (#{currency_symbol})"]
|
||||
|
||||
columns = [ proc { |payments| payments.first.order.payment_state },
|
||||
proc { |payments| payments.first.order.distributor.name },
|
||||
proc { |payments| payments.first.payment_method.name },
|
||||
proc { |payments| payments.sum { |payment| payment.amount } } ]
|
||||
|
||||
rules = [ { group_by: proc { |payment| payment.order.payment_state },
|
||||
sort_by: proc { |payment_state| payment_state } },
|
||||
{ group_by: proc { |payment| payment.order.distributor },
|
||||
sort_by: proc { |distributor| distributor.name } },
|
||||
{ group_by: proc { |payment| Spree::PaymentMethod.unscoped { payment.payment_method } },
|
||||
sort_by: proc { |method| method.name } } ]
|
||||
|
||||
when "itemised_payment_totals"
|
||||
table_items = orders
|
||||
|
||||
header = ["Payment State", "Distributor", "Product Total (#{currency_symbol})", "Shipping Total (#{currency_symbol})", "Outstanding Balance (#{currency_symbol})", "Total (#{currency_symbol})"]
|
||||
|
||||
columns = [ proc { |orders| orders.first.payment_state },
|
||||
proc { |orders| orders.first.distributor.name },
|
||||
proc { |orders| orders.sum { |o| o.item_total } },
|
||||
proc { |orders| orders.sum { |o| o.ship_total } },
|
||||
proc { |orders| orders.sum { |o| o.outstanding_balance } },
|
||||
proc { |orders| orders.sum { |o| o.total } } ]
|
||||
|
||||
rules = [ { group_by: proc { |order| order.payment_state },
|
||||
sort_by: proc { |payment_state| payment_state } },
|
||||
{ group_by: proc { |order| order.distributor },
|
||||
sort_by: proc { |distributor| distributor.name } } ]
|
||||
|
||||
when "payment_totals"
|
||||
table_items = orders
|
||||
|
||||
header = ["Payment State", "Distributor", "Product Total (#{currency_symbol})", "Shipping Total (#{currency_symbol})", "Total (#{currency_symbol})", "EFT (#{currency_symbol})", "PayPal (#{currency_symbol})", "Outstanding Balance (#{currency_symbol})"]
|
||||
|
||||
columns = [ proc { |orders| orders.first.payment_state },
|
||||
proc { |orders| orders.first.distributor.name },
|
||||
proc { |orders| orders.sum { |o| o.item_total } },
|
||||
proc { |orders| orders.sum { |o| o.ship_total } },
|
||||
proc { |orders| orders.sum { |o| o.total } },
|
||||
proc { |orders| orders.sum { |o| o.payments.select { |payment| payment.completed? && (payment.payment_method.name.to_s.include? "EFT") }.sum { |payment| payment.amount } } },
|
||||
proc { |orders| orders.sum { |o| o.payments.select { |payment| payment.completed? && (payment.payment_method.name.to_s.include? "PayPal") }.sum{ |payment| payment.amount } } },
|
||||
proc { |orders| orders.sum { |o| o.outstanding_balance } } ]
|
||||
|
||||
rules = [ { group_by: proc { |order| order.payment_state },
|
||||
sort_by: proc { |payment_state| payment_state } },
|
||||
{ group_by: proc { |order| order.distributor },
|
||||
sort_by: proc { |distributor| distributor.name } } ]
|
||||
|
||||
else
|
||||
table_items = payments
|
||||
|
||||
header = ["Payment State", "Distributor", "Payment Type", "Total (#{currency_symbol})"]
|
||||
|
||||
columns = [ proc { |payments| payments.first.order.payment_state },
|
||||
proc { |payments| payments.first.order.distributor.name },
|
||||
proc { |payments| payments.first.payment_method.name },
|
||||
proc { |payments| payments.sum { |payment| payment.amount } } ]
|
||||
|
||||
rules = [ { group_by: proc { |payment| payment.order.payment_state },
|
||||
sort_by: proc { |payment_state| payment_state } },
|
||||
{ group_by: proc { |payment| payment.order.distributor },
|
||||
sort_by: proc { |distributor| distributor.name } },
|
||||
{ group_by: proc { |payment| payment.payment_method },
|
||||
sort_by: proc { |method| method.name } } ]
|
||||
|
||||
end
|
||||
|
||||
order_grouper = OpenFoodNetwork::OrderGrouper.new rules, columns
|
||||
|
||||
@header = header
|
||||
@table = order_grouper.table(table_items)
|
||||
# -- Build Report with Order Grouper
|
||||
@report = OpenFoodNetwork::PaymentsReport.new spree_current_user, params
|
||||
order_grouper = OpenFoodNetwork::OrderGrouper.new @report.rules, @report.columns
|
||||
@table = order_grouper.table(@report.table_items)
|
||||
csv_file_name = "payments_#{timestamp}.csv"
|
||||
|
||||
render_report(@header, @table, params[:csv], csv_file_name)
|
||||
|
||||
render_report(@report.header, @table, params[:csv], csv_file_name)
|
||||
end
|
||||
|
||||
def orders_and_fulfillment
|
||||
# -- Prepare parameters
|
||||
params[:q] ||= {}
|
||||
|
||||
if params[:q][:completed_at_gt].blank?
|
||||
params[:q][:completed_at_gt] = Time.zone.now.beginning_of_month
|
||||
else
|
||||
params[:q][:completed_at_gt] = Time.zone.parse(params[:q][:completed_at_gt]) rescue Time.zone.now.beginning_of_month
|
||||
end
|
||||
|
||||
if params[:q] && !params[:q][:completed_at_lt].blank?
|
||||
params[:q][:completed_at_lt] = Time.zone.parse(params[:q][:completed_at_lt]) rescue ""
|
||||
end
|
||||
params[:q][:meta_sort] ||= "completed_at.desc"
|
||||
# -- Prepare Date Params
|
||||
prepare_date_params params
|
||||
|
||||
# -- Prepare Form Options
|
||||
permissions = OpenFoodNetwork::Permissions.new(spree_current_user)
|
||||
|
||||
# -- Search
|
||||
|
||||
@search = Spree::Order.complete.not_state(:canceled).search(params[:q])
|
||||
orders = permissions.visible_orders.merge(@search.result)
|
||||
|
||||
@line_items = permissions.visible_line_items.merge(Spree::LineItem.where(order_id: orders))
|
||||
@line_items = @line_items.supplied_by_any(params[:supplier_id_in]) if params[:supplier_id_in].present?
|
||||
|
||||
line_items_with_hidden_details = @line_items.where('"spree_line_items"."id" NOT IN (?)', permissions.editable_line_items)
|
||||
@line_items.select{ |li| line_items_with_hidden_details.include? li }.each do |line_item|
|
||||
# TODO We should really be hiding customer code here too, but until we
|
||||
# have an actual association between order and customer, it's a bit tricky
|
||||
line_item.order.bill_address.assign_attributes(firstname: "HIDDEN", lastname: "", phone: "", address1: "", address2: "", city: "", zipcode: "", state: nil)
|
||||
line_item.order.ship_address.assign_attributes(firstname: "HIDDEN", lastname: "", phone: "", address1: "", address2: "", city: "", zipcode: "", state: nil)
|
||||
line_item.order.assign_attributes(email: "HIDDEN")
|
||||
end
|
||||
|
||||
# My distributors and any distributors distributing products I supply
|
||||
@distributors = permissions.visible_enterprises_for_order_reports.is_distributor
|
||||
|
||||
# My suppliers and any suppliers supplying products I distribute
|
||||
@suppliers = permissions.visible_enterprises_for_order_reports.is_primary_producer
|
||||
|
||||
@@ -438,239 +200,25 @@ Spree::Admin::ReportsController.class_eval do
|
||||
@report_types = REPORT_TYPES[:orders_and_fulfillment]
|
||||
@report_type = params[:report_type]
|
||||
|
||||
# -- Format according to report type
|
||||
case params[:report_type]
|
||||
when "order_cycle_supplier_totals"
|
||||
table_items = @line_items
|
||||
@include_blank = 'All'
|
||||
@include_blank = 'All'
|
||||
|
||||
header = ["Producer", "Product", "Variant", "Amount", "Total Units", "Curr. Cost per Unit", "Total Cost", "Status", "Incoming Transport"]
|
||||
|
||||
columns = [ proc { |line_items| line_items.first.variant.product.supplier.name },
|
||||
proc { |line_items| line_items.first.variant.product.name },
|
||||
proc { |line_items| line_items.first.variant.full_name },
|
||||
proc { |line_items| line_items.sum { |li| li.quantity } },
|
||||
proc { |line_items| total_units(line_items) },
|
||||
proc { |line_items| line_items.first.price },
|
||||
proc { |line_items| line_items.sum { |li| li.amount } },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "incoming transport" } ]
|
||||
|
||||
rules = [ { group_by: proc { |line_item| line_item.variant.product.supplier },
|
||||
sort_by: proc { |supplier| supplier.name } },
|
||||
{ group_by: proc { |line_item| line_item.variant.product },
|
||||
sort_by: proc { |product| product.name } },
|
||||
{ group_by: proc { |line_item| line_item.variant },
|
||||
sort_by: proc { |variant| variant.full_name } } ]
|
||||
|
||||
when "order_cycle_supplier_totals_by_distributor"
|
||||
table_items = @line_items
|
||||
@include_blank = 'All'
|
||||
|
||||
header = ["Producer", "Product", "Variant", "To Hub", "Amount", "Curr. Cost per Unit", "Total Cost", "Shipping Method"]
|
||||
|
||||
columns = [ proc { |line_items| line_items.first.variant.product.supplier.name },
|
||||
proc { |line_items| line_items.first.variant.product.name },
|
||||
proc { |line_items| line_items.first.variant.full_name },
|
||||
proc { |line_items| line_items.first.order.distributor.name },
|
||||
proc { |line_items| line_items.sum { |li| li.quantity } },
|
||||
proc { |line_items| line_items.first.price },
|
||||
proc { |line_items| line_items.sum { |li| li.amount } },
|
||||
proc { |line_items| "shipping method" } ]
|
||||
|
||||
rules = [ { group_by: proc { |line_item| line_item.variant.product.supplier },
|
||||
sort_by: proc { |supplier| supplier.name } },
|
||||
{ group_by: proc { |line_item| line_item.variant.product },
|
||||
sort_by: proc { |product| product.name } },
|
||||
{ group_by: proc { |line_item| line_item.variant },
|
||||
sort_by: proc { |variant| variant.full_name },
|
||||
summary_columns: [ proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "TOTAL" },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| line_items.sum { |li| li.amount } },
|
||||
proc { |line_items| "" } ] },
|
||||
{ group_by: proc { |line_item| line_item.order.distributor },
|
||||
sort_by: proc { |distributor| distributor.name } } ]
|
||||
|
||||
when "order_cycle_distributor_totals_by_supplier"
|
||||
table_items = @line_items
|
||||
@include_blank = 'All'
|
||||
|
||||
header = ["Hub", "Producer", "Product", "Variant", "Amount", "Curr. Cost per Unit", "Total Cost", "Total Shipping Cost", "Shipping Method"]
|
||||
|
||||
columns = [ proc { |line_items| line_items.first.order.distributor.name },
|
||||
proc { |line_items| line_items.first.variant.product.supplier.name },
|
||||
proc { |line_items| line_items.first.variant.product.name },
|
||||
proc { |line_items| line_items.first.variant.full_name },
|
||||
proc { |line_items| line_items.sum { |li| li.quantity } },
|
||||
proc { |line_items| line_items.first.price },
|
||||
proc { |line_items| line_items.sum { |li| li.amount } },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "shipping method" } ]
|
||||
|
||||
rules = [ { group_by: proc { |line_item| line_item.order.distributor },
|
||||
sort_by: proc { |distributor| distributor.name },
|
||||
summary_columns: [ proc { |line_items| "" },
|
||||
proc { |line_items| "TOTAL" },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| line_items.sum { |li| li.amount } },
|
||||
proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.ship_total } },
|
||||
proc { |line_items| "" } ] },
|
||||
{ group_by: proc { |line_item| line_item.variant.product.supplier },
|
||||
sort_by: proc { |supplier| supplier.name } },
|
||||
{ group_by: proc { |line_item| line_item.variant.product },
|
||||
sort_by: proc { |product| product.name } },
|
||||
{ group_by: proc { |line_item| line_item.variant },
|
||||
sort_by: proc { |variant| variant.full_name } } ]
|
||||
|
||||
when "order_cycle_customer_totals"
|
||||
table_items = @line_items
|
||||
@include_blank = 'All'
|
||||
|
||||
header = ["Hub", "Customer", "Email", "Phone", "Producer", "Product", "Variant",
|
||||
"Amount", "Item (#{currency_symbol})", "Item + Fees (#{currency_symbol})", "Admin & Handling (#{currency_symbol})", "Ship (#{currency_symbol})", "Total (#{currency_symbol})", "Paid?",
|
||||
"Shipping", "Delivery?",
|
||||
"Ship Street", "Ship Street 2", "Ship City", "Ship Postcode", "Ship State",
|
||||
"Comments", "SKU",
|
||||
"Order Cycle", "Payment Method", "Customer Code", "Tags",
|
||||
"Billing Street 1", "Billing Street 2", "Billing City", "Billing Postcode", "Billing State"
|
||||
]
|
||||
|
||||
rsa = proc { |line_items| line_items.first.order.shipping_method.andand.require_ship_address }
|
||||
|
||||
columns = [
|
||||
proc { |line_items| line_items.first.order.distributor.name },
|
||||
proc { |line_items| line_items.first.order.bill_address.firstname + " " + line_items.first.order.bill_address.lastname },
|
||||
proc { |line_items| line_items.first.order.email },
|
||||
proc { |line_items| line_items.first.order.bill_address.phone },
|
||||
proc { |line_items| line_items.first.variant.product.supplier.name },
|
||||
proc { |line_items| line_items.first.variant.product.name },
|
||||
proc { |line_items| line_items.first.variant.full_name },
|
||||
|
||||
proc { |line_items| line_items.sum { |li| li.quantity } },
|
||||
proc { |line_items| line_items.sum { |li| li.amount } },
|
||||
proc { |line_items| line_items.sum { |li| li.amount_with_adjustments } },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
|
||||
proc { |line_items| line_items.first.order.shipping_method.andand.name },
|
||||
proc { |line_items| rsa.call(line_items) ? 'Y' : 'N' },
|
||||
|
||||
proc { |line_items| line_items.first.order.ship_address.andand.address1 if rsa.call(line_items) },
|
||||
proc { |line_items| line_items.first.order.ship_address.andand.address2 if rsa.call(line_items) },
|
||||
proc { |line_items| line_items.first.order.ship_address.andand.city if rsa.call(line_items) },
|
||||
proc { |line_items| line_items.first.order.ship_address.andand.zipcode if rsa.call(line_items) },
|
||||
proc { |line_items| line_items.first.order.ship_address.andand.state if rsa.call(line_items) },
|
||||
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| line_items.first.variant.product.sku },
|
||||
|
||||
proc { |line_items| line_items.first.order.order_cycle.andand.name },
|
||||
proc { |line_items| line_items.first.order.payments.first.andand.payment_method.andand.name },
|
||||
proc { |line_items| line_items.first.order.user.andand.customer_of(line_items.first.order.distributor).andand.code },
|
||||
proc { |line_items| "" },
|
||||
|
||||
proc { |line_items| line_items.first.order.bill_address.andand.address1 },
|
||||
proc { |line_items| line_items.first.order.bill_address.andand.address2 },
|
||||
proc { |line_items| line_items.first.order.bill_address.andand.city },
|
||||
proc { |line_items| line_items.first.order.bill_address.andand.zipcode },
|
||||
proc { |line_items| line_items.first.order.bill_address.andand.state } ]
|
||||
|
||||
rules = [ { group_by: proc { |line_item| line_item.order.distributor },
|
||||
sort_by: proc { |distributor| distributor.name } },
|
||||
{ group_by: proc { |line_item| line_item.order },
|
||||
sort_by: proc { |order| order.bill_address.lastname + " " + order.bill_address.firstname },
|
||||
summary_columns: [
|
||||
proc { |line_items| line_items.first.order.distributor.name },
|
||||
proc { |line_items| line_items.first.order.bill_address.firstname + " " + line_items.first.order.bill_address.lastname },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "TOTAL" },
|
||||
proc { |line_items| "" },
|
||||
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| line_items.sum { |li| li.amount } },
|
||||
proc { |line_items| line_items.sum { |li| li.amount_with_adjustments } },
|
||||
proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.admin_and_handling_total } },
|
||||
proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.ship_total } },
|
||||
proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.total } },
|
||||
proc { |line_items| line_items.all? { |li| li.order.paid? } ? "Yes" : "No" },
|
||||
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
|
||||
proc { |line_items| line_items.first.order.special_instructions } ,
|
||||
proc { |line_items| "" },
|
||||
|
||||
proc { |line_items| line_items.first.order.order_cycle.andand.name },
|
||||
proc { |line_items| line_items.first.order.payments.first.andand.payment_method.andand.name },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "" }
|
||||
] },
|
||||
|
||||
{ group_by: proc { |line_item| line_item.variant.product },
|
||||
sort_by: proc { |product| product.name } },
|
||||
{ group_by: proc { |line_item| line_item.variant },
|
||||
sort_by: proc { |variant| variant.full_name } } ]
|
||||
|
||||
else
|
||||
table_items = @line_items
|
||||
@include_blank = 'All'
|
||||
|
||||
header = ["Producer", "Product", "Variant", "Amount", "Curr. Cost per Unit", "Total Cost", "Status", "Incoming Transport"]
|
||||
|
||||
columns = [ proc { |line_items| line_items.first.variant.product.supplier.name },
|
||||
proc { |line_items| line_items.first.variant.product.name },
|
||||
proc { |line_items| line_items.first.variant.full_name },
|
||||
proc { |line_items| line_items.sum { |li| li.quantity } },
|
||||
proc { |line_items| line_items.first.price },
|
||||
proc { |line_items| line_items.sum { |li| li.quantity * li.price } },
|
||||
proc { |line_items| "" },
|
||||
proc { |line_items| "incoming transport" } ]
|
||||
|
||||
rules = [ { group_by: proc { |line_item| line_item.variant.product.supplier },
|
||||
sort_by: proc { |supplier| supplier.name } },
|
||||
{ group_by: proc { |line_item| line_item.variant.product },
|
||||
sort_by: proc { |product| product.name } },
|
||||
{ group_by: proc { |line_item| line_item.variant },
|
||||
sort_by: proc { |variant| variant.full_name } } ]
|
||||
|
||||
end
|
||||
|
||||
order_grouper = OpenFoodNetwork::OrderGrouper.new rules, columns
|
||||
|
||||
@header = header
|
||||
@table = order_grouper.table(table_items)
|
||||
# -- Build Report with Order Grouper
|
||||
@report = OpenFoodNetwork::OrdersAndFulfillmentsReport.new spree_current_user, params
|
||||
order_grouper = OpenFoodNetwork::OrderGrouper.new @report.rules, @report.columns
|
||||
@table = order_grouper.table(@report.table_items)
|
||||
csv_file_name = "#{params[:report_type]}_#{timestamp}.csv"
|
||||
|
||||
render_report(@header, @table, params[:csv], csv_file_name)
|
||||
render_report(@report.header, @table, params[:csv], csv_file_name)
|
||||
|
||||
end
|
||||
|
||||
def products_and_inventory
|
||||
@report_types = REPORT_TYPES[:products_and_inventory]
|
||||
@report = OpenFoodNetwork::ProductsAndInventoryReport.new spree_current_user, params
|
||||
if params[:report_type] != 'lettuce_share'
|
||||
@report = OpenFoodNetwork::ProductsAndInventoryReport.new spree_current_user, params
|
||||
else
|
||||
@report = OpenFoodNetwork::LettuceShareReport.new spree_current_user, params
|
||||
end
|
||||
render_report(@report.header, @report.table, params[:csv], "products_and_inventory_#{timestamp}.csv")
|
||||
end
|
||||
|
||||
@@ -709,6 +257,20 @@ Spree::Admin::ReportsController.class_eval do
|
||||
|
||||
private
|
||||
|
||||
def prepare_date_params(params)
|
||||
# -- Prepare parameters
|
||||
params[:q] ||= {}
|
||||
if params[:q][:completed_at_gt].blank?
|
||||
params[:q][:completed_at_gt] = Time.zone.now.beginning_of_month
|
||||
else
|
||||
params[:q][:completed_at_gt] = Time.zone.parse(params[:q][:completed_at_gt]) rescue Time.zone.now.beginning_of_month
|
||||
end
|
||||
if params[:q] && !params[:q][:completed_at_lt].blank?
|
||||
params[:q][:completed_at_lt] = Time.zone.parse(params[:q][:completed_at_lt]) rescue ""
|
||||
end
|
||||
params[:q][:meta_sort] ||= "completed_at.desc"
|
||||
end
|
||||
|
||||
def load_data
|
||||
# Load distributors either owned by the user or selling their enterprises products.
|
||||
my_distributors = Enterprise.is_distributor.managed_by(spree_current_user)
|
||||
@@ -732,23 +294,14 @@ 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 => ''},
|
||||
:packing => {:name => "Packing Reports", :description => ''},
|
||||
: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 }
|
||||
end
|
||||
|
||||
def total_units(line_items)
|
||||
return " " if line_items.map{ |li| li.variant.unit_value.nil? }.any?
|
||||
total_units = line_items.sum do |li|
|
||||
scale_factor = ( li.product.variant_unit == 'weight' ? 1000 : 1 )
|
||||
li.quantity * li.variant.unit_value / scale_factor
|
||||
end
|
||||
total_units.round(3)
|
||||
end
|
||||
|
||||
def timestamp
|
||||
Time.now.strftime("%Y%m%d")
|
||||
end
|
||||
|
||||
@@ -14,9 +14,10 @@ Spree::Admin::VariantsController.class_eval do
|
||||
if params[:distributor_id].present?
|
||||
distributor = Enterprise.find params[:distributor_id]
|
||||
@variants = @variants.in_distributor(distributor)
|
||||
scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor)
|
||||
# Perform scoping after all filtering is done.
|
||||
# Filtering could be a problem on scoped variants.
|
||||
@variants.each { |v| v.scope_to_hub(distributor) }
|
||||
@variants.each { |v| scoper.scope(v) }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -15,6 +15,16 @@ module Spree
|
||||
|
||||
link_to_with_icon('icon-trash', name, url, :class => "remove_fields #{options[:class]}", :data => {:action => 'remove'}, :title => t(:remove)) + f.hidden_field(:_destroy)
|
||||
end
|
||||
|
||||
|
||||
def preference_field_tag_with_files(name, value, options)
|
||||
if options[:type] == :file
|
||||
file_field_tag name, preference_field_options(options)
|
||||
else
|
||||
preference_field_tag_without_files name, value, options
|
||||
end
|
||||
end
|
||||
alias_method_chain :preference_field_tag, :files
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,39 @@
|
||||
class ContentConfiguration < Spree::Preferences::Configuration
|
||||
preference :home_tagline_cta, :string, default: "Browse Open Food Network Australia"
|
||||
preference :home_whats_happening, :string, default: "Thanks for making the Open Food Network possible. Our vision is a better food system, and we're proud of what we're achieving together."
|
||||
require 'open_food_network/paperclippable'
|
||||
|
||||
class ContentConfiguration < Spree::Preferences::FileConfiguration
|
||||
include OpenFoodNetwork::Paperclippable
|
||||
|
||||
# Header
|
||||
preference :logo, :file
|
||||
preference :logo_mobile, :file
|
||||
preference :logo_mobile_svg, :file
|
||||
has_attached_file :logo
|
||||
has_attached_file :logo_mobile
|
||||
has_attached_file :logo_mobile_svg
|
||||
|
||||
# Home page
|
||||
preference :home_hero, :file
|
||||
preference :home_show_stats, :boolean, default: true
|
||||
has_attached_file :home_hero
|
||||
|
||||
# Producer sign-up page
|
||||
preference :producer_signup_pricing_table_html, :text, default: "(TODO: Pricing table)"
|
||||
preference :producer_signup_case_studies_html, :text, default: "(TODO: Case studies)"
|
||||
preference :producer_signup_detail_html, :text, default: "(TODO: Detail)"
|
||||
|
||||
# Hubs sign-up page
|
||||
preference :hub_signup_pricing_table_html, :text, default: "(TODO: Pricing table)"
|
||||
preference :hub_signup_case_studies_html, :text, default: "(TODO: Case studies)"
|
||||
preference :hub_signup_detail_html, :text, default: "(TODO: Detail)"
|
||||
|
||||
# Groups sign-up page
|
||||
preference :group_signup_pricing_table_html, :text, default: "(TODO: Pricing table)"
|
||||
preference :group_signup_case_studies_html, :text, default: "(TODO: Case studies)"
|
||||
preference :group_signup_detail_html, :text, default: "(TODO: Detail)"
|
||||
|
||||
# Footer
|
||||
preference :footer_logo, :file
|
||||
has_attached_file :footer_logo
|
||||
preference :footer_facebook_url, :string, default: "https://www.facebook.com/OpenFoodNet"
|
||||
preference :footer_twitter_url, :string, default: "https://twitter.com/OpenFoodNet"
|
||||
preference :footer_instagram_url, :string, default: ""
|
||||
@@ -18,4 +50,5 @@ class ContentConfiguration < Spree::Preferences::Configuration
|
||||
EOS
|
||||
|
||||
preference :footer_about_url, :string, default: "http://www.openfoodnetwork.org/ofn-local/open-food-network-australia/"
|
||||
preference :footer_tos_url, :string, default: "/Terms-of-service.pdf"
|
||||
end
|
||||
|
||||
@@ -15,6 +15,8 @@ class Enterprise < ActiveRecord::Base
|
||||
|
||||
acts_as_gmappable :process_geocoding => false
|
||||
|
||||
has_many :relationships_as_parent, class_name: 'EnterpriseRelationship', foreign_key: 'parent_id', dependent: :destroy
|
||||
has_many :relationships_as_child, class_name: 'EnterpriseRelationship', foreign_key: 'child_id', dependent: :destroy
|
||||
has_and_belongs_to_many :groups, class_name: 'EnterpriseGroup'
|
||||
has_many :producer_properties, foreign_key: 'producer_id'
|
||||
has_many :properties, through: :producer_properties
|
||||
@@ -56,6 +58,7 @@ class Enterprise < ActiveRecord::Base
|
||||
|
||||
|
||||
validates :name, presence: true
|
||||
validate :name_is_unique
|
||||
validates :sells, presence: true, inclusion: {in: SELLS}
|
||||
validates :address, presence: true, associated: true
|
||||
validates :email, presence: true
|
||||
@@ -333,6 +336,15 @@ class Enterprise < ActiveRecord::Base
|
||||
|
||||
private
|
||||
|
||||
def name_is_unique
|
||||
dups = Enterprise.where(name: name)
|
||||
dups = dups.where('id != ?', id) unless new_record?
|
||||
|
||||
if dups.any?
|
||||
errors.add :name, "has already been taken. If this is your enterprise and you would like to claim ownership, please contact the current manager of this profile at #{dups.first.owner.email}."
|
||||
end
|
||||
end
|
||||
|
||||
def email_is_known?
|
||||
owner.enterprises.confirmed.map(&:email).include?(email)
|
||||
end
|
||||
|
||||
@@ -32,10 +32,13 @@ class EnterpriseRelationship < ActiveRecord::Base
|
||||
relationships = EnterpriseRelationship.includes(:child, :parent)
|
||||
relatives = {}
|
||||
|
||||
Enterprise.all.each do |e|
|
||||
relatives[e.id] ||= { distributors: Set.new, producers: Set.new }
|
||||
relatives[e.id][:producers] << e.id if e.is_primary_producer
|
||||
relatives[e.id][:distributors] << e.id if e.is_distributor
|
||||
Enterprise.is_primary_producer.pluck(:id).each do |enterprise_id|
|
||||
relatives[enterprise_id] ||= { distributors: Set.new, producers: Set.new }
|
||||
relatives[enterprise_id][:producers] << enterprise_id
|
||||
end
|
||||
Enterprise.is_distributor.pluck(:id).each do |enterprise_id|
|
||||
relatives[enterprise_id] ||= { distributors: Set.new, producers: Set.new }
|
||||
relatives[enterprise_id][:distributors] << enterprise_id
|
||||
end
|
||||
|
||||
relationships.each do |r|
|
||||
|
||||
@@ -132,7 +132,12 @@ class OrderCycle < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def variants
|
||||
self.exchanges.map(&:variants).flatten.uniq.reject(&:deleted?)
|
||||
Spree::Variant.
|
||||
joins(:exchanges).
|
||||
merge(Exchange.in_order_cycle(self)).
|
||||
not_deleted.
|
||||
select('DISTINCT spree_variants.*').
|
||||
to_a # http://stackoverflow.com/q/15110166
|
||||
end
|
||||
|
||||
def distributed_variants
|
||||
|
||||
@@ -123,7 +123,7 @@ class AbilityDecorator
|
||||
can [:admin, :index, :read, :create, :edit], Spree::Classification
|
||||
|
||||
# Reports page
|
||||
can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management], :report
|
||||
can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing], :report
|
||||
end
|
||||
|
||||
def add_order_cycle_management_abilities(user)
|
||||
@@ -186,7 +186,7 @@ class AbilityDecorator
|
||||
end
|
||||
|
||||
# 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, :customers, :group_buys, :bulk_coop, :sales_tax, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :xero_invoices], :report
|
||||
|
||||
can [:admin, :index, :update], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id)
|
||||
end
|
||||
|
||||
@@ -4,9 +4,10 @@ module Spree
|
||||
return [] unless order.completed?
|
||||
|
||||
#increase inventory to meet initial requirements
|
||||
scoper = OpenFoodNetwork::ScopeVariantToHub.new(order.distributor)
|
||||
order.line_items.each do |line_item|
|
||||
# Scope variant to hub so that stock levels may be subtracted from VariantOverride.
|
||||
line_item.variant.scope_to_hub order.distributor
|
||||
scoper.scope(line_item.variant)
|
||||
|
||||
increase(order, line_item.variant, line_item.quantity)
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Spree::LineItem.class_eval do
|
||||
attr_accessible :max_quantity, :unit_value
|
||||
attr_accessible :unit_value, :price, :as => :api
|
||||
attr_accessible :max_quantity, :final_weight_volume
|
||||
attr_accessible :final_weight_volume, :price, :as => :api
|
||||
|
||||
# -- Scopes
|
||||
scope :managed_by, lambda { |user|
|
||||
|
||||
@@ -130,7 +130,7 @@ Spree::Order.class_eval do
|
||||
else
|
||||
current_item = Spree::LineItem.new(:quantity => quantity, max_quantity: max_quantity)
|
||||
current_item.variant = variant
|
||||
current_item.unit_value = variant.unit_value
|
||||
current_item.final_weight_volume = variant.unit_value * quantity
|
||||
if currency
|
||||
current_item.currency = currency unless currency.nil?
|
||||
current_item.price = variant.price_in(currency).amount
|
||||
|
||||
@@ -9,7 +9,7 @@ Spree::OrderPopulator.class_eval do
|
||||
errors.add(:base, "That distributor or order cycle can't supply all the products in your cart. Please choose another.")
|
||||
end
|
||||
|
||||
if valid?
|
||||
if valid?
|
||||
@order.with_lock do
|
||||
@order.empty! if overwrite
|
||||
|
||||
@@ -33,7 +33,7 @@ Spree::OrderPopulator.class_eval do
|
||||
def attempt_cart_add(variant_id, quantity, max_quantity = nil)
|
||||
quantity = quantity.to_i
|
||||
variant = Spree::Variant.find(variant_id)
|
||||
variant.scope_to_hub @distributor
|
||||
OpenFoodNetwork::ScopeVariantToHub.new(@distributor).scope(variant)
|
||||
if quantity > 0
|
||||
if check_stock_levels(variant, quantity) &&
|
||||
check_order_cycle_provided_for(variant) &&
|
||||
|
||||
50
app/models/spree/preferences/file_configuration.rb
Normal file
50
app/models/spree/preferences/file_configuration.rb
Normal file
@@ -0,0 +1,50 @@
|
||||
module Spree::Preferences
|
||||
class FileConfiguration < Configuration
|
||||
|
||||
def self.preference(name, type, *args)
|
||||
if type == :file
|
||||
super "#{name}_file_name", :string, *args
|
||||
super "#{name}_content_type", :string, *args
|
||||
super "#{name}_file_size", :integer, *args
|
||||
super "#{name}_updated_at", :string, *args
|
||||
|
||||
else
|
||||
super name, type, *args
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def get_preference(key)
|
||||
if !has_preference?(key) && has_attachment?(key)
|
||||
send key
|
||||
else
|
||||
super key
|
||||
end
|
||||
end
|
||||
alias :[] :get_preference
|
||||
|
||||
|
||||
def preference_type(name)
|
||||
if has_attachment? name
|
||||
:file
|
||||
else
|
||||
super name
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Spree's Configuration responds to preference methods via method_missing, but doesn't
|
||||
# override respond_to?, which consequently reports those methods as unavailable. Paperclip
|
||||
# errors if respond_to? isn't correct, so we override it here.
|
||||
def respond_to?(method, include_all=false)
|
||||
name = method.to_s.gsub('=', '')
|
||||
super(self.class.preference_getter_method(name), include_all) || super(method, include_all)
|
||||
end
|
||||
|
||||
|
||||
def has_attachment?(name)
|
||||
self.class.respond_to?(:attachment_definitions) &&
|
||||
self.class.attachment_definitions.keys.include?(name.to_sym)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,8 +1,4 @@
|
||||
require 'open_food_network/scope_product_to_hub'
|
||||
|
||||
Spree::Product.class_eval do
|
||||
include OpenFoodNetwork::ProductScopableToHub
|
||||
|
||||
# We have an after_destroy callback on Spree::ProductOptionType. However, if we
|
||||
# don't specify dependent => destroy on this association, it is not called. See:
|
||||
# https://github.com/rails/rails/issues/7618
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
require 'open_food_network/scope_variant_to_hub'
|
||||
require 'open_food_network/enterprise_fee_calculator'
|
||||
require 'open_food_network/option_value_namer'
|
||||
|
||||
Spree::Variant.class_eval do
|
||||
include OpenFoodNetwork::VariantScopableToHub
|
||||
|
||||
has_many :exchange_variants, dependent: :destroy
|
||||
has_many :exchanges, through: :exchange_variants
|
||||
has_many :variant_overrides
|
||||
|
||||
@@ -8,6 +8,12 @@ class VariantOverride < ActiveRecord::Base
|
||||
where(hub_id: hubs)
|
||||
}
|
||||
|
||||
def self.indexed(hub)
|
||||
Hash[
|
||||
for_hubs(hub).map { |vo| [vo.variant, vo] }
|
||||
]
|
||||
end
|
||||
|
||||
def self.price_for(hub, variant)
|
||||
self.for(hub, variant).andand.price
|
||||
end
|
||||
@@ -25,12 +31,21 @@ class VariantOverride < ActiveRecord::Base
|
||||
|
||||
if vo.nil?
|
||||
Bugsnag.notify RuntimeError.new "Attempting to decrement stock level for a variant without a VariantOverride."
|
||||
|
||||
elsif vo.count_on_hand.blank?
|
||||
Bugsnag.notify RuntimeError.new "Attempting to decrement stock level on a VariantOverride without a count_on_hand specified."
|
||||
|
||||
else
|
||||
vo.decrement! :count_on_hand, quantity
|
||||
vo.decrement_stock! quantity
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def stock_overridden?
|
||||
count_on_hand.present?
|
||||
end
|
||||
|
||||
def decrement_stock!(quantity)
|
||||
if stock_overridden?
|
||||
decrement! :count_on_hand, quantity
|
||||
else
|
||||
Bugsnag.notify RuntimeError.new "Attempting to decrement stock level on a VariantOverride without a count_on_hand specified."
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/ insert_bottom "[data-hook='admin_tabs'], #admin_tabs[data-hook]"
|
||||
= tab :customers, :url => main_app.admin_customers_path
|
||||
@@ -1,5 +1,5 @@
|
||||
class Api::Admin::LineItemSerializer < ActiveModel::Serializer
|
||||
attributes :id, :quantity, :max_quantity, :supplier, :price, :unit_value, :units_product, :units_variant
|
||||
attributes :id, :quantity, :max_quantity, :supplier, :price, :final_weight_volume, :units_product, :units_variant
|
||||
|
||||
def supplier
|
||||
Api::Admin::IdNameSerializer.new(object.product.supplier).serializable_hash
|
||||
@@ -13,7 +13,7 @@ class Api::Admin::LineItemSerializer < ActiveModel::Serializer
|
||||
Api::Admin::UnitsVariantSerializer.new(object.variant).serializable_hash
|
||||
end
|
||||
|
||||
def unit_value
|
||||
object.unit_value.to_f
|
||||
def final_weight_volume
|
||||
object.final_weight_volume.to_f
|
||||
end
|
||||
end
|
||||
|
||||
@@ -22,7 +22,12 @@ class Api::UncachedProductSerializer < ActiveModel::Serializer
|
||||
attributes :price
|
||||
|
||||
def price
|
||||
object.master.price_with_fees(options[:current_distributor], options[:current_order_cycle])
|
||||
if options[:enterprise_fee_calculator]
|
||||
object.master.price + options[:enterprise_fee_calculator].indexed_fees_for(object.master)
|
||||
else
|
||||
object.master.price_with_fees(options[:current_distributor], options[:current_order_cycle])
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
Content
|
||||
|
||||
|
||||
= form_tag main_app.admin_content_path, method: :put do
|
||||
= form_tag main_app.admin_content_path, method: :put, multipart: true do
|
||||
#preferences
|
||||
= render 'fieldset', name: 'Home page', preferences: @preferences_home
|
||||
= render 'fieldset', name: 'Footer', preferences: @preferences_footer
|
||||
- @preference_sections.each do |preference_section|
|
||||
= render 'fieldset', name: preference_section[:name], preferences: preference_section[:preferences]
|
||||
|
||||
.form-buttons.filter-actions.actions{"data-hook" => "buttons"}
|
||||
= button t(:update), 'icon-refresh'
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
.distributor-details{'data-hook' => 'distributor-details'}
|
||||
%h2= distributor.name
|
||||
%p
|
||||
%strong Address:
|
||||
%br/
|
||||
= render 'spree/shared/address', :address => distributor.address
|
||||
%p
|
||||
%strong Next collection time:
|
||||
%br/
|
||||
= distributor.next_collection_at
|
||||
%p
|
||||
%strong Regular collection times:
|
||||
%br/
|
||||
= distributor.pickup_times
|
||||
%p
|
||||
%strong Contact:
|
||||
%br/
|
||||
= distributor.contact
|
||||
%br/
|
||||
= "Phone: #{distributor.phone}"
|
||||
%br/
|
||||
= "Email: #{distributor.email}"
|
||||
%p= distributor.description
|
||||
%p= link_to distributor.website, distributor.website if distributor.website
|
||||
@@ -1,12 +0,0 @@
|
||||
- content_for :sidebar do
|
||||
%div{'data-hook' => "homepage_sidebar_navigation"}
|
||||
= render 'spree/sidebar'
|
||||
|
||||
|
||||
%h1 Distributors
|
||||
|
||||
= cms_page_content(:content, Cms::Page.find_by_full_path('/enterprises/distributors'))
|
||||
|
||||
%ul.enterprises
|
||||
- @distributors.each do |distributor|
|
||||
%li= link_to distributor.name, distributor
|
||||
@@ -1 +0,0 @@
|
||||
distributors = <%= @distributor_details.to_json.html_safe %>;
|
||||
@@ -1,12 +0,0 @@
|
||||
- content_for :sidebar do
|
||||
%div{'data-hook' => "homepage_sidebar_navigation"}
|
||||
= render 'spree/sidebar'
|
||||
|
||||
|
||||
%h1 Enterprises
|
||||
|
||||
= cms_page_content(:content, Cms::Page.find_by_full_path('/enterprises'))
|
||||
|
||||
%ul.enterprises
|
||||
- @enterprises.each do |enterprise|
|
||||
%li= link_to enterprise.name, enterprise
|
||||
@@ -16,7 +16,7 @@
|
||||
/ Will this label should be a variable to reflect 'Ready for pickup / delivery' as appropriate
|
||||
|
||||
%select.avenir#order_cycle_id{"ng-model" => "order_cycle.order_cycle_id",
|
||||
"ng-change" => "changeOrderCycle()",
|
||||
"ofn-change-order-cycle" => true,
|
||||
"ng-options" => "oc.id as oc.time for oc in #{@order_cycles.map {|oc| {time: pickup_time(oc), id: oc.id}}.to_json}",
|
||||
"popover-placement" => "left", "popover" => "Choose when you want your order:", "popover-trigger" => "openTrigger"}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
.row
|
||||
.large-12.columns
|
||||
%h2= @enterprise.name
|
||||
.row
|
||||
.large-12.columns= @enterprise.long_description.andand.html_safe
|
||||
|
||||
.row
|
||||
.large-12.columns
|
||||
.products
|
||||
- @products.each_slice(4).to_a.each do |products_row|
|
||||
.row
|
||||
- products_row.each do |product|
|
||||
.large-4.columns.centered
|
||||
.clearfix= link_to small_image(product), product
|
||||
= link_to product.name, product
|
||||
@@ -1,18 +0,0 @@
|
||||
- if @enterprise != current_distributor
|
||||
%h2= @enterprise.name
|
||||
|
||||
.enterprise-description= @enterprise.long_description.andand.html_safe
|
||||
|
||||
- if current_distributor
|
||||
= render :template => 'spree/products/index'
|
||||
|
||||
- else
|
||||
%h3 Hubs that distribute our products
|
||||
%p.hint Select a hub to start shopping:
|
||||
|
||||
%ul#supplier-distributors
|
||||
- if @distributors.delete @enterprise
|
||||
%li= link_to "Buy direct from the farm", enterprise_shop_path(@enterprise), {class: distributor_link_class(@enterprise)}
|
||||
|
||||
- @distributors.each do |distributor|
|
||||
%li= render partial: "shared/distributor", object: distributor
|
||||
@@ -1,12 +0,0 @@
|
||||
- content_for :sidebar do
|
||||
%div{'data-hook' => "homepage_sidebar_navigation"}
|
||||
= render 'spree/sidebar'
|
||||
|
||||
|
||||
%h1 Suppliers
|
||||
|
||||
= cms_page_content(:content, Cms::Page.find_by_full_path('/enterprises/suppliers'))
|
||||
|
||||
%ul.enterprises
|
||||
- @suppliers.each do |supplier|
|
||||
%li= link_to supplier.name, supplier
|
||||
@@ -10,7 +10,7 @@
|
||||
.small-12.medium-6.medium-offset-3.columns.text-center
|
||||
%p.text-big We're an amazing platform for collaborative marketing, the easiest way for your members and stakeholders to reach new markets. We're non-profit, affordable, and simple.
|
||||
%br
|
||||
%a.button.transparent{href: "hello@openfoodnetwork.org".reverse, target: '_blank', mailto: true}
|
||||
%a.button.transparent{href: "hello@openfoodnetwork.org?subject=I'd%20like%20to%20talk%20to%20you%20about%20groups%20on%20the%20Open%20Food%20Network".reverse, target: '_blank', mailto: true}
|
||||
Email us
|
||||
|
||||
.groups-details.pane
|
||||
@@ -35,96 +35,27 @@
|
||||
-# / If there is a time-sensitive offer you can write it here, e.g.
|
||||
-# Time-sensitive offer goes here!
|
||||
%br
|
||||
%table.signup-table.hubs-table{cellpadding: "0", cellspacing: "0"}
|
||||
%thead
|
||||
%tr
|
||||
%td
|
||||
%h5
|
||||
%td.text-center{width: "24%"}
|
||||
%h5 OFN Group
|
||||
%tr
|
||||
%td
|
||||
%p
|
||||
%strong Promote your members
|
||||
%br
|
||||
%span.text-small Promote your local and/or member businesses through a group page
|
||||
%td.text-center
|
||||
%i.ofn-i_003-check.text-big
|
||||
%tr
|
||||
%td
|
||||
%p
|
||||
%strong Unique URL
|
||||
%br
|
||||
%span.text-small Your own organisation page/url on the Open Food Network.
|
||||
%td.text-center
|
||||
%i.ofn-i_003-check.text-big
|
||||
%tr
|
||||
%td
|
||||
%p
|
||||
%strong Region map & listings
|
||||
%br
|
||||
%span.text-small Easy search from your own list view and your own map - links through to profile pages for your member/local producers and/or food businesses.
|
||||
%td.text-center
|
||||
%i.ofn-i_003-check.text-big
|
||||
%tr
|
||||
%td
|
||||
%p
|
||||
%strong Opt-in extras
|
||||
%p.text-small Available additional support:
|
||||
%ul.small
|
||||
%li Tailored workshop(s) for your stakeholders
|
||||
%li Tailored training and support package
|
||||
%td.text-center
|
||||
%tfoot
|
||||
%tr
|
||||
%td
|
||||
%td.text-center{valign: "top"}
|
||||
%h2
|
||||
$5,500
|
||||
= ContentConfig.group_signup_pricing_table_html.html_safe
|
||||
|
||||
#shops-case-studies
|
||||
.row
|
||||
.small-12.medium-10.medium-offset-1.columns
|
||||
%h2.text-center Case studies
|
||||
%br
|
||||
.row
|
||||
.small-12.medium-6.columns
|
||||
= render 'shared/case_study', img_src: "/assets/case-studies/South_East_Food_Hub.png", title: "South East Food Group", description: "The South East Food Hub lists all its participating producers and hubs (food clubs) on its group page on the Open Food Network.", link: "https://openfoodnetwork.org.au/groups/6"
|
||||
.small-12.medium-6.columns
|
||||
= render 'shared/case_study', img_src: "/assets/case-studies/mt-alexander.png", title: "Mt Alexander Local Produce Network", description: "This community organisation promotes local sustainable food production and consumption through its group page. It lists sources of local sustenance — direct from the grower or through a range local shops and markets.".html_safe, link: "https://openfoodnetwork.org.au/groups/10"
|
||||
= ContentConfig.group_signup_case_studies_html.html_safe
|
||||
|
||||
.pane#cta
|
||||
.row
|
||||
.small-12.medium-6.medium-offset-3.columns.text-center
|
||||
%h2 Ready to discuss?
|
||||
%p.text-big Get in touch to discover what OFN can do for you:
|
||||
%a.button.transparent{href: "hello@openfoodnetwork.org".reverse, target: '_blank', mailto: true}
|
||||
%a.button.transparent{href: "hello@openfoodnetwork.org?subject=I'd%20like%20to%20talk%20to%20you%20about%20groups%20on%20the%20Open%20Food%20Network".reverse, target: '_blank', mailto: true}
|
||||
Email us
|
||||
|
||||
#hub-details.pane.footer-pad
|
||||
.row
|
||||
.small-12.medium-10.medium-offset-1.columns
|
||||
%h2.text-center Here's the detail.
|
||||
.row
|
||||
.small-12.medium-6.columns
|
||||
%h4 A sub-heading goes here
|
||||
%p Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer in odio vel ex efficitur auctor. Nam rhoncus, tortor vel varius varius, magna lorem dignissim libero, at dignissim erat leo id tortor. Donec gravida quis augue sed pellentesque. Morbi laoreet efficitur mi, id tempus diam lacinia eu.
|
||||
%h5 Smaller sub-heading goes here
|
||||
%p.text-small Vestibulum eu quam neque. Aenean porta velit sit amet metus mattis, ut pulvinar dui semper. Nunc ornare scelerisque varius. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras a magna nec augue suscipit fermentum at ac urna.
|
||||
%ul.text-small
|
||||
%li A bullet point
|
||||
%li Another point
|
||||
%li A third point goes here
|
||||
%p.text-small Quisque urna lacus, tristique sed rutrum a, volutpat eu diam. Nam placerat mi nec enim tincidunt, nec dapibus risus molestie. Praesent mattis eu dolor nec sollicitudin. Cras ut magna sem. Etiam vitae commodo augue, sit amet feugiat diam.
|
||||
.small-12.medium-6.columns
|
||||
%h4 A sub-heading goes here
|
||||
%p Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer in odio vel ex efficitur auctor. Nam rhoncus, tortor vel varius varius, magna lorem dignissim libero, at dignissim erat leo id tortor. Donec gravida quis augue sed pellentesque. Morbi laoreet efficitur mi, id tempus diam lacinia eu.
|
||||
%h5 Smaller sub-heading goes here
|
||||
%p.text-small Vestibulum eu quam neque. Aenean porta velit sit amet metus mattis, ut pulvinar dui semper. Nunc ornare scelerisque varius. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras a magna nec augue suscipit fermentum at ac urna.
|
||||
%ul.text-small
|
||||
%li A bullet point
|
||||
%li Another point
|
||||
%li A third point goes here
|
||||
%p.text-small Quisque urna lacus, tristique sed rutrum a, volutpat eu diam. Nam placerat mi nec enim tincidunt, nec dapibus risus molestie. Praesent mattis eu dolor nec sollicitudin. Cras ut magna sem. Etiam vitae commodo augue, sit amet feugiat diam.
|
||||
= ContentConfig.group_signup_detail_html.html_safe
|
||||
|
||||
= render partial: "shared/footer"
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
%h2 Food, unincorporated.
|
||||
%p Sometimes the best way to fix the system is to start a new one…
|
||||
|
||||
-# TODO: Make this slide down/up
|
||||
.hide-show{"ng-show" => "brandStoryExpanded"}
|
||||
#brand-story-text.hide-show.slideable
|
||||
%p We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can seriously change the world.
|
||||
%p Then we need a way to make it real. A way to empower everyone who grows, sells and buys food. A way to tell all the stories, to handle all the logistics. A way to turn transaction into transformation every day.
|
||||
%p So we build an online marketplace that levels the playing field. It’s transparent, so it creates real relationships. It’s open source, so it’s owned by everyone. It scales to regions and nations, so people start versions across the world.
|
||||
@@ -14,6 +13,6 @@
|
||||
%strong We call it Open Food Network.
|
||||
%p We all love food. Now we can love our food system too.
|
||||
|
||||
%a.text-vbig{"ng-click" => "toggleBrandStory()"}
|
||||
%a.text-vbig{"slide-toggle" => "#brand-story-text", "ng-click" => "toggleBrandStory()"}
|
||||
%i.ofn-i_005-caret-down{"ng-hide" => "brandStoryExpanded"}
|
||||
%i.ofn-i_006-caret-up{ "ng-show" => "brandStoryExpanded"}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
.row.active_table_row{"ng-show" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : !ofn-i_032-closed-sign()}"}
|
||||
.row.active_table_row{"ng-show" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : !ofn-i_032-closed-sign()}", bindonce: true}
|
||||
.columns.small-12.medium-6.large-5.fat
|
||||
%div{"bo-if" => "hub.taxons"}
|
||||
%label Shop for
|
||||
.trans-sentence
|
||||
%span.fat-taxons{"ng-repeat" => "taxon in hub.taxons"}
|
||||
%render-svg{path: "{{taxon.icon}}"}
|
||||
%span{"bo-text" => "taxon.name"}
|
||||
%span{"bo-text" => "taxon.name"}
|
||||
%div.show-for-medium-up{"bo-if" => "hub.taxons.length==0"}
|
||||
|
||||
.columns.small-12.medium-3.large-2.fat
|
||||
%div{"bo-if" => "hub.pickup || hub.delivery"}
|
||||
%label Delivery options
|
||||
%ul.small-block-grid-2.medium-block-grid-1.large-block-grid-1
|
||||
%li.pickup{"bo-if" => "hub.pickup"}
|
||||
%li.pickup{"bo-if" => "hub.pickup"}
|
||||
%i.ofn-i_038-takeaway
|
||||
Pickup
|
||||
%li.delivery{"bo-if" => "hub.delivery"}
|
||||
%li.delivery{"bo-if" => "hub.delivery"}
|
||||
%i.ofn-i_039-delivery
|
||||
Delivery
|
||||
.columns.small-12.medium-3.large-5.fat
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
%h5.tdhead
|
||||
.light Filter by
|
||||
Type
|
||||
%filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-5{ objects: "Enterprises.hubs | searchEnterprises:query | taxonsOf", "active-selectors" => "activeTaxons" }
|
||||
%filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-5{ objects: "visibleMatches | visible | taxonsOf", "active-selectors" => "activeTaxons" }
|
||||
.small-12.large-3.columns
|
||||
%h5.tdhead
|
||||
.light Filter by
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
= inject_enterprises
|
||||
#hubs.hubs{"ng-controller" => "EnterprisesCtrl"}
|
||||
= inject_enterprises
|
||||
|
||||
#hubs.hubs{"ng-controller" => "EnterprisesCtrl", "ng-cloak" => true}
|
||||
.row
|
||||
.small-12.columns
|
||||
%h1 Shop in your local area
|
||||
%h1{"scroll-after-load" => (spree_current_user ? true : nil)} Shop in your local area
|
||||
|
||||
= render partial: "shared/components/enterprise_search"
|
||||
= render partial: "home/filters"
|
||||
= render "shared/components/enterprise_search"
|
||||
= render "home/filters"
|
||||
|
||||
.row{bindonce: true}
|
||||
.row
|
||||
.small-12.columns
|
||||
.active_table
|
||||
%hub.active_table_node.row.animate-repeat{"ng-repeat" => "hub in filteredEnterprises = (Enterprises.hubs | visible | searchEnterprises:query | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles | orderBy:['-active', '+orders_close_at'])",
|
||||
"ng-class" => "{'is_profile' : hub.category == 'hub_profile', 'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}",
|
||||
"scroll-after-load" => true,
|
||||
"ng-controller" => "HubNodeCtrl",
|
||||
id: "{{hub.hash}}"}
|
||||
.small-12.columns
|
||||
= render partial: 'home/skinny'
|
||||
= render partial: 'home/fat'
|
||||
.name-matches{"ng-show" => "nameMatchesFiltered.length > 0"}
|
||||
%h2 Did you mean?
|
||||
= render "home/hubs_table", enterprises: "nameMatches"
|
||||
|
||||
= render partial: 'shared/components/enterprise_no_results'
|
||||
.distance-matches{"ng-if" => "nameMatchesFiltered.length == 0 || distanceMatchesShown"}
|
||||
%h2{"ng-show" => "nameMatchesFiltered.length > 0 || query.length > 0"}
|
||||
Closest to
|
||||
%span{"ng-show" => "nameMatchesFiltered.length > 0"} {{ nameMatchesFiltered[0].name }}...
|
||||
%span{"ng-hide" => "nameMatchesFiltered.length > 0"} {{ query }}...
|
||||
|
||||
= render "home/hubs_table", enterprises: "distanceMatches"
|
||||
|
||||
.show-distance-matches{"ng-show" => "nameMatchesFiltered.length > 0 && !distanceMatchesShown"}
|
||||
%a{href: "", "ng-click" => "showDistanceMatches()"} Show me shops near {{ nameMatchesFiltered[0].name }}
|
||||
|
||||
10
app/views/home/_hubs_table.html.haml
Normal file
10
app/views/home/_hubs_table.html.haml
Normal file
@@ -0,0 +1,10 @@
|
||||
.active_table
|
||||
%hub.active_table_node.row{"ng-repeat" => "hub in #{enterprises}Filtered = (#{enterprises} | visible | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles | orderBy:['-active', '+distance', '+orders_close_at'])",
|
||||
"ng-class" => "{'is_profile' : hub.category == 'hub_profile', 'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}",
|
||||
"ng-controller" => "HubNodeCtrl",
|
||||
id: "{{hub.hash}}"}
|
||||
.small-12.columns
|
||||
= render 'home/skinny'
|
||||
= render 'home/fat'
|
||||
|
||||
= render 'shared/components/enterprise_no_results', enterprises: "#{enterprises}Filtered"
|
||||
@@ -1,7 +1,6 @@
|
||||
.row.active_table_row{"ng-if" => "hub.is_distributor", "ng-click" => "toggle($event)", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}", bindonce: true}
|
||||
|
||||
.columns.small-12.medium-5.large-5.skinny-head
|
||||
%a.hub{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub", "data-is-link" => "true"}
|
||||
%a.hub{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub", "data-is-link" => "true"}
|
||||
%i{bo: {class: "hub.icon_font"}}
|
||||
%span.margin-top.hub-name-listing{"bo-bind" => "hub.name | truncate:40"}
|
||||
|
||||
@@ -9,9 +8,10 @@
|
||||
%span.margin-top{"bo-text" => "hub.address.city"}
|
||||
.columns.small-2.medium-1.large-1
|
||||
%span.margin-top{"bo-bind" => "hub.address.state_name | uppercase"}
|
||||
%span.margin-top{"ng-if" => "hub.distance != null && hub.distance > 0"} ({{ hub.distance / 1000 | number:0 }} km)
|
||||
|
||||
.columns.small-4.medium-3.large-3.text-right{"bo-if" => "hub.active"}
|
||||
%a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"}
|
||||
%a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"}
|
||||
%i.ofn-i_033-open-sign
|
||||
%span.margin-top{ bo: { if: "current()" } }
|
||||
%em Shopping here
|
||||
@@ -19,17 +19,17 @@
|
||||
%span{"bo-bind" => "hub.orders_close_at | sensible_timeframe"}
|
||||
|
||||
.columns.small-4.medium-3.large-3.text-right{"bo-if" => "!hub.active"}
|
||||
%a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"}
|
||||
%a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"}
|
||||
%i.ofn-i_032-closed-sign
|
||||
%span.margin-top{ bo: { if: "current()" } }
|
||||
%em Shopping here
|
||||
%span.margin-top{ bo: { if: "!current()" } } Orders closed
|
||||
|
||||
.columns.small-2.medium-1.large-1.text-right
|
||||
%span.margin-top
|
||||
%span.margin-top
|
||||
%i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"}
|
||||
|
||||
.row.active_table_row{"ng-if" => "!hub.is_distributor", "ng-class" => "closed"}
|
||||
.row.active_table_row{"ng-if" => "!hub.is_distributor", "ng-class" => "closed", bindonce: true}
|
||||
.columns.small-12.medium-6.large-5.skinny-head
|
||||
%a.hub{"ng-click" => "openModal(hub)", "ng-class" => "{primary: hub.active, secondary: !hub.active}"}
|
||||
%i{ng: {class: "hub.icon_font"}}
|
||||
@@ -43,4 +43,3 @@
|
||||
.columns.small-6.medium-3.large-4.text-right
|
||||
%span.margin-top{ bo: { if: "!current()" } }
|
||||
%em Profile only
|
||||
|
||||
|
||||
@@ -4,19 +4,20 @@
|
||||
%h2 We're creating a new food system.
|
||||
|
||||
.row.content
|
||||
.small-12.medium-3.columns.text-center
|
||||
%h4
|
||||
%strong= number_with_delimiter @num_producers
|
||||
food producers
|
||||
.small-12.medium-3.columns.text-center
|
||||
%h4
|
||||
%strong= number_with_delimiter @num_distributors
|
||||
food shops
|
||||
.small-12.medium-3.columns.text-center
|
||||
%h4
|
||||
%strong= number_with_delimiter @num_users
|
||||
food shoppers
|
||||
.small-12.medium-3.columns.text-center
|
||||
%h4
|
||||
%strong= number_with_delimiter @num_orders
|
||||
food orders
|
||||
- if ContentConfig.home_show_stats
|
||||
.small-12.medium-3.columns.text-center
|
||||
%h4
|
||||
%strong= number_with_delimiter @num_producers
|
||||
food producers
|
||||
.small-12.medium-3.columns.text-center
|
||||
%h4
|
||||
%strong= number_with_delimiter @num_distributors
|
||||
food shops
|
||||
.small-12.medium-3.columns.text-center
|
||||
%h4
|
||||
%strong= number_with_delimiter @num_users
|
||||
food shoppers
|
||||
.small-12.medium-3.columns.text-center
|
||||
%h4
|
||||
%strong= number_with_delimiter @num_orders
|
||||
food orders
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
:css
|
||||
#tagline:before { background-image: url("#{ContentConfig.home_hero.url}") }
|
||||
|
||||
|
||||
%div{"ng-controller" => "HomeCtrl"}
|
||||
= render partial: "shared/menu/alert"
|
||||
|
||||
@@ -6,8 +10,7 @@
|
||||
.small-12.text-center.columns
|
||||
%h1
|
||||
/ TODO: Rohan - logo asset & width is content manageable:
|
||||
-# TODO: SVGify
|
||||
%img{src: "/assets/logo-white-notext.png", width: "250", title: "Open Food Network Australia"}
|
||||
%img{src: "/assets/logo-white-notext.png", width: "250", title: Spree::Config.site_name}
|
||||
%br/
|
||||
%a.button.transparent{href: "/shops"}
|
||||
Shop Now
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
%meta{charset: 'utf-8'}/
|
||||
%meta{name: 'viewport', content: "width=device-width,initial-scale=1.0"}/
|
||||
|
||||
%title= content_for?(:title) ? "#{yield(:title)} - Open Food Network" : 'Welcome to Open Food Network'
|
||||
%title= content_for?(:title) ? "#{yield(:title)} - Open Food Network".html_safe : 'Welcome to Open Food Network'
|
||||
- if Rails.env.production?
|
||||
= favicon_link_tag
|
||||
- else
|
||||
@@ -11,7 +11,7 @@
|
||||
%link{href: "https://fonts.googleapis.com/css?family=Roboto:400,300italic,400italic,300,700,700italic|Oswald:300,400,700", rel: "stylesheet", type: "text/css"}
|
||||
|
||||
= yield :scripts
|
||||
%script{src: "//maps.googleapis.com/maps/api/js?libraries=places&sensor=false"}
|
||||
%script{src: "//maps.googleapis.com/maps/api/js?libraries=places,geometry&sensor=false"}
|
||||
= split_stylesheet_link_tag "darkswarm/all"
|
||||
= javascript_include_tag "darkswarm/all"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.row.active_table_row{"ng-if" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : !ofn-i_032-closed-sign()}"}
|
||||
|
||||
|
||||
.columns.small-12.medium-7.large-7.fat
|
||||
/ Will add in long description available once clean up HTML formatting producer.long_description
|
||||
%div{"bo-if" => "producer.description"}
|
||||
@@ -10,23 +10,23 @@
|
||||
%label
|
||||
|
||||
.columns.small-12.medium-5.large-5.fat
|
||||
|
||||
|
||||
%div{"bo-if" => "producer.supplied_taxons"}
|
||||
%label Shop for
|
||||
%p.trans-sentence
|
||||
%span.fat-taxons{"ng-repeat" => "taxon in producer.supplied_taxons"}
|
||||
%render-svg{path: "{{taxon.icon}}"}
|
||||
%span{"bo-text" => "taxon.name"}
|
||||
|
||||
|
||||
%div.show-for-medium-up{"ng-if" => "producer.supplied_taxons.length==0"}
|
||||
|
||||
|
||||
%div{"bo-if" => "producer.email || producer.website || producer.phone"}
|
||||
%label Contact
|
||||
|
||||
|
||||
%p.word-wrap{"bo-if" => "producer.phone"}
|
||||
Call
|
||||
%span{"bo-text" => "producer.phone"}
|
||||
Call
|
||||
%span{"bo-text" => "producer.phone"}
|
||||
|
||||
%p.word-wrap{"bo-if" => "producer.email"}
|
||||
%a{"bo-href" => "producer.email | stripUrl", target: "_blank", mailto: true}
|
||||
@@ -39,20 +39,20 @@
|
||||
%div{"bo-if" => "producer.twitter || producer.facebook || producer.linkedin || producer.instagram"}
|
||||
%label Follow
|
||||
.follow-icons{bindonce: true}
|
||||
%span{"bo-if" => "producer.twitter"}
|
||||
%span{"bo-if" => "producer.twitter"}
|
||||
%a{"bo-href-i" => "http://twitter.com/{{producer.twitter}}", target: "_blank"}
|
||||
%i.ofn-i_041-twitter
|
||||
|
||||
|
||||
%span{"bo-if" => "producer.facebook"}
|
||||
%a{"bo-href-i" => "http://{{producer.facebook | stripUrl}}", target: "_blank"}
|
||||
%i.ofn-i_044-facebook
|
||||
|
||||
|
||||
%span{"bo-if" => "producer.linkedin"}
|
||||
%a{"bo-href-i" => "http://{{producer.linkedin | stripUrl}}", target: "_blank"}
|
||||
%i.ofn-i_042-linkedin
|
||||
|
||||
|
||||
%span{"bo-if" => "producer.instagram"}
|
||||
%a{"bo-href-i" => "http://instagram.com/{{producer.instagram}}", target: "_blank"}
|
||||
%a{"bo-href-i" => "http://instagram.com/{{producer.instagram}}", target: "_blank"}
|
||||
%i.ofn-i_043-instagram
|
||||
|
||||
.row.active_table_row.pad-top{"ng-if" => "open()", "bo-if" => "producer.hubs"}
|
||||
@@ -60,19 +60,18 @@
|
||||
.row
|
||||
.columns.small-12.fat
|
||||
%div{"bo-if" => "producer.name"}
|
||||
%label
|
||||
%label
|
||||
Shop for
|
||||
%span.turquoise{"bo-text" => "producer.name"}
|
||||
%span.turquoise{"bo-text" => "producer.name"}
|
||||
products at:
|
||||
%div.show-for-medium-up{"bo-if" => "!producer.name"}
|
||||
|
||||
.row.cta-container
|
||||
.columns.small-12
|
||||
%a.cta-hub{"ng-repeat" => "hub in producer.hubs | orderBy:'-active'",
|
||||
"bo-href" => "hub.path", "ofn-empties-cart" => "hub",
|
||||
%a.cta-hub{"ng-repeat" => "hub in producer.hubs | visible | orderBy:'-active'",
|
||||
"bo-href" => "hub.path", "ofn-change-hub" => "hub",
|
||||
"bo-class" => "{primary: hub.active, secondary: !hub.active}"}
|
||||
%i.ofn-i_033-open-sign{"bo-if" => "hub.active"}
|
||||
%i.ofn-i_032-closed-sign{"bo-if" => "!hub.active"}
|
||||
.hub-name{"bo-text" => "hub.name"}
|
||||
.button-address{"bo-bind" => "[hub.address.city, hub.address.state_name] | printArray"}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
.small-12.columns
|
||||
.active_table
|
||||
%producer.active_table_node.row.animate-repeat{id: "{{producer.path}}",
|
||||
"scroll-after-load" => true,
|
||||
"ng-repeat" => "producer in filteredEnterprises = (Enterprises.producers | visible | searchEnterprises:query | taxons:activeTaxons)",
|
||||
"ng-controller" => "ProducerNodeCtrl",
|
||||
"ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !producer.active}",
|
||||
|
||||
@@ -20,79 +20,14 @@
|
||||
-# If there is a time-sensitive offer you can write it here, e.g.
|
||||
-# Sign up before 30th June for an extra month free!
|
||||
%br
|
||||
%table.signup-table.producers-table{cellpadding: "0", cellspacing: "0"}
|
||||
%thead
|
||||
%tr
|
||||
%td
|
||||
%h5
|
||||
%td.text-center{width: "20%"}
|
||||
%h5 Basic
|
||||
%td.text-center{width: "24%"}
|
||||
%h5 Plus
|
||||
%tr
|
||||
%td
|
||||
%p
|
||||
%strong Profile
|
||||
%p
|
||||
%span.text-small Get a profile on the Open Food Network - including a listing on the Producers page and a pin on the OFN Map. These help people to find and connect with you.
|
||||
%p
|
||||
%span.text-small Option to add and manage your products on OFN, enabling you to act as a supplier to other OFN shopfronts.
|
||||
%td.text-center
|
||||
%i.ofn-i_003-check.text-big
|
||||
%td.text-center
|
||||
%i.ofn-i_003-check.text-big
|
||||
%tr
|
||||
%td
|
||||
%p
|
||||
%strong Shop
|
||||
%p.text-small Get a shopfront on OFN to sell your produce direct!
|
||||
%p.text-small Basic support included:
|
||||
%ul.small
|
||||
%li User manual
|
||||
%li Post queries to discussion forum/via contact form
|
||||
%li Report bugs/suggestions
|
||||
%li Regular release notes – what's new
|
||||
%td.text-center
|
||||
%td.text-center
|
||||
%i.ofn-i_003-check.text-big
|
||||
%tfoot
|
||||
%tr
|
||||
%td
|
||||
%td.text-center{valign: "top"}
|
||||
%h2 Free
|
||||
%td.text-center{valign: "top"}
|
||||
%h2
|
||||
.text-small
|
||||
first
|
||||
%br
|
||||
month
|
||||
Free
|
||||
%p.text-small
|
||||
Then, 2% of total transactions
|
||||
%br
|
||||
%em
|
||||
Capped at
|
||||
%strong $50
|
||||
per month
|
||||
%br
|
||||
%em (Special offer for 2015)
|
||||
= ContentConfig.producer_signup_pricing_table_html.html_safe
|
||||
|
||||
#producer-case-studies
|
||||
.row
|
||||
.small-12.medium-10.medium-offset-1.columns
|
||||
%h2.text-center Stories from our producers.
|
||||
%br
|
||||
.row
|
||||
.small-12.medium-6.columns
|
||||
= render 'shared/case_study', img_src: "/assets/case-studies/jonai.png", title: "Jonai Farms", description: "Jonai Farms is an ethical pork and beef “Community Supported Agriculture” farm that delivers to a range of hubs in Melbourne and central Victoria every month.", link: "http://www.jonaifarms.com.au"
|
||||
.small-12.medium-6.columns
|
||||
= render 'shared/case_study', img_src: "/assets/case-studies/wandiful.png", title: "Wandiful Produce", description: "Biodynamically grown in NE Victoria, Wandiful Produce supplies hazelnuts, chestnuts and associated products (bliss balls - yum!) shipped to your door.", link: "https://openfoodnetwork.org.au/wandiful-produce/shop"
|
||||
.row
|
||||
.small-12.medium-6.columns
|
||||
= render 'shared/case_study', img_src: "/assets/case-studies/longley.png", title: "Longley Organic Farm", description: "Near Hobart, Longley Organic Farm provides berries and vegetables for local food cooperatives and small shops and through its roadside stall.", link: "https://openfoodnetwork.org.au/longley-organic-farm/shop"
|
||||
.small-12.medium-6.columns
|
||||
= render 'shared/case_study', img_src: "/assets/case-studies/jindivick.jpg", title: "Jindivick Hydroponics", description: "Selling through farmers markets and the South East Food Hub, Jindivick Hydroponics grow an ever increasing range of vegetables including tomatoes, cucumbers and beans.", link: "https://openfoodnetwork.org.au/producers#/#jindivick-hydroponics"
|
||||
|
||||
= ContentConfig.producer_signup_case_studies_html.html_safe
|
||||
|
||||
.pane#cta
|
||||
.row
|
||||
@@ -106,17 +41,6 @@
|
||||
.row
|
||||
.small-12.medium-10.medium-offset-1.columns
|
||||
%h2.text-center Here's the detail.
|
||||
= render 'shared/enterprise_type_flowchart', type: 'producers'
|
||||
%h4 Join a marketplace of independent online food stores
|
||||
%p Create an online shop front and join the network of online farmers markets on the Open Food Network. Set up a Producer Shop and sell your products direct to customers, or create a Food Hub and sell products from multiple producers.
|
||||
|
||||
%h4 Connect with a new source of customers
|
||||
%p Gain access to the growing crowd of conscientious buyers on the Open Food Network, all looking to make more ethical and sustainable choices when purchasing food.
|
||||
%h4 Manage your business online
|
||||
%p The Open Food Network provides online tools to help with the day-to-day running of your business. Manage your product listing and stock levels. Create, receive and manage orders from your buyers, and organise payments online. Generate invoices for orders and export them to your accounting software, and access a range of reports.
|
||||
%h4 Start with a simple listing on our directory
|
||||
%p Create a profile on the Open Food Network and gain exposure to a new marketplace of potential buyers. Tell your story in words and images, provide your contact details, and drive connections to your social and online presence.
|
||||
%p Access new wholesale opportunities by connecting with food hubs in your region who can sell and distribute your products to buyers on the Open Food Network.
|
||||
|
||||
= ContentConfig.producer_signup_detail_html.html_safe
|
||||
|
||||
= render partial: "shared/footer"
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
.enterprise-type-flowchart
|
||||
= image_tag "enterprise-type.png"
|
||||
%br
|
||||
%br
|
||||
%br
|
||||
%br
|
||||
%br
|
||||
%p{style: "text-align: center;"}
|
||||
%a{href: "http://www.openfoodnetwork.org/platform/features/"} More Features
|
||||
\|
|
||||
%a{href: "http://www.openfoodnetwork.org/platform/user-guide/"} User Guide
|
||||
\|
|
||||
- if type == 'hubs'
|
||||
%a{href: "http://www.openfoodnetwork.org/platform/user-guide/faqs/hub-faqs/"} Hub FAQs
|
||||
- elsif type == 'producers'
|
||||
%a{href: "http://www.openfoodnetwork.org/platform/user-guide/faqs/producer-faqs/"} Producer FAQs
|
||||
@@ -116,11 +116,11 @@
|
||||
.row
|
||||
.small-12.medium-3.medium-offset-2.columns.text-left
|
||||
%a{href: root_path}
|
||||
%img{src: "/assets/logo-color.png", srcset: "/assets/logo-color.svg", width: "220px"}
|
||||
%img{src: ContentConfig.footer_logo.url, width: "220"}
|
||||
.small-12.medium-5.columns.text-left
|
||||
%p.text-small
|
||||
Read our
|
||||
%a{href: "/Terms-of-service.pdf"} Terms & conditions
|
||||
%a{href: ContentConfig.footer_tos_url} Terms & conditions
|
||||
|
|
||||
Find us on
|
||||
%a{href:"https://github.com/openfoodfoundation/openfoodnetwork", target: "_blank"} Github
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
%producer.row{"ng-show" => "filteredEnterprises.length == 0"}
|
||||
- enterprises ||= 'filteredEnterprises'
|
||||
%producer.row{"ng-show" => "#{enterprises}.length == 0"}
|
||||
%p.no-results
|
||||
Sorry, no results found for
|
||||
%strong {{query}}.
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
%input{type: :text,
|
||||
"ng-model" => "query",
|
||||
placeholder: t('search_by_name'),
|
||||
"ng-debounce" => "150",
|
||||
"ng-debounce" => "500",
|
||||
"ofn-disable-enter" => true}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
%a.alert-cta{href: "http://www.openfoodnetwork.org", target: "_blank"}
|
||||
%h6
|
||||
Interested in selling food on the Open Food Network?
|
||||
%strong Start here
|
||||
%i.ofn-i_054-point-right
|
||||
%strong
|
||||
Start here
|
||||
%i.ofn-i_054-point-right
|
||||
%a.close{ ng: { click: "close()" } } ×
|
||||
|
||||
@@ -9,7 +9,11 @@
|
||||
.joyride-tip-guide{"ng-class" => "{ in: open }", "ng-show" => "open"}
|
||||
%span.joyride-nub.top
|
||||
.joyride-content-wrapper
|
||||
%h5 Your shopping cart
|
||||
%h5.text-left Your shopping cart
|
||||
.buttons.text-right
|
||||
%a.button.secondary.tiny.add_to_cart{ href: cart_path, type: :submit, "ng-disabled" => "Cart.dirty || Cart.empty()", "ng-class" => "{ dirty: Cart.dirty }" }
|
||||
{{ Cart.dirty ? 'Updating cart...' : (Cart.empty() ? 'Cart empty' : 'Edit your cart' ) }}
|
||||
%a.button.primary.tiny{href: checkout_path, "ng-disabled" => "Cart.dirty || Cart.empty()"} Checkout now
|
||||
%table
|
||||
%tr.product-cart{"ng-repeat" => "line_item in Cart.line_items_present()",
|
||||
"ng-controller" => "LineItemCtrl", "id" => "cart-variant-{{ line_item.variant.id }}"}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user