diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index f8d3ceb721..7dc9d1116f 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -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 diff --git a/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee index 46eb4a84c1..cfcf6319af 100644 --- a/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee +++ b/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee @@ -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 diff --git a/app/assets/javascripts/admin/enterprises/enterprises.js.coffee b/app/assets/javascripts/admin/enterprises/enterprises.js.coffee index 9b67bc14f4..e1e43854d1 100644 --- a/app/assets/javascripts/admin/enterprises/enterprises.js.coffee +++ b/app/assets/javascripts/admin/enterprises/enterprises.js.coffee @@ -1 +1 @@ -angular.module("admin.enterprises", ["admin.payment_methods", "admin.shipping_methods", "admin.users", "textAngular"]) \ No newline at end of file +angular.module("admin.enterprises", ["admin.payment_methods", "admin.utils", "admin.shipping_methods", "admin.users", "textAngular"]) \ No newline at end of file diff --git a/app/assets/javascripts/admin/order_cycle.js.erb.coffee b/app/assets/javascripts/admin/order_cycle.js.erb.coffee index 64f2550466..6a7d565bae 100644 --- a/app/assets/javascripts/admin/order_cycle.js.erb.coffee +++ b/app/assets/javascripts/admin/order_cycle.js.erb.coffee @@ -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 diff --git a/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee new file mode 100644 index 0000000000..e2a60e424e --- /dev/null +++ b/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee @@ -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() diff --git a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee new file mode 100644 index 0000000000..bcdaa64e91 --- /dev/null +++ b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee @@ -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() diff --git a/app/assets/javascripts/admin/order_cycles/services/enterprise.js.coffee b/app/assets/javascripts/admin/order_cycles/services/enterprise.js.coffee new file mode 100644 index 0000000000..244d050aba --- /dev/null +++ b/app/assets/javascripts/admin/order_cycles/services/enterprise.js.coffee @@ -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 + }) \ No newline at end of file diff --git a/app/assets/javascripts/admin/order_cycles/services/enterprise_fee.js.coffee b/app/assets/javascripts/admin/order_cycles/services/enterprise_fee.js.coffee new file mode 100644 index 0000000000..330d7c031e --- /dev/null +++ b/app/assets/javascripts/admin/order_cycles/services/enterprise_fee.js.coffee @@ -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 + }) + diff --git a/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee new file mode 100644 index 0000000000..687a0164fa --- /dev/null +++ b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee @@ -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 + }) diff --git a/app/assets/javascripts/admin/utils/directives/navigation_check.js.coffee b/app/assets/javascripts/admin/utils/directives/navigation_check.js.coffee new file mode 100644 index 0000000000..95f52505eb --- /dev/null +++ b/app/assets/javascripts/admin/utils/directives/navigation_check.js.coffee @@ -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) diff --git a/app/assets/javascripts/admin/utils/services/navigation_check.js.coffee b/app/assets/javascripts/admin/utils/services/navigation_check.js.coffee new file mode 100644 index 0000000000..ff1041474c --- /dev/null +++ b/app/assets/javascripts/admin/utils/services/navigation_check.js.coffee @@ -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 + diff --git a/app/assets/javascripts/admin/utils/utils.js.coffee b/app/assets/javascripts/admin/utils/utils.js.coffee new file mode 100644 index 0000000000..4d58ae930a --- /dev/null +++ b/app/assets/javascripts/admin/utils/utils.js.coffee @@ -0,0 +1 @@ +angular.module("admin.utils", []) \ No newline at end of file diff --git a/app/controllers/spree/admin/reports_controller_decorator.rb b/app/controllers/spree/admin/reports_controller_decorator.rb index f0fa2c592b..22dd41549f 100644 --- a/app/controllers/spree/admin/reports_controller_decorator.rb +++ b/app/controllers/spree/admin/reports_controller_decorator.rb @@ -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) diff --git a/app/helpers/order_cycles_helper.rb b/app/helpers/order_cycles_helper.rb index dfd957a143..627d117368 100644 --- a/app/helpers/order_cycles_helper.rb +++ b/app/helpers/order_cycles_helper.rb @@ -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 diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index 9ff835b868..3fc89aae14 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -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 diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 35652e7348..23994a4580 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -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 diff --git a/app/views/admin/enterprises/_form.html.haml b/app/views/admin/enterprises/_form.html.haml index c1d25fd6f5..20bd174de9 100644 --- a/app/views/admin/enterprises/_form.html.haml +++ b/app/views/admin/enterprises/_form.html.haml @@ -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' diff --git a/app/views/admin/enterprises/_ng_form.html.haml b/app/views/admin/enterprises/_ng_form.html.haml index 18e9d6ed36..e3ebe95877 100644 --- a/app/views/admin/enterprises/_ng_form.html.haml +++ b/app/views/admin/enterprises/_ng_form.html.haml @@ -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   diff --git a/app/views/admin/enterprises/edit.html.haml b/app/views/admin/enterprises/edit.html.haml index 6a8d48b24b..3c132bf5e2 100644 --- a/app/views/admin/enterprises/edit.html.haml +++ b/app/views/admin/enterprises/edit.html.haml @@ -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' diff --git a/app/views/admin/enterprises/index.html.haml b/app/views/admin/enterprises/index.html.haml index a29c7e3a30..ee1bdde996 100644 --- a/app/views/admin/enterprises/index.html.haml +++ b/app/views/admin/enterprises/index.html.haml @@ -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%;"}/ diff --git a/app/views/admin/enterprises/new.html.haml b/app/views/admin/enterprises/new.html.haml index 32f6ea0a8d..3df3551f47 100644 --- a/app/views/admin/enterprises/new.html.haml +++ b/app/views/admin/enterprises/new.html.haml @@ -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' diff --git a/app/views/admin/order_cycles/_form.html.haml b/app/views/admin/order_cycles/_form.html.haml index e859374a3b..82146fabdd 100644 --- a/app/views/admin/order_cycles/_form.html.haml +++ b/app/views/admin/order_cycles/_form.html.haml @@ -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 diff --git a/app/views/admin/order_cycles/_name_and_timing_form.html.haml b/app/views/admin/order_cycles/_name_and_timing_form.html.haml new file mode 100644 index 0000000000..ed24d20f39 --- /dev/null +++ b/app/views/admin/order_cycles/_name_and_timing_form.html.haml @@ -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' diff --git a/app/views/admin/order_cycles/_row.html.haml b/app/views/admin/order_cycles/_row.html.haml index 75e84de025..882484c091 100644 --- a/app/views/admin/order_cycles/_row.html.haml +++ b/app/views/admin/order_cycles/_row.html.haml @@ -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 diff --git a/app/views/admin/order_cycles/_simple_form.html.haml b/app/views/admin/order_cycles/_simple_form.html.haml new file mode 100644 index 0000000000..9bffb9753e --- /dev/null +++ b/app/views/admin/order_cycles/_simple_form.html.haml @@ -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... + diff --git a/app/views/admin/order_cycles/edit.html.haml b/app/views/admin/order_cycles/edit.html.haml index 9bf0a8ca31..ff40cfbbbc 100644 --- a/app/views/admin/order_cycles/edit.html.haml +++ b/app/views/admin/order_cycles/edit.html.haml @@ -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 diff --git a/app/views/admin/order_cycles/index.html.haml b/app/views/admin/order_cycles/index.html.haml index 1fac06f507..aa54f9bff9 100644 --- a/app/views/admin/order_cycles/index.html.haml +++ b/app/views/admin/order_cycles/index.html.haml @@ -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 diff --git a/app/views/admin/order_cycles/new.html.haml b/app/views/admin/order_cycles/new.html.haml index c2e9c924f2..716e537874 100644 --- a/app/views/admin/order_cycles/new.html.haml +++ b/app/views/admin/order_cycles/new.html.haml @@ -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 diff --git a/app/views/checkout/_shipping.html.haml b/app/views/checkout/_shipping.html.haml index 27089527a2..5ba5ed15e4 100644 --- a/app/views/checkout/_shipping.html.haml +++ b/app/views/checkout/_shipping.html.haml @@ -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 diff --git a/lib/open_food_network/permissions.rb b/lib/open_food_network/permissions.rb index 60c2acbeaf..476db23692 100644 --- a/lib/open_food_network/permissions.rb +++ b/lib/open_food_network/permissions.rb @@ -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 diff --git a/script/backup.sh b/script/backup.sh index 462aef6abe..114a8352c6 100755 --- a/script/backup.sh +++ b/script/backup.sh @@ -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 diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index febd966109..c1aec54926 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -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 diff --git a/spec/javascripts/unit/admin/order_cycles/controllers/simple_create.js.coffee b/spec/javascripts/unit/admin/order_cycles/controllers/simple_create.js.coffee new file mode 100644 index 0000000000..bf2498e234 --- /dev/null +++ b/spec/javascripts/unit/admin/order_cycles/controllers/simple_create.js.coffee @@ -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 \ No newline at end of file diff --git a/spec/javascripts/unit/admin/order_cycles/controllers/simple_edit.js.coffee b/spec/javascripts/unit/admin/order_cycles/controllers/simple_edit.js.coffee new file mode 100644 index 0000000000..0e3275d886 --- /dev/null +++ b/spec/javascripts/unit/admin/order_cycles/controllers/simple_edit.js.coffee @@ -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 diff --git a/spec/javascripts/unit/order_cycle_spec.js.coffee b/spec/javascripts/unit/order_cycle_spec.js.coffee index 2a8d001804..7ecd2b4782 100644 --- a/spec/javascripts/unit/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/order_cycle_spec.js.coffee @@ -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 diff --git a/spec/models/enterprise_spec.rb b/spec/models/enterprise_spec.rb index 6f80a33a4a..7ace25917a 100644 --- a/spec/models/enterprise_spec.rb +++ b/spec/models/enterprise_spec.rb @@ -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{ diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index e4a98872af..1e793239ac 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -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 = []