mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-04-06 07:29:16 +00:00
Merge master into onbaording
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
//= require ./payment_methods/payment_methods
|
||||
//= require ./products/products
|
||||
//= require ./shipping_methods/shipping_methods
|
||||
//= require ./utils/utils
|
||||
//= require ./users/users
|
||||
//= require textAngular.min.js
|
||||
//= require textAngular-sanitize.min.js
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
angular.module("admin.enterprises")
|
||||
.controller "enterpriseCtrl", ($scope, Enterprise, longDescription, PaymentMethods, ShippingMethods) ->
|
||||
.controller "enterpriseCtrl", ($scope, longDescription, NavigationCheck, Enterprise, PaymentMethods, ShippingMethods) ->
|
||||
$scope.Enterprise = Enterprise.enterprise
|
||||
$scope.PaymentMethods = PaymentMethods.paymentMethods
|
||||
$scope.ShippingMethods = ShippingMethods.shippingMethods
|
||||
$scope.navClear = NavigationCheck.clear
|
||||
# htmlVariable is used by textAngular wysiwyg for the long descrtiption.
|
||||
$scope.htmlVariable = longDescription
|
||||
|
||||
# Provide a callback for generating warning messages displayed before leaving the page. This is passed in
|
||||
# from a directive "nav-check" in the page - if we pass it here it will be called in the test suite,
|
||||
# and on all new uses of this contoller, and we might not want that .
|
||||
$scope.enterpriseNavCallback = ->
|
||||
"You are editing an enterprise!"
|
||||
|
||||
for payment_method in $scope.PaymentMethods
|
||||
payment_method.selected = payment_method.id in $scope.Enterprise.payment_method_ids
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
angular.module("admin.enterprises", ["admin.payment_methods", "admin.shipping_methods", "admin.users", "textAngular"])
|
||||
angular.module("admin.enterprises", ["admin.payment_methods", "admin.utils", "admin.shipping_methods", "admin.users", "textAngular"])
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module('order_cycle', ['ngResource'])
|
||||
angular.module('admin.order_cycles', ['ngResource'])
|
||||
.controller('AdminCreateOrderCycleCtrl', ['$scope', 'OrderCycle', 'Enterprise', 'EnterpriseFee', ($scope, OrderCycle, Enterprise, EnterpriseFee) ->
|
||||
$scope.enterprises = Enterprise.index()
|
||||
$scope.supplied_products = Enterprise.supplied_products
|
||||
@@ -162,235 +162,6 @@ angular.module('order_cycle', ['ngResource'])
|
||||
$httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
|
||||
])
|
||||
|
||||
.factory('OrderCycle', ['$resource', '$window', ($resource, $window) ->
|
||||
OrderCycle = $resource '/admin/order_cycles/:order_cycle_id.json', {}, {
|
||||
'index': { method: 'GET', isArray: true}
|
||||
'create': { method: 'POST'}
|
||||
'update': { method: 'PUT'}}
|
||||
|
||||
{
|
||||
order_cycle:
|
||||
incoming_exchanges: []
|
||||
outgoing_exchanges: []
|
||||
coordinator_fees: []
|
||||
|
||||
loaded: false
|
||||
|
||||
exchangeSelectedVariants: (exchange) ->
|
||||
numActiveVariants = 0
|
||||
numActiveVariants++ for id, active of exchange.variants when active
|
||||
numActiveVariants
|
||||
|
||||
exchangeDirection: (exchange) ->
|
||||
if this.order_cycle.incoming_exchanges.indexOf(exchange) == -1 then 'outgoing' else 'incoming'
|
||||
|
||||
toggleProducts: (exchange) ->
|
||||
exchange.showProducts = !exchange.showProducts
|
||||
|
||||
setExchangeVariants: (exchange, variants, selected) ->
|
||||
exchange.variants[variant] = selected for variant in variants
|
||||
|
||||
addSupplier: (new_supplier_id) ->
|
||||
this.order_cycle.incoming_exchanges.push({enterprise_id: new_supplier_id, incoming: true, active: true, variants: {}, enterprise_fees: []})
|
||||
|
||||
addDistributor: (new_distributor_id) ->
|
||||
this.order_cycle.outgoing_exchanges.push({enterprise_id: new_distributor_id, incoming: false, active: true, variants: {}, enterprise_fees: []})
|
||||
|
||||
removeExchange: (exchange) ->
|
||||
if exchange.incoming
|
||||
incoming_index = this.order_cycle.incoming_exchanges.indexOf exchange
|
||||
this.order_cycle.incoming_exchanges.splice(incoming_index, 1)
|
||||
this.removeDistributionOfVariant(variant_id) for variant_id, active of exchange.variants when active
|
||||
else
|
||||
outgoing_index = this.order_cycle.outgoing_exchanges.indexOf exchange
|
||||
this.order_cycle.outgoing_exchanges.splice(outgoing_index, 1) if outgoing_index > -1
|
||||
|
||||
addCoordinatorFee: ->
|
||||
this.order_cycle.coordinator_fees.push({})
|
||||
|
||||
removeCoordinatorFee: (index) ->
|
||||
this.order_cycle.coordinator_fees.splice(index, 1)
|
||||
|
||||
addExchangeFee: (exchange) ->
|
||||
exchange.enterprise_fees.push({})
|
||||
|
||||
removeExchangeFee: (exchange, index) ->
|
||||
exchange.enterprise_fees.splice(index, 1)
|
||||
|
||||
productSuppliedToOrderCycle: (product) ->
|
||||
product_variant_ids = (variant.id for variant in product.variants)
|
||||
variant_ids = [product.master_id].concat(product_variant_ids)
|
||||
incomingExchangesVariants = this.incomingExchangesVariants()
|
||||
|
||||
# TODO: This is an O(n^2) implementation of set intersection and thus is slooow.
|
||||
# Use a better algorithm if needed.
|
||||
# Also, incomingExchangesVariants is called every time, when it only needs to be
|
||||
# called once per change to incoming variants. Some sort of caching?
|
||||
ids = (variant_id for variant_id in variant_ids when incomingExchangesVariants.indexOf(variant_id) != -1)
|
||||
ids.length > 0
|
||||
|
||||
variantSuppliedToOrderCycle: (variant) ->
|
||||
this.incomingExchangesVariants().indexOf(variant.id) != -1
|
||||
|
||||
incomingExchangesVariants: ->
|
||||
variant_ids = []
|
||||
|
||||
for exchange in this.order_cycle.incoming_exchanges
|
||||
variant_ids.push(parseInt(id)) for id, active of exchange.variants when active
|
||||
variant_ids
|
||||
|
||||
participatingEnterpriseIds: ->
|
||||
suppliers = (exchange.enterprise_id for exchange in this.order_cycle.incoming_exchanges)
|
||||
distributors = (exchange.enterprise_id for exchange in this.order_cycle.outgoing_exchanges)
|
||||
jQuery.unique(suppliers.concat(distributors)).sort()
|
||||
|
||||
removeDistributionOfVariant: (variant_id) ->
|
||||
for exchange in this.order_cycle.outgoing_exchanges
|
||||
exchange.variants[variant_id] = false
|
||||
|
||||
load: (order_cycle_id) ->
|
||||
service = this
|
||||
OrderCycle.get {order_cycle_id: order_cycle_id}, (oc) ->
|
||||
angular.extend(service.order_cycle, oc)
|
||||
service.order_cycle.incoming_exchanges = []
|
||||
service.order_cycle.outgoing_exchanges = []
|
||||
for exchange in service.order_cycle.exchanges
|
||||
if exchange.incoming
|
||||
angular.extend(exchange, {enterprise_id: exchange.sender_id, active: true})
|
||||
delete(exchange.receiver_id)
|
||||
service.order_cycle.incoming_exchanges.push(exchange)
|
||||
|
||||
else
|
||||
angular.extend(exchange, {enterprise_id: exchange.receiver_id, active: true})
|
||||
delete(exchange.sender_id)
|
||||
service.order_cycle.outgoing_exchanges.push(exchange)
|
||||
|
||||
delete(service.order_cycle.exchanges)
|
||||
service.loaded = true
|
||||
|
||||
this.order_cycle
|
||||
|
||||
create: ->
|
||||
oc = new OrderCycle({order_cycle: this.dataForSubmit()})
|
||||
oc.$create (data) ->
|
||||
if data['success']
|
||||
$window.location = '/admin/order_cycles'
|
||||
else
|
||||
console.log('Failed to create order cycle')
|
||||
|
||||
update: ->
|
||||
oc = new OrderCycle({order_cycle: this.dataForSubmit()})
|
||||
oc.$update {order_cycle_id: this.order_cycle.id}, (data) ->
|
||||
if data['success']
|
||||
$window.location = '/admin/order_cycles'
|
||||
else
|
||||
console.log('Failed to update order cycle')
|
||||
|
||||
dataForSubmit: ->
|
||||
data = this.deepCopy()
|
||||
data = this.removeInactiveExchanges(data)
|
||||
data = this.translateCoordinatorFees(data)
|
||||
data = this.translateExchangeFees(data)
|
||||
data
|
||||
|
||||
deepCopy: ->
|
||||
data = angular.extend({}, this.order_cycle)
|
||||
|
||||
# Copy exchanges
|
||||
data.incoming_exchanges = (angular.extend {}, exchange for exchange in this.order_cycle.incoming_exchanges) if this.order_cycle.incoming_exchanges?
|
||||
data.outgoing_exchanges = (angular.extend {}, exchange for exchange in this.order_cycle.outgoing_exchanges) if this.order_cycle.outgoing_exchanges?
|
||||
|
||||
# Copy exchange fees
|
||||
all_exchanges = (data.incoming_exchanges || []) + (data.outgoing_exchanges || [])
|
||||
for exchange in all_exchanges
|
||||
if exchange.enterprise_fees?
|
||||
exchange.enterprise_fees = (angular.extend {}, fee for fee in exchange.enterprise_fees)
|
||||
|
||||
data
|
||||
|
||||
removeInactiveExchanges: (order_cycle) ->
|
||||
order_cycle.incoming_exchanges =
|
||||
(exchange for exchange in order_cycle.incoming_exchanges when exchange.active)
|
||||
order_cycle.outgoing_exchanges =
|
||||
(exchange for exchange in order_cycle.outgoing_exchanges when exchange.active)
|
||||
order_cycle
|
||||
|
||||
translateCoordinatorFees: (order_cycle) ->
|
||||
order_cycle.coordinator_fee_ids = (fee.id for fee in order_cycle.coordinator_fees)
|
||||
delete order_cycle.coordinator_fees
|
||||
order_cycle
|
||||
|
||||
translateExchangeFees: (order_cycle) ->
|
||||
for exchange in order_cycle.incoming_exchanges
|
||||
exchange.enterprise_fee_ids = (fee.id for fee in exchange.enterprise_fees)
|
||||
delete exchange.enterprise_fees
|
||||
for exchange in order_cycle.outgoing_exchanges
|
||||
exchange.enterprise_fee_ids = (fee.id for fee in exchange.enterprise_fees)
|
||||
delete exchange.enterprise_fees
|
||||
order_cycle
|
||||
}])
|
||||
|
||||
.factory('Enterprise', ['$resource', ($resource) ->
|
||||
Enterprise = $resource('/admin/enterprises/for_order_cycle/:enterprise_id.json', {}, {'index': {method: 'GET', isArray: true}})
|
||||
|
||||
{
|
||||
Enterprise: Enterprise
|
||||
enterprises: {}
|
||||
supplied_products: []
|
||||
loaded: false
|
||||
|
||||
index: ->
|
||||
service = this
|
||||
|
||||
Enterprise.index (data) ->
|
||||
for enterprise in data
|
||||
service.enterprises[enterprise.id] = enterprise
|
||||
|
||||
for product in enterprise.supplied_products
|
||||
service.supplied_products.push(product)
|
||||
|
||||
service.loaded = true
|
||||
|
||||
this.enterprises
|
||||
|
||||
suppliedVariants: (enterprise_id) ->
|
||||
vs = (this.variantsOf(product) for product in this.enterprises[enterprise_id].supplied_products)
|
||||
[].concat vs...
|
||||
|
||||
variantsOf: (product) ->
|
||||
if product.variants.length > 0
|
||||
variant.id for variant in product.variants
|
||||
else
|
||||
[product.master_id]
|
||||
|
||||
totalVariants: (enterprise) ->
|
||||
numVariants = 0
|
||||
|
||||
if enterprise
|
||||
counts = for product in enterprise.supplied_products
|
||||
numVariants += if product.variants.length == 0 then 1 else product.variants.length
|
||||
|
||||
numVariants
|
||||
}])
|
||||
|
||||
.factory('EnterpriseFee', ['$resource', ($resource) ->
|
||||
EnterpriseFee = $resource('/admin/enterprise_fees/:enterprise_fee_id.json', {}, {'index': {method: 'GET', isArray: true}})
|
||||
|
||||
{
|
||||
EnterpriseFee: EnterpriseFee
|
||||
enterprise_fees: {}
|
||||
loaded: false
|
||||
|
||||
index: ->
|
||||
service = this
|
||||
EnterpriseFee.index (data) ->
|
||||
service.enterprise_fees = data
|
||||
service.loaded = true
|
||||
|
||||
forEnterprise: (enterprise_id) ->
|
||||
enterprise_fee for enterprise_fee in this.enterprise_fees when enterprise_fee.enterprise_id == enterprise_id
|
||||
}])
|
||||
|
||||
.directive('datetimepicker', ['$parse', ($parse) ->
|
||||
(scope, element, attrs) ->
|
||||
# using $parse instead of scope[attrs.datetimepicker] for cases
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
angular.module('admin.order_cycles').controller "AdminSimpleCreateOrderCycleCtrl", ($scope, OrderCycle, Enterprise, EnterpriseFee) ->
|
||||
$scope.enterprises = Enterprise.index (enterprises) =>
|
||||
$scope.init(enterprises)
|
||||
$scope.enterprise_fees = EnterpriseFee.index()
|
||||
$scope.order_cycle = OrderCycle.order_cycle
|
||||
|
||||
$scope.init = (enterprises) ->
|
||||
enterprise = enterprises[Object.keys(enterprises)[0]]
|
||||
OrderCycle.addSupplier enterprise.id
|
||||
OrderCycle.addDistributor enterprise.id
|
||||
$scope.outgoing_exchange = OrderCycle.order_cycle.outgoing_exchanges[0]
|
||||
|
||||
# All variants start as checked
|
||||
OrderCycle.setExchangeVariants(OrderCycle.order_cycle.incoming_exchanges[0],
|
||||
Enterprise.suppliedVariants(enterprise.id), true)
|
||||
|
||||
OrderCycle.order_cycle.coordinator_id = enterprise.id
|
||||
|
||||
$scope.loaded = ->
|
||||
Enterprise.loaded && EnterpriseFee.loaded
|
||||
|
||||
$scope.removeDistributionOfVariant = angular.noop
|
||||
|
||||
$scope.setExchangeVariants = (exchange, variants, selected) ->
|
||||
OrderCycle.setExchangeVariants(exchange, variants, selected)
|
||||
|
||||
$scope.suppliedVariants = (enterprise_id) ->
|
||||
Enterprise.suppliedVariants(enterprise_id)
|
||||
|
||||
$scope.addCoordinatorFee = ($event) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addCoordinatorFee()
|
||||
|
||||
$scope.removeCoordinatorFee = ($event, index) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.removeCoordinatorFee(index)
|
||||
|
||||
$scope.enterpriseFeesForEnterprise = (enterprise_id) ->
|
||||
EnterpriseFee.forEnterprise(parseInt(enterprise_id))
|
||||
|
||||
$scope.submit = ->
|
||||
OrderCycle.mirrorIncomingToOutgoingProducts()
|
||||
OrderCycle.create()
|
||||
@@ -0,0 +1,37 @@
|
||||
angular.module('admin.order_cycles').controller "AdminSimpleEditOrderCycleCtrl", ($scope, $location, OrderCycle, Enterprise, EnterpriseFee) ->
|
||||
$scope.orderCycleId = ->
|
||||
$location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1]
|
||||
|
||||
$scope.enterprises = Enterprise.index()
|
||||
$scope.enterprise_fees = EnterpriseFee.index()
|
||||
$scope.order_cycle = OrderCycle.load $scope.orderCycleId(), (order_cycle) =>
|
||||
$scope.init()
|
||||
|
||||
$scope.loaded = ->
|
||||
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded
|
||||
|
||||
$scope.init = ->
|
||||
$scope.outgoing_exchange = OrderCycle.order_cycle.outgoing_exchanges[0]
|
||||
|
||||
$scope.enterpriseFeesForEnterprise = (enterprise_id) ->
|
||||
EnterpriseFee.forEnterprise(parseInt(enterprise_id))
|
||||
|
||||
$scope.removeDistributionOfVariant = angular.noop
|
||||
|
||||
$scope.setExchangeVariants = (exchange, variants, selected) ->
|
||||
OrderCycle.setExchangeVariants(exchange, variants, selected)
|
||||
|
||||
$scope.suppliedVariants = (enterprise_id) ->
|
||||
Enterprise.suppliedVariants(enterprise_id)
|
||||
|
||||
$scope.addCoordinatorFee = ($event) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addCoordinatorFee()
|
||||
|
||||
$scope.removeCoordinatorFee = ($event, index) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.removeCoordinatorFee(index)
|
||||
|
||||
$scope.submit = ->
|
||||
OrderCycle.mirrorIncomingToOutgoingProducts()
|
||||
OrderCycle.update()
|
||||
@@ -0,0 +1,43 @@
|
||||
angular.module('admin.order_cycles').factory('Enterprise', ($resource) ->
|
||||
Enterprise = $resource('/admin/enterprises/for_order_cycle/:enterprise_id.json', {}, {'index': {method: 'GET', isArray: true}})
|
||||
|
||||
{
|
||||
Enterprise: Enterprise
|
||||
enterprises: {}
|
||||
supplied_products: []
|
||||
loaded: false
|
||||
|
||||
index: (callback=null) ->
|
||||
service = this
|
||||
|
||||
Enterprise.index (data) ->
|
||||
for enterprise in data
|
||||
service.enterprises[enterprise.id] = enterprise
|
||||
|
||||
for product in enterprise.supplied_products
|
||||
service.supplied_products.push(product)
|
||||
|
||||
service.loaded = true
|
||||
(callback || angular.noop)(service.enterprises)
|
||||
|
||||
this.enterprises
|
||||
|
||||
suppliedVariants: (enterprise_id) ->
|
||||
vs = (this.variantsOf(product) for product in this.enterprises[enterprise_id].supplied_products)
|
||||
[].concat vs...
|
||||
|
||||
variantsOf: (product) ->
|
||||
if product.variants.length > 0
|
||||
variant.id for variant in product.variants
|
||||
else
|
||||
[product.master_id]
|
||||
|
||||
totalVariants: (enterprise) ->
|
||||
numVariants = 0
|
||||
|
||||
if enterprise
|
||||
counts = for product in enterprise.supplied_products
|
||||
numVariants += if product.variants.length == 0 then 1 else product.variants.length
|
||||
|
||||
numVariants
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
angular.module('admin.order_cycles').factory('EnterpriseFee', ($resource) ->
|
||||
EnterpriseFee = $resource('/admin/enterprise_fees/:enterprise_fee_id.json', {}, {'index': {method: 'GET', isArray: true}})
|
||||
|
||||
{
|
||||
EnterpriseFee: EnterpriseFee
|
||||
enterprise_fees: {}
|
||||
loaded: false
|
||||
|
||||
index: ->
|
||||
service = this
|
||||
EnterpriseFee.index (data) ->
|
||||
service.enterprise_fees = data
|
||||
service.loaded = true
|
||||
|
||||
forEnterprise: (enterprise_id) ->
|
||||
enterprise_fee for enterprise_fee in this.enterprise_fees when enterprise_fee.enterprise_id == enterprise_id
|
||||
})
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
angular.module('admin.order_cycles').factory('OrderCycle', ($resource, $window) ->
|
||||
OrderCycle = $resource '/admin/order_cycles/:order_cycle_id.json', {}, {
|
||||
'index': { method: 'GET', isArray: true}
|
||||
'create': { method: 'POST'}
|
||||
'update': { method: 'PUT'}}
|
||||
|
||||
{
|
||||
order_cycle:
|
||||
incoming_exchanges: []
|
||||
outgoing_exchanges: []
|
||||
coordinator_fees: []
|
||||
|
||||
loaded: false
|
||||
|
||||
exchangeSelectedVariants: (exchange) ->
|
||||
numActiveVariants = 0
|
||||
numActiveVariants++ for id, active of exchange.variants when active
|
||||
numActiveVariants
|
||||
|
||||
exchangeDirection: (exchange) ->
|
||||
if this.order_cycle.incoming_exchanges.indexOf(exchange) == -1 then 'outgoing' else 'incoming'
|
||||
|
||||
toggleProducts: (exchange) ->
|
||||
exchange.showProducts = !exchange.showProducts
|
||||
|
||||
setExchangeVariants: (exchange, variants, selected) ->
|
||||
exchange.variants[variant] = selected for variant in variants
|
||||
|
||||
addSupplier: (new_supplier_id) ->
|
||||
this.order_cycle.incoming_exchanges.push({enterprise_id: new_supplier_id, incoming: true, active: true, variants: {}, enterprise_fees: []})
|
||||
|
||||
addDistributor: (new_distributor_id) ->
|
||||
this.order_cycle.outgoing_exchanges.push({enterprise_id: new_distributor_id, incoming: false, active: true, variants: {}, enterprise_fees: []})
|
||||
|
||||
removeExchange: (exchange) ->
|
||||
if exchange.incoming
|
||||
incoming_index = this.order_cycle.incoming_exchanges.indexOf exchange
|
||||
this.order_cycle.incoming_exchanges.splice(incoming_index, 1)
|
||||
this.removeDistributionOfVariant(variant_id) for variant_id, active of exchange.variants when active
|
||||
else
|
||||
outgoing_index = this.order_cycle.outgoing_exchanges.indexOf exchange
|
||||
this.order_cycle.outgoing_exchanges.splice(outgoing_index, 1) if outgoing_index > -1
|
||||
|
||||
addCoordinatorFee: ->
|
||||
this.order_cycle.coordinator_fees.push({})
|
||||
|
||||
removeCoordinatorFee: (index) ->
|
||||
this.order_cycle.coordinator_fees.splice(index, 1)
|
||||
|
||||
addExchangeFee: (exchange) ->
|
||||
exchange.enterprise_fees.push({})
|
||||
|
||||
removeExchangeFee: (exchange, index) ->
|
||||
exchange.enterprise_fees.splice(index, 1)
|
||||
|
||||
productSuppliedToOrderCycle: (product) ->
|
||||
product_variant_ids = (variant.id for variant in product.variants)
|
||||
variant_ids = [product.master_id].concat(product_variant_ids)
|
||||
incomingExchangesVariants = this.incomingExchangesVariants()
|
||||
|
||||
# TODO: This is an O(n^2) implementation of set intersection and thus is slooow.
|
||||
# Use a better algorithm if needed.
|
||||
# Also, incomingExchangesVariants is called every time, when it only needs to be
|
||||
# called once per change to incoming variants. Some sort of caching?
|
||||
ids = (variant_id for variant_id in variant_ids when incomingExchangesVariants.indexOf(variant_id) != -1)
|
||||
ids.length > 0
|
||||
|
||||
variantSuppliedToOrderCycle: (variant) ->
|
||||
this.incomingExchangesVariants().indexOf(variant.id) != -1
|
||||
|
||||
incomingExchangesVariants: ->
|
||||
variant_ids = []
|
||||
|
||||
for exchange in this.order_cycle.incoming_exchanges
|
||||
variant_ids.push(parseInt(id)) for id, active of exchange.variants when active
|
||||
variant_ids
|
||||
|
||||
participatingEnterpriseIds: ->
|
||||
suppliers = (exchange.enterprise_id for exchange in this.order_cycle.incoming_exchanges)
|
||||
distributors = (exchange.enterprise_id for exchange in this.order_cycle.outgoing_exchanges)
|
||||
jQuery.unique(suppliers.concat(distributors)).sort()
|
||||
|
||||
removeDistributionOfVariant: (variant_id) ->
|
||||
for exchange in this.order_cycle.outgoing_exchanges
|
||||
exchange.variants[variant_id] = false
|
||||
|
||||
load: (order_cycle_id, callback=null) ->
|
||||
service = this
|
||||
OrderCycle.get {order_cycle_id: order_cycle_id}, (oc) ->
|
||||
angular.extend(service.order_cycle, oc)
|
||||
service.order_cycle.incoming_exchanges = []
|
||||
service.order_cycle.outgoing_exchanges = []
|
||||
for exchange in service.order_cycle.exchanges
|
||||
if exchange.incoming
|
||||
angular.extend(exchange, {enterprise_id: exchange.sender_id, active: true})
|
||||
delete(exchange.receiver_id)
|
||||
service.order_cycle.incoming_exchanges.push(exchange)
|
||||
|
||||
else
|
||||
angular.extend(exchange, {enterprise_id: exchange.receiver_id, active: true})
|
||||
delete(exchange.sender_id)
|
||||
service.order_cycle.outgoing_exchanges.push(exchange)
|
||||
|
||||
delete(service.order_cycle.exchanges)
|
||||
service.loaded = true
|
||||
|
||||
(callback || angular.noop)(service.order_cycle)
|
||||
|
||||
this.order_cycle
|
||||
|
||||
create: ->
|
||||
oc = new OrderCycle({order_cycle: this.dataForSubmit()})
|
||||
oc.$create (data) ->
|
||||
if data['success']
|
||||
$window.location = '/admin/order_cycles'
|
||||
else
|
||||
console.log('Failed to create order cycle')
|
||||
|
||||
update: ->
|
||||
oc = new OrderCycle({order_cycle: this.dataForSubmit()})
|
||||
oc.$update {order_cycle_id: this.order_cycle.id}, (data) ->
|
||||
if data['success']
|
||||
$window.location = '/admin/order_cycles'
|
||||
else
|
||||
console.log('Failed to update order cycle')
|
||||
|
||||
dataForSubmit: ->
|
||||
data = this.deepCopy()
|
||||
data = this.removeInactiveExchanges(data)
|
||||
data = this.translateCoordinatorFees(data)
|
||||
data = this.translateExchangeFees(data)
|
||||
data
|
||||
|
||||
deepCopy: ->
|
||||
data = angular.extend({}, this.order_cycle)
|
||||
|
||||
# Copy exchanges
|
||||
data.incoming_exchanges = (angular.extend {}, exchange for exchange in this.order_cycle.incoming_exchanges) if this.order_cycle.incoming_exchanges?
|
||||
data.outgoing_exchanges = (angular.extend {}, exchange for exchange in this.order_cycle.outgoing_exchanges) if this.order_cycle.outgoing_exchanges?
|
||||
|
||||
# Copy exchange fees
|
||||
all_exchanges = (data.incoming_exchanges || []) + (data.outgoing_exchanges || [])
|
||||
for exchange in all_exchanges
|
||||
if exchange.enterprise_fees?
|
||||
exchange.enterprise_fees = (angular.extend {}, fee for fee in exchange.enterprise_fees)
|
||||
|
||||
data
|
||||
|
||||
removeInactiveExchanges: (order_cycle) ->
|
||||
order_cycle.incoming_exchanges =
|
||||
(exchange for exchange in order_cycle.incoming_exchanges when exchange.active)
|
||||
order_cycle.outgoing_exchanges =
|
||||
(exchange for exchange in order_cycle.outgoing_exchanges when exchange.active)
|
||||
order_cycle
|
||||
|
||||
translateCoordinatorFees: (order_cycle) ->
|
||||
order_cycle.coordinator_fee_ids = (fee.id for fee in order_cycle.coordinator_fees)
|
||||
delete order_cycle.coordinator_fees
|
||||
order_cycle
|
||||
|
||||
translateExchangeFees: (order_cycle) ->
|
||||
for exchange in order_cycle.incoming_exchanges
|
||||
exchange.enterprise_fee_ids = (fee.id for fee in exchange.enterprise_fees)
|
||||
delete exchange.enterprise_fees
|
||||
for exchange in order_cycle.outgoing_exchanges
|
||||
exchange.enterprise_fee_ids = (fee.id for fee in exchange.enterprise_fees)
|
||||
delete exchange.enterprise_fees
|
||||
order_cycle
|
||||
|
||||
# In the simple UI, we don't list outgoing products. Instead, all products are considered
|
||||
# part of both incoming and outgoing enterprises. This method mirrors the former to the
|
||||
# latter **for order cycles with a single incoming and outgoing exchange only**.
|
||||
mirrorIncomingToOutgoingProducts: ->
|
||||
incoming = this.order_cycle.incoming_exchanges[0]
|
||||
outgoing = this.order_cycle.outgoing_exchanges[0]
|
||||
|
||||
for id, active of incoming.variants
|
||||
outgoing.variants[id] = active
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
angular.module("admin.utils").directive "navCheck", (NavigationCheck)->
|
||||
restrict: 'A'
|
||||
scope:
|
||||
navCallback: '&'
|
||||
link: (scope,element,attributes) ->
|
||||
# Define navigationCallback on a controller in scope, otherwise this default will be used:
|
||||
scope.navCallback ||= ->
|
||||
"You will lose any unsaved work!"
|
||||
NavigationCheck.register(scope.navCallback)
|
||||
@@ -0,0 +1,46 @@
|
||||
angular.module("admin.utils")
|
||||
.factory "NavigationCheck", ($window, $rootScope) ->
|
||||
new class NavigationCheck
|
||||
callbacks = []
|
||||
constructor: ->
|
||||
if $window.addEventListener
|
||||
$window.addEventListener "beforeunload", @onBeforeUnloadHandler
|
||||
else
|
||||
$window.onbeforeunload = @onBeforeUnloadHandler
|
||||
|
||||
$rootScope.$on "$locationChangeStart", @locationChangeStartHandler
|
||||
|
||||
|
||||
# Action for regular browser navigation.
|
||||
onBeforeUnloadHandler: ($event) =>
|
||||
message = @getMessage()
|
||||
if message
|
||||
($event or $window.event).preventDefault()
|
||||
message
|
||||
|
||||
# Action for angular navigation.
|
||||
locationChangeStartHandler: ($event) =>
|
||||
message = @getMessage()
|
||||
if message and not $window.confirm(message)
|
||||
$event.stopPropagation() if $event.stopPropagation
|
||||
$event.preventDefault() if $event.preventDefault
|
||||
$event.cancelBubble = true
|
||||
$event.returnValue = false
|
||||
|
||||
# Runs callback functions to retreive most recently added non-empty message.
|
||||
getMessage: ->
|
||||
message = null
|
||||
message = callback() ? message for callback in callbacks
|
||||
message
|
||||
|
||||
register: (callback) =>
|
||||
callbacks.push callback
|
||||
|
||||
clear: =>
|
||||
if $window.addEventListener
|
||||
$window.removeEventListener "beforeunload", @onBeforeUnloadHandler
|
||||
else
|
||||
$window.onbeforeunload = null
|
||||
|
||||
$rootScope.$on "$locationChangeStart", null
|
||||
|
||||
1
app/assets/javascripts/admin/utils/utils.js.coffee
Normal file
1
app/assets/javascripts/admin/utils/utils.js.coffee
Normal file
@@ -0,0 +1 @@
|
||||
angular.module("admin.utils", [])
|
||||
@@ -6,24 +6,6 @@ require 'open_food_network/order_grouper'
|
||||
require 'open_food_network/customers_report'
|
||||
|
||||
Spree::Admin::ReportsController.class_eval do
|
||||
# Fetches user's distributors, suppliers and order_cycles
|
||||
before_filter :load_data, only: [:customers, :products_and_inventory]
|
||||
|
||||
# Render a partial for orders and fulfillment description
|
||||
respond_override :index => { :html => { :success => lambda {
|
||||
@reports[:orders_and_fulfillment][:description] =
|
||||
render_to_string(partial: 'orders_and_fulfillment_description', layout: false, locals: {report_types: REPORT_TYPES[:orders_and_fulfillment]}).html_safe
|
||||
@reports[:products_and_inventory][:description] =
|
||||
render_to_string(partial: 'products_and_inventory_description', layout: false, locals: {report_types: REPORT_TYPES[:products_and_inventory]}).html_safe
|
||||
@reports[:customers][:description] =
|
||||
render_to_string(partial: 'customers_description', layout: false, locals: {report_types: REPORT_TYPES[:customers]}).html_safe
|
||||
} } }
|
||||
|
||||
# OVERRIDING THIS so we use a method not a constant for available reports
|
||||
def index
|
||||
@reports = available_reports
|
||||
respond_with(@reports)
|
||||
end
|
||||
|
||||
REPORT_TYPES = {
|
||||
orders_and_fulfillment: [
|
||||
@@ -42,6 +24,26 @@ Spree::Admin::ReportsController.class_eval do
|
||||
]
|
||||
}
|
||||
|
||||
# Fetches user's distributors, suppliers and order_cycles
|
||||
before_filter :load_data, only: [:customers, :products_and_inventory]
|
||||
|
||||
# Render a partial for orders and fulfillment description
|
||||
respond_override :index => { :html => { :success => lambda {
|
||||
@reports[:orders_and_fulfillment][:description] =
|
||||
render_to_string(partial: 'orders_and_fulfillment_description', layout: false, locals: {report_types: REPORT_TYPES[:orders_and_fulfillment]}).html_safe
|
||||
@reports[:products_and_inventory][:description] =
|
||||
render_to_string(partial: 'products_and_inventory_description', layout: false, locals: {report_types: REPORT_TYPES[:products_and_inventory]}).html_safe
|
||||
@reports[:customers][:description] =
|
||||
render_to_string(partial: 'customers_description', layout: false, locals: {report_types: REPORT_TYPES[:customers]}).html_safe
|
||||
} } }
|
||||
|
||||
|
||||
# Overide spree reports list.
|
||||
def index
|
||||
@reports = authorized_reports
|
||||
respond_with(@reports)
|
||||
end
|
||||
|
||||
# This action is short because we refactored it like bosses
|
||||
def customers
|
||||
@report_types = REPORT_TYPES[:customers]
|
||||
@@ -603,19 +605,18 @@ Spree::Admin::ReportsController.class_eval do
|
||||
@order_cycles = OrderCycle.active_or_complete.accessible_by(spree_current_user).order('orders_close_at DESC')
|
||||
end
|
||||
|
||||
def available_reports
|
||||
def authorized_reports
|
||||
reports = {
|
||||
:orders_and_distributors => {:name => "Orders And Distributors", :description => "Orders with distributor details"},
|
||||
:bulk_coop => {:name => "Bulk Co-Op", :description => "Reports for Bulk Co-Op orders"},
|
||||
:payments => {:name => "Payment Reports", :description => "Reports for Payments"},
|
||||
:orders_and_fulfillment => {:name => "Orders & Fulfillment Reports", :description => ''},
|
||||
:customers => {:name => "Customers", :description => 'Customer details'},
|
||||
:products_and_inventory => {:name => "Products & Inventory", :description => ''}
|
||||
:products_and_inventory => {:name => "Products & Inventory", :description => ''},
|
||||
:sales_total => { :name => "Sales Total", :description => "Sales Total For All Orders" }
|
||||
}
|
||||
if spree_current_user.has_spree_role? 'admin'
|
||||
reports[:sales_total] = { :name => "Sales Total", :description => "Sales Total For All Orders" }
|
||||
end
|
||||
reports
|
||||
# Return only reports the user is authorized to view.
|
||||
reports.select { |action| can? action, :report }
|
||||
end
|
||||
|
||||
def total_units(line_items)
|
||||
|
||||
@@ -62,6 +62,10 @@ module OrderCyclesHelper
|
||||
OrderCycle.active.with_distributor(@distributor).present?
|
||||
end
|
||||
|
||||
def order_cycles_simple_view
|
||||
@order_cycles_simple_view ||= !OpenFoodNetwork::Permissions.new(spree_current_user).can_manage_complex_order_cycles?
|
||||
end
|
||||
|
||||
def order_cycles_enabled?
|
||||
OpenFoodNetwork::FeatureToggle.enabled? :order_cycles
|
||||
end
|
||||
|
||||
@@ -55,6 +55,7 @@ class Enterprise < ActiveRecord::Base
|
||||
validates :email, presence: true
|
||||
validates_presence_of :owner
|
||||
validate :enforce_ownership_limit, if: lambda { owner_id_changed? && !owner_id.nil? }
|
||||
validates_length_of :description, :maximum => 255
|
||||
|
||||
before_validation :ensure_owner_is_manager, if: lambda { owner_id_changed? && !owner_id.nil? }
|
||||
before_validation :set_unused_address_fields
|
||||
|
||||
@@ -121,6 +121,8 @@ class AbilityDecorator
|
||||
(user.enterprises & shipping_method.distributors).any?
|
||||
end
|
||||
|
||||
# Reports page
|
||||
can [:admin, :index, :customers, :group_buys, :bulk_coop, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory], :report
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
.alpha.three.columns
|
||||
= f.label :description, 'Short Description'
|
||||
.omega.eight.columns
|
||||
= f.text_field :description, placeholder: 'Tell us about your enterprise in one or two sentences'
|
||||
= f.text_field :description, maxlength: 255, placeholder: 'Tell us about your enterprise in one or two sentences'
|
||||
.row
|
||||
.alpha.three.columns
|
||||
= f.label :long_description, 'About Us'
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
= admin_inject_payment_methods
|
||||
= admin_inject_shipping_methods
|
||||
|
||||
.sixteen.columns.alpha{ ng: { app: 'admin.enterprises', controller: 'enterpriseCtrl' } }
|
||||
.sixteen.columns.alpha{ ng: { app: 'admin.enterprises', controller: 'enterpriseCtrl' }, nav: { check: '', callback: 'enterpriseNavCallback()' }}
|
||||
.eleven.columns.alpha
|
||||
= render 'form', f: f
|
||||
.one.column
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
Editing:
|
||||
= @enterprise.name
|
||||
|
||||
= form_for [main_app, :admin, @enterprise] do |f|
|
||||
= form_for [main_app, :admin, @enterprise], html: { "ng-app" => 'admin.enterprises', "ng-submit" => "navClear()", "ng-controller" => 'enterpriseCtrl' , "nav-check" => '', "nav-callback" => 'enterpriseNavCallback()' } do |f|
|
||||
= render 'ng_form', f: f
|
||||
.twelve.columns.alpha
|
||||
= render partial: 'spree/admin/shared/edit_resource_links'
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
= render 'admin/shared/enterprises_sub_menu'
|
||||
|
||||
= form_for @enterprise_set, :url => main_app.bulk_update_admin_enterprises_path do |f|
|
||||
= form_for @enterprise_set, url: main_app.bulk_update_admin_enterprises_path do |f|
|
||||
%table#listing_enterprises.index
|
||||
%colgroup
|
||||
%col{style: "width: 25%;"}/
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
- content_for :page_title do
|
||||
New Enterprise
|
||||
|
||||
= form_for [main_app, :admin, @enterprise] do |f|
|
||||
= render partial: 'ng_form', :locals => { f: f }
|
||||
= form_for [main_app, :admin, @enterprise], html: { "ng-app" => 'admin.enterprises', "ng-submit" => "navClear()", "ng-controller" => 'enterpriseCtrl' , "nav-check" => '', "nav-callback" => 'enterpriseNavCallback()' } do |f|
|
||||
= render 'ng_form', f: f
|
||||
.twelve.columns.alpha
|
||||
= render partial: 'spree/admin/shared/new_resource_links'
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
= f.label :name
|
||||
= f.text_field :name, 'ng-model' => 'order_cycle.name', 'required' => true
|
||||
%br/
|
||||
|
||||
.date-field
|
||||
= f.label :orders_open_at, 'Orders open'
|
||||
= f.text_field :orders_open_at, 'datetimepicker' => 'order_cycle.orders_open_at', 'ng-model' => 'order_cycle.orders_open_at'
|
||||
.date-field
|
||||
= f.label :orders_close_at, 'Orders close'
|
||||
= f.text_field :orders_close_at, 'datetimepicker' => 'order_cycle.orders_close_at', 'ng-model' => 'order_cycle.orders_close_at'
|
||||
%br/
|
||||
= render 'name_and_timing_form', f: f
|
||||
|
||||
|
||||
%h2 Incoming
|
||||
|
||||
15
app/views/admin/order_cycles/_name_and_timing_form.html.haml
Normal file
15
app/views/admin/order_cycles/_name_and_timing_form.html.haml
Normal file
@@ -0,0 +1,15 @@
|
||||
.row
|
||||
.alpha.two.columns
|
||||
= f.label :name
|
||||
.fourteen.columns.omega
|
||||
= f.text_field :name, 'ng-model' => 'order_cycle.name', 'required' => true
|
||||
|
||||
.row
|
||||
.alpha.two.columns
|
||||
= f.label :orders_open_at, 'Orders open'
|
||||
.six.columns
|
||||
= f.text_field :orders_open_at, 'datetimepicker' => 'order_cycle.orders_open_at', 'ng-model' => 'order_cycle.orders_open_at'
|
||||
.two.columns
|
||||
= f.label :orders_close_at, 'Orders close'
|
||||
.six.columns.omega
|
||||
= f.text_field :orders_close_at, 'datetimepicker' => 'order_cycle.orders_close_at', 'ng-model' => 'order_cycle.orders_close_at'
|
||||
@@ -4,15 +4,17 @@
|
||||
%td= link_to order_cycle.name, main_app.edit_admin_order_cycle_path(order_cycle)
|
||||
%td= order_cycle_form.text_field :orders_open_at, :class => 'datetimepicker', :value => order_cycle.orders_open_at
|
||||
%td= order_cycle_form.text_field :orders_close_at, :class => 'datetimepicker', :value => order_cycle.orders_close_at
|
||||
%td.suppliers
|
||||
- order_cycle.suppliers.managed_by(spree_current_user).each do |s|
|
||||
= s.name
|
||||
%br/
|
||||
%td= order_cycle.coordinator.name
|
||||
%td.distributors
|
||||
- order_cycle.distributors.managed_by(spree_current_user).each do |d|
|
||||
= d.name
|
||||
%br/
|
||||
|
||||
- unless order_cycles_simple_view
|
||||
%td.suppliers
|
||||
- order_cycle.suppliers.merge(OpenFoodNetwork::Permissions.new(spree_current_user).order_cycle_enterprises).each do |s|
|
||||
= s.name
|
||||
%br/
|
||||
%td= order_cycle.coordinator.name
|
||||
%td.distributors
|
||||
- order_cycle.distributors.merge(OpenFoodNetwork::Permissions.new(spree_current_user).order_cycle_enterprises).each do |d|
|
||||
= d.name
|
||||
%br/
|
||||
|
||||
%td.products
|
||||
- variant_images = capture do
|
||||
|
||||
29
app/views/admin/order_cycles/_simple_form.html.haml
Normal file
29
app/views/admin/order_cycles/_simple_form.html.haml
Normal file
@@ -0,0 +1,29 @@
|
||||
= render 'name_and_timing_form', f: f
|
||||
|
||||
.row
|
||||
.alpha.two.columns
|
||||
= label_tag 'Ready for'
|
||||
.six.columns
|
||||
= text_field_tag 'order_cycle_outgoing_exchange_0_pickup_time', '', 'id' => 'order_cycle_outgoing_exchange_0_pickup_time', 'placeholder' => 'Date / time', 'ng-model' => 'outgoing_exchange.pickup_time', 'size' => 30
|
||||
.two.columns
|
||||
= label_tag 'Customer instructions'
|
||||
.six.columns.omega
|
||||
= text_field_tag 'order_cycle_outgoing_exchange_0_pickup_instructions', '', 'id' => 'order_cycle_outgoing_exchange_0_pickup_instructions', 'placeholder' => 'Pick-up or delivery notes', 'ng-model' => 'outgoing_exchange.pickup_instructions', 'size' => 30
|
||||
|
||||
= label_tag 'Products'
|
||||
%table.exchanges
|
||||
%tbody{ng: {repeat: "exchange in order_cycle.incoming_exchanges"}}
|
||||
%tr.products
|
||||
= render 'exchange_supplied_products_form'
|
||||
|
||||
%br/
|
||||
= label_tag 'Fees'
|
||||
= render 'coordinator_fees', f: f
|
||||
|
||||
.actions
|
||||
= f.submit @order_cycle.new_record? ? 'Create' : 'Update', 'ng-disabled' => '!loaded()'
|
||||
%span{'ng-show' => 'loaded()'}
|
||||
or
|
||||
= link_to 'Cancel', main_app.admin_order_cycles_path
|
||||
%span{'ng-hide' => 'loaded()'} Loading...
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
%h1 Edit Order Cycle
|
||||
|
||||
= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'order_cycle', 'ng-controller' => 'AdminEditOrderCycleCtrl', 'ng-submit' => 'submit()'} do |f|
|
||||
= render 'form', :f => f
|
||||
- ng_controller = order_cycles_simple_view ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl'
|
||||
|
||||
= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.order_cycles', 'ng-controller' => ng_controller, 'ng-submit' => 'submit()'} do |f|
|
||||
- if order_cycles_simple_view
|
||||
= render 'simple_form', f: f
|
||||
- else
|
||||
= render 'form', f: f
|
||||
|
||||
@@ -5,17 +5,16 @@
|
||||
%li#new_order_cycle_link
|
||||
= button_link_to "New Order Cycle", main_app.new_admin_order_cycle_path, :icon => 'icon-plus', :id => 'admin_new_order_cycle_link'
|
||||
|
||||
|
||||
|
||||
= form_for @order_cycle_set, :url => main_app.bulk_update_admin_order_cycles_path do |f|
|
||||
%table.index#listing_order_cycles
|
||||
%colgroup
|
||||
%col
|
||||
%col{'style' => 'width: 20%;'}
|
||||
%col{'style' => 'width: 20%;'}
|
||||
%col
|
||||
%col
|
||||
%col
|
||||
- unless order_cycles_simple_view
|
||||
%col
|
||||
%col
|
||||
%col
|
||||
%col
|
||||
%col
|
||||
%col
|
||||
@@ -25,9 +24,10 @@
|
||||
%th Name
|
||||
%th Open
|
||||
%th Close
|
||||
%th Suppliers
|
||||
%th Coordinator
|
||||
%th Distributors
|
||||
- unless order_cycles_simple_view
|
||||
%th Suppliers
|
||||
%th Coordinator
|
||||
%th Distributors
|
||||
%th Products
|
||||
%th.actions
|
||||
%th.actions
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
%h1 New Order Cycle
|
||||
|
||||
= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'order_cycle', 'ng-controller' => 'AdminCreateOrderCycleCtrl', 'ng-submit' => 'submit()'} do |f|
|
||||
= render 'form', :f => f
|
||||
- ng_controller = order_cycles_simple_view ? 'AdminSimpleCreateOrderCycleCtrl' : 'AdminCreateOrderCycleCtrl'
|
||||
|
||||
= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.order_cycles', 'ng-controller' => ng_controller, 'ng-submit' => 'submit()'} do |f|
|
||||
- if order_cycles_simple_view
|
||||
= render 'simple_form', f: f
|
||||
- else
|
||||
= render 'form', f: f
|
||||
|
||||
@@ -83,4 +83,4 @@
|
||||
|
||||
.row
|
||||
.small-12.columns.text-right
|
||||
%button.primary{"ng-disabled" => "shipping.$invalid", "ng-click" => "next($event)", "ofn-focus" => "accordion['shipping']"} Next
|
||||
%button.primary{"ng-disabled" => "shipping.$invalid", "ng-click" => "next($event)"} Next
|
||||
|
||||
@@ -4,6 +4,12 @@ module OpenFoodNetwork
|
||||
@user = user
|
||||
end
|
||||
|
||||
def can_manage_complex_order_cycles?
|
||||
managed_and_related_enterprises_with(:add_to_order_cycle).any? do |e|
|
||||
e.sells == 'any'
|
||||
end
|
||||
end
|
||||
|
||||
# Find enterprises that an admin is allowed to add to an order cycle
|
||||
def order_cycle_enterprises
|
||||
managed_and_related_enterprises_with :add_to_order_cycle
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
|
||||
set -e
|
||||
|
||||
mkdir -p db/backup
|
||||
ssh $1 "pg_dump -h localhost -U openfoodweb openfoodweb_production |gzip" > db/backup/$1-`date +%Y%m%d`.sql.gz
|
||||
|
||||
@@ -131,16 +131,16 @@ feature %q{
|
||||
page.should have_selector 'td.distributors', text: 'My distributor'
|
||||
|
||||
# And it should have some fees
|
||||
OrderCycle.last.exchanges.incoming.first.enterprise_fees.should == [supplier_fee]
|
||||
OrderCycle.last.coordinator_fees.should == [coordinator_fee]
|
||||
OrderCycle.last.exchanges.outgoing.first.enterprise_fees.should == [distributor_fee]
|
||||
oc = OrderCycle.last
|
||||
oc.exchanges.incoming.first.enterprise_fees.should == [supplier_fee]
|
||||
oc.coordinator_fees.should == [coordinator_fee]
|
||||
oc.exchanges.outgoing.first.enterprise_fees.should == [distributor_fee]
|
||||
|
||||
# And it should have some variants selected
|
||||
OrderCycle.last.exchanges.first.variants.count.should == 2
|
||||
OrderCycle.last.exchanges.last.variants.count.should == 2
|
||||
oc.exchanges.first.variants.count.should == 2
|
||||
oc.exchanges.last.variants.count.should == 2
|
||||
|
||||
# And my pickup time and instructions should have been saved
|
||||
oc = OrderCycle.last
|
||||
exchange = oc.exchanges.where(:sender_id => oc.coordinator_id).first
|
||||
exchange.pickup_time.should == 'pickup time'
|
||||
exchange.pickup_instructions.should == 'pickup instructions'
|
||||
@@ -471,6 +471,10 @@ feature %q{
|
||||
# I should see only the order cycle I am coordinating
|
||||
page.should have_content oc_user_coordinating.name
|
||||
page.should_not have_content oc_for_other_user.name
|
||||
|
||||
# The order cycle should show enterprises that I manage
|
||||
page.should have_selector 'td.suppliers', text: supplier_managed.name
|
||||
page.should have_selector 'td.distributors', text: distributor_managed.name
|
||||
|
||||
# The order cycle should not show enterprises that I don't manage
|
||||
page.should_not have_selector 'td.suppliers', text: supplier_unmanaged.name
|
||||
@@ -572,7 +576,165 @@ feature %q{
|
||||
occ = OrderCycle.last
|
||||
occ.name.should == "COPY OF #{oc.name}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "simplified interface for enterprise users selling only their own produce" do
|
||||
let(:user) { create_enterprise_user }
|
||||
let(:enterprise) { create(:enterprise, is_primary_producer: true, sells: 'own') }
|
||||
let!(:p1) { create(:simple_product, supplier: enterprise) }
|
||||
let!(:p2) { create(:simple_product, supplier: enterprise) }
|
||||
let!(:p3) { create(:simple_product, supplier: enterprise) }
|
||||
let!(:v) { create(:variant, product: p3) }
|
||||
let!(:fee) { create(:enterprise_fee, enterprise: enterprise, name: 'Coord fee') }
|
||||
|
||||
use_short_wait
|
||||
|
||||
before do
|
||||
user.enterprise_roles.create! enterprise: enterprise
|
||||
login_to_admin_as user
|
||||
end
|
||||
|
||||
it "shows me an index of order cycles without enterprise columns" do
|
||||
create(:order_cycle, coordinator: enterprise)
|
||||
visit admin_order_cycles_path
|
||||
page.should_not have_selector 'th', text: 'SUPPLIERS'
|
||||
page.should_not have_selector 'th', text: 'COORDINATOR'
|
||||
page.should_not have_selector 'th', text: 'DISTRIBUTORS'
|
||||
end
|
||||
|
||||
it "creates order cycles", js: true do
|
||||
# When I go to the new order cycle page
|
||||
visit admin_order_cycles_path
|
||||
click_link 'New Order Cycle'
|
||||
|
||||
# And I fill in the basic fields
|
||||
fill_in 'order_cycle_name', with: 'Plums & Avos'
|
||||
fill_in 'order_cycle_orders_open_at', with: '2014-10-17 06:00:00'
|
||||
fill_in 'order_cycle_orders_close_at', with: '2014-10-24 17:00:00'
|
||||
fill_in 'order_cycle_outgoing_exchange_0_pickup_time', with: 'pickup time'
|
||||
fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'pickup instructions'
|
||||
|
||||
# Then my products / variants should already be selected
|
||||
page.should have_checked_field "order_cycle_incoming_exchange_0_variants_#{p1.master.id}"
|
||||
page.should have_checked_field "order_cycle_incoming_exchange_0_variants_#{p2.master.id}"
|
||||
page.should have_checked_field "order_cycle_incoming_exchange_0_variants_#{v.id}"
|
||||
|
||||
# When I unselect a product
|
||||
uncheck "order_cycle_incoming_exchange_0_variants_#{p2.master.id}"
|
||||
|
||||
# And I add a fee and save
|
||||
click_button 'Add coordinator fee'
|
||||
click_button 'Add coordinator fee'
|
||||
click_link 'order_cycle_coordinator_fee_1_remove'
|
||||
page.should have_select 'order_cycle_coordinator_fee_0_id'
|
||||
page.should_not have_select 'order_cycle_coordinator_fee_1_id'
|
||||
|
||||
select 'Coord fee', from: 'order_cycle_coordinator_fee_0_id'
|
||||
click_button 'Create'
|
||||
|
||||
# Then my order cycle should have been created
|
||||
page.should have_content 'Your order cycle has been created.'
|
||||
page.should have_selector 'a', text: 'Plums & Avos'
|
||||
page.should have_selector "input[value='2014-10-17 06:00:00 +1100']"
|
||||
page.should have_selector "input[value='2014-10-24 17:00:00 +1100']"
|
||||
|
||||
# And it should have some variants selected
|
||||
oc = OrderCycle.last
|
||||
oc.exchanges.incoming.first.variants.count.should == 2
|
||||
oc.exchanges.outgoing.first.variants.count.should == 2
|
||||
|
||||
# And it should have the fee
|
||||
oc.coordinator_fees.should == [fee]
|
||||
|
||||
# And my pickup time and instructions should have been saved
|
||||
ex = oc.exchanges.outgoing.first
|
||||
ex.pickup_time.should == 'pickup time'
|
||||
ex.pickup_instructions.should == 'pickup instructions'
|
||||
end
|
||||
|
||||
scenario "editing an order cycle" do
|
||||
# Given an order cycle with pickup time and instructions
|
||||
fee = create(:enterprise_fee, name: 'my fee', enterprise: enterprise)
|
||||
oc = create(:simple_order_cycle, suppliers: [enterprise], coordinator: enterprise, distributors: [enterprise], variants: [p1.master], coordinator_fees: [fee])
|
||||
ex = oc.exchanges.outgoing.first
|
||||
ex.update_attributes! pickup_time: 'pickup time', pickup_instructions: 'pickup instructions'
|
||||
|
||||
# When I edit it
|
||||
login_to_admin_section
|
||||
click_link 'Order Cycles'
|
||||
click_link oc.name
|
||||
wait_until { page.find('#order_cycle_name').value.present? }
|
||||
|
||||
# Then I should see the basic settings
|
||||
page.should have_field 'order_cycle_name', with: oc.name
|
||||
page.should have_field 'order_cycle_orders_open_at', with: oc.orders_open_at.to_s
|
||||
page.should have_field 'order_cycle_orders_close_at', with: oc.orders_close_at.to_s
|
||||
page.should have_field 'order_cycle_outgoing_exchange_0_pickup_time', with: 'pickup time'
|
||||
page.should have_field 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'pickup instructions'
|
||||
|
||||
# And I should see the products
|
||||
page.should have_checked_field "order_cycle_incoming_exchange_0_variants_#{p1.master.id}"
|
||||
page.should have_unchecked_field "order_cycle_incoming_exchange_0_variants_#{p2.master.id}"
|
||||
page.should have_unchecked_field "order_cycle_incoming_exchange_0_variants_#{v.id}"
|
||||
|
||||
# And I should see the coordinator fees
|
||||
page.should have_select 'order_cycle_coordinator_fee_0_id', selected: 'my fee'
|
||||
end
|
||||
|
||||
scenario "updating an order cycle" do
|
||||
# Given an order cycle with pickup time and instructions
|
||||
fee1 = create(:enterprise_fee, name: 'my fee', enterprise: enterprise)
|
||||
fee2 = create(:enterprise_fee, name: 'that fee', enterprise: enterprise)
|
||||
oc = create(:simple_order_cycle, suppliers: [enterprise], coordinator: enterprise, distributors: [enterprise], variants: [p1.master], coordinator_fees: [fee1])
|
||||
ex = oc.exchanges.outgoing.first
|
||||
ex.update_attributes! pickup_time: 'pickup time', pickup_instructions: 'pickup instructions'
|
||||
|
||||
# When I edit it
|
||||
login_to_admin_section
|
||||
visit edit_admin_order_cycle_path oc
|
||||
wait_until { page.find('#order_cycle_name').value.present? }
|
||||
|
||||
# And I fill in the basic fields
|
||||
fill_in 'order_cycle_name', with: 'Plums & Avos'
|
||||
fill_in 'order_cycle_orders_open_at', with: '2014-10-17 06:00:00'
|
||||
fill_in 'order_cycle_orders_close_at', with: '2014-10-24 17:00:00'
|
||||
fill_in 'order_cycle_outgoing_exchange_0_pickup_time', with: 'xy'
|
||||
fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'zzy'
|
||||
|
||||
# And I make some product selections
|
||||
uncheck "order_cycle_incoming_exchange_0_variants_#{p1.master.id}"
|
||||
check "order_cycle_incoming_exchange_0_variants_#{p2.master.id}"
|
||||
check "order_cycle_incoming_exchange_0_variants_#{v.id}"
|
||||
uncheck "order_cycle_incoming_exchange_0_variants_#{v.id}"
|
||||
|
||||
# And I select some fees and update
|
||||
click_link 'order_cycle_coordinator_fee_0_remove'
|
||||
page.should_not have_select 'order_cycle_coordinator_fee_0_id'
|
||||
click_button 'Add coordinator fee'
|
||||
select 'that fee', from: 'order_cycle_coordinator_fee_0_id'
|
||||
|
||||
click_button 'Update'
|
||||
|
||||
# Then my order cycle should have been updated
|
||||
page.should have_content 'Your order cycle has been updated.'
|
||||
page.should have_selector 'a', text: 'Plums & Avos'
|
||||
page.should have_selector "input[value='2014-10-17 06:00:00 +1100']"
|
||||
page.should have_selector "input[value='2014-10-24 17:00:00 +1100']"
|
||||
|
||||
# And it should have a variant selected
|
||||
oc = OrderCycle.last
|
||||
oc.exchanges.incoming.first.variants.should == [p2.master]
|
||||
oc.exchanges.outgoing.first.variants.should == [p2.master]
|
||||
|
||||
# And it should have the fee
|
||||
oc.coordinator_fees.should == [fee2]
|
||||
|
||||
# And my pickup time and instructions should have been saved
|
||||
ex = oc.exchanges.outgoing.first
|
||||
ex.pickup_time.should == 'xy'
|
||||
ex.pickup_instructions.should == 'zzy'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
describe "AdminSimpleCreateOrderCycleCtrl", ->
|
||||
ctrl = null
|
||||
scope = {}
|
||||
OrderCycle = {}
|
||||
Enterprise = {}
|
||||
EnterpriseFee = {}
|
||||
incoming_exchange = {}
|
||||
outgoing_exchange = {}
|
||||
|
||||
beforeEach ->
|
||||
scope = {}
|
||||
OrderCycle =
|
||||
order_cycle:
|
||||
incoming_exchanges: [incoming_exchange]
|
||||
outgoing_exchanges: [outgoing_exchange]
|
||||
addSupplier: jasmine.createSpy()
|
||||
addDistributor: jasmine.createSpy()
|
||||
setExchangeVariants: jasmine.createSpy()
|
||||
Enterprise =
|
||||
index: jasmine.createSpy()
|
||||
suppliedVariants: jasmine.createSpy().andReturn('supplied variants')
|
||||
EnterpriseFee =
|
||||
index: jasmine.createSpy()
|
||||
|
||||
module('admin.order_cycles')
|
||||
inject ($controller) ->
|
||||
ctrl = $controller 'AdminSimpleCreateOrderCycleCtrl', {$scope: scope, OrderCycle: OrderCycle, Enterprise: Enterprise, EnterpriseFee: EnterpriseFee}
|
||||
|
||||
describe "initialisation", ->
|
||||
enterprise = {id: 123}
|
||||
enterprises = {123: enterprise}
|
||||
|
||||
beforeEach ->
|
||||
scope.init(enterprises)
|
||||
|
||||
it "sets up an incoming and outgoing exchange", ->
|
||||
expect(OrderCycle.addSupplier).toHaveBeenCalledWith(enterprise.id)
|
||||
expect(OrderCycle.addDistributor).toHaveBeenCalledWith(enterprise.id)
|
||||
expect(scope.outgoing_exchange).toEqual outgoing_exchange
|
||||
|
||||
it "selects all variants", ->
|
||||
expect(Enterprise.suppliedVariants).
|
||||
toHaveBeenCalledWith(enterprise.id)
|
||||
|
||||
expect(OrderCycle.setExchangeVariants).
|
||||
toHaveBeenCalledWith(incoming_exchange, 'supplied variants', true)
|
||||
|
||||
it "sets the coordinator", ->
|
||||
expect(OrderCycle.order_cycle.coordinator_id).toEqual enterprise.id
|
||||
@@ -0,0 +1,38 @@
|
||||
describe "AdminSimpleEditOrderCycleCtrl", ->
|
||||
ctrl = null
|
||||
scope = {}
|
||||
location = {}
|
||||
OrderCycle = {}
|
||||
Enterprise = {}
|
||||
EnterpriseFee = {}
|
||||
incoming_exchange = {}
|
||||
outgoing_exchange = {}
|
||||
|
||||
beforeEach ->
|
||||
scope = {}
|
||||
location =
|
||||
absUrl: ->
|
||||
'example.com/admin/order_cycles/27/edit'
|
||||
OrderCycle =
|
||||
order_cycle:
|
||||
incoming_exchanges: [incoming_exchange]
|
||||
outgoing_exchanges: [outgoing_exchange]
|
||||
load: jasmine.createSpy()
|
||||
Enterprise =
|
||||
index: jasmine.createSpy()
|
||||
EnterpriseFee =
|
||||
index: jasmine.createSpy()
|
||||
|
||||
module('admin.order_cycles')
|
||||
inject ($controller) ->
|
||||
ctrl = $controller 'AdminSimpleEditOrderCycleCtrl', {$scope: scope, $location: location, OrderCycle: OrderCycle, Enterprise: Enterprise, EnterpriseFee: EnterpriseFee}
|
||||
|
||||
describe "initialisation", ->
|
||||
enterprise = {id: 123}
|
||||
enterprises = {123: enterprise}
|
||||
|
||||
beforeEach ->
|
||||
scope.init(enterprises)
|
||||
|
||||
it "sets the outgoing exchange", ->
|
||||
expect(scope.outgoing_exchange).toEqual outgoing_exchange
|
||||
@@ -38,7 +38,7 @@ describe 'OrderCycle controllers', ->
|
||||
index: jasmine.createSpy('index').andReturn('enterprise fees list')
|
||||
forEnterprise: jasmine.createSpy('forEnterprise').andReturn('enterprise fees for enterprise')
|
||||
|
||||
module('order_cycle')
|
||||
module('admin.order_cycles')
|
||||
inject ($controller) ->
|
||||
ctrl = $controller 'AdminCreateOrderCycleCtrl', {$scope: scope, OrderCycle: OrderCycle, Enterprise: Enterprise, EnterpriseFee: EnterpriseFee}
|
||||
|
||||
@@ -198,7 +198,7 @@ describe 'OrderCycle controllers', ->
|
||||
index: jasmine.createSpy('index').andReturn('enterprise fees list')
|
||||
forEnterprise: jasmine.createSpy('forEnterprise').andReturn('enterprise fees for enterprise')
|
||||
|
||||
module('order_cycle')
|
||||
module('admin.order_cycles')
|
||||
inject ($controller) ->
|
||||
ctrl = $controller 'AdminEditOrderCycleCtrl', {$scope: scope, $location: location, OrderCycle: OrderCycle, Enterprise: Enterprise, EnterpriseFee: EnterpriseFee}
|
||||
|
||||
@@ -323,7 +323,7 @@ describe 'OrderCycle services', ->
|
||||
Enterprise = null
|
||||
|
||||
beforeEach ->
|
||||
module 'order_cycle'
|
||||
module 'admin.order_cycles'
|
||||
inject ($injector, _$httpBackend_)->
|
||||
Enterprise = $injector.get('Enterprise')
|
||||
$httpBackend = _$httpBackend_
|
||||
@@ -389,7 +389,7 @@ describe 'OrderCycle services', ->
|
||||
EnterpriseFee = null
|
||||
|
||||
beforeEach ->
|
||||
module 'order_cycle'
|
||||
module 'admin.order_cycles'
|
||||
inject ($injector, _$httpBackend_)->
|
||||
EnterpriseFee = $injector.get('EnterpriseFee')
|
||||
$httpBackend = _$httpBackend_
|
||||
@@ -431,7 +431,7 @@ describe 'OrderCycle services', ->
|
||||
beforeEach ->
|
||||
$window = {navigator: {userAgent: 'foo'}}
|
||||
|
||||
module 'order_cycle', ($provide)->
|
||||
module 'admin.order_cycles', ($provide)->
|
||||
$provide.value('$window', $window)
|
||||
null
|
||||
|
||||
|
||||
@@ -105,6 +105,7 @@ describe Enterprise do
|
||||
subject { FactoryGirl.create(:distributor_enterprise, :address => FactoryGirl.create(:address)) }
|
||||
it { should validate_presence_of(:name) }
|
||||
it { should validate_presence_of(:email) }
|
||||
it { should ensure_length_of(:description).is_at_most(255) }
|
||||
|
||||
it "requires an owner" do
|
||||
expect{
|
||||
|
||||
@@ -12,8 +12,11 @@ module Spree
|
||||
let(:enterprise_any) { create(:enterprise, sells: 'any') }
|
||||
let(:enterprise_own) { create(:enterprise, sells: 'own') }
|
||||
let(:enterprise_none) { create(:enterprise, sells: 'none') }
|
||||
let(:enterprise_any_producer) { create(:enterprise, sells: 'any', is_primary_producer: true) }
|
||||
let(:enterprise_own_producer) { create(:enterprise, sells: 'own', is_primary_producer: true) }
|
||||
let(:enterprise_none_producer) { create(:enterprise, sells: 'none', is_primary_producer: true) }
|
||||
|
||||
context "as manager of a 'any' type enterprise" do
|
||||
context "as manager of an enterprise who sells 'any'" do
|
||||
before do
|
||||
user.enterprise_roles.create! enterprise: enterprise_any
|
||||
end
|
||||
@@ -23,7 +26,7 @@ module Spree
|
||||
it { subject.can_manage_orders?(user).should be_true }
|
||||
end
|
||||
|
||||
context "as manager of a 'own' type enterprise" do
|
||||
context "as manager of an enterprise who sell 'own'" do
|
||||
before do
|
||||
user.enterprise_roles.create! enterprise: enterprise_own
|
||||
end
|
||||
@@ -33,16 +36,46 @@ module Spree
|
||||
it { subject.can_manage_orders?(user).should be_true }
|
||||
end
|
||||
|
||||
context "as manager of a 'none' type enterprise" do
|
||||
context "as manager of an enterprise who sells 'none'" do
|
||||
before do
|
||||
user.enterprise_roles.create! enterprise: enterprise_none
|
||||
end
|
||||
|
||||
it { subject.can_manage_products?(user).should be_false }
|
||||
it { subject.can_manage_enterprises?(user).should be_true }
|
||||
it { subject.can_manage_orders?(user).should be_false }
|
||||
end
|
||||
|
||||
context "as manager of a producer enterprise who sells 'any'" do
|
||||
before do
|
||||
user.enterprise_roles.create! enterprise: enterprise_any_producer
|
||||
end
|
||||
|
||||
it { subject.can_manage_products?(user).should be_true }
|
||||
it { subject.can_manage_enterprises?(user).should be_true }
|
||||
it { subject.can_manage_orders?(user).should be_true }
|
||||
end
|
||||
|
||||
context "as manager of a producer enterprise who sell 'own'" do
|
||||
before do
|
||||
user.enterprise_roles.create! enterprise: enterprise_own_producer
|
||||
end
|
||||
|
||||
it { subject.can_manage_products?(user).should be_true }
|
||||
it { subject.can_manage_enterprises?(user).should be_true }
|
||||
it { subject.can_manage_orders?(user).should be_true }
|
||||
end
|
||||
|
||||
context "as manager of a producer enterprise who sells 'none'" do
|
||||
before do
|
||||
user.enterprise_roles.create! enterprise: enterprise_none_producer
|
||||
end
|
||||
|
||||
context "as a non profile" do
|
||||
before do
|
||||
enterprise_none.is_primary_producer = true
|
||||
enterprise_none.producer_profile_only = false
|
||||
enterprise_none.save!
|
||||
enterprise_none_producer.is_primary_producer = true
|
||||
enterprise_none_producer.producer_profile_only = false
|
||||
enterprise_none_producer.save!
|
||||
end
|
||||
|
||||
it { subject.can_manage_products?(user).should be_true }
|
||||
@@ -52,9 +85,9 @@ module Spree
|
||||
|
||||
context "as a profile" do
|
||||
before do
|
||||
enterprise_none.is_primary_producer = true
|
||||
enterprise_none.producer_profile_only = true
|
||||
enterprise_none.save!
|
||||
enterprise_none_producer.is_primary_producer = true
|
||||
enterprise_none_producer.producer_profile_only = true
|
||||
enterprise_none_producer.save!
|
||||
end
|
||||
|
||||
it { subject.can_manage_products?(user).should be_false }
|
||||
@@ -171,6 +204,14 @@ module Spree
|
||||
should_not have_ability(:destroy, for: er2)
|
||||
end
|
||||
|
||||
it "should be able to read some reports" do
|
||||
should have_ability([:admin, :index, :customers, :bulk_coop, :orders_and_fulfillment, :products_and_inventory], for: :report)
|
||||
end
|
||||
|
||||
it "should not be able to read other reports" do
|
||||
should_not have_ability([:sales_total, :group_buys, :payments, :orders_and_distributors], for: :report)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "when is a distributor enterprise user" do
|
||||
@@ -257,9 +298,18 @@ module Spree
|
||||
it "should not be able to destroy enterprise relationships for other enterprises" do
|
||||
should_not have_ability(:destroy, for: er1)
|
||||
end
|
||||
|
||||
it "should be able to read some reports" do
|
||||
should have_ability([:admin, :index, :customers, :group_buys, :bulk_coop, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory], for: :report)
|
||||
end
|
||||
|
||||
it "should not be able to read other reports" do
|
||||
should_not have_ability([:sales_total], for: :report)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'Order Cycle co-ordinator, distriutor enterprise manager' do
|
||||
context 'Order Cycle co-ordinator, distributor enterprise manager' do
|
||||
let (:user) do
|
||||
user = create(:user)
|
||||
user.spree_roles = []
|
||||
|
||||
Reference in New Issue
Block a user