Auto-merged master into delivery_email_updates on deployment.

This commit is contained in:
Maikel
2016-01-26 11:01:47 +11:00
97 changed files with 1294 additions and 707 deletions

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)
@@ -730,6 +730,3 @@ DEPENDENCIES
whenever
wicked_pdf
wkhtmltopdf-binary
BUNDLED WITH
1.10.6

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 changes 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 "Some errors must be resolved be before you can update orders.\nAny fields with red borders contain errors."
StatusMessage.display 'failure', "Fields with red borders contain errors."
else
alert "Some errors must be resolved be before you can update orders.\nAny fields with red borders contain errors."
StatusMessage.display 'failure', "Fields with red borders contain errors."
$scope.deleteLineItem = (lineItem) ->
if ($scope.confirmDelete && confirm("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

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

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

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

@@ -12,25 +12,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: '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
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: 'Go', 'ng-click' => 'selectHub()' }

View File

@@ -1,10 +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 Producer
%th Product
%th Price
%th On hand
%tbody{ng: {repeat: 'product in products | hubPermissions:hubPermissions:hub.id'}}
%tr{ ng: { controller: "ColumnsCtrl" } }
%th.producer{ ng: { show: 'columns.producer.visible' } } Producer
%th.product{ ng: { show: 'columns.product.visible' } } Product
%th.sku{ ng: { show: 'columns.sku.visible' } } SKU
%th.price{ ng: { show: 'columns.price.visible' } } Price
%th.on_hand{ ng: { show: 'columns.on_hand.visible' } } 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

@@ -5,7 +5,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' }Start Date
@@ -32,8 +32,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'}
@@ -70,32 +72,23 @@
%div{ :class => "eight columns alpha", 'ng-hide' => 'allFinalWeightVolumesPresent()' }
%span{ :class => "eight columns alpha", style: 'color:red' }
WARNING: Some variants do not have a unit value
%hr{ :class => "sixteen columns alpha", :style => "margin-bottom: 15px" }
%div{ 'ng-hide' => 'RequestMonitor.loading || lineItems.length == 0' }
.controls{ :class => "sixteen columns alpha", :style => "margin-bottom: 15px;" }
%div{ :class => "three columns alpha" }
%input{ :class => "fullwidth", :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' }
%div{ :class => "three columns" }
%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' => "$eval(action.callback)(filteredLineItems)", 'ofn-close-on-click' => true }
%span{ :class => 'three columns omega' } {{action.name }}
%div{ :class => "seven columns" } &nbsp;
%div{ :class => "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 }}
%hr.divider.sixteen.columns.alpha.omega
.controls.sixteen.columns.alpha.omega{ ng: { hide: 'RequestMonitor.loading || lineItems.length == 0' } }
%div.three.columns.alpha
%input.fullwidth{ :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' }
= render 'admin/shared/bulk_actions_dropdown'
%div.seven.columns &nbsp;
= render 'admin/shared/columns_dropdown'
%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 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" }
@@ -129,6 +122,7 @@
%th.actions
Ask?&nbsp;
%input{ :type => 'checkbox', 'ng-model' => "confirmDelete" }
%tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" }
%td.bulk
%input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked', '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,18 +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" }
%input.quick-search.fullwidth{ 'ng-model' => 'query', :name => "quick_filter", :type => 'text', 'placeholder' => 'Quick Search' }
.filter_select.four.columns
%label{ :for => 'producer_filter' }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" }
%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' }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

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

@@ -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
producers_join: Australian producers are now welcome to join the Open Food Network.
@@ -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"

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"

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

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

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

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

View File

@@ -26,14 +26,6 @@ feature %q{
page.should have_select2 'hub_id', options: ['', hub.name, hub2.name]
end
it "displays the hub" do
visit '/admin/variant_overrides'
select2_select hub.name, from: 'hub_id'
click_button 'Go'
page.should have_selector 'h2', text: hub.name
end
end
context "when a hub is selected" do
@@ -59,29 +51,60 @@ feature %q{
before do
visit '/admin/variant_overrides'
select2_select hub.name, from: 'hub_id'
click_button 'Go'
end
it "displays the list of products with variants" do
page.should have_table_row ['PRODUCER', 'PRODUCT', 'PRICE', 'ON HAND']
page.should have_table_row [producer.name, product.name, '', '']
page.should have_input "variant-overrides-#{variant.id}-price", placeholder: '1.23'
page.should have_input "variant-overrides-#{variant.id}-count-on-hand", placeholder: '12'
page.should have_input "variant-overrides-#{variant.id}-count_on_hand", placeholder: '12'
page.should have_table_row [producer_related.name, product_related.name, '', '']
page.should have_input "variant-overrides-#{variant_related.id}-price", placeholder: '2.34'
page.should have_input "variant-overrides-#{variant_related.id}-count-on-hand", placeholder: '23'
end
page.should have_input "variant-overrides-#{variant_related.id}-count_on_hand", placeholder: '23'
it "filters the products to those the hub can override" do
# filters the products to those the hub can override
page.should_not have_content producer_unrelated.name
page.should_not have_content product_unrelated.name
# Filters based on the producer select filter
expect(page).to have_selector "#v_#{variant.id}"
expect(page).to have_selector "#v_#{variant_related.id}"
select2_select producer.name, from: 'producer_filter'
expect(page).to have_selector "#v_#{variant.id}"
expect(page).to_not have_selector "#v_#{variant_related.id}"
select2_select 'All', from: 'producer_filter'
# Filters based on the quick search box
expect(page).to have_selector "#v_#{variant.id}"
expect(page).to have_selector "#v_#{variant_related.id}"
fill_in 'query', with: product.name
expect(page).to have_selector "#v_#{variant.id}"
expect(page).to_not have_selector "#v_#{variant_related.id}"
fill_in 'query', with: ''
# Clears the filters
expect(page).to have_selector "#v_#{variant.id}"
expect(page).to have_selector "#v_#{variant_related.id}"
select2_select producer.name, from: 'producer_filter'
fill_in 'query', with: product_related.name
expect(page).to_not have_selector "#v_#{variant.id}"
expect(page).to_not have_selector "#v_#{variant_related.id}"
click_button 'Clear All'
expect(page).to have_selector "#v_#{variant.id}"
expect(page).to have_selector "#v_#{variant_related.id}"
end
it "creates new overrides" do
first("div#columns-dropdown", :text => "COLUMNS").click
first("div#columns-dropdown div.menu div.menu_item", text: "SKU").click
first("div#columns-dropdown div.menu div.menu_item", text: "On Demand").click
first("div#columns-dropdown", :text => "COLUMNS").click
fill_in "variant-overrides-#{variant.id}-sku", with: 'NEWSKU'
fill_in "variant-overrides-#{variant.id}-price", with: '777.77'
fill_in "variant-overrides-#{variant.id}-count-on-hand", with: '123'
fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '123'
check "variant-overrides-#{variant.id}-on_demand"
page.should have_content "Changes to one override remain unsaved."
expect do
@@ -92,15 +115,17 @@ feature %q{
vo = VariantOverride.last
vo.variant_id.should == variant.id
vo.hub_id.should == hub.id
vo.sku.should == "NEWSKU"
vo.price.should == 777.77
vo.count_on_hand.should == 123
vo.on_demand.should == true
end
describe "creating and then updating the new override" do
it "updates the same override instead of creating a duplicate" do
# When I create a new override
fill_in "variant-overrides-#{variant.id}-price", with: '777.77'
fill_in "variant-overrides-#{variant.id}-count-on-hand", with: '123'
fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '123'
page.should have_content "Changes to one override remain unsaved."
expect do
@@ -110,7 +135,7 @@ feature %q{
# And I update its settings without reloading the page
fill_in "variant-overrides-#{variant.id}-price", with: '111.11'
fill_in "variant-overrides-#{variant.id}-count-on-hand", with: '111'
fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '111'
page.should have_content "Changes to one override remain unsaved."
# Then I shouldn't see a new override
@@ -130,7 +155,7 @@ feature %q{
it "displays an error when unauthorised to access the page" do
fill_in "variant-overrides-#{variant.id}-price", with: '777.77'
fill_in "variant-overrides-#{variant.id}-count-on-hand", with: '123'
fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '123'
page.should have_content "Changes to one override remain unsaved."
user.enterprises.clear
@@ -143,7 +168,7 @@ feature %q{
it "displays an error when unauthorised to update a particular override" do
fill_in "variant-overrides-#{variant_related.id}-price", with: '777.77'
fill_in "variant-overrides-#{variant_related.id}-count-on-hand", with: '123'
fill_in "variant-overrides-#{variant_related.id}-count_on_hand", with: '123'
page.should have_content "Changes to one override remain unsaved."
er2.destroy
@@ -156,23 +181,27 @@ feature %q{
end
context "with overrides" do
let!(:vo) { create(:variant_override, variant: variant, hub: hub, price: 77.77, count_on_hand: 11111) }
let!(:vo) { create(:variant_override, variant: variant, hub: hub, price: 77.77, count_on_hand: 11111, default_stock: 1000, resettable: true) }
let!(:vo_no_auth) { create(:variant_override, variant: variant, hub: hub3, price: 1, count_on_hand: 2) }
let!(:product2) { create(:simple_product, supplier: producer, variant_unit: 'weight', variant_unit_scale: 1) }
let!(:variant2) { create(:variant, product: product2, unit_value: 8, price: 1.00, on_hand: 12) }
let!(:vo_no_reset) { create(:variant_override, variant: variant2, hub: hub, price: 3.99, count_on_hand: 40, default_stock: 100, resettable: false) }
let!(:variant3) { create(:variant, product: product, unit_value: 2, price: 5.00, on_hand: 6) }
let!(:vo3) { create(:variant_override, variant: variant3, hub: hub, price: 6, count_on_hand: 7, sku: "SOMESKU", default_stock: 100, resettable: false) }
before do
visit '/admin/variant_overrides'
select2_select hub.name, from: 'hub_id'
click_button 'Go'
end
it "product values are affected by overrides" do
page.should have_input "variant-overrides-#{variant.id}-price", with: '77.77', placeholder: '1.23'
page.should have_input "variant-overrides-#{variant.id}-count-on-hand", with: '11111', placeholder: '12'
page.should have_input "variant-overrides-#{variant.id}-count_on_hand", with: '11111', placeholder: '12'
end
it "updates existing overrides" do
fill_in "variant-overrides-#{variant.id}-price", with: '22.22'
fill_in "variant-overrides-#{variant.id}-count-on-hand", with: '8888'
fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '8888'
page.should have_content "Changes to one override remain unsaved."
expect do
@@ -187,17 +216,54 @@ feature %q{
vo.count_on_hand.should == 8888
end
# Any new fields added to the VO model need to be added to this test
it "deletes overrides when values are cleared" do
first("div#columns-dropdown", :text => "COLUMNS").click
first("div#columns-dropdown div.menu div.menu_item", text: "On Demand").click
first("div#columns-dropdown div.menu div.menu_item", text: "Reset Stock Level").click
first("div#columns-dropdown", :text => "COLUMNS").click
# Clearing values manually
fill_in "variant-overrides-#{variant.id}-price", with: ''
fill_in "variant-overrides-#{variant.id}-count-on-hand", with: ''
fill_in "variant-overrides-#{variant.id}-count_on_hand", with: ''
fill_in "variant-overrides-#{variant.id}-default_stock", with: ''
page.uncheck "variant-overrides-#{variant.id}-resettable"
page.should have_content "Changes to one override remain unsaved."
# Clearing values by 'inheriting'
first("div#columns-dropdown", :text => "COLUMNS").click
first("div#columns-dropdown div.menu div.menu_item", text: "Inheritance").click
first("div#columns-dropdown", :text => "COLUMNS").click
page.check "variant-overrides-#{variant3.id}-inherit"
expect do
click_button 'Save Changes'
page.should have_content "Changes saved."
end.to change(VariantOverride, :count).by(-1)
end.to change(VariantOverride, :count).by(-2)
VariantOverride.where(id: vo.id).should be_empty
VariantOverride.where(id: vo3.id).should be_empty
end
it "resets stock to defaults" do
click_button 'Reset Stock to Defaults'
page.should have_content 'Stocks reset to defaults.'
vo.reload
page.should have_input "variant-overrides-#{variant.id}-count_on_hand", with: '1000', placeholder: '12'
vo.count_on_hand.should == 1000
end
it "doesn't reset stock levels if the behaviour is disabled" do
click_button 'Reset Stock to Defaults'
vo_no_reset.reload
page.should have_input "variant-overrides-#{variant2.id}-count_on_hand", with: '40', placeholder: '12'
vo_no_reset.count_on_hand.should == 40
end
it "prompts to save changes before reset if any are pending" do
fill_in "variant-overrides-#{variant.id}-price", with: '200'
click_button 'Reset Stock to Defaults'
page.should have_content "Save changes first"
end
end
end

View File

@@ -187,7 +187,7 @@ feature "As a consumer I want to shop with a distributor", js: true do
visit shop_path
end
it "should save group buy data to the cart" do
it "should save group buy data to the cart and display it on shopfront reload" do
# -- Quantity
fill_in "variants[#{variant.id}]", with: 6
page.should have_in_cart product.name
@@ -202,6 +202,11 @@ feature "As a consumer I want to shop with a distributor", js: true do
li = Spree::Order.order(:created_at).last.line_items.order(:created_at).last
li.max_quantity.should == 7
# -- Reload
visit shop_path
page.should have_field "variants[#{variant.id}]", with: 6
page.should have_field "variant_attributes[#{variant.id}][max_quantity]", with: 7
end
end
end

View File

@@ -22,12 +22,12 @@ feature "shopping with variant overrides defined", js: true do
let(:v4) { create(:variant, product: p1, price: 44.44, unit_value: 4) }
let(:v5) { create(:variant, product: p3, price: 55.55, unit_value: 5, on_demand: true) }
let(:v6) { create(:variant, product: p3, price: 66.66, unit_value: 6, on_demand: true) }
let!(:vo1) { create(:variant_override, hub: hub, variant: v1, price: 55.55, count_on_hand: nil) }
let!(:vo2) { create(:variant_override, hub: hub, variant: v2, count_on_hand: 0) }
let!(:vo3) { create(:variant_override, hub: hub, variant: v3, count_on_hand: 0) }
let!(:vo4) { create(:variant_override, hub: hub, variant: v4, count_on_hand: 3) }
let!(:vo5) { create(:variant_override, hub: hub, variant: v5, count_on_hand: 0) }
let!(:vo6) { create(:variant_override, hub: hub, variant: v6, count_on_hand: 6) }
let!(:vo1) { create(:variant_override, hub: hub, variant: v1, price: 55.55, count_on_hand: nil, default_stock: nil, resettable: false) }
let!(:vo2) { create(:variant_override, hub: hub, variant: v2, count_on_hand: 0, default_stock: nil, resettable: false) }
let!(:vo3) { create(:variant_override, hub: hub, variant: v3, count_on_hand: 0, default_stock: nil, resettable: false) }
let!(:vo4) { create(:variant_override, hub: hub, variant: v4, count_on_hand: 3, default_stock: nil, resettable: false) }
let!(:vo5) { create(:variant_override, hub: hub, variant: v5, count_on_hand: 0, default_stock: nil, resettable: false) }
let!(:vo6) { create(:variant_override, hub: hub, variant: v6, count_on_hand: 6, default_stock: nil, resettable: false) }
let(:ef) { create(:enterprise_fee, enterprise: hub, fee_type: 'packing', calculator: Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10)) }
before do
@@ -143,7 +143,6 @@ feature "shopping with variant overrides defined", js: true do
it "does not subtract stock from overrides that do not override count_on_hand" do
fill_in "variants[#{v1.id}]", with: "2"
click_checkout
expect do
complete_checkout
end.to change { v1.reload.count_on_hand }.by(-2)
@@ -192,7 +191,7 @@ feature "shopping with variant overrides defined", js: true do
within "#payment" do
choose pm.name
end
place_order
page.should have_content "Your order has been processed successfully"
end

View File

@@ -1,29 +1,39 @@
describe "VariantOverridesCtrl", ->
ctrl = null
scope = null
scope = {}
hubs = [{id: 1, name: 'Hub'}]
producers = [{id: 2, name: 'Producer'}]
products = [{id: 1, name: 'Product'}]
hubPermissions = {}
VariantOverrides = null
variantOverrides = {}
DirtyVariantOverrides = null
dirtyVariantOverrides = {}
StatusMessage = null
statusMessage = {}
beforeEach ->
module 'ofn.admin'
module 'admin.variantOverrides'
module ($provide) ->
$provide.value 'SpreeApiKey', 'API_KEY'
$provide.value 'variantOverrides', variantOverrides
$provide.value 'dirtyVariantOverrides', dirtyVariantOverrides
null
scope = {}
inject ($controller, Indexer, _VariantOverrides_) ->
inject ($controller, _VariantOverrides_, _DirtyVariantOverrides_, _StatusMessage_) ->
VariantOverrides = _VariantOverrides_
ctrl = $controller 'AdminVariantOverridesCtrl', {$scope: scope, Indexer: Indexer, hubs: hubs, producers: producers, products: products, hubPermissions: hubPermissions, VariantOverrides: _VariantOverrides_}
DirtyVariantOverrides = _DirtyVariantOverrides_
StatusMessage = _StatusMessage_
ctrl = $controller 'AdminVariantOverridesCtrl', { $scope: scope, hubs: hubs, producers: producers, products: products, hubPermissions: hubPermissions, VariantOverrides: VariantOverrides, DirtyVariantOverrides: DirtyVariantOverrides, StatusMessage: StatusMessage}
it "initialises the hub list and the chosen hub", ->
expect(scope.hubs).toEqual hubs
expect(scope.hubs).toEqual { 1: {id: 1, name: 'Hub'} }
expect(scope.hub).toBeNull()
it "initialises select filters", ->
expect(scope.producerFilter).toEqual 0
expect(scope.query).toEqual ''
it "adds products", ->
spyOn(VariantOverrides, "ensureDataFor")
expect(scope.products).toEqual []
@@ -54,4 +64,23 @@ describe "VariantOverridesCtrl", ->
expect(scope.updateError(data, 400)).toEqual "I had some trouble saving: Hub can't be blank, Variant can't be blank"
it "returns a generic message otherwise", ->
expect(scope.updateError({}, 500)).toEqual "Oh no! I was unable to save your changes."
expect(scope.updateError({}, 500)).toEqual "Oh no! I was unable to save your changes."
describe "setting stock to defaults", ->
it "prompts to save changes if there are any pending", ->
spyOn(StatusMessage, "display")
DirtyVariantOverrides.add {hub_id: 1, variant_id: 1}
scope.resetStock()
expect(StatusMessage.display).toHaveBeenCalledWith 'alert', 'Save changes first.'
it "updates and refreshes on hand value for variant overrides with a default stock level", inject ($httpBackend) ->
scope.hub_id = 123
variant_overrides_mock = "mock object"
spyOn(StatusMessage, "display")
spyOn(VariantOverrides, "updateData")
$httpBackend.expectPOST("/admin/variant_overrides/bulk_reset", hub_id: 123).respond 200, variant_overrides_mock
scope.resetStock()
expect(StatusMessage.display).toHaveBeenCalledWith 'progress', 'Changing on hand stock levels...'
$httpBackend.flush()
expect(VariantOverrides.updateData).toHaveBeenCalledWith variant_overrides_mock
expect(StatusMessage.display).toHaveBeenCalledWith 'success', 'Stocks reset to defaults.'

View File

@@ -7,7 +7,7 @@ describe "maintaining a list of dirty variant overrides", ->
count_on_hand: 4
beforeEach ->
module "ofn.admin"
module "admin.variantOverrides"
beforeEach inject (_DirtyVariantOverrides_) ->
DirtyVariantOverrides = _DirtyVariantOverrides_

View File

@@ -2,7 +2,7 @@ describe "indexer", ->
Indexer = null
beforeEach ->
module "ofn.admin"
module "admin.indexUtils"
beforeEach inject (_Indexer_) ->
Indexer = _Indexer_

View File

@@ -1,27 +1,28 @@
describe "VariantOverrides service", ->
VariantOverrides = null
VariantOverrides = $httpBackend = null
variantOverrides = [
{id: 1, hub_id: 10, variant_id: 100, price: 1, count_on_hand: 1}
{id: 2, hub_id: 10, variant_id: 200, price: 2, count_on_hand: 2}
{id: 3, hub_id: 20, variant_id: 300, price: 3, count_on_hand: 3}
{id: 1, hub_id: 10, variant_id: 100, sku: "V100", price: 1, count_on_hand: 1, on_demand: null, default_stock: null, resettable: false }
{id: 2, hub_id: 10, variant_id: 200, sku: "V200", price: 2, count_on_hand: 2, on_demand: null, default_stock: null, resettable: false}
{id: 3, hub_id: 20, variant_id: 300, sku: "V300", price: 3, count_on_hand: 3, on_demand: null, default_stock: null, resettable: false}
]
beforeEach ->
module "ofn.admin"
module "admin.variantOverrides"
module ($provide) ->
$provide.value "variantOverrides", variantOverrides
null
beforeEach inject (_VariantOverrides_) ->
beforeEach inject (_VariantOverrides_, _$httpBackend_) ->
VariantOverrides = _VariantOverrides_
$httpBackend = _$httpBackend_
it "indexes variant overrides by hub_id -> variant_id", ->
expect(VariantOverrides.variantOverrides).toEqual
10:
100: {id: 1, hub_id: 10, variant_id: 100, price: 1, count_on_hand: 1}
200: {id: 2, hub_id: 10, variant_id: 200, price: 2, count_on_hand: 2}
100: {id: 1, hub_id: 10, variant_id: 100, sku: "V100", price: 1, count_on_hand: 1, on_demand: null, default_stock: null, resettable: false }
200: {id: 2, hub_id: 10, variant_id: 200, sku: "V200", price: 2, count_on_hand: 2, on_demand: null, default_stock: null, resettable: false }
20:
300: {id: 3, hub_id: 20, variant_id: 300, price: 3, count_on_hand: 3}
300: {id: 3, hub_id: 20, variant_id: 300, sku: "V300", price: 3, count_on_hand: 3, on_demand: null, default_stock: null, resettable: false }
it "ensures blank data available for some products", ->
hubs = [{id: 10}, {id: 20}, {id: 30}]
@@ -32,37 +33,50 @@ describe "VariantOverrides service", ->
}
]
VariantOverrides.ensureDataFor hubs, products
expect(VariantOverrides.variantOverrides).toEqual
10:
100: {id: 1, hub_id: 10, variant_id: 100, price: 1, count_on_hand: 1}
200: {id: 2, hub_id: 10, variant_id: 200, price: 2, count_on_hand: 2}
300: { hub_id: 10, variant_id: 300, price: '', count_on_hand: ''}
400: { hub_id: 10, variant_id: 400, price: '', count_on_hand: ''}
500: { hub_id: 10, variant_id: 500, price: '', count_on_hand: ''}
20:
100: { hub_id: 20, variant_id: 100, price: '', count_on_hand: ''}
200: { hub_id: 20, variant_id: 200, price: '', count_on_hand: ''}
300: {id: 3, hub_id: 20, variant_id: 300, price: 3, count_on_hand: 3}
400: { hub_id: 20, variant_id: 400, price: '', count_on_hand: ''}
500: { hub_id: 20, variant_id: 500, price: '', count_on_hand: ''}
30:
100: { hub_id: 30, variant_id: 100, price: '', count_on_hand: ''}
200: { hub_id: 30, variant_id: 200, price: '', count_on_hand: ''}
300: { hub_id: 30, variant_id: 300, price: '', count_on_hand: ''}
400: { hub_id: 30, variant_id: 400, price: '', count_on_hand: ''}
500: { hub_id: 30, variant_id: 500, price: '', count_on_hand: ''}
expect(VariantOverrides.variantOverrides[10]).toEqual
100: { id: 1, hub_id: 10, variant_id: 100, sku: "V100", price: 1, count_on_hand: 1, on_demand: null, default_stock: null, resettable: false }
200: { id: 2, hub_id: 10, variant_id: 200, sku: "V200", price: 2, count_on_hand: 2, on_demand: null, default_stock: null, resettable: false }
300: { hub_id: 10, variant_id: 300, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false }
400: { hub_id: 10, variant_id: 400, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false }
500: { hub_id: 10, variant_id: 500, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false }
expect(VariantOverrides.variantOverrides[20]).toEqual
100: { hub_id: 20, variant_id: 100, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false }
200: { hub_id: 20, variant_id: 200, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false }
300: { id: 3, hub_id: 20, variant_id: 300, sku: "V300", price: 3, count_on_hand: 3, on_demand: null, default_stock: null, resettable: false }
400: { hub_id: 20, variant_id: 400, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false }
500: { hub_id: 20, variant_id: 500, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false }
expect(VariantOverrides.variantOverrides[30]).toEqual
100: { hub_id: 30, variant_id: 100, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false }
200: { hub_id: 30, variant_id: 200, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false }
300: { hub_id: 30, variant_id: 300, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false }
400: { hub_id: 30, variant_id: 400, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false }
500: { hub_id: 30, variant_id: 500, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false }
it "updates the IDs of variant overrides", ->
VariantOverrides.variantOverrides[2] = {}
VariantOverrides.variantOverrides[2][3] = {hub_id: 2, variant_id: 3, price: "4.0", count_on_hand: 5}
VariantOverrides.variantOverrides[2][8] = {hub_id: 2, variant_id: 8, price: "9.0", count_on_hand: 10}
VariantOverrides.variantOverrides[2][3] = {hub_id: 2, variant_id: 3, price: "4.0", count_on_hand: 5, default_stock: '', resettable: false}
VariantOverrides.variantOverrides[2][8] = {hub_id: 2, variant_id: 8, price: "9.0", count_on_hand: 10, default_stock: '', resettable: false}
updatedVos = [
{id: 1, hub_id: 2, variant_id: 3, price: "4.0", count_on_hand: 5}
{id: 6, hub_id: 2, variant_id: 8, price: "9.0", count_on_hand: 10}
{id: 1, hub_id: 2, variant_id: 3, price: "4.0", count_on_hand: 5, default_stock: '', resettable: false}
{id: 6, hub_id: 2, variant_id: 8, price: "9.0", count_on_hand: 10, default_stock: '', resettable: false}
]
VariantOverrides.updateIds updatedVos
expect(VariantOverrides.variantOverrides[2][3].id).toEqual 1
expect(VariantOverrides.variantOverrides[2][8].id).toEqual 6
it "updates the variant overrides on the page with new data", ->
VariantOverrides.variantOverrides[1] =
3: {id: 1, hub_id: 1, variant_id: 3, price: "4.0", count_on_hand: 5, default_stock: 3, resettable: true}
8: {id: 2, hub_id: 1, variant_id: 8, price: "9.0", count_on_hand: 10, default_stock: '', resettable: false}
# Updated count on hand to 3
updatedVos = [
{id: 1, hub_id: 1, variant_id: 3, price: "4.0", count_on_hand: 3, default_stock: 3, resettable: true}
]
VariantOverrides.updateData(updatedVos)
expect(VariantOverrides.variantOverrides[1]).toEqual
3: {id: 1, hub_id: 1, variant_id: 3, price: "4.0", count_on_hand: 3, default_stock: 3, resettable: true}
8: {id: 2, hub_id: 1, variant_id: 8, price: "9.0", count_on_hand: 10, default_stock: '', resettable: false}

View File

@@ -3,8 +3,8 @@ require 'open_food_network/scope_variant_to_hub'
module OpenFoodNetwork
describe ScopeVariantToHub do
let(:hub) { create(:distributor_enterprise) }
let(:v) { create(:variant, price: 11.11, count_on_hand: 1) }
let(:vo) { create(:variant_override, hub: hub, variant: v, price: 22.22, count_on_hand: 2) }
let(:v) { create(:variant, price: 11.11, count_on_hand: 1, on_demand: true, sku: "VARIANTSKU") }
let(:vo) { create(:variant_override, hub: hub, variant: v, price: 22.22, count_on_hand: 2, on_demand: false, sku: "VOSKU") }
let(:vo_price_only) { create(:variant_override, hub: hub, variant: v, price: 22.22, count_on_hand: nil) }
let(:scoper) { ScopeVariantToHub.new(hub) }
@@ -66,6 +66,75 @@ module OpenFoodNetwork
v.on_demand.should be_true
end
end
describe "overriding on_demand" do
context "when an override exists" do
before { vo }
context "with an on_demand set" do
it "returns the overridden on_demand" do
scoper.scope v
expect(v.on_demand).to be_false
end
end
context "without an on_demand set" do
before { vo.update_column(:on_demand, nil) }
context "when count_on_hand is set" do
it "returns false" do
scoper.scope v
expect(v.on_demand).to be_false
end
end
context "when count_on_hand is not set" do
before { vo.update_column(:count_on_hand, nil) }
it "returns the variant's on_demand" do
scoper.scope v
expect(v.on_demand).to be_true
end
end
end
end
context "when no override exists" do
it "returns the variant's on_demand" do
scoper.scope v
expect(v.on_demand).to be_true
end
end
end
describe "overriding sku" do
context "when an override exists" do
before { vo }
context "with an sku set" do
it "returns the overridden sku" do
scoper.scope v
expect(v.sku).to eq "VOSKU"
end
end
context "without an sku set" do
before { vo.update_column(:sku, nil) }
it "returns the variant's sku" do
scoper.scope v
expect(v.sku).to eq "VARIANTSKU"
end
end
end
context "when no override exists" do
it "returns the variant's sku" do
scoper.scope v
expect(v.sku).to eq "VARIANTSKU"
end
end
end
end
end
end

View File

@@ -323,7 +323,7 @@ module Spree
let!(:er1) { create(:enterprise_relationship, parent: s1, child: d1, permissions_list: [:create_variant_overrides]) }
it "should be able to access variant overrides page" do
should have_ability([:admin, :index, :bulk_update], for: VariantOverride)
should have_ability([:admin, :index, :bulk_update, :bulk_reset], for: VariantOverride)
end
it "should be able to read/write their own variant overrides" do

View File

@@ -49,16 +49,16 @@ describe VariantOverride do
describe "checking if stock levels have been overriden" do
it "returns true when stock level has been overridden" do
create(:variant_override, variant: variant, hub: hub, count_on_hand: 12)
VariantOverride.stock_overridden?(hub, variant).should be_true
VariantOverride.stock_overridden?(hub, variant).should be true
end
it "returns false when the override has no stock level" do
create(:variant_override, variant: variant, hub: hub, count_on_hand: nil)
VariantOverride.stock_overridden?(hub, variant).should be_false
VariantOverride.stock_overridden?(hub, variant).should be false
end
it "returns false when there is no override for the hub/variant" do
VariantOverride.stock_overridden?(hub, variant).should be_false
VariantOverride.stock_overridden?(hub, variant).should be false
end
end
@@ -69,16 +69,40 @@ describe VariantOverride do
vo.reload.count_on_hand.should == 10
end
it "silently logs an error if the variant override doesn't have a stock level" do
vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: nil)
Bugsnag.should_receive(:notify)
VariantOverride.decrement_stock! hub, variant, 2
vo.reload.count_on_hand.should be_nil
end
it "silently logs an error if the variant override does not exist" do
Bugsnag.should_receive(:notify)
VariantOverride.decrement_stock! hub, variant, 2
end
end
describe "checking default stock value is present" do
it "returns true when a default stock level has been set" do
vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock: 20)
vo.default_stock?.should be true
end
it "returns false when the override has no default stock level" do
vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock:nil)
vo.default_stock?.should be false
end
end
describe "resetting stock levels" do
it "resets the on hand level to the value in the default_stock field" do
vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock: 20, resettable: true)
vo.reset_stock!
vo.reload.count_on_hand.should == 20
end
it "silently logs an error if the variant override doesn't have a default stock level" do
vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock:nil, resettable: true)
Bugsnag.should_receive(:notify)
vo.reset_stock!
vo.reload.count_on_hand.should == 12
end
it "doesn't reset the level if the behaviour is disabled" do
vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock: 10, resettable: false)
vo.reset_stock!
vo.reload.count_on_hand.should == 12
end
end
end