i18n from berlin hackathon

This commit is contained in:
Nicolas Blanc
2016-01-31 12:16:33 +00:00
114 changed files with 1629 additions and 720 deletions

View File

@@ -14,12 +14,13 @@ env:
global:
- TZ="Australia/Melbourne"
- TIMEZONE="Australia/Melbourne"
- CI_NODE_TOTAL=5
matrix:
- TEST_CASES="./spec/features/admin" GITHUB_DEPLOY="true"
- TEST_CASES="./spec/features/consumer ./spec/serializers ./spec/performance"
- TEST_CASES="./spec/models"
- TEST_CASES="./spec/controllers ./spec/views ./spec/jobs"
- TEST_CASES="./spec/requests ./spec/helpers ./spec/mailers ./spec/lib" KARMA="true"
- CI_NODE_INDEX=0
- CI_NODE_INDEX=1
- CI_NODE_INDEX=2
- CI_NODE_INDEX=3
- CI_NODE_INDEX=4 KARMA="true" GITHUB_DEPLOY="true"
before_script:
- cp config/database.travis.yml config/database.yml
@@ -35,8 +36,9 @@ before_script:
fi
script:
- '[ "$KARMA" = "true" ] && bundle exec rake karma:run || echo "Skipping karma run"'
- "bundle exec rspec $TEST_CASES"
- 'if [ "$KARMA" = "true" ]; then bundle exec rake karma:run; else echo "Skipping karma run"; fi'
#- "KNAPSACK_GENERATE_REPORT=true bundle exec rspec spec"
- "bundle exec rake knapsack:rspec"
after_success:
- >

View File

@@ -105,6 +105,7 @@ group :test, :development do
gem 'json_spec'
gem 'unicorn-rails'
gem 'atomic'
gem 'knapsack'
end
group :test do

View File

@@ -122,7 +122,7 @@ GEM
rack-test (~> 0.6.1)
sprockets (~> 2.2.1)
active_link_to (1.0.0)
active_model_serializers (0.8.1)
active_model_serializers (0.8.3)
activemodel (>= 3.0)
activemerchant (1.48.0)
activesupport (>= 3.2.14, < 5.0.0)
@@ -431,6 +431,9 @@ GEM
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
kgio (2.9.3)
knapsack (1.5.1)
rake
timecop (>= 0.1.0)
launchy (2.1.2)
addressable (~> 2.3)
letter_opener (1.0.0)
@@ -684,6 +687,7 @@ DEPENDENCIES
immigrant
jquery-rails
json_spec
knapsack
letter_opener
momentjs-rails
newrelic_rpm

View File

@@ -5,3 +5,5 @@
require File.expand_path('../config/application', __FILE__)
Openfoodnetwork::Application.load_tasks
Knapsack.load_tasks if defined?(Knapsack)

View File

@@ -39,6 +39,7 @@
//= require ./taxons/taxons
//= require ./utils/utils
//= require ./users/users
//= require ./variant_overrides/variant_overrides
//= require textAngular.min.js
//= require textAngular-sanitize.min.js
//= require ../shared/bindonce.min.js

View File

@@ -352,6 +352,9 @@ filterSubmitVariant = (variant) ->
filteredVariant = {}
if not variant.deleted_at? and variant.hasOwnProperty("id")
filteredVariant.id = variant.id unless variant.id <= 0
if variant.hasOwnProperty("sku")
filteredVariant.sku = variant.sku
hasUpdatableProperty = true
if variant.hasOwnProperty("on_hand")
filteredVariant.on_hand = variant.on_hand
hasUpdatableProperty = true

View File

@@ -1,67 +0,0 @@
angular.module("ofn.admin").controller "AdminVariantOverridesCtrl", ($scope, $timeout, Indexer, SpreeApiAuth, PagedFetcher, StatusMessage, hubs, producers, hubPermissions, VariantOverrides, DirtyVariantOverrides) ->
$scope.hubs = hubs
$scope.hub = null
$scope.products = []
$scope.producers = Indexer.index producers
$scope.hubPermissions = hubPermissions
$scope.variantOverrides = VariantOverrides.variantOverrides
$scope.StatusMessage = StatusMessage
$scope.initialise = ->
SpreeApiAuth.authorise()
.then ->
$scope.spree_api_key_ok = true
$scope.fetchProducts()
.catch (message) ->
$scope.api_error_msg = message
$scope.fetchProducts = ->
url = "/api/products/overridable?page=::page::;per_page=100"
PagedFetcher.fetch url, (data) => $scope.addProducts data.products
$scope.addProducts = (products) ->
$scope.products = $scope.products.concat products
VariantOverrides.ensureDataFor hubs, products
$scope.selectHub = ->
$scope.hub = (hub for hub in hubs when hub.id == $scope.hub_id)[0]
$scope.displayDirty = ->
if DirtyVariantOverrides.count() > 0
num = if DirtyVariantOverrides.count() == 1 then "one override" else "#{DirtyVariantOverrides.count()} overrides"
StatusMessage.display 'notice', "Changes to #{num} remain unsaved."
else
StatusMessage.clear()
$scope.update = ->
if DirtyVariantOverrides.count() == 0
StatusMessage.display 'alert', 'No change to save '
else
StatusMessage.display 'progress', 'Saving...'
DirtyVariantOverrides.save()
.success (updatedVos) ->
DirtyVariantOverrides.clear()
VariantOverrides.updateIds updatedVos
$timeout -> StatusMessage.display 'success', 'Changes saved.'
.error (data, status) ->
$timeout -> StatusMessage.display 'failure', $scope.updateError(data, status)
$scope.updateError = (data, status) ->
if status == 401
"I couldn't get authorisation to save those changes, so they remain unsaved."
else if status == 400 && data.errors?
errors = []
for field, field_errors of data.errors
errors = errors.concat field_errors
errors = errors.join ', '
"I had some trouble saving: #{errors}"
else
"Oh no! I was unable to save your changes."

View File

@@ -1,8 +1,9 @@
angular.module("admin.dropdown").directive "ofnDropDown", ($document) ->
restrict: 'C'
link: (scope, element, attrs) ->
outsideClickListener = (event) ->
unless $(event.target).is("div.ofn_drop_down##{attrs.id} div.menu") ||
$(event.target).parents("div.ofn_drop_down##{attrs.id} div.menu").length > 0
unless $(event.target).is("div.ofn-drop-down##{attrs.id} div.menu") ||
$(event.target).parents("div.ofn-drop-down##{attrs.id} div.menu").length > 0
scope.$emit "offClick"
element.click (event) ->

View File

@@ -0,0 +1,28 @@
angular.module("admin.indexUtils").directive "ofnSelect2", ($timeout, blankOption) ->
require: 'ngModel'
restrict: 'C'
scope:
data: "="
minSearch: "@?"
text: "@?"
blank: "=?"
link: (scope, element, attrs, ngModel) ->
$timeout ->
scope.text ||= 'name'
scope.data.unshift(scope.blank) if scope.blank? && typeof scope.blank is "object"
element.select2
minimumResultsForSearch: scope.minSearch || 0
data: { results: scope.data, text: scope.text }
initSelection: (element, callback) ->
callback scope.data[0]
formatSelection: (item) ->
item[scope.text]
formatResult: (item) ->
item[scope.text]
attrs.$observe 'disabled', (value) ->
element.select2('enable', !value)
ngModel.$formatters.push (value) ->
element.select2('val', value)
value

View File

@@ -1,7 +0,0 @@
angular.module("admin.indexUtils").directive "saveBar", ->
restrict: "E"
scope:
save: "&"
saving: "&"
dirty: "&"
templateUrl: "admin/save_bar.html"

View File

@@ -0,0 +1,12 @@
# Used like a regular angular filter where an object is passed
# Adds the additional special case that a value of 0 for the filter
# acts as a bypass for that particular attribute
angular.module("admin.indexUtils").filter "attrFilter", ($filter) ->
return (objects, filters) ->
Object.keys(filters).reduce (filtered, attr) ->
filter = filters[attr]
return filtered if !filter? || filter == 0
return $filter('filter')(filtered, (object) ->
object[attr] == filter
)
, objects

View File

@@ -1,4 +1,4 @@
angular.module("ofn.admin").factory "dataFetcher", [
angular.module("admin.indexUtils").factory "dataFetcher", [
"$http", "$q"
($http, $q) ->
return (dataLocation) ->
@@ -9,4 +9,4 @@ angular.module("ofn.admin").factory "dataFetcher", [
deferred.reject()
deferred.promise
]
]

View File

@@ -4,7 +4,7 @@
# Indexer.index producers
# -> {1: {id: 1, name: 'one'}, 2: {id: 2, name: 'two'}}
angular.module("ofn.admin").factory 'Indexer', ->
angular.module("admin.indexUtils").factory 'Indexer', ->
new class Indexer
index: (data, key='id') ->
index = {}

View File

@@ -1,4 +1,4 @@
angular.module("ofn.admin").factory "PagedFetcher", (dataFetcher) ->
angular.module("admin.indexUtils").factory "PagedFetcher", (dataFetcher) ->
new class PagedFetcher
# Given a URL like http://example.com/foo?page=::page::&per_page=20
# And the response includes an attribute pages with the number of pages to fetch
@@ -13,4 +13,4 @@ angular.module("ofn.admin").factory "PagedFetcher", (dataFetcher) ->
processData data
urlForPage: (url, page) ->
url.replace("::page::", page)
url.replace("::page::", page)

View File

@@ -1,4 +1,4 @@
angular.module("ofn.admin").factory "SpreeApiAuth", ($q, $http, SpreeApiKey) ->
angular.module("admin.indexUtils").factory "SpreeApiAuth", ($q, $http, SpreeApiKey) ->
new class SpreeApiAuth
authorise: ->
deferred = $q.defer()

View File

@@ -1,7 +1,6 @@
angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $http, $q, Columns, Dereferencer, Orders, LineItems, Enterprises, OrderCycles, blankOption, VariantUnitManager, RequestMonitor) ->
angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $http, $q, StatusMessage, Columns, Dereferencer, Orders, LineItems, Enterprises, OrderCycles, blankOption, VariantUnitManager, RequestMonitor) ->
$scope.initialized = false
$scope.RequestMonitor = RequestMonitor
$scope.saving = false
$scope.filteredLineItems = []
$scope.confirmDelete = true
$scope.startDate = formatDate daysFromToday -7
@@ -55,6 +54,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
Dereferencer.dereferenceAttr $scope.lineItems, "supplier", Enterprises.enterprisesByID
Dereferencer.dereferenceAttr $scope.lineItems, "order", Orders.ordersByID
$scope.bulk_order_form.$setPristine()
StatusMessage.clear()
unless $scope.initialized
$scope.initialized = true
$timeout ->
@@ -62,16 +62,20 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.refreshData()
$scope.submit = =>
$scope.$watch 'bulk_order_form.$dirty', (newVal, oldVal) ->
if newVal == true
StatusMessage.display 'notice', "You have unsaved changes"
$scope.submit = ->
if $scope.bulk_order_form.$valid
$scope.saving = true
StatusMessage.display 'progress', "Saving..."
$q.all(LineItems.saveAll()).then(->
StatusMessage.display 'success', "All changes saved"
$scope.bulk_order_form.$setPristine()
$scope.saving = false
).catch ->
alert "#{t("unsaved_changes_warning")}"
StatusMessage.display 'failure', #{t("unsaved_changes_warning")}
else
alert "#{t("unsaved_changes_warning")}"
StatusMessage.display 'failure', #{t("unsaved_changes_warning")}
$scope.deleteLineItem = (lineItem) ->
if ($scope.confirmDelete && confirm(t("are_you_sure"))) || !$scope.confirmDelete

View File

@@ -1 +1 @@
angular.module("admin.lineItems", ["admin.indexUtils", "admin.products", "admin.orders", "admin.enterprises", "admin.orderCycles"])
angular.module("admin.lineItems", ["admin.indexUtils", "admin.utils", "admin.products", "admin.orders", "admin.enterprises", "admin.orderCycles"])

View File

@@ -0,0 +1,83 @@
angular.module('admin.orderCycles')
.controller 'AdminCreateOrderCycleCtrl', ($scope, $filter, OrderCycle, Enterprise, EnterpriseFee, ocInstance, StatusMessage) ->
$scope.enterprises = Enterprise.index(coordinator_id: ocInstance.coordinator_id)
$scope.supplier_enterprises = Enterprise.producer_enterprises
$scope.distributor_enterprises = Enterprise.hub_enterprises
$scope.supplied_products = Enterprise.supplied_products
$scope.enterprise_fees = EnterpriseFee.index(coordinator_id: ocInstance.coordinator_id)
$scope.OrderCycle = OrderCycle
$scope.order_cycle = OrderCycle.new({ coordinator_id: ocInstance.coordinator_id})
$scope.StatusMessage = StatusMessage
$scope.loaded = ->
Enterprise.loaded && EnterpriseFee.loaded
$scope.suppliedVariants = (enterprise_id) ->
Enterprise.suppliedVariants(enterprise_id)
$scope.exchangeSelectedVariants = (exchange) ->
OrderCycle.exchangeSelectedVariants(exchange)
$scope.setExchangeVariants = (exchange, variants, selected) ->
OrderCycle.setExchangeVariants(exchange, variants, selected)
$scope.enterpriseTotalVariants = (enterprise) ->
Enterprise.totalVariants(enterprise)
$scope.productSuppliedToOrderCycle = (product) ->
OrderCycle.productSuppliedToOrderCycle(product)
$scope.variantSuppliedToOrderCycle = (variant) ->
OrderCycle.variantSuppliedToOrderCycle(variant)
$scope.incomingExchangeVariantsFor = (enterprise_id) ->
$filter('filterExchangeVariants')(OrderCycle.incomingExchangesVariants(), $scope.order_cycle.visible_variants_for_outgoing_exchanges[enterprise_id])
$scope.exchangeDirection = (exchange) ->
OrderCycle.exchangeDirection(exchange)
$scope.enterprisesWithFees = ->
$scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0
$scope.toggleProducts = ($event, exchange) ->
$event.preventDefault()
OrderCycle.toggleProducts(exchange)
$scope.enterpriseFeesForEnterprise = (enterprise_id) ->
EnterpriseFee.forEnterprise(parseInt(enterprise_id))
$scope.addSupplier = ($event) ->
$event.preventDefault()
OrderCycle.addSupplier($scope.new_supplier_id)
$scope.addDistributor = ($event) ->
$event.preventDefault()
OrderCycle.addDistributor($scope.new_distributor_id)
$scope.removeExchange = ($event, exchange) ->
$event.preventDefault()
OrderCycle.removeExchange(exchange)
$scope.addCoordinatorFee = ($event) ->
$event.preventDefault()
OrderCycle.addCoordinatorFee()
$scope.removeCoordinatorFee = ($event, index) ->
$event.preventDefault()
OrderCycle.removeCoordinatorFee(index)
$scope.addExchangeFee = ($event, exchange) ->
$event.preventDefault()
OrderCycle.addExchangeFee(exchange)
$scope.removeExchangeFee = ($event, exchange, index) ->
$event.preventDefault()
OrderCycle.removeExchangeFee(exchange, index)
$scope.removeDistributionOfVariant = (variant_id) ->
OrderCycle.removeDistributionOfVariant(variant_id)
$scope.submit = (destination) ->
OrderCycle.create(destination)

View File

@@ -0,0 +1,84 @@
angular.module('admin.orderCycles')
.controller 'AdminEditOrderCycleCtrl', ($scope, $filter, $location, OrderCycle, Enterprise, EnterpriseFee, StatusMessage) ->
order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1]
$scope.enterprises = Enterprise.index(order_cycle_id: order_cycle_id)
$scope.supplier_enterprises = Enterprise.producer_enterprises
$scope.distributor_enterprises = Enterprise.hub_enterprises
$scope.supplied_products = Enterprise.supplied_products
$scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: order_cycle_id)
$scope.OrderCycle = OrderCycle
$scope.order_cycle = OrderCycle.load(order_cycle_id)
$scope.StatusMessage = StatusMessage
$scope.loaded = ->
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded
$scope.suppliedVariants = (enterprise_id) ->
Enterprise.suppliedVariants(enterprise_id)
$scope.exchangeSelectedVariants = (exchange) ->
OrderCycle.exchangeSelectedVariants(exchange)
$scope.setExchangeVariants = (exchange, variants, selected) ->
OrderCycle.setExchangeVariants(exchange, variants, selected)
$scope.enterpriseTotalVariants = (enterprise) ->
Enterprise.totalVariants(enterprise)
$scope.productSuppliedToOrderCycle = (product) ->
OrderCycle.productSuppliedToOrderCycle(product)
$scope.variantSuppliedToOrderCycle = (variant) ->
OrderCycle.variantSuppliedToOrderCycle(variant)
$scope.incomingExchangeVariantsFor = (enterprise_id) ->
$filter('filterExchangeVariants')(OrderCycle.incomingExchangesVariants(), $scope.order_cycle.visible_variants_for_outgoing_exchanges[enterprise_id])
$scope.exchangeDirection = (exchange) ->
OrderCycle.exchangeDirection(exchange)
$scope.enterprisesWithFees = ->
$scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0
$scope.toggleProducts = ($event, exchange) ->
$event.preventDefault()
OrderCycle.toggleProducts(exchange)
$scope.enterpriseFeesForEnterprise = (enterprise_id) ->
EnterpriseFee.forEnterprise(parseInt(enterprise_id))
$scope.addSupplier = ($event) ->
$event.preventDefault()
OrderCycle.addSupplier($scope.new_supplier_id)
$scope.addDistributor = ($event) ->
$event.preventDefault()
OrderCycle.addDistributor($scope.new_distributor_id)
$scope.removeExchange = ($event, exchange) ->
$event.preventDefault()
OrderCycle.removeExchange(exchange)
$scope.addCoordinatorFee = ($event) ->
$event.preventDefault()
OrderCycle.addCoordinatorFee()
$scope.removeCoordinatorFee = ($event, index) ->
$event.preventDefault()
OrderCycle.removeCoordinatorFee(index)
$scope.addExchangeFee = ($event, exchange) ->
$event.preventDefault()
OrderCycle.addExchangeFee(exchange)
$scope.removeExchangeFee = ($event, exchange, index) ->
$event.preventDefault()
OrderCycle.removeExchangeFee(exchange, index)
$scope.removeDistributionOfVariant = (variant_id) ->
OrderCycle.removeDistributionOfVariant(variant_id)
$scope.submit = (destination) ->
OrderCycle.update(destination)

View File

@@ -1,204 +0,0 @@
angular.module('admin.orderCycles', ['ngResource', 'admin.utils'])
.controller('AdminCreateOrderCycleCtrl', ['$scope', '$filter', 'OrderCycle', 'Enterprise', 'EnterpriseFee', 'ocInstance', 'StatusMessage', ($scope, $filter, OrderCycle, Enterprise, EnterpriseFee, ocInstance, StatusMessage) ->
$scope.enterprises = Enterprise.index(coordinator_id: ocInstance.coordinator_id)
$scope.supplier_enterprises = Enterprise.producer_enterprises
$scope.distributor_enterprises = Enterprise.hub_enterprises
$scope.supplied_products = Enterprise.supplied_products
$scope.enterprise_fees = EnterpriseFee.index(coordinator_id: ocInstance.coordinator_id)
$scope.OrderCycle = OrderCycle
$scope.order_cycle = OrderCycle.new({ coordinator_id: ocInstance.coordinator_id})
$scope.StatusMessage = StatusMessage
$scope.loaded = ->
Enterprise.loaded && EnterpriseFee.loaded
$scope.suppliedVariants = (enterprise_id) ->
Enterprise.suppliedVariants(enterprise_id)
$scope.exchangeSelectedVariants = (exchange) ->
OrderCycle.exchangeSelectedVariants(exchange)
$scope.setExchangeVariants = (exchange, variants, selected) ->
OrderCycle.setExchangeVariants(exchange, variants, selected)
$scope.enterpriseTotalVariants = (enterprise) ->
Enterprise.totalVariants(enterprise)
$scope.productSuppliedToOrderCycle = (product) ->
OrderCycle.productSuppliedToOrderCycle(product)
$scope.variantSuppliedToOrderCycle = (variant) ->
OrderCycle.variantSuppliedToOrderCycle(variant)
$scope.incomingExchangeVariantsFor = (enterprise_id) ->
$filter('filterExchangeVariants')(OrderCycle.incomingExchangesVariants(), $scope.order_cycle.visible_variants_for_outgoing_exchanges[enterprise_id])
$scope.exchangeDirection = (exchange) ->
OrderCycle.exchangeDirection(exchange)
$scope.enterprisesWithFees = ->
$scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0
$scope.toggleProducts = ($event, exchange) ->
$event.preventDefault()
OrderCycle.toggleProducts(exchange)
$scope.enterpriseFeesForEnterprise = (enterprise_id) ->
EnterpriseFee.forEnterprise(parseInt(enterprise_id))
$scope.addSupplier = ($event) ->
$event.preventDefault()
OrderCycle.addSupplier($scope.new_supplier_id)
$scope.addDistributor = ($event) ->
$event.preventDefault()
OrderCycle.addDistributor($scope.new_distributor_id)
$scope.removeExchange = ($event, exchange) ->
$event.preventDefault()
OrderCycle.removeExchange(exchange)
$scope.addCoordinatorFee = ($event) ->
$event.preventDefault()
OrderCycle.addCoordinatorFee()
$scope.removeCoordinatorFee = ($event, index) ->
$event.preventDefault()
OrderCycle.removeCoordinatorFee(index)
$scope.addExchangeFee = ($event, exchange) ->
$event.preventDefault()
OrderCycle.addExchangeFee(exchange)
$scope.removeExchangeFee = ($event, exchange, index) ->
$event.preventDefault()
OrderCycle.removeExchangeFee(exchange, index)
$scope.removeDistributionOfVariant = (variant_id) ->
OrderCycle.removeDistributionOfVariant(variant_id)
$scope.submit = (destination) ->
OrderCycle.create(destination)
])
.controller('AdminEditOrderCycleCtrl', ['$scope', '$filter', '$location', 'OrderCycle', 'Enterprise', 'EnterpriseFee', 'StatusMessage', ($scope, $filter, $location, OrderCycle, Enterprise, EnterpriseFee, StatusMessage) ->
order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1]
$scope.enterprises = Enterprise.index(order_cycle_id: order_cycle_id)
$scope.supplier_enterprises = Enterprise.producer_enterprises
$scope.distributor_enterprises = Enterprise.hub_enterprises
$scope.supplied_products = Enterprise.supplied_products
$scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: order_cycle_id)
$scope.OrderCycle = OrderCycle
$scope.order_cycle = OrderCycle.load(order_cycle_id)
$scope.StatusMessage = StatusMessage
$scope.loaded = ->
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded
$scope.suppliedVariants = (enterprise_id) ->
Enterprise.suppliedVariants(enterprise_id)
$scope.exchangeSelectedVariants = (exchange) ->
OrderCycle.exchangeSelectedVariants(exchange)
$scope.setExchangeVariants = (exchange, variants, selected) ->
OrderCycle.setExchangeVariants(exchange, variants, selected)
$scope.enterpriseTotalVariants = (enterprise) ->
Enterprise.totalVariants(enterprise)
$scope.productSuppliedToOrderCycle = (product) ->
OrderCycle.productSuppliedToOrderCycle(product)
$scope.variantSuppliedToOrderCycle = (variant) ->
OrderCycle.variantSuppliedToOrderCycle(variant)
$scope.incomingExchangeVariantsFor = (enterprise_id) ->
$filter('filterExchangeVariants')(OrderCycle.incomingExchangesVariants(), $scope.order_cycle.visible_variants_for_outgoing_exchanges[enterprise_id])
$scope.exchangeDirection = (exchange) ->
OrderCycle.exchangeDirection(exchange)
$scope.enterprisesWithFees = ->
$scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0
$scope.toggleProducts = ($event, exchange) ->
$event.preventDefault()
OrderCycle.toggleProducts(exchange)
$scope.enterpriseFeesForEnterprise = (enterprise_id) ->
EnterpriseFee.forEnterprise(parseInt(enterprise_id))
$scope.addSupplier = ($event) ->
$event.preventDefault()
OrderCycle.addSupplier($scope.new_supplier_id)
$scope.addDistributor = ($event) ->
$event.preventDefault()
OrderCycle.addDistributor($scope.new_distributor_id)
$scope.removeExchange = ($event, exchange) ->
$event.preventDefault()
OrderCycle.removeExchange(exchange)
$scope.addCoordinatorFee = ($event) ->
$event.preventDefault()
OrderCycle.addCoordinatorFee()
$scope.removeCoordinatorFee = ($event, index) ->
$event.preventDefault()
OrderCycle.removeCoordinatorFee(index)
$scope.addExchangeFee = ($event, exchange) ->
$event.preventDefault()
OrderCycle.addExchangeFee(exchange)
$scope.removeExchangeFee = ($event, exchange, index) ->
$event.preventDefault()
OrderCycle.removeExchangeFee(exchange, index)
$scope.removeDistributionOfVariant = (variant_id) ->
OrderCycle.removeDistributionOfVariant(variant_id)
$scope.submit = (destination) ->
OrderCycle.update(destination)
])
.config(['$httpProvider', ($httpProvider) ->
$httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
])
.directive('datetimepicker', ['$parse', ($parse) ->
(scope, element, attrs) ->
# using $parse instead of scope[attrs.datetimepicker] for cases
# where attrs.datetimepicker is 'foo.bar.lol'
$(element).datetimepicker
dateFormat: 'yy-mm-dd'
timeFormat: 'HH:mm:ss'
showOn: "button"
buttonImage: "<%= asset_path 'datepicker/cal.gif' %>"
buttonImageOnly: true
stepMinute: 15
onSelect: (dateText, inst) ->
scope.$apply ->
parsed = $parse(attrs.datetimepicker)
parsed.assign(scope, dateText)
])
.directive('ofnOnChange', ->
(scope, element, attrs) ->
element.bind 'change', ->
scope.$apply(attrs.ofnOnChange)
)
.directive('ofnSyncDistributions', ->
(scope, element, attrs) ->
element.bind 'change', ->
if !$(this).is(':checked')
scope.$apply ->
scope.removeDistributionOfVariant(attrs.ofnSyncDistributions)
)

View File

@@ -1 +1,32 @@
angular.module('admin.orderCycles', ['ngResource', 'admin.indexUtils'])
angular.module('admin.orderCycles', ['ngResource', 'admin.utils', 'admin.indexUtils'])
.config ($httpProvider) ->
$httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
.directive 'datetimepicker', ($parse) ->
(scope, element, attrs) ->
# using $parse instead of scope[attrs.datetimepicker] for cases
# where attrs.datetimepicker is 'foo.bar.lol'
$(element).datetimepicker
dateFormat: 'yy-mm-dd'
timeFormat: 'HH:mm:ss'
showOn: "button"
buttonImage: "<%= asset_path 'datepicker/cal.gif' %>"
buttonImageOnly: true
stepMinute: 15
onSelect: (dateText, inst) ->
scope.$apply ->
parsed = $parse(attrs.datetimepicker)
parsed.assign(scope, dateText)
.directive 'ofnOnChange', ->
(scope, element, attrs) ->
element.bind 'change', ->
scope.$apply(attrs.ofnOnChange)
.directive 'ofnSyncDistributions', ->
(scope, element, attrs) ->
element.bind 'change', ->
if !$(this).is(':checked')
scope.$apply ->
scope.removeDistributionOfVariant(attrs.ofnSyncDistributions)

View File

@@ -1,26 +0,0 @@
angular.module("ofn.admin").factory "StatusMessage", ($timeout) ->
new class StatusMessage
types:
progress: {timeout: false, style: {color: '#ff9906'}}
alert: {timeout: 5000, style: {color: 'grey'}}
notice: {timeout: false, style: {color: 'grey'}}
success: {timeout: 5000, style: {color: '#9fc820'}}
failure: {timeout: false, style: {color: '#da5354'}}
statusMessage:
text: ""
style: {}
display: (type, text) ->
@statusMessage.text = text
@statusMessage.style = @types[type].style
$timeout.cancel @statusMessage.timeout if @statusMessage.timeout
timeout = @types[type].timeout
if timeout
@statusMessage.timeout = $timeout =>
@clear()
, timeout, true
clear: ->
@statusMessage.text = ''
@statusMessage.style = {}

View File

@@ -1,23 +0,0 @@
angular.module("ofn.admin").factory "VariantOverrides", (variantOverrides, Indexer) ->
new class VariantOverrides
variantOverrides: {}
constructor: ->
for vo in variantOverrides
@variantOverrides[vo.hub_id] ||= {}
@variantOverrides[vo.hub_id][vo.variant_id] = vo
ensureDataFor: (hubs, products) ->
for hub in hubs
@variantOverrides[hub.id] ||= {}
for product in products
for variant in product.variants
@variantOverrides[hub.id][variant.id] ||=
variant_id: variant.id
hub_id: hub.id
price: ''
count_on_hand: ''
updateIds: (updatedVos) ->
for vo in updatedVos
@variantOverrides[vo.hub_id][vo.variant_id].id = vo.id

View File

@@ -0,0 +1,8 @@
angular.module("admin.utils").directive "saveBar", (StatusMessage) ->
restrict: "E"
scope:
save: "&"
form: "="
templateUrl: "admin/save_bar.html"
link: (scope, element, attrs) ->
scope.StatusMessage = StatusMessage

View File

@@ -11,6 +11,9 @@ angular.module("admin.utils").factory "StatusMessage", ($timeout) ->
text: ""
style: {}
active: ->
@statusMessage.text != ''
display: (type, text) ->
@statusMessage.text = text
@statusMessage.style = @types[type].style
@@ -20,6 +23,7 @@ angular.module("admin.utils").factory "StatusMessage", ($timeout) ->
@statusMessage.timeout = $timeout =>
@clear()
, timeout, true
null # So we don't return weird timeouts
clear: ->
@statusMessage.text = ''

View File

@@ -0,0 +1,102 @@
angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl", ($scope, $http, $timeout, Indexer, Columns, SpreeApiAuth, PagedFetcher, StatusMessage, hubs, producers, hubPermissions, VariantOverrides, DirtyVariantOverrides) ->
$scope.hubs = Indexer.index hubs
$scope.hub = null
$scope.products = []
$scope.producers = producers
$scope.producersByID = Indexer.index producers
$scope.hubPermissions = hubPermissions
$scope.variantOverrides = VariantOverrides.variantOverrides
$scope.StatusMessage = StatusMessage
$scope.columns = Columns.setColumns
producer: { name: "Producer", visible: true }
product: { name: "Product", visible: true }
sku: { name: "SKU", visible: false }
price: { name: "Price", visible: true }
on_hand: { name: "On Hand", visible: true }
on_demand: { name: "On Demand", visible: false }
reset: { name: "Reset Stock Level", visible: false }
inheritance: { name: "Inheritance", visible: false }
$scope.resetSelectFilters = ->
$scope.producerFilter = 0
$scope.query = ''
$scope.resetSelectFilters()
$scope.initialise = ->
SpreeApiAuth.authorise()
.then ->
$scope.spree_api_key_ok = true
$scope.fetchProducts()
.catch (message) ->
$scope.api_error_msg = message
$scope.fetchProducts = ->
url = "/api/products/overridable?page=::page::;per_page=100"
PagedFetcher.fetch url, (data) => $scope.addProducts data.products
$scope.addProducts = (products) ->
$scope.products = $scope.products.concat products
VariantOverrides.ensureDataFor hubs, products
$scope.selectHub = ->
$scope.hub = $scope.hubs[$scope.hub_id]
$scope.displayDirty = ->
if DirtyVariantOverrides.count() > 0
num = if DirtyVariantOverrides.count() == 1 then "one override" else "#{DirtyVariantOverrides.count()} overrides"
StatusMessage.display 'notice', "Changes to #{num} remain unsaved."
else
StatusMessage.clear()
$scope.update = ->
if DirtyVariantOverrides.count() == 0
StatusMessage.display 'alert', 'No changes to save.'
else
StatusMessage.display 'progress', 'Saving...'
DirtyVariantOverrides.save()
.success (updatedVos) ->
DirtyVariantOverrides.clear()
VariantOverrides.updateIds updatedVos
$scope.variant_overrides_form.$setPristine()
StatusMessage.display 'success', 'Changes saved.'
VariantOverrides.updateData updatedVos # Refresh page data
.error (data, status) ->
StatusMessage.display 'failure', $scope.updateError(data, status)
$scope.updateError = (data, status) ->
if status == 401
"I couldn't get authorisation to save those changes, so they remain unsaved."
else if status == 400 && data.errors?
errors = []
for field, field_errors of data.errors
errors = errors.concat field_errors
errors = errors.join ', '
"I had some trouble saving: #{errors}"
else
"Oh no! I was unable to save your changes."
$scope.resetStock = ->
if DirtyVariantOverrides.count() > 0
StatusMessage.display 'alert', 'Save changes first.'
$timeout ->
$scope.displayDirty()
, 3000 # 3 second delay
else
return unless $scope.hub_id?
StatusMessage.display 'progress', 'Changing on hand stock levels...'
$http
method: "POST"
url: "/admin/variant_overrides/bulk_reset"
data: { hub_id: $scope.hub_id }
.success (updatedVos) ->
VariantOverrides.updateData updatedVos
StatusMessage.display 'success', 'Stocks reset to defaults.'
.error (data, status) ->
$timeout -> StatusMessage.display 'failure', $scope.updateError(data, status)

View File

@@ -0,0 +1,12 @@
angular.module("admin.variantOverrides").directive "trackInheritance", (VariantOverrides, DirtyVariantOverrides) ->
require: "ngModel"
link: (scope, element, attrs, ngModel) ->
# This is a bit hacky, but it allows us to load the inherit property on the VO, but then not submit it
scope.inherit = angular.equals scope.variantOverrides[scope.hub.id][scope.variant.id], VariantOverrides.newFor scope.hub.id, scope.variant.id
ngModel.$parsers.push (viewValue) ->
if ngModel.$dirty && viewValue
variantOverride = VariantOverrides.inherit(scope.hub.id, scope.variant.id)
DirtyVariantOverrides.add variantOverride
scope.displayDirty()
viewValue

View File

@@ -1,9 +1,10 @@
angular.module("ofn.admin").directive "ofnTrackVariantOverride", (DirtyVariantOverrides) ->
angular.module("admin.variantOverrides").directive "ofnTrackVariantOverride", (DirtyVariantOverrides) ->
require: "ngModel"
link: (scope, element, attrs, ngModel) ->
ngModel.$parsers.push (viewValue) ->
if ngModel.$dirty
variantOverride = scope.variantOverrides[scope.hub.id][scope.variant.id]
scope.inherit = false
DirtyVariantOverrides.add variantOverride
scope.displayDirty()
viewValue

View File

@@ -1,4 +1,4 @@
angular.module("ofn.admin").filter "hubPermissions", ($filter) ->
angular.module("admin.variantOverrides").filter "hubPermissions", ($filter) ->
return (products, hubPermissions, hub_id) ->
return [] if !hub_id
return $filter('filter')(products, ((product) -> hubPermissions[hub_id].indexOf(product.producer_id) > -1), true)

View File

@@ -1,4 +1,4 @@
angular.module("ofn.admin").factory "DirtyVariantOverrides", ($http) ->
angular.module("admin.variantOverrides").factory "DirtyVariantOverrides", ($http) ->
new class DirtyVariantOverrides
dirtyVariantOverrides: {}

View File

@@ -0,0 +1,40 @@
angular.module("admin.variantOverrides").factory "VariantOverrides", (variantOverrides) ->
new class VariantOverrides
variantOverrides: {}
constructor: ->
for vo in variantOverrides
@variantOverrides[vo.hub_id] ||= {}
@variantOverrides[vo.hub_id][vo.variant_id] = vo
ensureDataFor: (hubs, products) ->
for hub_id, hub of hubs
@variantOverrides[hub.id] ||= {}
for product in products
for variant in product.variants
@inherit(hub.id, variant.id) unless @variantOverrides[hub.id][variant.id]
inherit: (hub_id, variant_id) ->
# This method is called from the trackInheritance directive, to reinstate inheritance
@variantOverrides[hub_id][variant_id] ||= {}
angular.extend @variantOverrides[hub_id][variant_id], @newFor hub_id, variant_id
newFor: (hub_id, variant_id) ->
# These properties need to match those checked in VariantOverrideSet.deletable?
hub_id: hub_id
variant_id: variant_id
sku: null
price: null
count_on_hand: null
on_demand: null
default_stock: null
resettable: false
updateIds: (updatedVos) ->
for vo in updatedVos
@variantOverrides[vo.hub_id][vo.variant_id].id = vo.id
updateData: (updatedVos) ->
for vo in updatedVos
@variantOverrides[vo.hub_id][vo.variant_id] = vo

View File

@@ -0,0 +1 @@
angular.module("admin.variantOverrides", ["pasvaz.bindonce", "admin.indexUtils", "admin.utils", "admin.dropdown"])

View File

@@ -2,8 +2,11 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, storage)->
# Handles syncing of current cart/order state to server
new class Cart
dirty: false
update_running: false
update_enqueued: false
order: CurrentOrder.order
line_items: CurrentOrder.order?.line_items || []
constructor: ->
for line_item in @line_items
line_item.variant.line_item = line_item
@@ -12,15 +15,31 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, storage)->
orderChanged: =>
@unsaved()
if !@update_running
@scheduleUpdate()
else
@update_enqueued = true
scheduleUpdate: =>
if @promise
$timeout.cancel(@promise)
@promise = $timeout @update, 1000
update: =>
@update_running = true
$http.post('/orders/populate', @data()).success (data, status)=>
@saved()
@update_running = false
@popQueue() if @update_enqueued
.error (response, status)=>
@scheduleRetry()
@scheduleRetry(status)
@update_running = false
popQueue: =>
@update_enqueued = false
@scheduleUpdate()
data: =>
variants = {}
@@ -30,7 +49,7 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, storage)->
max_quantity: li.max_quantity
{variants: variants}
scheduleRetry: =>
scheduleRetry: (status) =>
console.log "Error updating cart: #{status}. Retrying in 3 seconds..."
$timeout =>
console.log "Retrying cart update"

View File

@@ -1,4 +1,4 @@
.ofn_drop_down{ "ofn-drop-down" => true }
.ofn-drop-down
%span
%i.icon-check
Actions

View File

@@ -1,10 +1,6 @@
#save-bar.animate-show{ ng: { show: 'dirty()' } }
#save-bar.animate-show{ ng: { show: 'form.$dirty || StatusMessage.active()' } }
.twelve.columns.alpha
%h5{ ng: { show: "dirty() && !saving()" } }
You have unsaved changes
%h5{ ng: { hide: "dirty() || saving()" } }
All changes saved
%h5{ ng: { show: "saving()" } }
Saving...
%h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } }
{{ StatusMessage.statusMessage.text || "&nbsp;" }}
.four.columns.omega.text-right
%input.red{type: "button", value: "Save Changes", ng: { click: "save()" } }
%input.red{type: "button", value: "Save Changes", ng: { disabled: '!form.$dirty', click: "save()" } }

View File

@@ -0,0 +1,13 @@
label.disabled {
color: #c3c3c3;
pointer-events: none;
}
input[type='button']:disabled {
background-color: #c3c3c3;
color: #ffffff;
}
.select2-container-disabled {
pointer-events: none;
}

View File

@@ -0,0 +1,70 @@
#content-header .ofn-drop-down {
border: none;
background-color: #5498da;
color: #fff;
float: none;
margin-left: 3px;
}
.ofn-drop-down:hover, .ofn-drop-down.expanded {
border: 1px solid #adadad;
color: #575757;
}
.ofn-drop-down {
padding: 7px 15px;
border-radius: 3px;
border: 1px solid #d4d4d4;
background-color: #f5f5f5;
position: relative;
display: block;
float: left;
color: #828282;
cursor: pointer;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
text-align: center;
&.right {
float: right;
}
&:hover, &.expanded {
border: 1px solid #adadad;
color: #575757;
}
> span {
width: auto;
text-transform: uppercase;
font-size: 85%;
font-weight: 600;
}
.menu {
margin-top: 1px;
position: absolute;
float: none;
top:100%;
left: 0px;
padding: 5px 0px;
border: 1px solid #adadad;
background-color: #ffffff;
box-shadow: 1px 3px 10px #888888;
z-index: 100;
.menu_item {
margin: 0px;
padding: 2px 0px;
color: #454545;
text-align: left;
}
.menu_item:hover {
background-color: #ededed;
}
}
}

View File

@@ -0,0 +1,3 @@
.filters, .controls, .divider {
margin-bottom: 15px;
}

View File

@@ -184,68 +184,6 @@ table#listing_enterprise_groups {
}
}
#content-header .ofn_drop_down {
border: none;
background-color: #5498da;
color: #fff;
float: none;
margin-left: 3px;
}
.ofn_drop_down {
padding: 6px 15px;
border-radius: 3px;
border: 1px solid #d4d4d4;
background-color: #f5f5f5;
position: relative;
display: block;
float: left;
color: #828282;
cursor: pointer;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
text-align: center;
> span {
width: auto;
text-transform: uppercase;
font-size: 85%;
font-weight: 600;
}
.menu {
margin-top: 1px;
position: absolute;
float: none;
top:100%;
left: 0px;
padding: 5px 0px;
border: 1px solid #adadad;
background-color: #ffffff;
box-shadow: 1px 3px 10px #888888;
z-index: 100;
.menu_item {
margin: 0px;
padding: 2px 0px;
color: #454545;
text-align: left;
}
.menu_item:hover {
background-color: #ededed;
}
}
}
.ofn_drop_down:hover, .ofn_drop_down.expanded {
border: 1px solid #adadad;
color: #575757;
}
.field_with_errors > input {
border-color: red;
}

View File

@@ -1,7 +1,3 @@
.filter_select, .date_filter {
margin-bottom: 10px;
}
input, div {
&.update-pending {
border: solid 1px orange;

View File

@@ -13,6 +13,11 @@
right: 10px
top: 55px
width: 480px
@media screen and (min-width: 641px)
overflow-y: auto
max-height: calc(95vh - 55px)
@media screen and (max-width: 640px)
width: 96%
@@ -48,7 +53,7 @@
.cart-item-delete
a.delete
font-size: 1.125em
.item-thumb-image
display: none
@media screen and (min-width: 640px)

View File

@@ -89,7 +89,7 @@ module Admin
end
def collection_actions
[:index, :for_order_cycle]
[:index, :for_order_cycle, :bulk_update]
end
def current_enterprise

View File

@@ -175,5 +175,9 @@ module Admin
def ams_prefix_whitelist
[:basic]
end
def collection_actions
[:index, :bulk_update]
end
end
end

View File

@@ -4,32 +4,43 @@ module Admin
class VariantOverridesController < ResourceController
include OpenFoodNetwork::SpreeApiKeyLoader
prepend_before_filter :load_data
before_filter :load_collection, only: [:bulk_update]
before_filter :load_spree_api_key, only: :index
before_filter :load_data
def index
end
def bulk_update
collection_hash = Hash[params[:variant_overrides].each_with_index.map { |vo, i| [i, vo] }]
vo_set = VariantOverrideSet.new @variant_overrides, collection_attributes: collection_hash
# Ensure we're authorised to update all variant overrides
vo_set.collection.each { |vo| authorize! :update, vo }
@vo_set.collection.each { |vo| authorize! :update, vo }
if vo_set.save
if @vo_set.save
# Return saved VOs with IDs
render json: vo_set.collection, each_serializer: Api::Admin::VariantOverrideSerializer
render json: @vo_set.collection, each_serializer: Api::Admin::VariantOverrideSerializer
else
if vo_set.errors.present?
render json: { errors: vo_set.errors }, status: 400
if @vo_set.errors.present?
render json: { errors: @vo_set.errors }, status: 400
else
render nothing: true, status: 500
end
end
end
def bulk_reset
# Ensure we're authorised to update all variant overrides.
@collection.each { |vo| authorize! :bulk_reset, vo }
@collection.each(&:reset_stock!)
if collection_errors.present?
render json: { errors: collection_errors }, status: 400
else
render json: @collection, each_serializer: Api::Admin::VariantOverrideSerializer
end
end
private
@@ -43,10 +54,28 @@ module Admin
@hub_permissions = OpenFoodNetwork::Permissions.new(spree_current_user).
variant_override_enterprises_per_hub
@variant_overrides = VariantOverride.for_hubs(@hubs)
end
def load_collection
collection_hash = Hash[params[:variant_overrides].each_with_index.map { |vo, i| [i, vo] }]
@vo_set = VariantOverrideSet.new @variant_overrides, collection_attributes: collection_hash
end
def collection
@variant_overrides = VariantOverride.for_hubs(params[:hub_id] || @hubs)
end
def collection_actions
[:index, :bulk_update, :bulk_reset]
end
# This has been pulled from ModelSet as it is useful for compiling a list of errors on any generic collection (not necessarily a ModelSet)
# Could be pulled down into a lower level controller if it is useful in other high level controllers
def collection_errors
errors = ActiveModel::Errors.new self
full_messages = @collection.map { |element| element.errors.full_messages }.flatten
full_messages.each { |fm| errors.add(:base, fm) }
errors
end
end
end

View File

@@ -23,13 +23,23 @@ Spree::OrdersController.class_eval do
end
def populate
populator = Spree::OrderPopulator.new(current_order(true), current_currency)
if populator.populate(params.slice(:products, :variants, :quantity), true)
fire_event('spree.cart.add')
fire_event('spree.order.contents_changed')
render json: true, status: 200
else
render json: false, status: 402
# Without intervention, the Spree::Adjustment#update_adjustable callback is called many times
# during cart population, for both taxation and enterprise fees. This operation triggers a
# costly Spree::Order#update!, which only needs to be run once. We avoid this by disabling
# callbacks on Spree::Adjustment and then manually invoke Spree::Order#update! on success.
Spree::Adjustment.without_callbacks do
populator = Spree::OrderPopulator.new(current_order(true), current_currency)
if populator.populate(params.slice(:products, :variants, :quantity), true)
fire_event('spree.cart.add')
fire_event('spree.order.contents_changed')
current_order.update!
render json: true, status: 200
else
render json: false, status: 402
end
end
end

View File

@@ -31,12 +31,12 @@ module Admin
admin_inject_json_ams_array ngModule, "shops", @shops, Api::Admin::IdNameSerializer
end
def admin_inject_hubs
admin_inject_json_ams_array "ofn.admin", "hubs", @hubs, Api::Admin::IdNameSerializer
def admin_inject_hubs(opts={module: 'ofn.admin'})
admin_inject_json_ams_array opts[:module], "hubs", @hubs, Api::Admin::IdNameSerializer
end
def admin_inject_producers
admin_inject_json_ams_array "ofn.admin", "producers", @producers, Api::Admin::IdNameSerializer
def admin_inject_producers(opts={module: 'ofn.admin'})
admin_inject_json_ams_array opts[:module], "producers", @producers, Api::Admin::IdNameSerializer
end
def admin_inject_enterprise_permissions
@@ -49,7 +49,7 @@ module Admin
end
def admin_inject_hub_permissions
render partial: "admin/json/injection_ams", locals: {ngModule: "ofn.admin", name: "hubPermissions", json: @hub_permissions.to_json}
render partial: "admin/json/injection_ams", locals: {ngModule: "admin.variantOverrides", name: "hubPermissions", json: @hub_permissions.to_json}
end
def admin_inject_products
@@ -69,7 +69,7 @@ module Admin
end
def admin_inject_variant_overrides
admin_inject_json_ams_array "ofn.admin", "variantOverrides", @variant_overrides, Api::Admin::VariantOverrideSerializer
admin_inject_json_ams_array "admin.variantOverrides", "variantOverrides", @variant_overrides, Api::Admin::VariantOverrideSerializer
end
def admin_inject_order_cycle_instance
@@ -85,7 +85,7 @@ module Admin
end
def admin_inject_spree_api_key
render partial: "admin/json/injection_ams", locals: {ngModule: 'ofn.admin', name: 'SpreeApiKey', json: "'#{@spree_api_key.to_s}'"}
render partial: "admin/json/injection_ams", locals: {ngModule: 'admin.indexUtils', name: 'SpreeApiKey', json: "'#{@spree_api_key.to_s}'"}
end
def admin_inject_json_ams(ngModule, name, data, serializer, opts = {})

View File

@@ -10,4 +10,4 @@ Spree::BaseMailer.class_eval do
# This lets us specify assets using relative paths in email templates
super.merge(url_options: {host: URI(spree.root_url).host })
end
end
end

View File

@@ -16,8 +16,8 @@ class ModelSet
end
end
def collection_attributes=(attributes)
attributes.each do |k, attributes|
def collection_attributes=(collection_attributes)
collection_attributes.each do |k, attributes|
# attributes == {:id => 123, :next_collection_at => '...'}
e = @collection.detect { |e| e.id.to_s == attributes[:id].to_s && !e.id.nil? }
if e.nil?
@@ -41,7 +41,11 @@ class ModelSet
end
def collection_to_delete
collection.select { |e| @delete_if.andand.call(e.attributes) }
# Remove all elements to be deleted from collection and return them
# Allows us to render @model_set.collection without deleted elements
deleted = []
collection.delete_if { |e| deleted << e if @delete_if.andand.call(e.attributes) }
deleted
end
def collection_to_keep
@@ -51,5 +55,4 @@ class ModelSet
def persisted?
false
end
end

View File

@@ -66,7 +66,7 @@ class AbilityDecorator
def add_enterprise_management_abilities(user)
# Spree performs authorize! on (:create, nil) when creating a new order from admin, and also (:search, nil)
# when searching for variants to add to the order
can [:create, :search, :bulk_update], nil
can [:create, :search], nil
can [:admin, :index], :overview
@@ -111,7 +111,9 @@ class AbilityDecorator
OpenFoodNetwork::Permissions.new(user).managed_product_enterprises.include? variant.product.supplier
end
can [:admin, :index, :read, :update, :bulk_update], VariantOverride do |vo|
can [:admin, :index, :read, :update, :bulk_update, :bulk_reset], VariantOverride do |vo|
next false unless vo.hub.present? && vo.variant.andand.product.andand.supplier.present?
hub_auth = OpenFoodNetwork::Permissions.new(user).
variant_override_hubs.
include? vo.hub

View File

@@ -35,5 +35,19 @@ module Spree
def display_included_tax
Spree::Money.new(included_tax, { :currency => currency })
end
def self.without_callbacks
skip_callback :save, :after, :update_adjustable
skip_callback :destroy, :after, :update_adjustable
result = yield
ensure
set_callback :save, :after, :update_adjustable
set_callback :destroy, :after, :update_adjustable
result
end
end
end

View File

@@ -3,6 +3,8 @@ class VariantOverride < ActiveRecord::Base
belongs_to :variant, class_name: 'Spree::Variant'
validates_presence_of :hub_id, :variant_id
# Default stock can be nil, indicating stock should not be reset or zero, meaning reset to zero. Need to ensure this can be set by the user.
validates :default_stock, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
scope :for_hubs, lambda { |hubs|
where(hub_id: hubs)
@@ -49,6 +51,21 @@ class VariantOverride < ActiveRecord::Base
end
end
def default_stock?
default_stock.present?
end
def reset_stock!
if resettable
if default_stock?
self.attributes = { count_on_hand: default_stock }
self.save
else
Bugsnag.notify RuntimeError.new "Attempting to reset stock level for a variant with no default stock level."
end
end
self
end
private

View File

@@ -1,6 +1,16 @@
class VariantOverrideSet < ModelSet
def initialize(collection, attributes={})
super(VariantOverride, collection, attributes, nil,
proc { |attrs| attrs['price'].blank? && attrs['count_on_hand'].blank? } )
super(VariantOverride, collection, attributes, nil, proc { |attrs| deletable?(attrs) } )
end
private
def deletable?(attrs)
attrs['price'].blank? &&
attrs['count_on_hand'].blank? &&
attrs['default_stock'].blank? &&
attrs['resettable'].blank? &&
attrs['sku'].nil? &&
attrs['on_demand'].nil?
end
end

View File

@@ -1,5 +1,5 @@
class Api::Admin::ExchangeSerializer < ActiveModel::Serializer
attributes :id, :sender_id, :receiver_id, :incoming, :variants, :pickup_time, :pickup_instructions
attributes :id, :sender_id, :receiver_id, :incoming, :variants, :receival_instructions, :pickup_time, :pickup_instructions
has_many :enterprise_fees, serializer: Api::Admin::BasicEnterpriseFeeSerializer

View File

@@ -1,3 +1,3 @@
class Api::Admin::VariantOverrideSerializer < ActiveModel::Serializer
attributes :id, :hub_id, :variant_id, :price, :count_on_hand
attributes :id, :hub_id, :variant_id, :sku, :price, :count_on_hand, :on_demand, :default_stock, :resettable
end

View File

@@ -1,5 +1,5 @@
class Api::Admin::VariantSerializer < ActiveModel::Serializer
attributes :id, :options_text, :unit_value, :unit_description, :unit_to_display, :on_demand, :display_as, :display_name, :name_to_display
attributes :id, :options_text, :unit_value, :unit_description, :unit_to_display, :on_demand, :display_as, :display_name, :name_to_display, :sku
attributes :on_hand, :price
has_many :variant_overrides

View File

@@ -1,5 +1,5 @@
class Api::LineItemSerializer < ActiveModel::Serializer
attributes :id, :quantity, :price
attributes :id, :quantity, :max_quantity, :price
has_one :variant, serializer: Api::VariantSerializer
end

View File

@@ -14,25 +14,13 @@
.seven.columns.omega &nbsp;
.row{ 'ng-hide' => '!loaded() || filteredCustomers.length == 0' }
.controls{ :class => "sixteen columns alpha", :style => "margin-bottom: 15px;" }
.controls.sixteen.columns.alpha.omega
.five.columns.alpha
%input{ :class => "fullwidth", :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' }
%input.fullwidth{ :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' }
.five.columns &nbsp;
-# %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "bulk_actions_dropdown", 'ofn-drop-down' => true }
-# %span{ :class => 'icon-check' } &nbsp; Actions
-# %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
-# %div.menu{ 'ng-show' => "expanded" }
-# %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "action in bulkActions", 'ng-click' => "selectedBulkAction.callback(filteredCustomers)", 'ofn-close-on-click' => true }
-# %span{ :class => 'three columns omega' } {{action.name }}
-# =render 'admin/shared/bulk_actions_dropdown'
.three.columns &nbsp;
.three.columns.omega
%div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "columns_dropdown", 'ofn-drop-down' => true, :style => 'float:right;' }
%span{ :class => 'icon-reorder' } &nbsp; Columns
%span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
%div.menu{ 'ng-show' => "expanded" }
%div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true }
%span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "&#10003;" || !column.visible && "&nbsp;" }}
%span{ :class => 'two columns omega' } {{column.name }}
= render 'admin/shared/columns_dropdown'
.row{ 'ng-if' => 'shop && !loaded()' }
.sixteen.columns.alpha#loading
%img.spinner{ src: "/assets/spinning-circles.svg" }

View File

@@ -4,21 +4,9 @@
.four.columns.alpha
%input{ :class => "fullwidth", :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Search By Name' }
.six.columns &nbsp;
-# %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "bulk_actions_dropdown", 'ofn-drop-down' => true }
-# %span{ :class => 'icon-check' } &nbsp; Actions
-# %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
-# %div.menu{ 'ng-show' => "expanded" }
-# %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "action in bulkActions", 'ng-click' => "selectedBulkAction.callback(filteredEnterprises)", 'ofn-close-on-click' => true }
-# %span{ :class => 'three columns omega' } {{action.name }}
-# = render 'admin/shared/bulk_actions_dropdown'
.three.columns &nbsp;
.three.columns.omega
%div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "columns_dropdown", 'ofn-drop-down' => true, :style => 'float:right;' }
%span{ :class => 'icon-reorder' } &nbsp; Columns
%span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
%div.menu{ 'ng-show' => "expanded" }
%div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true }
%span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "&#10003;" || !column.visible && "&nbsp;" }}
%span{ :class => 'two columns omega' } {{column.name }}
= render 'admin/shared/columns_dropdown'
.row{ 'ng-if' => '!loaded' }
.sixteen.columns.alpha#loading
%img.spinner{ src: "/assets/spinning-circles.svg" }

View File

@@ -0,0 +1,7 @@
.three.columns
.ofn-drop-down#bulk-actions-dropdown{ 'ng-controller' => "DropDownCtrl" }
%span.icon-check &nbsp; Actions
%span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
%div.menu{ 'ng-show' => "expanded" }
.three.columns.alpha.menu_item{ 'ng-repeat' => "action in bulkActions", 'ng-click' => "$eval(action.callback)(filteredLineItems)", 'ofn-close-on-click' => true }
%span.three.columns.omega {{action.name }}

View File

@@ -0,0 +1,8 @@
%div.three.columns.omega
%div.ofn-drop-down.right#columns-dropdown{ 'ng-controller' => "DropDownCtrl" }
%span{ :class => 'icon-reorder' } &nbsp; Columns
%span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
%div.menu{ 'ng-show' => "expanded" }
%div.menu_item.three.columns.alpha.omega{ 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true }
%span.one.column.alpha.text-center {{ column.visible && "&#10003;" || !column.visible && "&nbsp;" }}
%span.two.columns.omega {{column.name }}

View File

@@ -1,4 +0,0 @@
.row
%input.four.columns.alpha{type: 'button', value: t(:save_changes), 'ng-click' => 'update()'}
.twelve.columns.omega
= render 'spree/admin/shared/status_message'

View File

@@ -1,5 +1,5 @@
= admin_inject_spree_api_key
= admin_inject_hubs
= admin_inject_hubs module: 'admin.variantOverrides'
= admin_inject_hub_permissions
= admin_inject_producers
= admin_inject_producers module: 'admin.variantOverrides'
= admin_inject_variant_overrides

View File

@@ -0,0 +1,26 @@
.filters.sixteen.columns.alpha
.filter.four.columns.alpha
%label{ :for => 'query', ng: {class: '{disabled: !hub.id}'} }Quick Search
%br
%input.fullwidth{ :type => "text", :id => 'query', ng: { model: 'query', disabled: '!hub.id'} }
.two.columns &nbsp;
.filter_select.four.columns
%label{ :for => 'hub_id', ng: { bind: 'hub_id ? "Shop" : "Select a shop"' } }
%br
%select.select2.fullwidth#hub_id{ 'ng-model' => 'hub_id', name: 'hub_id', ng: { options: 'hub.id as hub.name for (id, hub) in hubs', change: 'selectHub()' } }
.filter_select.four.columns
%label{ :for => 'producer_filter', ng: {class: '{disabled: !hub.id}'} }Producer
%br
%input.ofn-select2.fullwidth{ :id => 'producer_filter', type: 'number', style: 'display:none', data: 'producers', blank: "{id: 0, name: 'All'}", ng: { model: 'producerFilter', disabled: '!hub.id' } }
-# .filter_select{ :class => "three columns" }
-# %label{ :for => 'distributor_filter' }Hub
-# %br
-# %select{ :class => "three columns alpha", :id => 'distributor_filter', 'select2-min-search' => 5, 'ng-model' => 'distributorFilter', 'ng-options' => 'd.id as d.name for d in distributors'}
-# .filter_select{ :class => "three columns" }
-# %label{ :for => 'order_cycle_filter' }Order Cycle
-# %br
-# %select{ :class => "three columns alpha", :id => 'order_cycle_filter', 'select2-min-search' => 5, 'ng-model' => 'orderCycleFilter', 'ng-options' => 'oc.id as oc.name for oc in orderCycles', 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()'}
.filter_clear.two.columns.omega
%label{ :for => 'clear_all_filters' }
%br
%input.red.fullwidth{ :type => 'button', :id => 'clear_all_filters', :value => "Clear All", ng: { click: "resetSelectFilters()", disabled: '!hub.id'} }

View File

@@ -1,7 +0,0 @@
.row
.two.columns.alpha
=t :hub
.four.columns
%select.select2.fullwidth#hub_id{ 'ng-model' => 'hub_id', name: 'hub_id', 'ng-options' => 'hub.id as hub.name for hub in hubs' }
.ten.columns.omega
%input{ type: 'button', value: t(:go), 'ng-click' => 'selectHub()' }

View File

@@ -1,14 +1,23 @@
%table.index.bulk{ng: {show: 'hub'}}
%table.index.bulk{ ng: {show: 'hub'}}
%col.producer{ width: "20%", ng: { show: 'columns.producer.visible' } }
%col.product{ width: "20%", ng: { show: 'columns.product.visible' } }
%col.sku{ width: "20%", ng: { show: 'columns.sku.visible' } }
%col.price{ width: "10%", ng: { show: 'columns.price.visible' } }
%col.on_hand{ width: "10%", ng: { show: 'columns.on_hand.visible' } }
%col.on_demand{ width: "10%", ng: { show: 'columns.on_demand.visible' } }
%col.reset{ width: "1%", ng: { show: 'columns.reset.visible' } }
%col.reset{ width: "15%", ng: { show: 'columns.reset.visible' } }
%col.inheritance{ width: "5%", ng: { show: 'columns.inheritance.visible' } }
%thead
%tr
%th
= t(:producer)
%th
= t(:product)
%th
= t(:price)
%th
= t(:on_hand)
%tbody{ng: {repeat: 'product in products | hubPermissions:hubPermissions:hub.id'}}
%tr{ ng: { controller: "ColumnsCtrl" } }
%th.producer{ ng: { show: 'columns.producer.visible' } } = t(:producer)
%th.product{ ng: { show: 'columns.product.visible' } } = t(:product)
%th.sku{ ng: { show: 'columns.sku.visible' } } SKU
%th.price{ ng: { show: 'columns.price.visible' } } = t(:price)
%th.on_hand{ ng: { show: 'columns.on_hand.visible' } } = t(:on_hand)
%th.on_demand{ ng: { show: 'columns.on_demand.visible' } } On Demand?
%th.reset{ colspan: 2, ng: { show: 'columns.reset.visible' } } Enable Stock Level Reset?
%th.inheritance{ ng: { show: 'columns.inheritance.visible' } } Inherit?
%tbody{bindonce: true, ng: {repeat: 'product in products | hubPermissions:hubPermissions:hub.id | attrFilter:{producer_id:producerFilter} | filter:query' } }
= render 'admin/variant_overrides/products_product'
= render 'admin/variant_overrides/products_variants'

View File

@@ -1,5 +1,9 @@
%tr.product.even
%td {{ producers[product.producer_id].name }}
%td {{ product.name }}
%td
%td
%td.producer{ ng: { show: 'columns.producer.visible' }, bo: { bind: 'producersByID[product.producer_id].name'} }
%td.product{ ng: { show: 'columns.product.visible' }, bo: { bind: 'product.name'} }
%td.sku{ ng: { show: 'columns.sku.visible' } }
%td.price{ ng: { show: 'columns.price.visible' } }
%td.on_hand{ ng: { show: 'columns.on_hand.visible' } }
%td.on_demand{ ng: { show: 'columns.on_demand.visible' } }
%td.reset{ colspan: 2, ng: { show: 'columns.reset.visible' } }
%td.inheritance{ ng: { show: 'columns.inheritance.visible' } }

View File

@@ -1,10 +1,19 @@
%tr.variant{ng: {repeat: 'variant in product.variants'}}
%td
%td
{{ variant.display_name }}
.variant-override-unit {{ variant.unit_to_display }}
%td
%tr.variant{ id: "v_{{variant.id}}", ng: {repeat: 'variant in product.variants'}}
%td.producer{ ng: { show: 'columns.producer.visible' } }
%td.product{ ng: { show: 'columns.product.visible' } }
%span{ bo: { bind: 'variant.display_name || ""'} }
.variant-override-unit{ bo: { bind: 'variant.unit_to_display'} }
%td.sku{ ng: { show: 'columns.sku.visible' } }
%input{name: 'variant-overrides-{{ variant.id }}-sku', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].sku'}, placeholder: '{{ variant.sku }}', 'ofn-track-variant-override' => 'sku'}
%td.price{ ng: { show: 'columns.price.visible' } }
%input{name: 'variant-overrides-{{ variant.id }}-price', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].price'}, placeholder: '{{ variant.price }}', 'ofn-track-variant-override' => 'price'}
%td
%input{name: 'variant-overrides-{{ variant.id }}-count-on-hand', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].count_on_hand'}, placeholder: '{{ variant.on_hand }}', 'ofn-track-variant-override' => 'price'}
%td.on_hand{ ng: { show: 'columns.on_hand.visible' } }
%input{name: 'variant-overrides-{{ variant.id }}-count_on_hand', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].count_on_hand'}, placeholder: '{{ variant.on_hand }}', 'ofn-track-variant-override' => 'count_on_hand'}
%td.on_demand{ ng: { show: 'columns.on_demand.visible' } }
%input.field{ :type => 'checkbox', name: 'variant-overrides-{{ variant.id }}-on_demand', ng: { model: 'variantOverrides[hub.id][variant.id].on_demand' }, 'ofn-track-variant-override' => 'on_demand' }
%td.reset{ ng: { show: 'columns.reset.visible' } }
%input{name: 'variant-overrides-{{ variant.id }}-resettable', type: 'checkbox', ng: {model: 'variantOverrides[hub.id][variant.id].resettable'}, placeholder: '{{ variant.resettable }}', 'ofn-track-variant-override' => 'resettable'}
%td.reset{ ng: { show: 'columns.reset.visible' } }
%input{name: 'variant-overrides-{{ variant.id }}-default_stock', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].default_stock'}, placeholder: '{{ variant.default_stock ? variant.default_stock : "Default stock"}}', 'ofn-track-variant-override' => 'default_stock'}
%td.inheritance{ ng: { show: 'columns.inheritance.visible' } }
%input.field{ :type => 'checkbox', name: 'variant-overrides-{{ variant.id }}-inherit', ng: { model: 'inherit' }, 'track-inheritance' => true }

View File

@@ -1,11 +1,14 @@
= render 'admin/variant_overrides/header'
= render 'admin/variant_overrides/data'
%div{ ng: { app: 'ofn.admin', controller: 'AdminVariantOverridesCtrl', init: 'initialise()' } }
= render 'admin/variant_overrides/hub_choice'
%div{ ng: { app: 'admin.variantOverrides', controller: 'AdminVariantOverridesCtrl', init: 'initialise()' } }
= render 'admin/variant_overrides/filters'
%hr.divider.sixteen.columns.alpha.omega{ ng: { show: 'hub' } }
.controls.sixteen.columns.alpha.omega{ ng: { show: 'hub' } }
%input.four.columns.alpha{ type: 'button', value: 'Reset Stock to Defaults', 'ng-click' => 'resetStock()' }
%div.nine.columns.alpha &nbsp;
= render 'admin/shared/columns_dropdown'
%div{ng: {show: 'hub'}}
%h2 {{ hub.name }}
= render 'admin/variant_overrides/actions'
= render 'admin/variant_overrides/products'
%form{ name: 'variant_overrides_form' }
%save-bar{ save: "update()", form: "variant_overrides_form" }
= render 'admin/variant_overrides/products'

View File

@@ -1,5 +1,9 @@
- content_for(:title) do
= current_distributor.name
- content_for(:description) do
= current_distributor.description
- content_for(:image) do
= current_distributor.logo.url
= inject_enterprises

View File

@@ -1,5 +1,9 @@
- content_for(:title) do
= @group.name
- content_for(:description) do
= @group.description
- content_for(:image) do
= @group.logo.url
-# inject all enterprises as "enterprises"
-# it could be more efficient to inject only the enterprises that are related to the group

View File

@@ -2,7 +2,9 @@
%head
%meta{charset: 'utf-8'}/
%meta{name: 'viewport', content: "width=device-width,initial-scale=1.0"}/
%meta{property: "og:title", content: content_for?(:title) ? yield(:title) : t(:title)}
%meta{property: "og:description", content: content_for?(:description) ? yield(:description) : t(:description)}
%meta{property: "og:image", content: content_for?(:image) ? yield(:image) : ContentConfig.logo.url}
%title= content_for?(:title) ? "#{yield(:title)} - #{t(:title)}".html_safe : "#{t(:welcome_to)} #{t(:title)}"
- if Rails.env.production?
= favicon_link_tag

View File

@@ -7,7 +7,7 @@
= render :partial => 'spree/admin/shared/order_sub_menu'
%div{ ng: { app: 'admin.lineItems', controller: 'LineItemsCtrl' } }
%save-bar{ save: "submit()", saving: 'saving', dirty: "bulk_order_form.$dirty" }
%save-bar{ save: "submit()", form: "bulk_order_form" }
.filters{ :class => "sixteen columns alpha" }
.date_filter{ :class => "two columns alpha" }
%label{ :for => 'start_date_filter' }
@@ -38,8 +38,10 @@
%label{ :for => 'clear_all_filters' }
%br
%input.red.fullwidth{ :type => 'button', :id => 'clear_all_filters', :value => "Clear All", 'ng-click' => "resetSelectFilters()" }
%hr{ :class => "sixteen columns alpha", 'ng-show' => 'unitsVariantSelected()' }
%div#group_buy_calculation{ :class => "sixteen columns alpha", 'ng-show' => 'unitsVariantSelected()' }
%hr.divider.sixteen.columns.alpha.omega{ ng: { show: 'unitsVariantSelected()' } }
%div.sixteen.columns.alpha.omega#group_buy_calculation{ ng: { show: 'unitsVariantSelected()' } }
%div.shared_resource{ :class => "four columns alpha" }
%span{ :class => 'three columns alpha' }
%input{ type: 'checkbox', :id => 'shared_resource', 'ng-model' => 'sharedResource'}
@@ -106,9 +108,10 @@
%div.sixteen.columns.alpha#loading{ 'ng-if' => 'RequestMonitor.loading' }
%img.spinner{ src: "/assets/spinning-circles.svg" }
%h1 LOADING ORDERS
%div{ :class => "sixteen columns alpha", 'ng-show' => '!RequestMonitor.loading && filteredLineItems.length == 0'}
%h1#no_results
= t "no_orders_found"
%h1#no_results No orders found.
%div{ 'ng-hide' => 'RequestMonitor.loading || filteredLineItems.length == 0' }
%form{ name: 'bulk_order_form' }
%table.index#listing_orders.bulk{ :class => "sixteen columns alpha" }
@@ -150,6 +153,7 @@
%th.actions
= t "ask"
%input{ :type => 'checkbox', 'ng-model' => "confirmDelete" }
%tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" }
%td.bulk
%input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked', 'ignore-dirty' => true }

View File

@@ -4,7 +4,7 @@
%div{ ng: { app: 'ofn.admin', controller: 'AdminProductEditCtrl', init: 'initialise()' } }
= render 'spree/admin/products/bulk_edit/filters'
%hr.sixteen.columns.alpha
%hr.divider.sixteen.columns.alpha.omega
= render 'spree/admin/products/bulk_edit/actions'
= render 'spree/admin/products/bulk_edit/indicators'
= render 'spree/admin/products/bulk_edit/products'

View File

@@ -1,13 +1,6 @@
%div.sixteen.columns.alpha{ 'ng-hide' => 'loading || products.length == 0', style: "margin-bottom: 10px" }
%div.four.columns.alpha
.controls.sixteen.columns.alpha{ 'ng-hide' => 'loading || products.length == 0' }
.four.columns.alpha
%input.four.columns.alpha{ :type => 'button', :value => 'Save Changes', 'ng-click' => 'submitProducts()'}
%div.nine.columns
.nine.columns
= render 'spree/admin/shared/status_message'
%div.three.columns.omega
%div.ofn_drop_down.three.columns.omega{ 'ng-controller' => "DropDownCtrl", :id => "columns_dropdown", 'ofn-drop-down' => true, :style => 'float:right;' }
%span{ :class => 'icon-reorder' } &nbsp; Columns
%span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
%div.menu{ 'ng-show' => "expanded" }
%div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true }
%span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "&#10003;" || !column.visible && "&nbsp;" }}
%span{ :class => 'two columns omega' } {{column.name }}
= render 'admin/shared/columns_dropdown'

View File

@@ -1,20 +1,18 @@
%div.sixteen.columns.alpha
%div.quick_search{ :class => "four columns alpha" }
.filters.sixteen.columns.alpha.omega
.quick_search.four.columns.alpha
%label{ :for => 'quick_filter' }
%br
%input.search{ :class => "four columns alpha", 'ng-model' => 'query', :name => "quick_filter", :type => 'text', 'placeholder' => 'Quick Search' }
.filter_select{ :class => "four columns" }
%label{ :for => 'producer_filter' }
= t 'producer'
%input.quick-search.fullwidth{ 'ng-model' => 'query', :name => "quick_filter", :type => 'text', 'placeholder' => 'Quick Search' }
.filter_select.four.columns
%label{ :for => 'producer_filter' }= t 'producer'
%br
%select{ :class => "four columns alpha", :id => 'producer_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'producerFilter', 'ng-options' => 'producer.id as producer.name for producer in filterProducers' }
.filter_select{ :class => "four columns" }
%label{ :for => 'category_filter' }
= t 'category'
%select.fullwidth{ :id => 'producer_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'producerFilter', 'ng-options' => 'producer.id as producer.name for producer in filterProducers' }
.filter_select.four.columns
%label{ :for => 'category_filter' }= t 'category'
%br
%select{ :class => "four columns alpha", :id => 'category_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'categoryFilter', 'ng-options' => 'taxon.id as taxon.name for taxon in filterTaxons'}
%select.fullwidth{ :id => 'category_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'categoryFilter', 'ng-options' => 'taxon.id as taxon.name for taxon in filterTaxons'}
%div{ :class => "one column" } &nbsp;
.filter_clear{ :class => "three columns omega" }
.filter_clear.three.columns.omega
%label{ :for => 'clear_all_filters' }
%br
%input.fullwidth.red{ :type => 'button', :id => 'clear_all_filters', :value => "Clear Filters", 'ng-click' => "resetSelectFilters()" }

View File

@@ -4,6 +4,7 @@
%a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "$last" }
%td{ 'ng-show' => 'columns.producer.visible' }
%td{ 'ng-show' => 'columns.sku.visible' }
%input{ 'ng-model' => "variant.sku", :name => 'variant_sku', 'ofn-track-variant' => 'sku', :type => 'text' }
%td{ 'ng-show' => 'columns.name.visible' }
%input{ 'ng-model' => 'variant.display_name', :name => 'variant_display_name', 'ofn-track-variant' => 'display_name', :type => 'text', placeholder: "{{ product.name }}" }
%td.unit_value{ 'ng-show' => 'columns.unit.visible' }

View File

@@ -62,7 +62,7 @@
%strong= order.shipping_method.name
.pad
.text-big
Ready for collection
= t :order_pickup_time
%strong #{order.order_cycle.pickup_time_for(order.distributor)}
%p.text-small.text-skinny.pre-line
%em= order.shipping_method.description.andand.html_safe || ""
@@ -140,7 +140,7 @@
%tr.total
%td.text-right{colspan: "3"}
%h5
= t :order_produce
= t :order_total_price
%td.text-right.total
%h5#order_total= order.display_total.to_html

View File

@@ -66,6 +66,7 @@ module Openfoodnetwork
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
config.i18n.default_locale = ENV["LOCALE"]
I18n.locale = config.i18n.locale = config.i18n.default_locale
# Setting this to true causes a performance regression in Rails 3.2.17
# When we're on a version with the fix below, we can set it to true

View File

@@ -29,6 +29,7 @@ en:
home: "OFN"
title: Open Food Network
welcome_to: 'Welcome to '
description: "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…"
search_by_name: Search by name or suburb...
producers: Aussie Producers #FIXME
producers_join: Australian producers are now welcome to join the Open Food Network. #FIXME
@@ -52,6 +53,8 @@ en:
free: "free"
plus_tax: "plus GST"
total_monthly_bill_incl_tax: "Total Monthly Bill (Incl. Tax)"
say_no: "No"
say_yes: "Yes"
sort_order_cycles_on_shopfront_by: "Sort Order Cycles On Shopfront By"
@@ -227,6 +230,7 @@ en:
order_delivery_on: Delivery on
order_delivery_address: Delivery address
order_special_instructions: "Your notes:"
order_pickup_time: Ready for collection
order_pickup_instructions: Collection Instructions
order_produce: Produce
order_total_price: Total

View File

@@ -166,6 +166,7 @@ fr:
order_delivery_on: Livraison prévue
order_delivery_address: Adresse de livraison
order_special_instructions: "Vos commentaires:"
order_pickup_time: Disponible pour retrait
order_pickup_instructions: Instructions de retrait
order_produce: Produit
order_total_price: Total

View File

@@ -103,6 +103,7 @@ Openfoodnetwork::Application.routes.draw do
resources :variant_overrides do
post :bulk_update, on: :collection
post :bulk_reset, on: :collection
end
resources :customers, only: [:index, :update]

View File

@@ -0,0 +1,5 @@
class AddDefaultStockToVariantOverrides < ActiveRecord::Migration
def change
add_column :variant_overrides, :default_stock, :integer
end
end

View File

@@ -0,0 +1,5 @@
class AddEnableResetToVariantOverrides < ActiveRecord::Migration
def change
add_column :variant_overrides, :enable_reset, :boolean
end
end

View File

@@ -0,0 +1,6 @@
class AddOnDemandAndSkuToVariantOverrides < ActiveRecord::Migration
def change
add_column :variant_overrides, :sku, :string, :default => nil, :after => :hub_id
add_column :variant_overrides, :on_demand, :boolean, :default => nil, :after => :count_on_hand
end
end

View File

@@ -0,0 +1,3 @@
class RenameEnableResetToResettable < ActiveRecord::Migration
rename_column :variant_overrides, :enable_reset, :resettable
end

View File

@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20151125051510) do
ActiveRecord::Schema.define(:version => 20151128185900) do
create_table "account_invoices", :force => true do |t|
t.integer "user_id", :null => false
@@ -1159,6 +1159,10 @@ ActiveRecord::Schema.define(:version => 20151125051510) do
t.integer "hub_id", :null => false
t.decimal "price", :precision => 8, :scale => 2
t.integer "count_on_hand"
t.integer "default_stock"
t.boolean "resettable"
t.string "sku"
t.boolean "on_demand"
end
add_index "variant_overrides", ["variant_id", "hub_id"], :name => "index_variant_overrides_on_variant_id_and_hub_id"

173
knapsack_rspec_report.json Normal file
View File

@@ -0,0 +1,173 @@
{
"spec/controllers/admin/accounts_and_billing_settings_controller_spec.rb": 5.547292709350586,
"spec/controllers/admin/business_model_configuration_controller_spec.rb": 0.3683593273162842,
"spec/controllers/admin/customers_controller_spec.rb": 0.8933048248291016,
"spec/controllers/admin/enterprises_controller_spec.rb": 5.984264850616455,
"spec/controllers/admin/order_cycles_controller_spec.rb": 2.839667558670044,
"spec/controllers/api/enterprises_controller_spec.rb": 0.2780017852783203,
"spec/controllers/api/order_cycles_controller_spec.rb": 1.8730568885803223,
"spec/controllers/base_controller_spec.rb": 0.02932429313659668,
"spec/controllers/cart_controller_spec.rb": 1.062530517578125,
"spec/controllers/checkout_controller_spec.rb": 1.6658811569213867,
"spec/controllers/enterprise_confirmations_controller_spec.rb": 1.1228001117706299,
"spec/controllers/enterprises_controller_spec.rb": 2.2625372409820557,
"spec/controllers/groups_controller_spec.rb": 0.40616846084594727,
"spec/controllers/registration_controller_spec.rb": 0.2145981788635254,
"spec/controllers/shop_controller_spec.rb": 5.298644304275513,
"spec/controllers/shops_controller_spec.rb": 0.2002561092376709,
"spec/controllers/spree/admin/adjustments_controller_spec.rb": 1.023233413696289,
"spec/controllers/spree/admin/base_controller_spec.rb": 0.28871917724609375,
"spec/controllers/spree/admin/line_items_controller_spec.rb": 14.042466402053833,
"spec/controllers/spree/admin/orders_controller_spec.rb": 12.639750480651855,
"spec/controllers/spree/admin/overview_controller_spec.rb": 0.691641092300415,
"spec/controllers/spree/admin/payment_methods_controller_spec.rb": 0.7098217010498047,
"spec/controllers/spree/admin/products_controller_spec.rb": 1.4383087158203125,
"spec/controllers/spree/admin/reports_controller_spec.rb": 47.79633665084839,
"spec/controllers/spree/admin/search_controller_spec.rb": 0.9386723041534424,
"spec/controllers/spree/admin/variants_controller_spec.rb": 2.0663084983825684,
"spec/controllers/spree/api/line_items_controller_spec.rb": 0.4743325710296631,
"spec/controllers/spree/api/products_controller_spec.rb": 8.339523792266846,
"spec/controllers/spree/api/variants_controller_spec.rb": 4.835069179534912,
"spec/controllers/spree/checkout_controller_spec.rb": 0.687798023223877,
"spec/controllers/spree/orders_controller_spec.rb": 1.7623963356018066,
"spec/controllers/spree/paypal_controller_spec.rb": 0.437147855758667,
"spec/controllers/spree/store_controller_spec.rb": 0.03699040412902832,
"spec/controllers/spree/user_sessions_controller_spec.rb": 0.09967947006225586,
"spec/controllers/user_passwords_controller_spec.rb": 0.31070899963378906,
"spec/controllers/user_registrations_controller_spec.rb": 0.36581993103027344,
"spec/features/admin/account_spec.rb": 0.32449865341186523,
"spec/features/admin/accounts_and_billing_settings_spec.rb": 15.864763259887695,
"spec/features/admin/adjustments_spec.rb": 6.825028896331787,
"spec/features/admin/authentication_spec.rb": 22.29801869392395,
"spec/features/admin/bulk_order_management_spec.rb": 112.38913011550903,
"spec/features/admin/bulk_product_update_spec.rb": 59.00568914413452,
"spec/features/admin/business_model_configuration_spec.rb": 2.5152199268341064,
"spec/features/admin/cms_spec.rb": 2.5085999965667725,
"spec/features/admin/content_spec.rb": 1.2907540798187256,
"spec/features/admin/customers_spec.rb": 33.99929761886597,
"spec/features/admin/enterprise_fees_spec.rb": 13.33712100982666,
"spec/features/admin/enterprise_groups_spec.rb": 8.689672231674194,
"spec/features/admin/enterprise_relationships_spec.rb": 7.257282733917236,
"spec/features/admin/enterprise_roles_spec.rb": 5.535412788391113,
"spec/features/admin/enterprise_user_spec.rb": 2.5493221282958984,
"spec/features/admin/enterprises/index_spec.rb": 5.77092719078064,
"spec/features/admin/enterprises_spec.rb": 34.78606820106506,
"spec/features/admin/image_settings_spec.rb": 0.4501008987426758,
"spec/features/admin/order_cycles_spec.rb": 64.186044216156,
"spec/features/admin/orders_spec.rb": 49.190918922424316,
"spec/features/admin/overview_spec.rb": 5.788672208786011,
"spec/features/admin/payment_method_spec.rb": 15.959310531616211,
"spec/features/admin/products_spec.rb": 21.46337914466858,
"spec/features/admin/reports_spec.rb": 150.51152086257935,
"spec/features/admin/shipping_methods_spec.rb": 8.671862363815308,
"spec/features/admin/tax_settings_spec.rb": 0.7941949367523193,
"spec/features/admin/variant_overrides_spec.rb": 29.70982050895691,
"spec/features/admin/variants_spec.rb": 5.565031290054321,
"spec/features/consumer/authentication_spec.rb": 12.449390649795532,
"spec/features/consumer/groups_spec.rb": 1.545715093612671,
"spec/features/consumer/producers_spec.rb": 3.3242862224578857,
"spec/features/consumer/registration_spec.rb": 2.421873092651367,
"spec/features/consumer/shopping/cart_spec.rb": 1.6924467086791992,
"spec/features/consumer/shopping/checkout_auth_spec.rb": 8.496914863586426,
"spec/features/consumer/shopping/checkout_spec.rb": 39.204933881759644,
"spec/features/consumer/shopping/shopping_spec.rb": 23.358332633972168,
"spec/features/consumer/shopping/variant_overrides_spec.rb": 58.16736888885498,
"spec/features/consumer/shops_spec.rb": 6.636866092681885,
"spec/helpers/admin/business_model_configuration_helper_spec.rb": 0.2595028877258301,
"spec/helpers/checkout_helper_spec.rb": 0.10617446899414062,
"spec/helpers/groups_helper_spec.rb": 0.007729053497314453,
"spec/helpers/html_helper_spec.rb": 0.05157279968261719,
"spec/helpers/injection_helper_spec.rb": 0.6142556667327881,
"spec/helpers/navigation_helper_spec.rb": 0.02951979637145996,
"spec/helpers/order_cycles_helper_spec.rb": 0.5953588485717773,
"spec/helpers/products_helper_spec.rb": 0.009511232376098633,
"spec/helpers/shared_helper_spec.rb": 0.017564058303833008,
"spec/helpers/shop_helper_spec.rb": 0.05760025978088379,
"spec/jobs/confirm_order_job_spec.rb": 0.0458524227142334,
"spec/jobs/confirm_signup_job_spec.rb": 0.021564006805419922,
"spec/jobs/finalize_account_invoices_spec.rb": 4.505181312561035,
"spec/jobs/order_cycle_notification_job_spec.rb": 2.0606272220611572,
"spec/jobs/update_account_invoices_spec.rb": 18.434475898742676,
"spec/jobs/update_billable_periods_spec.rb": 4.850176572799683,
"spec/jobs/welcome_enterprise_job_spec.rb": 0.07065534591674805,
"spec/lib/open_food_network/bulk_coop_report_spec.rb": 4.789663553237915,
"spec/lib/open_food_network/customers_report_spec.rb": 2.419727325439453,
"spec/lib/open_food_network/distribution_change_validator_spec.rb": 0.10607743263244629,
"spec/lib/open_food_network/enterprise_fee_applicator_spec.rb": 0.7333858013153076,
"spec/lib/open_food_network/enterprise_fee_calculator_spec.rb": 7.406745195388794,
"spec/lib/open_food_network/enterprise_injection_data_spec.rb": 0.291548490524292,
"spec/lib/open_food_network/enterprise_issue_validator_spec.rb": 0.09764814376831055,
"spec/lib/open_food_network/feature_toggle_spec.rb": 0.010193109512329102,
"spec/lib/open_food_network/group_buy_report_spec.rb": 3.708569049835205,
"spec/lib/open_food_network/last_used_address_spec.rb": 0.0254666805267334,
"spec/lib/open_food_network/lettuce_share_report_spec.rb": 2.3206725120544434,
"spec/lib/open_food_network/option_value_namer_spec.rb": 0.06185555458068848,
"spec/lib/open_food_network/order_and_distributor_report_spec.rb": 1.0406858921051025,
"spec/lib/open_food_network/order_cycle_form_applicator_spec.rb": 4.533008337020874,
"spec/lib/open_food_network/order_cycle_management_report_spec.rb": 2.036308526992798,
"spec/lib/open_food_network/order_cycle_permissions_spec.rb": 23.74185061454773,
"spec/lib/open_food_network/order_grouper_spec.rb": 0.029039621353149414,
"spec/lib/open_food_network/orders_and_fulfillments_report_spec.rb": 5.135573148727417,
"spec/lib/open_food_network/packing_report_spec.rb": 5.088447093963623,
"spec/lib/open_food_network/permissions_spec.rb": 8.881855249404907,
"spec/lib/open_food_network/products_and_inventory_report_spec.rb": 3.55375337600708,
"spec/lib/open_food_network/referer_parser_spec.rb": 0.014271259307861328,
"spec/lib/open_food_network/reports/report_spec.rb": 0.02238297462463379,
"spec/lib/open_food_network/reports/row_spec.rb": 0.0031762123107910156,
"spec/lib/open_food_network/reports/rule_spec.rb": 0.013959169387817383,
"spec/lib/open_food_network/sales_tax_report_spec.rb": 0.10717129707336426,
"spec/lib/open_food_network/scope_variant_to_hub_spec.rb": 2.4846229553222656,
"spec/lib/open_food_network/user_balance_calculator_spec.rb": 3.4277901649475098,
"spec/lib/open_food_network/users_and_enterprises_report_spec.rb": 0.40532779693603516,
"spec/lib/open_food_network/xero_invoices_report_spec.rb": 1.1586685180664062,
"spec/lib/spree/product_filters_spec.rb": 0.13163042068481445,
"spec/mailers/enterprise_mailer_spec.rb": 0.4537942409515381,
"spec/mailers/order_mailer_spec.rb": 1.452355146408081,
"spec/mailers/producer_mailer_spec.rb": 8.775528192520142,
"spec/mailers/user_mailer_spec.rb": 0.057527780532836914,
"spec/models/adjustment_metadata_spec.rb": 0.22016620635986328,
"spec/models/billable_period_spec.rb": 2.06524658203125,
"spec/models/calculator/weight_spec.rb": 0.009344100952148438,
"spec/models/cart_spec.rb": 4.099429130554199,
"spec/models/customer_spec.rb": 0.07328605651855469,
"spec/models/enterprise_caching_spec.rb": 0.8475983142852783,
"spec/models/enterprise_fee_spec.rb": 3.1999905109405518,
"spec/models/enterprise_group_spec.rb": 0.30861926078796387,
"spec/models/enterprise_relationship_spec.rb": 2.1849746704101562,
"spec/models/enterprise_spec.rb": 17.679611682891846,
"spec/models/exchange_spec.rb": 13.899227857589722,
"spec/models/model_set_spec.rb": 0.22760748863220215,
"spec/models/order_cycle_spec.rb": 10.680967569351196,
"spec/models/product_distribution_spec.rb": 2.227938413619995,
"spec/models/spree/ability_spec.rb": 15.278357028961182,
"spec/models/spree/addresses_spec.rb": 0.055602312088012695,
"spec/models/spree/adjustment_spec.rb": 9.196375846862793,
"spec/models/spree/classification_spec.rb": 0.161299467086792,
"spec/models/spree/image_spec.rb": 0.007464408874511719,
"spec/models/spree/line_item_spec.rb": 13.545411586761475,
"spec/models/spree/order_populator_spec.rb": 1.635932207107544,
"spec/models/spree/order_spec.rb": 10.645411968231201,
"spec/models/spree/payment_method_spec.rb": 0.0733034610748291,
"spec/models/spree/payment_spec.rb": 1.691227912902832,
"spec/models/spree/preferences/file_configuration_spec.rb": 0.03429675102233887,
"spec/models/spree/product_spec.rb": 17.406191110610962,
"spec/models/spree/shipping_method_spec.rb": 3.0447566509246826,
"spec/models/spree/tax_rate_spec.rb": 0.44750261306762695,
"spec/models/spree/taxon_spec.rb": 0.553098201751709,
"spec/models/spree/user_spec.rb": 1.2693369388580322,
"spec/models/spree/variant_spec.rb": 13.75825023651123,
"spec/models/variant_override_spec.rb": 4.086935520172119,
"spec/performance/injection_helper_spec.rb": 6.890667676925659,
"spec/performance/orders_controller_spec.rb": 0.031180143356323242,
"spec/performance/shop_controller_spec.rb": 18.19426918029785,
"spec/requests/large_request_spec.rb": 0.02229022979736328,
"spec/requests/shop_spec.rb": 1.0012562274932861,
"spec/serializers/admin/enterprise_serializer_spec.rb": 0.10484433174133301,
"spec/serializers/admin/exchange_serializer_spec.rb": 0.7569985389709473,
"spec/serializers/admin/for_order_cycle/enterprise_serializer_spec.rb": 0.4293792247772217,
"spec/serializers/admin/index_enterprise_serializer_spec.rb": 1.2506742477416992,
"spec/serializers/admin/variant_override_serializer_spec.rb": 0.38981151580810547,
"spec/serializers/enterprise_serializer_spec.rb": 0.3511006832122803,
"spec/serializers/spree/product_serializer_spec.rb": 0.26622653007507324,
"spec/serializers/spree/variant_serializer_spec.rb": 0.30304574966430664
}

View File

@@ -26,12 +26,16 @@ module OpenFoodNetwork
end
def on_demand
if @variant_override.andand.count_on_hand.present?
# If we're overriding the stock level of an on_demand variant, show it as not
# on_demand, so our stock control can take effect.
false
if @variant_override.andand.on_demand.nil?
if @variant_override.andand.count_on_hand.present?
# If we're overriding the stock level of an on_demand variant, show it as not
# on_demand, so our stock control can take effect.
false
else
super
end
else
super
@variant_override.andand.on_demand
end
end
@@ -42,7 +46,10 @@ module OpenFoodNetwork
super
end
end
end
def sku
@variant_override.andand.sku || super
end
end
end
end

View File

@@ -10,12 +10,20 @@ else
RAILS_RUN='bundle exec rails runner'
fi
if [[ $1 != 'ofn-no' ]]; then
DB_USER='openfoodweb'
DB_DATABASE='openfoodweb_production'
else
DB_USER='ofn_user'
DB_DATABASE='openfoodnetwork'
fi
# -- Mirror database
echo "Mirroring database..."
echo "drop database open_food_network_dev" | psql -h localhost -U ofn open_food_network_test
echo "create database open_food_network_dev" | psql -h localhost -U ofn open_food_network_test
ssh $1 "pg_dump -h localhost -U openfoodweb openfoodweb_production |gzip" |gunzip |psql -h localhost -U ofn open_food_network_dev
ssh $1 "pg_dump -h localhost -U $DB_USER $DB_DATABASE |gzip" |gunzip |psql -h localhost -U ofn open_food_network_dev
# -- Disable S3

View File

@@ -0,0 +1,135 @@
require 'spec_helper'
describe Admin::VariantOverridesController, type: :controller do
# include AuthenticationWorkflow
describe "bulk_update" do
context "json" do
let(:format) { :json }
let(:hub) { create(:distributor_enterprise) }
let(:variant) { create(:variant) }
let!(:variant_override) { create(:variant_override, hub: hub, variant: variant) }
let(:variant_override_params) { [ { id: variant_override.id, price: 123.45, count_on_hand: 321, sku: "MySKU", on_demand: false } ] }
context "where I don't manage the variant override hub" do
before do
user = create(:user)
user.owned_enterprises << create(:enterprise)
allow(controller).to receive(:spree_current_user) { user }
end
it "redirects to unauthorized" do
spree_put :bulk_update, format: format, variant_overrides: variant_override_params
expect(response).to redirect_to spree.unauthorized_path
end
end
context "where I manage the variant override hub" do
before do
allow(controller).to receive(:spree_current_user) { hub.owner }
end
context "but the producer has not granted VO permission" do
it "redirects to unauthorized" do
spree_put :bulk_update, format: format, variant_overrides: variant_override_params
expect(response).to redirect_to spree.unauthorized_path
end
end
context "and the producer has granted VO permission" do
before do
create(:enterprise_relationship, parent: variant.product.supplier, child: hub, permissions_list: [:create_variant_overrides])
end
it "allows me to update the variant override" do
spree_put :bulk_update, format: format, variant_overrides: variant_override_params
variant_override.reload
expect(variant_override.price).to eq 123.45
expect(variant_override.count_on_hand).to eq 321
expect(variant_override.sku).to eq "MySKU"
expect(variant_override.on_demand).to eq false
end
context "where params for a variant override are blank" do
let(:variant_override_params) { [ { id: variant_override.id, price: "", count_on_hand: "", default_stock: nil, resettable: nil, sku: nil, on_demand: nil } ] }
it "destroys the variant override" do
spree_put :bulk_update, format: format, variant_overrides: variant_override_params
expect(VariantOverride.find_by_id(variant_override.id)).to be_nil
end
end
end
end
end
end
describe "bulk_reset" do
context "json" do
let(:format) { :json }
let(:hub) { create(:distributor_enterprise) }
let(:producer) { create(:supplier_enterprise) }
let(:product) { create(:product, supplier: producer) }
let(:variant1) { create(:variant, product: product) }
let(:variant2) { create(:variant, product: product) }
let!(:variant_override1) { create(:variant_override, hub: hub, variant: variant1, count_on_hand: 5, default_stock: 7, resettable: true) }
let!(:variant_override2) { create(:variant_override, hub: hub, variant: variant2, count_on_hand: 2, default_stock: 1, resettable: false) }
let(:params) { { format: format, hub_id: hub.id } }
context "where I don't manage the variant override hub" do
before do
user = create(:user)
user.owned_enterprises << create(:enterprise)
allow(controller).to receive(:spree_current_user) { user }
end
it "redirects to unauthorized" do
spree_put :bulk_reset, params
expect(response).to redirect_to spree.unauthorized_path
end
end
context "where I manage the variant override hub" do
before do
allow(controller).to receive(:spree_current_user) { hub.owner }
end
context "where the producer has not granted create_variant_overrides permission to the hub" do
it "restricts access" do
spree_put :bulk_reset, params
expect(response).to redirect_to spree.unauthorized_path
end
end
context "where the producer has granted create_variant_overrides permission to the hub" do
let!(:er1) { create(:enterprise_relationship, parent: producer, child: hub, permissions_list: [:create_variant_overrides]) }
it "updates stock to default values where reset is enabled" do
expect(variant_override1.reload.count_on_hand).to eq 5 # reset enabled
expect(variant_override2.reload.count_on_hand).to eq 2 # reset disabled
spree_put :bulk_reset, params
expect(variant_override1.reload.count_on_hand).to eq 7 # reset enabled
expect(variant_override2.reload.count_on_hand).to eq 2 # reset disabled
end
context "and the producer has granted create_variant_overrides permission to another hub I manage" do
before { hub.owner.update_attribute(:enterprise_limit, 2) }
let(:hub2) { create(:distributor_enterprise, owner: hub.owner) }
let(:product) { create(:product, supplier: producer) }
let(:variant3) { create(:variant, product: product) }
let!(:variant_override3) { create(:variant_override, hub: hub2, variant: variant3, count_on_hand: 1, default_stock: 13, resettable: true) }
let!(:er2) { create(:enterprise_relationship, parent: producer, child: hub2, permissions_list: [:create_variant_overrides]) }
it "does not reset count_on_hand for variant_overrides not in params" do
expect {
spree_put :bulk_reset, params
}.to_not change{variant_override3.reload.count_on_hand}
end
end
end
end
end
end
end

View File

@@ -1,7 +1,7 @@
require 'spec_helper'
module Api
describe EnterprisesController do
describe EnterprisesController, :type => :controller do
include AuthenticationWorkflow
render_views

View File

@@ -2,7 +2,7 @@ require 'spec_helper'
require 'spree/api/testing_support/helpers'
module Api
describe OrderCyclesController do
describe OrderCyclesController, :type => :controller do
include Spree::Api::TestingSupport::Helpers
include AuthenticationWorkflow
render_views

View File

@@ -1,6 +1,6 @@
require 'spec_helper'
describe BaseController do
describe BaseController, :type => :controller do
let(:oc) { mock_model(OrderCycle) }
let(:hub) { mock_model(Enterprise, ready_for_checkout?: true) }
let(:order) { mock_model(Spree::Order, distributor: hub) }

View File

@@ -58,21 +58,21 @@ describe Spree::OrdersController do
end
it "returns HTTP success when successful" do
Spree::OrderPopulator.stub(:new).and_return(populator = mock())
Spree::OrderPopulator.stub(:new).and_return(populator = double())
populator.stub(:populate).and_return true
xhr :post, :populate, use_route: :spree, format: :json
response.status.should == 200
end
it "returns failure when unsuccessful" do
Spree::OrderPopulator.stub(:new).and_return(populator = mock())
Spree::OrderPopulator.stub(:new).and_return(populator = double())
populator.stub(:populate).and_return false
xhr :post, :populate, use_route: :spree, format: :json
response.status.should == 402
end
it "tells populator to overwrite" do
Spree::OrderPopulator.stub(:new).and_return(populator = mock())
Spree::OrderPopulator.stub(:new).and_return(populator = double())
populator.should_receive(:populate).with({}, true)
xhr :post, :populate, use_route: :spree, format: :json
end

View File

@@ -94,6 +94,8 @@ FactoryGirl.define do
factory :variant_override, :class => VariantOverride do
price 77.77
count_on_hand 11111
default_stock 2000
resettable false
end
factory :enterprise, :class => Enterprise do

View File

@@ -119,10 +119,9 @@ feature %q{
expect(page).to_not have_selector "#save-bar"
fill_in "quantity", :with => 2
expect(page).to have_selector "input[name='quantity'].ng-dirty"
expect(page).to have_selector "#save-bar"
expect(page).to have_button "Save Changes"
expect(page).to have_selector "#save-bar", text: "You have unsaved changes"
click_button "Save Changes"
expect(page).to_not have_selector "#save-bar"
expect(page).to have_selector "#save-bar", text: "All changes saved"
expect(page).to_not have_selector "input[name='quantity'].ng-dirty"
end
end
@@ -132,10 +131,9 @@ feature %q{
expect(page).to_not have_selector "#save-bar"
fill_in "quantity", :with => li1.variant.on_hand + li1.quantity + 10
expect(page).to have_selector "input[name='quantity'].ng-dirty"
expect(page).to have_selector "#save-bar"
expect(page).to have_button "Save Changes"
expect(page).to have_selector "#save-bar", text: "You have unsaved changes"
click_button "Save Changes"
expect(page).to have_selector "#save-bar"
expect(page).to have_selector "#save-bar", text: "Fields with red borders contain errors."
expect(page).to have_selector "input[name='quantity'].ng-dirty.update-error"
expect(page).to have_content "exceeds available stock. Please ensure line items have a valid quantity."
end
@@ -158,9 +156,9 @@ feature %q{
context "modifying the weight/volume of a line item" do
it "price is altered" do
visit '/admin/orders/bulk_management'
first("div#columns_dropdown", :text => "COLUMNS").click
first("div#columns_dropdown div.menu div.menu_item", text: "Weight/Volume").click
first("div#columns_dropdown div.menu div.menu_item", text: "Price").click
first("div#columns-dropdown", :text => "COLUMNS").click
first("div#columns-dropdown div.menu div.menu_item", text: "Weight/Volume").click
first("div#columns-dropdown div.menu div.menu_item", text: "Price").click
within "tr#li_#{li1.id}" do
expect(page).to have_field "price", with: "$50.00"
fill_in "final_weight_volume", :with => 2000
@@ -177,8 +175,8 @@ feature %q{
context "modifying the quantity of a line item" do
it "price is altered" do
visit '/admin/orders/bulk_management'
first("div#columns_dropdown", :text => "COLUMNS").click
first("div#columns_dropdown div.menu div.menu_item", text: "Price").click
first("div#columns-dropdown", :text => "COLUMNS").click
first("div#columns-dropdown div.menu div.menu_item", text: "Price").click
within "tr#li_#{li1.id}" do
expect(page).to have_field "price", with: "$#{format("%.2f",li1.price * 5)}"
fill_in "quantity", :with => 6
@@ -190,8 +188,8 @@ feature %q{
context "modifying the quantity of a line item" do
it "weight/volume is altered" do
visit '/admin/orders/bulk_management'
first("div#columns_dropdown", :text => "COLUMNS").click
first("div#columns_dropdown div.menu div.menu_item", text: "Weight/Volume").click
first("div#columns-dropdown", :text => "COLUMNS").click
first("div#columns-dropdown div.menu div.menu_item", text: "Weight/Volume").click
within "tr#li_#{li1.id}" do
expect(page).to have_field "final_weight_volume", with: "#{li1.final_weight_volume.round}"
fill_in "quantity", :with => 6
@@ -211,8 +209,8 @@ feature %q{
expect(page).to have_selector "th", :text => "QUANTITY"
expect(page).to have_selector "th", :text => "MAX"
first("div#columns_dropdown", :text => "COLUMNS").click
first("div#columns_dropdown div.menu div.menu_item", text: "Producer").click
first("div#columns-dropdown", :text => "COLUMNS").click
first("div#columns-dropdown div.menu div.menu_item", text: "Producer").click
expect(page).to_not have_selector "th", :text => "PRODUCER"
expect(page).to have_selector "th", :text => "NAME"
@@ -501,8 +499,8 @@ feature %q{
it "displays a bulk action select box with a list of actions" do
list_of_actions = ['Delete Selected']
find("div#bulk_actions_dropdown").click
within("div#bulk_actions_dropdown") do
find("div#bulk-actions-dropdown").click
within("div#bulk-actions-dropdown") do
list_of_actions.each { |action_name| expect(page).to have_selector "div.menu_item", text: action_name }
end
end
@@ -514,8 +512,8 @@ feature %q{
within("tr#li_#{li2.id} td.bulk") do
check "bulk"
end
find("div#bulk_actions_dropdown").click
find("div#bulk_actions_dropdown div.menu_item", :text => "Delete Selected" ).click
find("div#bulk-actions-dropdown").click
find("div#bulk-actions-dropdown div.menu_item", :text => "Delete Selected" ).click
expect(page).to have_selector "tr#li_#{li1.id}", visible: true
expect(page).to_not have_selector "tr#li_#{li2.id}", visible: true
end
@@ -534,8 +532,8 @@ feature %q{
it "only applies the delete action to filteredLineItems" do
check "toggle_bulk"
fill_in "quick_search", with: o1.number
find("div#bulk_actions_dropdown").click
find("div#bulk_actions_dropdown div.menu_item", :text => "Delete Selected" ).click
find("div#bulk-actions-dropdown").click
find("div#bulk-actions-dropdown div.menu_item", :text => "Delete Selected" ).click
fill_in "quick_search", with: ''
expect(page).to_not have_selector "tr#li_#{li1.id}", visible: true
expect(page).to have_selector "tr#li_#{li2.id}", visible: true

View File

@@ -46,8 +46,8 @@ feature %q{
p2 = FactoryGirl.create(:product, available_on: Date.current-1)
visit '/admin/products/bulk_edit'
first("div#columns_dropdown", :text => "COLUMNS").click
first("div#columns_dropdown div.menu div.menu_item", text: "Available On").click
first("div#columns-dropdown", :text => "COLUMNS").click
first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click
expect(page).to have_field "available_on", with: p1.available_on.strftime("%F %T")
expect(page).to have_field "available_on", with: p2.available_on.strftime("%F %T")
@@ -243,11 +243,11 @@ feature %q{
visit '/admin/products/bulk_edit'
first("div#columns_dropdown", :text => "COLUMNS").click
first("div#columns_dropdown div.menu div.menu_item", text: "Available On").click
first("div#columns_dropdown div.menu div.menu_item", text: "Category").click
first("div#columns_dropdown div.menu div.menu_item", text: "Inherits Properties?").click
first("div#columns_dropdown div.menu div.menu_item", text: "SKU").click
first("div#columns-dropdown", :text => "COLUMNS").click
first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click
first("div#columns-dropdown div.menu div.menu_item", text: "Category").click
first("div#columns-dropdown div.menu div.menu_item", text: "Inherits Properties?").click
first("div#columns-dropdown div.menu div.menu_item", text: "SKU").click
within "tr#p_#{p.id}" do
expect(page).to have_field "product_name", with: p.name
@@ -308,6 +308,7 @@ feature %q{
p = FactoryGirl.create(:product, supplier: s1, available_on: Date.current, variant_unit: 'volume', variant_unit_scale: 0.001,
price: 3.0, on_hand: 9, unit_value: 0.25, unit_description: '(bottle)' )
v = p.variants.first
v.update_column(:sku, "VARIANTSKU")
login_to_admin_section
@@ -315,12 +316,18 @@ feature %q{
expect(page).to have_selector "a.view-variants"
first("a.view-variants").trigger('click')
first("div#columns-dropdown", :text => "COLUMNS").click
first("div#columns-dropdown div.menu div.menu_item", text: "SKU").click
first("div#columns-dropdown", :text => "COLUMNS").click
expect(page).to have_field "variant_sku", with: "VARIANTSKU"
expect(page).to have_field "variant_price", with: "3.0"
expect(page).to have_field "variant_unit_value_with_description", with: "250 (bottle)"
expect(page).to have_field "variant_on_hand", with: "9"
expect(page).to have_selector "span[name='on_hand']", "9"
select "Volume (L)", from: "variant_unit_with_scale"
fill_in "variant_sku", with: "NEWSKU"
fill_in "variant_price", with: "4.0"
fill_in "variant_on_hand", with: "10"
fill_in "variant_unit_value_with_description", with: "2 (8x250 mL bottles)"
@@ -331,6 +338,7 @@ feature %q{
expect(page.find("#status-message")).to have_content "Changes saved."
v.reload
expect(v.sku).to eq "NEWSKU"
expect(v.price).to eq 4.0
expect(v.on_hand).to eq 10
expect(v.unit_value).to eq 2 # 2L in L
@@ -556,8 +564,8 @@ feature %q{
visit '/admin/products/bulk_edit'
first("div#columns_dropdown", :text => "COLUMNS").click
first("div#columns_dropdown div.menu div.menu_item", text: "Available On").click
first("div#columns-dropdown", :text => "COLUMNS").click
first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click
expect(page).to have_selector "th", :text => "NAME"
expect(page).to have_selector "th", :text => "PRODUCER"
@@ -565,7 +573,7 @@ feature %q{
expect(page).to have_selector "th", :text => "ON HAND"
expect(page).to have_selector "th", :text => "AV. ON"
first("div#columns_dropdown div.menu div.menu_item", text: /^.{0,1}Producer$/).click
first("div#columns-dropdown div.menu div.menu_item", text: /^.{0,1}Producer$/).click
expect(page).to have_no_selector "th", :text => "PRODUCER"
expect(page).to have_selector "th", :text => "NAME"
@@ -688,8 +696,8 @@ feature %q{
v = p.variants.first
visit '/admin/products/bulk_edit'
first("div#columns_dropdown", :text => "COLUMNS").click
first("div#columns_dropdown div.menu div.menu_item", text: "Available On").click
first("div#columns-dropdown", :text => "COLUMNS").click
first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click
within "tr#p_#{p.id}" do
expect(page).to have_field "product_name", with: p.name

View File

@@ -39,8 +39,8 @@ feature 'Customers' do
# Toggling columns
expect(page).to have_selector "th.email"
expect(page).to have_content customer1.email
first("div#columns_dropdown", :text => "COLUMNS").click
first("div#columns_dropdown div.menu div.menu_item", text: "Email").click
first("div#columns-dropdown", :text => "COLUMNS").click
first("div#columns-dropdown div.menu div.menu_item", text: "Email").click
expect(page).to_not have_selector "th.email"
expect(page).to_not have_content customer1.email
end

View File

@@ -102,6 +102,7 @@ feature %q{
# And I add a supplier and some products
select 'My supplier', from: 'new_supplier_id'
click_button 'Add supplier'
fill_in 'order_cycle_incoming_exchange_0_receival_instructions', with: 'receival instructions'
page.find('table.exchanges tr.supplier td.products input').click
check "order_cycle_incoming_exchange_0_variants_#{v1.id}"
check "order_cycle_incoming_exchange_0_variants_#{v2.id}"
@@ -157,8 +158,11 @@ feature %q{
oc.exchanges.first.variants.count.should == 2
oc.exchanges.last.variants.count.should == 2
# And my pickup time and instructions should have been saved
exchange = oc.exchanges.where(:sender_id => oc.coordinator_id).first
# And my receival and pickup time and instructions should have been saved
exchange = oc.exchanges.incoming.first
exchange.receival_instructions.should == 'receival instructions'
exchange = oc.exchanges.outgoing.first
exchange.pickup_time.should == 'pickup time'
exchange.pickup_instructions.should == 'pickup instructions'
end
@@ -188,6 +192,9 @@ feature %q{
page.should have_selector 'td.supplier_name', :text => oc.suppliers.first.name
page.should have_selector 'td.supplier_name', :text => oc.suppliers.last.name
page.should have_field 'order_cycle_incoming_exchange_0_receival_instructions', with: 'instructions 0'
page.should have_field 'order_cycle_incoming_exchange_1_receival_instructions', with: 'instructions 1'
# And the suppliers should have products
page.all('table.exchanges tbody tr.supplier').each do |row|
row.find('td.products input').click

View File

@@ -192,7 +192,7 @@ feature %q{
order = create(:completed_order_with_totals, distributor: distributor1)
visit spree.admin_order_path(order)
find("#links-dropdown .ofn_drop_down").click
find("#links-dropdown .ofn-drop-down").click
within "#links-dropdown" do
expect(page).to have_link "Edit", href: spree.edit_admin_order_path(order)
expect(page).to have_link "Resend Confirmation", href: spree.resend_admin_order_path(order)

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