diff --git a/Gemfile.lock b/Gemfile.lock index 16a319f0ab..37677c043c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -122,7 +122,7 @@ GEM rack-test (~> 0.6.1) sprockets (~> 2.2.1) active_link_to (1.0.0) - active_model_serializers (0.8.1) + active_model_serializers (0.8.3) activemodel (>= 3.0) activemerchant (1.48.0) activesupport (>= 3.2.14, < 5.0.0) @@ -730,6 +730,3 @@ DEPENDENCIES whenever wicked_pdf wkhtmltopdf-binary - -BUNDLED WITH - 1.10.6 diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index c0fa530626..7d3e94194d 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -39,6 +39,7 @@ //= require ./taxons/taxons //= require ./utils/utils //= require ./users/users +//= require ./variant_overrides/variant_overrides //= require textAngular.min.js //= require textAngular-sanitize.min.js //= require ../shared/bindonce.min.js diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index b3d24f00c0..6c85a4fd54 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -352,6 +352,9 @@ filterSubmitVariant = (variant) -> filteredVariant = {} if not variant.deleted_at? and variant.hasOwnProperty("id") filteredVariant.id = variant.id unless variant.id <= 0 + if variant.hasOwnProperty("sku") + filteredVariant.sku = variant.sku + hasUpdatableProperty = true if variant.hasOwnProperty("on_hand") filteredVariant.on_hand = variant.on_hand hasUpdatableProperty = true diff --git a/app/assets/javascripts/admin/controllers/variant_overrides_controller.js.coffee b/app/assets/javascripts/admin/controllers/variant_overrides_controller.js.coffee deleted file mode 100644 index bcc633805f..0000000000 --- a/app/assets/javascripts/admin/controllers/variant_overrides_controller.js.coffee +++ /dev/null @@ -1,67 +0,0 @@ -angular.module("ofn.admin").controller "AdminVariantOverridesCtrl", ($scope, $timeout, Indexer, SpreeApiAuth, PagedFetcher, StatusMessage, hubs, producers, hubPermissions, VariantOverrides, DirtyVariantOverrides) -> - $scope.hubs = hubs - $scope.hub = null - $scope.products = [] - $scope.producers = Indexer.index producers - $scope.hubPermissions = hubPermissions - $scope.variantOverrides = VariantOverrides.variantOverrides - $scope.StatusMessage = StatusMessage - - $scope.initialise = -> - SpreeApiAuth.authorise() - .then -> - $scope.spree_api_key_ok = true - $scope.fetchProducts() - .catch (message) -> - $scope.api_error_msg = message - - - $scope.fetchProducts = -> - url = "/api/products/overridable?page=::page::;per_page=100" - PagedFetcher.fetch url, (data) => $scope.addProducts data.products - - - $scope.addProducts = (products) -> - $scope.products = $scope.products.concat products - VariantOverrides.ensureDataFor hubs, products - - - $scope.selectHub = -> - $scope.hub = (hub for hub in hubs when hub.id == $scope.hub_id)[0] - - - $scope.displayDirty = -> - if DirtyVariantOverrides.count() > 0 - num = if DirtyVariantOverrides.count() == 1 then "one override" else "#{DirtyVariantOverrides.count()} overrides" - StatusMessage.display 'notice', "Changes to #{num} remain unsaved." - else - StatusMessage.clear() - - - $scope.update = -> - if DirtyVariantOverrides.count() == 0 - StatusMessage.display 'alert', 'No changes to save.' - else - StatusMessage.display 'progress', 'Saving...' - DirtyVariantOverrides.save() - .success (updatedVos) -> - DirtyVariantOverrides.clear() - VariantOverrides.updateIds updatedVos - $timeout -> StatusMessage.display 'success', 'Changes saved.' - .error (data, status) -> - $timeout -> StatusMessage.display 'failure', $scope.updateError(data, status) - - - $scope.updateError = (data, status) -> - if status == 401 - "I couldn't get authorisation to save those changes, so they remain unsaved." - - else if status == 400 && data.errors? - errors = [] - for field, field_errors of data.errors - errors = errors.concat field_errors - errors = errors.join ', ' - "I had some trouble saving: #{errors}" - - else - "Oh no! I was unable to save your changes." diff --git a/app/assets/javascripts/admin/dropdown/directives/dropdown.js.coffee b/app/assets/javascripts/admin/dropdown/directives/dropdown.js.coffee index 560598d23e..b4ca2869d7 100644 --- a/app/assets/javascripts/admin/dropdown/directives/dropdown.js.coffee +++ b/app/assets/javascripts/admin/dropdown/directives/dropdown.js.coffee @@ -1,8 +1,9 @@ angular.module("admin.dropdown").directive "ofnDropDown", ($document) -> + restrict: 'C' link: (scope, element, attrs) -> outsideClickListener = (event) -> - unless $(event.target).is("div.ofn_drop_down##{attrs.id} div.menu") || - $(event.target).parents("div.ofn_drop_down##{attrs.id} div.menu").length > 0 + unless $(event.target).is("div.ofn-drop-down##{attrs.id} div.menu") || + $(event.target).parents("div.ofn-drop-down##{attrs.id} div.menu").length > 0 scope.$emit "offClick" element.click (event) -> diff --git a/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee b/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee new file mode 100644 index 0000000000..ba7a4b54df --- /dev/null +++ b/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee @@ -0,0 +1,28 @@ +angular.module("admin.indexUtils").directive "ofnSelect2", ($timeout, blankOption) -> + require: 'ngModel' + restrict: 'C' + scope: + data: "=" + minSearch: "@?" + text: "@?" + blank: "=?" + link: (scope, element, attrs, ngModel) -> + $timeout -> + scope.text ||= 'name' + scope.data.unshift(scope.blank) if scope.blank? && typeof scope.blank is "object" + element.select2 + minimumResultsForSearch: scope.minSearch || 0 + data: { results: scope.data, text: scope.text } + initSelection: (element, callback) -> + callback scope.data[0] + formatSelection: (item) -> + item[scope.text] + formatResult: (item) -> + item[scope.text] + + attrs.$observe 'disabled', (value) -> + element.select2('enable', !value) + + ngModel.$formatters.push (value) -> + element.select2('val', value) + value diff --git a/app/assets/javascripts/admin/index_utils/directives/save_bar.js.coffee b/app/assets/javascripts/admin/index_utils/directives/save_bar.js.coffee deleted file mode 100644 index a991592d42..0000000000 --- a/app/assets/javascripts/admin/index_utils/directives/save_bar.js.coffee +++ /dev/null @@ -1,7 +0,0 @@ -angular.module("admin.indexUtils").directive "saveBar", -> - restrict: "E" - scope: - save: "&" - saving: "&" - dirty: "&" - templateUrl: "admin/save_bar.html" diff --git a/app/assets/javascripts/admin/index_utils/filters/attr_filter.js.coffee b/app/assets/javascripts/admin/index_utils/filters/attr_filter.js.coffee new file mode 100644 index 0000000000..c645b507f1 --- /dev/null +++ b/app/assets/javascripts/admin/index_utils/filters/attr_filter.js.coffee @@ -0,0 +1,12 @@ +# Used like a regular angular filter where an object is passed +# Adds the additional special case that a value of 0 for the filter +# acts as a bypass for that particular attribute +angular.module("admin.indexUtils").filter "attrFilter", ($filter) -> + return (objects, filters) -> + Object.keys(filters).reduce (filtered, attr) -> + filter = filters[attr] + return filtered if !filter? || filter == 0 + return $filter('filter')(filtered, (object) -> + object[attr] == filter + ) + , objects diff --git a/app/assets/javascripts/admin/services/data_fetcher.js.coffee b/app/assets/javascripts/admin/index_utils/services/data_fetcher.js.coffee similarity index 79% rename from app/assets/javascripts/admin/services/data_fetcher.js.coffee rename to app/assets/javascripts/admin/index_utils/services/data_fetcher.js.coffee index 735e4cc6bb..bf5580a3b2 100644 --- a/app/assets/javascripts/admin/services/data_fetcher.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/data_fetcher.js.coffee @@ -1,4 +1,4 @@ -angular.module("ofn.admin").factory "dataFetcher", [ +angular.module("admin.indexUtils").factory "dataFetcher", [ "$http", "$q" ($http, $q) -> return (dataLocation) -> @@ -9,4 +9,4 @@ angular.module("ofn.admin").factory "dataFetcher", [ deferred.reject() deferred.promise -] \ No newline at end of file +] diff --git a/app/assets/javascripts/admin/services/indexer.js.coffee b/app/assets/javascripts/admin/index_utils/services/indexer.js.coffee similarity index 85% rename from app/assets/javascripts/admin/services/indexer.js.coffee rename to app/assets/javascripts/admin/index_utils/services/indexer.js.coffee index f9a9688a2f..295df46be3 100644 --- a/app/assets/javascripts/admin/services/indexer.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/indexer.js.coffee @@ -4,7 +4,7 @@ # Indexer.index producers # -> {1: {id: 1, name: 'one'}, 2: {id: 2, name: 'two'}} -angular.module("ofn.admin").factory 'Indexer', -> +angular.module("admin.indexUtils").factory 'Indexer', -> new class Indexer index: (data, key='id') -> index = {} diff --git a/app/assets/javascripts/admin/services/paged_fetcher.js.coffee b/app/assets/javascripts/admin/index_utils/services/paged_fetcher.js.coffee similarity index 83% rename from app/assets/javascripts/admin/services/paged_fetcher.js.coffee rename to app/assets/javascripts/admin/index_utils/services/paged_fetcher.js.coffee index 9281ed6a42..d65887bb2c 100644 --- a/app/assets/javascripts/admin/services/paged_fetcher.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/paged_fetcher.js.coffee @@ -1,4 +1,4 @@ -angular.module("ofn.admin").factory "PagedFetcher", (dataFetcher) -> +angular.module("admin.indexUtils").factory "PagedFetcher", (dataFetcher) -> new class PagedFetcher # Given a URL like http://example.com/foo?page=::page::&per_page=20 # And the response includes an attribute pages with the number of pages to fetch @@ -13,4 +13,4 @@ angular.module("ofn.admin").factory "PagedFetcher", (dataFetcher) -> processData data urlForPage: (url, page) -> - url.replace("::page::", page) \ No newline at end of file + url.replace("::page::", page) diff --git a/app/assets/javascripts/admin/services/spree_api_auth.js.coffee b/app/assets/javascripts/admin/index_utils/services/spree_api_auth.js.coffee similarity index 84% rename from app/assets/javascripts/admin/services/spree_api_auth.js.coffee rename to app/assets/javascripts/admin/index_utils/services/spree_api_auth.js.coffee index e606882bc5..3ed4dd9bf7 100644 --- a/app/assets/javascripts/admin/services/spree_api_auth.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/spree_api_auth.js.coffee @@ -1,4 +1,4 @@ -angular.module("ofn.admin").factory "SpreeApiAuth", ($q, $http, SpreeApiKey) -> +angular.module("admin.indexUtils").factory "SpreeApiAuth", ($q, $http, SpreeApiKey) -> new class SpreeApiAuth authorise: -> deferred = $q.defer() diff --git a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee index 38c7f0ba33..c6709eb3b4 100644 --- a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee +++ b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee @@ -1,7 +1,6 @@ -angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $http, $q, Columns, Dereferencer, Orders, LineItems, Enterprises, OrderCycles, blankOption, VariantUnitManager, RequestMonitor) -> +angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $http, $q, StatusMessage, Columns, Dereferencer, Orders, LineItems, Enterprises, OrderCycles, blankOption, VariantUnitManager, RequestMonitor) -> $scope.initialized = false $scope.RequestMonitor = RequestMonitor - $scope.saving = false $scope.filteredLineItems = [] $scope.confirmDelete = true $scope.startDate = formatDate daysFromToday -7 @@ -55,6 +54,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, Dereferencer.dereferenceAttr $scope.lineItems, "supplier", Enterprises.enterprisesByID Dereferencer.dereferenceAttr $scope.lineItems, "order", Orders.ordersByID $scope.bulk_order_form.$setPristine() + StatusMessage.clear() unless $scope.initialized $scope.initialized = true $timeout -> @@ -62,16 +62,20 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $scope.refreshData() - $scope.submit = => + $scope.$watch 'bulk_order_form.$dirty', (newVal, oldVal) -> + if newVal == true + StatusMessage.display 'notice', "You have unsaved changes" + + $scope.submit = -> if $scope.bulk_order_form.$valid - $scope.saving = true + StatusMessage.display 'progress', "Saving..." $q.all(LineItems.saveAll()).then(-> + StatusMessage.display 'success', "All changes saved" $scope.bulk_order_form.$setPristine() - $scope.saving = false ).catch -> - alert "Some errors must be resolved be before you can update orders.\nAny fields with red borders contain errors." + StatusMessage.display 'failure', "Fields with red borders contain errors." else - alert "Some errors must be resolved be before you can update orders.\nAny fields with red borders contain errors." + StatusMessage.display 'failure', "Fields with red borders contain errors." $scope.deleteLineItem = (lineItem) -> if ($scope.confirmDelete && confirm("Are you sure?")) || !$scope.confirmDelete diff --git a/app/assets/javascripts/admin/line_items/line_items.js.coffee b/app/assets/javascripts/admin/line_items/line_items.js.coffee index a3328c572e..8128a50e8a 100644 --- a/app/assets/javascripts/admin/line_items/line_items.js.coffee +++ b/app/assets/javascripts/admin/line_items/line_items.js.coffee @@ -1 +1 @@ -angular.module("admin.lineItems", ["admin.indexUtils", "admin.products", "admin.orders", "admin.enterprises", "admin.orderCycles"]) +angular.module("admin.lineItems", ["admin.indexUtils", "admin.utils", "admin.products", "admin.orders", "admin.enterprises", "admin.orderCycles"]) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee new file mode 100644 index 0000000000..2c98d60f0e --- /dev/null +++ b/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee @@ -0,0 +1,83 @@ +angular.module('admin.orderCycles') + .controller 'AdminCreateOrderCycleCtrl', ($scope, $filter, OrderCycle, Enterprise, EnterpriseFee, ocInstance, StatusMessage) -> + $scope.enterprises = Enterprise.index(coordinator_id: ocInstance.coordinator_id) + $scope.supplier_enterprises = Enterprise.producer_enterprises + $scope.distributor_enterprises = Enterprise.hub_enterprises + $scope.supplied_products = Enterprise.supplied_products + $scope.enterprise_fees = EnterpriseFee.index(coordinator_id: ocInstance.coordinator_id) + + $scope.OrderCycle = OrderCycle + $scope.order_cycle = OrderCycle.new({ coordinator_id: ocInstance.coordinator_id}) + + $scope.StatusMessage = StatusMessage + + $scope.loaded = -> + Enterprise.loaded && EnterpriseFee.loaded + + $scope.suppliedVariants = (enterprise_id) -> + Enterprise.suppliedVariants(enterprise_id) + + $scope.exchangeSelectedVariants = (exchange) -> + OrderCycle.exchangeSelectedVariants(exchange) + + $scope.setExchangeVariants = (exchange, variants, selected) -> + OrderCycle.setExchangeVariants(exchange, variants, selected) + + $scope.enterpriseTotalVariants = (enterprise) -> + Enterprise.totalVariants(enterprise) + + $scope.productSuppliedToOrderCycle = (product) -> + OrderCycle.productSuppliedToOrderCycle(product) + + $scope.variantSuppliedToOrderCycle = (variant) -> + OrderCycle.variantSuppliedToOrderCycle(variant) + + $scope.incomingExchangeVariantsFor = (enterprise_id) -> + $filter('filterExchangeVariants')(OrderCycle.incomingExchangesVariants(), $scope.order_cycle.visible_variants_for_outgoing_exchanges[enterprise_id]) + + $scope.exchangeDirection = (exchange) -> + OrderCycle.exchangeDirection(exchange) + + $scope.enterprisesWithFees = -> + $scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0 + + $scope.toggleProducts = ($event, exchange) -> + $event.preventDefault() + OrderCycle.toggleProducts(exchange) + + $scope.enterpriseFeesForEnterprise = (enterprise_id) -> + EnterpriseFee.forEnterprise(parseInt(enterprise_id)) + + $scope.addSupplier = ($event) -> + $event.preventDefault() + OrderCycle.addSupplier($scope.new_supplier_id) + + $scope.addDistributor = ($event) -> + $event.preventDefault() + OrderCycle.addDistributor($scope.new_distributor_id) + + $scope.removeExchange = ($event, exchange) -> + $event.preventDefault() + OrderCycle.removeExchange(exchange) + + $scope.addCoordinatorFee = ($event) -> + $event.preventDefault() + OrderCycle.addCoordinatorFee() + + $scope.removeCoordinatorFee = ($event, index) -> + $event.preventDefault() + OrderCycle.removeCoordinatorFee(index) + + $scope.addExchangeFee = ($event, exchange) -> + $event.preventDefault() + OrderCycle.addExchangeFee(exchange) + + $scope.removeExchangeFee = ($event, exchange, index) -> + $event.preventDefault() + OrderCycle.removeExchangeFee(exchange, index) + + $scope.removeDistributionOfVariant = (variant_id) -> + OrderCycle.removeDistributionOfVariant(variant_id) + + $scope.submit = (destination) -> + OrderCycle.create(destination) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee new file mode 100644 index 0000000000..fd426eb455 --- /dev/null +++ b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee @@ -0,0 +1,84 @@ +angular.module('admin.orderCycles') + .controller 'AdminEditOrderCycleCtrl', ($scope, $filter, $location, OrderCycle, Enterprise, EnterpriseFee, StatusMessage) -> + order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1] + $scope.enterprises = Enterprise.index(order_cycle_id: order_cycle_id) + $scope.supplier_enterprises = Enterprise.producer_enterprises + $scope.distributor_enterprises = Enterprise.hub_enterprises + $scope.supplied_products = Enterprise.supplied_products + $scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: order_cycle_id) + + $scope.OrderCycle = OrderCycle + $scope.order_cycle = OrderCycle.load(order_cycle_id) + + $scope.StatusMessage = StatusMessage + + $scope.loaded = -> + Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded + + $scope.suppliedVariants = (enterprise_id) -> + Enterprise.suppliedVariants(enterprise_id) + + $scope.exchangeSelectedVariants = (exchange) -> + OrderCycle.exchangeSelectedVariants(exchange) + + $scope.setExchangeVariants = (exchange, variants, selected) -> + OrderCycle.setExchangeVariants(exchange, variants, selected) + + $scope.enterpriseTotalVariants = (enterprise) -> + Enterprise.totalVariants(enterprise) + + $scope.productSuppliedToOrderCycle = (product) -> + OrderCycle.productSuppliedToOrderCycle(product) + + $scope.variantSuppliedToOrderCycle = (variant) -> + OrderCycle.variantSuppliedToOrderCycle(variant) + + $scope.incomingExchangeVariantsFor = (enterprise_id) -> + $filter('filterExchangeVariants')(OrderCycle.incomingExchangesVariants(), $scope.order_cycle.visible_variants_for_outgoing_exchanges[enterprise_id]) + + $scope.exchangeDirection = (exchange) -> + OrderCycle.exchangeDirection(exchange) + + $scope.enterprisesWithFees = -> + $scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0 + + $scope.toggleProducts = ($event, exchange) -> + $event.preventDefault() + OrderCycle.toggleProducts(exchange) + + $scope.enterpriseFeesForEnterprise = (enterprise_id) -> + EnterpriseFee.forEnterprise(parseInt(enterprise_id)) + + $scope.addSupplier = ($event) -> + $event.preventDefault() + OrderCycle.addSupplier($scope.new_supplier_id) + + $scope.addDistributor = ($event) -> + $event.preventDefault() + OrderCycle.addDistributor($scope.new_distributor_id) + + $scope.removeExchange = ($event, exchange) -> + $event.preventDefault() + OrderCycle.removeExchange(exchange) + + $scope.addCoordinatorFee = ($event) -> + $event.preventDefault() + OrderCycle.addCoordinatorFee() + + $scope.removeCoordinatorFee = ($event, index) -> + $event.preventDefault() + OrderCycle.removeCoordinatorFee(index) + + $scope.addExchangeFee = ($event, exchange) -> + $event.preventDefault() + OrderCycle.addExchangeFee(exchange) + + $scope.removeExchangeFee = ($event, exchange, index) -> + $event.preventDefault() + OrderCycle.removeExchangeFee(exchange, index) + + $scope.removeDistributionOfVariant = (variant_id) -> + OrderCycle.removeDistributionOfVariant(variant_id) + + $scope.submit = (destination) -> + OrderCycle.update(destination) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/order_cycle_controller.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/order_cycle_controller.js.coffee deleted file mode 100644 index 40428e1f73..0000000000 --- a/app/assets/javascripts/admin/order_cycles/controllers/order_cycle_controller.js.coffee +++ /dev/null @@ -1,204 +0,0 @@ -angular.module('admin.orderCycles', ['ngResource', 'admin.utils']) - .controller('AdminCreateOrderCycleCtrl', ['$scope', '$filter', 'OrderCycle', 'Enterprise', 'EnterpriseFee', 'ocInstance', 'StatusMessage', ($scope, $filter, OrderCycle, Enterprise, EnterpriseFee, ocInstance, StatusMessage) -> - $scope.enterprises = Enterprise.index(coordinator_id: ocInstance.coordinator_id) - $scope.supplier_enterprises = Enterprise.producer_enterprises - $scope.distributor_enterprises = Enterprise.hub_enterprises - $scope.supplied_products = Enterprise.supplied_products - $scope.enterprise_fees = EnterpriseFee.index(coordinator_id: ocInstance.coordinator_id) - - $scope.OrderCycle = OrderCycle - $scope.order_cycle = OrderCycle.new({ coordinator_id: ocInstance.coordinator_id}) - - $scope.StatusMessage = StatusMessage - - $scope.loaded = -> - Enterprise.loaded && EnterpriseFee.loaded - - $scope.suppliedVariants = (enterprise_id) -> - Enterprise.suppliedVariants(enterprise_id) - - $scope.exchangeSelectedVariants = (exchange) -> - OrderCycle.exchangeSelectedVariants(exchange) - - $scope.setExchangeVariants = (exchange, variants, selected) -> - OrderCycle.setExchangeVariants(exchange, variants, selected) - - $scope.enterpriseTotalVariants = (enterprise) -> - Enterprise.totalVariants(enterprise) - - $scope.productSuppliedToOrderCycle = (product) -> - OrderCycle.productSuppliedToOrderCycle(product) - - $scope.variantSuppliedToOrderCycle = (variant) -> - OrderCycle.variantSuppliedToOrderCycle(variant) - - $scope.incomingExchangeVariantsFor = (enterprise_id) -> - $filter('filterExchangeVariants')(OrderCycle.incomingExchangesVariants(), $scope.order_cycle.visible_variants_for_outgoing_exchanges[enterprise_id]) - - $scope.exchangeDirection = (exchange) -> - OrderCycle.exchangeDirection(exchange) - - $scope.enterprisesWithFees = -> - $scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0 - - $scope.toggleProducts = ($event, exchange) -> - $event.preventDefault() - OrderCycle.toggleProducts(exchange) - - $scope.enterpriseFeesForEnterprise = (enterprise_id) -> - EnterpriseFee.forEnterprise(parseInt(enterprise_id)) - - $scope.addSupplier = ($event) -> - $event.preventDefault() - OrderCycle.addSupplier($scope.new_supplier_id) - - $scope.addDistributor = ($event) -> - $event.preventDefault() - OrderCycle.addDistributor($scope.new_distributor_id) - - $scope.removeExchange = ($event, exchange) -> - $event.preventDefault() - OrderCycle.removeExchange(exchange) - - $scope.addCoordinatorFee = ($event) -> - $event.preventDefault() - OrderCycle.addCoordinatorFee() - - $scope.removeCoordinatorFee = ($event, index) -> - $event.preventDefault() - OrderCycle.removeCoordinatorFee(index) - - $scope.addExchangeFee = ($event, exchange) -> - $event.preventDefault() - OrderCycle.addExchangeFee(exchange) - - $scope.removeExchangeFee = ($event, exchange, index) -> - $event.preventDefault() - OrderCycle.removeExchangeFee(exchange, index) - - $scope.removeDistributionOfVariant = (variant_id) -> - OrderCycle.removeDistributionOfVariant(variant_id) - - $scope.submit = (destination) -> - OrderCycle.create(destination) - ]) - - .controller('AdminEditOrderCycleCtrl', ['$scope', '$filter', '$location', 'OrderCycle', 'Enterprise', 'EnterpriseFee', 'StatusMessage', ($scope, $filter, $location, OrderCycle, Enterprise, EnterpriseFee, StatusMessage) -> - order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1] - $scope.enterprises = Enterprise.index(order_cycle_id: order_cycle_id) - $scope.supplier_enterprises = Enterprise.producer_enterprises - $scope.distributor_enterprises = Enterprise.hub_enterprises - $scope.supplied_products = Enterprise.supplied_products - $scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: order_cycle_id) - - $scope.OrderCycle = OrderCycle - $scope.order_cycle = OrderCycle.load(order_cycle_id) - - $scope.StatusMessage = StatusMessage - - $scope.loaded = -> - Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded - - $scope.suppliedVariants = (enterprise_id) -> - Enterprise.suppliedVariants(enterprise_id) - - $scope.exchangeSelectedVariants = (exchange) -> - OrderCycle.exchangeSelectedVariants(exchange) - - $scope.setExchangeVariants = (exchange, variants, selected) -> - OrderCycle.setExchangeVariants(exchange, variants, selected) - - $scope.enterpriseTotalVariants = (enterprise) -> - Enterprise.totalVariants(enterprise) - - $scope.productSuppliedToOrderCycle = (product) -> - OrderCycle.productSuppliedToOrderCycle(product) - - $scope.variantSuppliedToOrderCycle = (variant) -> - OrderCycle.variantSuppliedToOrderCycle(variant) - - $scope.incomingExchangeVariantsFor = (enterprise_id) -> - $filter('filterExchangeVariants')(OrderCycle.incomingExchangesVariants(), $scope.order_cycle.visible_variants_for_outgoing_exchanges[enterprise_id]) - - $scope.exchangeDirection = (exchange) -> - OrderCycle.exchangeDirection(exchange) - - $scope.enterprisesWithFees = -> - $scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0 - - $scope.toggleProducts = ($event, exchange) -> - $event.preventDefault() - OrderCycle.toggleProducts(exchange) - - $scope.enterpriseFeesForEnterprise = (enterprise_id) -> - EnterpriseFee.forEnterprise(parseInt(enterprise_id)) - - $scope.addSupplier = ($event) -> - $event.preventDefault() - OrderCycle.addSupplier($scope.new_supplier_id) - - $scope.addDistributor = ($event) -> - $event.preventDefault() - OrderCycle.addDistributor($scope.new_distributor_id) - - $scope.removeExchange = ($event, exchange) -> - $event.preventDefault() - OrderCycle.removeExchange(exchange) - - $scope.addCoordinatorFee = ($event) -> - $event.preventDefault() - OrderCycle.addCoordinatorFee() - - $scope.removeCoordinatorFee = ($event, index) -> - $event.preventDefault() - OrderCycle.removeCoordinatorFee(index) - - $scope.addExchangeFee = ($event, exchange) -> - $event.preventDefault() - OrderCycle.addExchangeFee(exchange) - - $scope.removeExchangeFee = ($event, exchange, index) -> - $event.preventDefault() - OrderCycle.removeExchangeFee(exchange, index) - - $scope.removeDistributionOfVariant = (variant_id) -> - OrderCycle.removeDistributionOfVariant(variant_id) - - $scope.submit = (destination) -> - OrderCycle.update(destination) - ]) - - .config(['$httpProvider', ($httpProvider) -> - $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content') - ]) - - .directive('datetimepicker', ['$parse', ($parse) -> - (scope, element, attrs) -> - # using $parse instead of scope[attrs.datetimepicker] for cases - # where attrs.datetimepicker is 'foo.bar.lol' - $(element).datetimepicker - dateFormat: 'yy-mm-dd' - timeFormat: 'HH:mm:ss' - showOn: "button" - buttonImage: "<%= asset_path 'datepicker/cal.gif' %>" - buttonImageOnly: true - stepMinute: 15 - onSelect: (dateText, inst) -> - scope.$apply -> - parsed = $parse(attrs.datetimepicker) - parsed.assign(scope, dateText) - ]) - - .directive('ofnOnChange', -> - (scope, element, attrs) -> - element.bind 'change', -> - scope.$apply(attrs.ofnOnChange) - ) - - .directive('ofnSyncDistributions', -> - (scope, element, attrs) -> - element.bind 'change', -> - if !$(this).is(':checked') - scope.$apply -> - scope.removeDistributionOfVariant(attrs.ofnSyncDistributions) - ) diff --git a/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee b/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee index 2f97440e71..a75fdad58c 100644 --- a/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee @@ -1 +1,32 @@ -angular.module('admin.orderCycles', ['ngResource', 'admin.indexUtils']) +angular.module('admin.orderCycles', ['ngResource', 'admin.utils', 'admin.indexUtils']) + + .config ($httpProvider) -> + $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content') + + .directive 'datetimepicker', ($parse) -> + (scope, element, attrs) -> + # using $parse instead of scope[attrs.datetimepicker] for cases + # where attrs.datetimepicker is 'foo.bar.lol' + $(element).datetimepicker + dateFormat: 'yy-mm-dd' + timeFormat: 'HH:mm:ss' + showOn: "button" + buttonImage: "<%= asset_path 'datepicker/cal.gif' %>" + buttonImageOnly: true + stepMinute: 15 + onSelect: (dateText, inst) -> + scope.$apply -> + parsed = $parse(attrs.datetimepicker) + parsed.assign(scope, dateText) + + .directive 'ofnOnChange', -> + (scope, element, attrs) -> + element.bind 'change', -> + scope.$apply(attrs.ofnOnChange) + + .directive 'ofnSyncDistributions', -> + (scope, element, attrs) -> + element.bind 'change', -> + if !$(this).is(':checked') + scope.$apply -> + scope.removeDistributionOfVariant(attrs.ofnSyncDistributions) diff --git a/app/assets/javascripts/admin/services/status_message.js.coffee b/app/assets/javascripts/admin/services/status_message.js.coffee deleted file mode 100644 index aaa55cf339..0000000000 --- a/app/assets/javascripts/admin/services/status_message.js.coffee +++ /dev/null @@ -1,26 +0,0 @@ -angular.module("ofn.admin").factory "StatusMessage", ($timeout) -> - new class StatusMessage - types: - progress: {timeout: false, style: {color: '#ff9906'}} - alert: {timeout: 5000, style: {color: 'grey'}} - notice: {timeout: false, style: {color: 'grey'}} - success: {timeout: 5000, style: {color: '#9fc820'}} - failure: {timeout: false, style: {color: '#da5354'}} - - statusMessage: - text: "" - style: {} - - display: (type, text) -> - @statusMessage.text = text - @statusMessage.style = @types[type].style - $timeout.cancel @statusMessage.timeout if @statusMessage.timeout - timeout = @types[type].timeout - if timeout - @statusMessage.timeout = $timeout => - @clear() - , timeout, true - - clear: -> - @statusMessage.text = '' - @statusMessage.style = {} diff --git a/app/assets/javascripts/admin/services/variant_overrides.js.coffee b/app/assets/javascripts/admin/services/variant_overrides.js.coffee deleted file mode 100644 index 28d65eab03..0000000000 --- a/app/assets/javascripts/admin/services/variant_overrides.js.coffee +++ /dev/null @@ -1,23 +0,0 @@ -angular.module("ofn.admin").factory "VariantOverrides", (variantOverrides, Indexer) -> - new class VariantOverrides - variantOverrides: {} - - constructor: -> - for vo in variantOverrides - @variantOverrides[vo.hub_id] ||= {} - @variantOverrides[vo.hub_id][vo.variant_id] = vo - - ensureDataFor: (hubs, products) -> - for hub in hubs - @variantOverrides[hub.id] ||= {} - for product in products - for variant in product.variants - @variantOverrides[hub.id][variant.id] ||= - variant_id: variant.id - hub_id: hub.id - price: '' - count_on_hand: '' - - updateIds: (updatedVos) -> - for vo in updatedVos - @variantOverrides[vo.hub_id][vo.variant_id].id = vo.id \ No newline at end of file diff --git a/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee b/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee new file mode 100644 index 0000000000..13e4f84bc6 --- /dev/null +++ b/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee @@ -0,0 +1,8 @@ +angular.module("admin.utils").directive "saveBar", (StatusMessage) -> + restrict: "E" + scope: + save: "&" + form: "=" + templateUrl: "admin/save_bar.html" + link: (scope, element, attrs) -> + scope.StatusMessage = StatusMessage diff --git a/app/assets/javascripts/admin/utils/services/status_message.js.coffee b/app/assets/javascripts/admin/utils/services/status_message.js.coffee index d317269ca4..6aac046a7f 100644 --- a/app/assets/javascripts/admin/utils/services/status_message.js.coffee +++ b/app/assets/javascripts/admin/utils/services/status_message.js.coffee @@ -11,6 +11,9 @@ angular.module("admin.utils").factory "StatusMessage", ($timeout) -> text: "" style: {} + active: -> + @statusMessage.text != '' + display: (type, text) -> @statusMessage.text = text @statusMessage.style = @types[type].style @@ -20,6 +23,7 @@ angular.module("admin.utils").factory "StatusMessage", ($timeout) -> @statusMessage.timeout = $timeout => @clear() , timeout, true + null # So we don't return weird timeouts clear: -> @statusMessage.text = '' diff --git a/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee b/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee new file mode 100644 index 0000000000..b65df80d66 --- /dev/null +++ b/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee @@ -0,0 +1,102 @@ +angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl", ($scope, $http, $timeout, Indexer, Columns, SpreeApiAuth, PagedFetcher, StatusMessage, hubs, producers, hubPermissions, VariantOverrides, DirtyVariantOverrides) -> + $scope.hubs = Indexer.index hubs + $scope.hub = null + $scope.products = [] + $scope.producers = producers + $scope.producersByID = Indexer.index producers + $scope.hubPermissions = hubPermissions + $scope.variantOverrides = VariantOverrides.variantOverrides + $scope.StatusMessage = StatusMessage + + $scope.columns = Columns.setColumns + producer: { name: "Producer", visible: true } + product: { name: "Product", visible: true } + sku: { name: "SKU", visible: false } + price: { name: "Price", visible: true } + on_hand: { name: "On Hand", visible: true } + on_demand: { name: "On Demand", visible: false } + reset: { name: "Reset Stock Level", visible: false } + inheritance: { name: "Inheritance", visible: false } + + $scope.resetSelectFilters = -> + $scope.producerFilter = 0 + $scope.query = '' + + $scope.resetSelectFilters() + + $scope.initialise = -> + SpreeApiAuth.authorise() + .then -> + $scope.spree_api_key_ok = true + $scope.fetchProducts() + .catch (message) -> + $scope.api_error_msg = message + + + $scope.fetchProducts = -> + url = "/api/products/overridable?page=::page::;per_page=100" + PagedFetcher.fetch url, (data) => $scope.addProducts data.products + + + $scope.addProducts = (products) -> + $scope.products = $scope.products.concat products + VariantOverrides.ensureDataFor hubs, products + + + $scope.selectHub = -> + $scope.hub = $scope.hubs[$scope.hub_id] + + $scope.displayDirty = -> + if DirtyVariantOverrides.count() > 0 + num = if DirtyVariantOverrides.count() == 1 then "one override" else "#{DirtyVariantOverrides.count()} overrides" + StatusMessage.display 'notice', "Changes to #{num} remain unsaved." + else + StatusMessage.clear() + + $scope.update = -> + if DirtyVariantOverrides.count() == 0 + StatusMessage.display 'alert', 'No changes to save.' + else + StatusMessage.display 'progress', 'Saving...' + DirtyVariantOverrides.save() + .success (updatedVos) -> + DirtyVariantOverrides.clear() + VariantOverrides.updateIds updatedVos + $scope.variant_overrides_form.$setPristine() + StatusMessage.display 'success', 'Changes saved.' + VariantOverrides.updateData updatedVos # Refresh page data + .error (data, status) -> + StatusMessage.display 'failure', $scope.updateError(data, status) + + + $scope.updateError = (data, status) -> + if status == 401 + "I couldn't get authorisation to save those changes, so they remain unsaved." + + else if status == 400 && data.errors? + errors = [] + for field, field_errors of data.errors + errors = errors.concat field_errors + errors = errors.join ', ' + "I had some trouble saving: #{errors}" + else + "Oh no! I was unable to save your changes." + + $scope.resetStock = -> + if DirtyVariantOverrides.count() > 0 + StatusMessage.display 'alert', 'Save changes first.' + $timeout -> + $scope.displayDirty() + , 3000 # 3 second delay + else + return unless $scope.hub_id? + StatusMessage.display 'progress', 'Changing on hand stock levels...' + $http + method: "POST" + url: "/admin/variant_overrides/bulk_reset" + data: { hub_id: $scope.hub_id } + .success (updatedVos) -> + VariantOverrides.updateData updatedVos + StatusMessage.display 'success', 'Stocks reset to defaults.' + .error (data, status) -> + $timeout -> StatusMessage.display 'failure', $scope.updateError(data, status) diff --git a/app/assets/javascripts/admin/variant_overrides/directives/track_inheritance.js.coffee b/app/assets/javascripts/admin/variant_overrides/directives/track_inheritance.js.coffee new file mode 100644 index 0000000000..20ac08c035 --- /dev/null +++ b/app/assets/javascripts/admin/variant_overrides/directives/track_inheritance.js.coffee @@ -0,0 +1,12 @@ +angular.module("admin.variantOverrides").directive "trackInheritance", (VariantOverrides, DirtyVariantOverrides) -> + require: "ngModel" + link: (scope, element, attrs, ngModel) -> + # This is a bit hacky, but it allows us to load the inherit property on the VO, but then not submit it + scope.inherit = angular.equals scope.variantOverrides[scope.hub.id][scope.variant.id], VariantOverrides.newFor scope.hub.id, scope.variant.id + + ngModel.$parsers.push (viewValue) -> + if ngModel.$dirty && viewValue + variantOverride = VariantOverrides.inherit(scope.hub.id, scope.variant.id) + DirtyVariantOverrides.add variantOverride + scope.displayDirty() + viewValue diff --git a/app/assets/javascripts/admin/directives/track_variant_override.js.coffee b/app/assets/javascripts/admin/variant_overrides/directives/track_variant_override.js.coffee similarity index 69% rename from app/assets/javascripts/admin/directives/track_variant_override.js.coffee rename to app/assets/javascripts/admin/variant_overrides/directives/track_variant_override.js.coffee index bb8117a757..919533967c 100644 --- a/app/assets/javascripts/admin/directives/track_variant_override.js.coffee +++ b/app/assets/javascripts/admin/variant_overrides/directives/track_variant_override.js.coffee @@ -1,9 +1,10 @@ -angular.module("ofn.admin").directive "ofnTrackVariantOverride", (DirtyVariantOverrides) -> +angular.module("admin.variantOverrides").directive "ofnTrackVariantOverride", (DirtyVariantOverrides) -> require: "ngModel" link: (scope, element, attrs, ngModel) -> ngModel.$parsers.push (viewValue) -> if ngModel.$dirty variantOverride = scope.variantOverrides[scope.hub.id][scope.variant.id] + scope.inherit = false DirtyVariantOverrides.add variantOverride scope.displayDirty() viewValue diff --git a/app/assets/javascripts/admin/filters/hub_permissions_filter.js.coffee b/app/assets/javascripts/admin/variant_overrides/filters/hub_permissions_filter.js.coffee similarity index 70% rename from app/assets/javascripts/admin/filters/hub_permissions_filter.js.coffee rename to app/assets/javascripts/admin/variant_overrides/filters/hub_permissions_filter.js.coffee index 5db7a6d40e..39b5e77839 100644 --- a/app/assets/javascripts/admin/filters/hub_permissions_filter.js.coffee +++ b/app/assets/javascripts/admin/variant_overrides/filters/hub_permissions_filter.js.coffee @@ -1,4 +1,4 @@ -angular.module("ofn.admin").filter "hubPermissions", ($filter) -> +angular.module("admin.variantOverrides").filter "hubPermissions", ($filter) -> return (products, hubPermissions, hub_id) -> return [] if !hub_id return $filter('filter')(products, ((product) -> hubPermissions[hub_id].indexOf(product.producer_id) > -1), true) diff --git a/app/assets/javascripts/admin/services/dirty_variant_overrides.js.coffee b/app/assets/javascripts/admin/variant_overrides/services/dirty_variant_overrides.js.coffee similarity index 88% rename from app/assets/javascripts/admin/services/dirty_variant_overrides.js.coffee rename to app/assets/javascripts/admin/variant_overrides/services/dirty_variant_overrides.js.coffee index 82e7772982..053c6cbfa1 100644 --- a/app/assets/javascripts/admin/services/dirty_variant_overrides.js.coffee +++ b/app/assets/javascripts/admin/variant_overrides/services/dirty_variant_overrides.js.coffee @@ -1,4 +1,4 @@ -angular.module("ofn.admin").factory "DirtyVariantOverrides", ($http) -> +angular.module("admin.variantOverrides").factory "DirtyVariantOverrides", ($http) -> new class DirtyVariantOverrides dirtyVariantOverrides: {} diff --git a/app/assets/javascripts/admin/variant_overrides/services/variant_overrides.js.coffee b/app/assets/javascripts/admin/variant_overrides/services/variant_overrides.js.coffee new file mode 100644 index 0000000000..4e1128572e --- /dev/null +++ b/app/assets/javascripts/admin/variant_overrides/services/variant_overrides.js.coffee @@ -0,0 +1,40 @@ + +angular.module("admin.variantOverrides").factory "VariantOverrides", (variantOverrides) -> + new class VariantOverrides + variantOverrides: {} + + constructor: -> + for vo in variantOverrides + @variantOverrides[vo.hub_id] ||= {} + @variantOverrides[vo.hub_id][vo.variant_id] = vo + + ensureDataFor: (hubs, products) -> + for hub_id, hub of hubs + @variantOverrides[hub.id] ||= {} + for product in products + for variant in product.variants + @inherit(hub.id, variant.id) unless @variantOverrides[hub.id][variant.id] + + inherit: (hub_id, variant_id) -> + # This method is called from the trackInheritance directive, to reinstate inheritance + @variantOverrides[hub_id][variant_id] ||= {} + angular.extend @variantOverrides[hub_id][variant_id], @newFor hub_id, variant_id + + newFor: (hub_id, variant_id) -> + # These properties need to match those checked in VariantOverrideSet.deletable? + hub_id: hub_id + variant_id: variant_id + sku: null + price: null + count_on_hand: null + on_demand: null + default_stock: null + resettable: false + + updateIds: (updatedVos) -> + for vo in updatedVos + @variantOverrides[vo.hub_id][vo.variant_id].id = vo.id + + updateData: (updatedVos) -> + for vo in updatedVos + @variantOverrides[vo.hub_id][vo.variant_id] = vo diff --git a/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee b/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee new file mode 100644 index 0000000000..ae46cd14c7 --- /dev/null +++ b/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee @@ -0,0 +1 @@ +angular.module("admin.variantOverrides", ["pasvaz.bindonce", "admin.indexUtils", "admin.utils", "admin.dropdown"]) diff --git a/app/assets/javascripts/templates/admin/links_dropdown.html.haml b/app/assets/javascripts/templates/admin/links_dropdown.html.haml index 4f85ed1319..1f44f2418c 100644 --- a/app/assets/javascripts/templates/admin/links_dropdown.html.haml +++ b/app/assets/javascripts/templates/admin/links_dropdown.html.haml @@ -1,4 +1,4 @@ -.ofn_drop_down{ "ofn-drop-down" => true } +.ofn-drop-down %span %i.icon-check Actions diff --git a/app/assets/javascripts/templates/admin/save_bar.html.haml b/app/assets/javascripts/templates/admin/save_bar.html.haml index 62842f0bfb..452e81f6e3 100644 --- a/app/assets/javascripts/templates/admin/save_bar.html.haml +++ b/app/assets/javascripts/templates/admin/save_bar.html.haml @@ -1,10 +1,6 @@ -#save-bar.animate-show{ ng: { show: 'dirty()' } } +#save-bar.animate-show{ ng: { show: 'form.$dirty || StatusMessage.active()' } } .twelve.columns.alpha - %h5{ ng: { show: "dirty() && !saving()" } } - You have unsaved changes - %h5{ ng: { hide: "dirty() || saving()" } } - All changes saved - %h5{ ng: { show: "saving()" } } - Saving... + %h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } } + {{ StatusMessage.statusMessage.text || " " }} .four.columns.omega.text-right - %input.red{type: "button", value: "Save Changes", ng: { click: "save()" } } + %input.red{type: "button", value: "Save Changes", ng: { disabled: '!form.$dirty', click: "save()" } } diff --git a/app/assets/stylesheets/admin/disabled.css.scss b/app/assets/stylesheets/admin/disabled.css.scss new file mode 100644 index 0000000000..a393c5d80b --- /dev/null +++ b/app/assets/stylesheets/admin/disabled.css.scss @@ -0,0 +1,13 @@ +label.disabled { + color: #c3c3c3; + pointer-events: none; +} + +input[type='button']:disabled { + background-color: #c3c3c3; + color: #ffffff; +} + +.select2-container-disabled { + pointer-events: none; +} diff --git a/app/assets/stylesheets/admin/dropdown.css.scss b/app/assets/stylesheets/admin/dropdown.css.scss new file mode 100644 index 0000000000..2dfa369c87 --- /dev/null +++ b/app/assets/stylesheets/admin/dropdown.css.scss @@ -0,0 +1,70 @@ +#content-header .ofn-drop-down { + border: none; + background-color: #5498da; + color: #fff; + float: none; + margin-left: 3px; +} + +.ofn-drop-down:hover, .ofn-drop-down.expanded { + border: 1px solid #adadad; + color: #575757; +} + +.ofn-drop-down { + padding: 7px 15px; + border-radius: 3px; + border: 1px solid #d4d4d4; + background-color: #f5f5f5; + position: relative; + display: block; + float: left; + color: #828282; + cursor: pointer; + -moz-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + text-align: center; + + &.right { + float: right; + } + + &:hover, &.expanded { + border: 1px solid #adadad; + color: #575757; + } + + > span { + width: auto; + text-transform: uppercase; + font-size: 85%; + font-weight: 600; + } + + .menu { + margin-top: 1px; + position: absolute; + float: none; + top:100%; + left: 0px; + padding: 5px 0px; + border: 1px solid #adadad; + background-color: #ffffff; + box-shadow: 1px 3px 10px #888888; + z-index: 100; + + .menu_item { + margin: 0px; + padding: 2px 0px; + color: #454545; + text-align: left; + } + + .menu_item:hover { + background-color: #ededed; + } + } +} diff --git a/app/assets/stylesheets/admin/filters_and_controls.css.scss b/app/assets/stylesheets/admin/filters_and_controls.css.scss new file mode 100644 index 0000000000..8dd188d9cd --- /dev/null +++ b/app/assets/stylesheets/admin/filters_and_controls.css.scss @@ -0,0 +1,3 @@ +.filters, .controls, .divider { + margin-bottom: 15px; +} diff --git a/app/assets/stylesheets/admin/openfoodnetwork.css.scss b/app/assets/stylesheets/admin/openfoodnetwork.css.scss index d5a53e11c4..e0916b4e79 100644 --- a/app/assets/stylesheets/admin/openfoodnetwork.css.scss +++ b/app/assets/stylesheets/admin/openfoodnetwork.css.scss @@ -184,68 +184,6 @@ table#listing_enterprise_groups { } } -#content-header .ofn_drop_down { - border: none; - background-color: #5498da; - color: #fff; - float: none; - margin-left: 3px; -} - -.ofn_drop_down { - padding: 6px 15px; - border-radius: 3px; - border: 1px solid #d4d4d4; - background-color: #f5f5f5; - position: relative; - display: block; - float: left; - color: #828282; - cursor: pointer; - -moz-user-select: none; - -khtml-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; - text-align: center; - - > span { - width: auto; - text-transform: uppercase; - font-size: 85%; - font-weight: 600; - } - - .menu { - margin-top: 1px; - position: absolute; - float: none; - top:100%; - left: 0px; - padding: 5px 0px; - border: 1px solid #adadad; - background-color: #ffffff; - box-shadow: 1px 3px 10px #888888; - z-index: 100; - - .menu_item { - margin: 0px; - padding: 2px 0px; - color: #454545; - text-align: left; - } - - .menu_item:hover { - background-color: #ededed; - } - } -} - -.ofn_drop_down:hover, .ofn_drop_down.expanded { - border: 1px solid #adadad; - color: #575757; -} - .field_with_errors > input { border-color: red; } diff --git a/app/assets/stylesheets/admin/orders.css.scss b/app/assets/stylesheets/admin/orders.css.scss index 4676d93b1a..544abfa899 100644 --- a/app/assets/stylesheets/admin/orders.css.scss +++ b/app/assets/stylesheets/admin/orders.css.scss @@ -1,7 +1,3 @@ -.filter_select, .date_filter { - margin-bottom: 10px; -} - input, div { &.update-pending { border: solid 1px orange; diff --git a/app/assets/stylesheets/darkswarm/shopping-cart.css.sass b/app/assets/stylesheets/darkswarm/shopping-cart.css.sass index 74cec02ce0..6fd48e0970 100644 --- a/app/assets/stylesheets/darkswarm/shopping-cart.css.sass +++ b/app/assets/stylesheets/darkswarm/shopping-cart.css.sass @@ -13,6 +13,11 @@ right: 10px top: 55px width: 480px + + @media screen and (min-width: 641px) + overflow-y: auto + max-height: calc(95vh - 55px) + @media screen and (max-width: 640px) width: 96% @@ -48,7 +53,7 @@ .cart-item-delete a.delete font-size: 1.125em - + .item-thumb-image display: none @media screen and (min-width: 640px) diff --git a/app/controllers/admin/enterprise_fees_controller.rb b/app/controllers/admin/enterprise_fees_controller.rb index b8ea46689f..866c05ea54 100644 --- a/app/controllers/admin/enterprise_fees_controller.rb +++ b/app/controllers/admin/enterprise_fees_controller.rb @@ -89,7 +89,7 @@ module Admin end def collection_actions - [:index, :for_order_cycle] + [:index, :for_order_cycle, :bulk_update] end def current_enterprise diff --git a/app/controllers/admin/order_cycles_controller.rb b/app/controllers/admin/order_cycles_controller.rb index 3880858e5b..300a2ca24e 100644 --- a/app/controllers/admin/order_cycles_controller.rb +++ b/app/controllers/admin/order_cycles_controller.rb @@ -175,5 +175,9 @@ module Admin def ams_prefix_whitelist [:basic] end + + def collection_actions + [:index, :bulk_update] + end end end diff --git a/app/controllers/admin/variant_overrides_controller.rb b/app/controllers/admin/variant_overrides_controller.rb index 9425565f6e..c886f80652 100644 --- a/app/controllers/admin/variant_overrides_controller.rb +++ b/app/controllers/admin/variant_overrides_controller.rb @@ -4,32 +4,43 @@ module Admin class VariantOverridesController < ResourceController include OpenFoodNetwork::SpreeApiKeyLoader + prepend_before_filter :load_data + before_filter :load_collection, only: [:bulk_update] before_filter :load_spree_api_key, only: :index - before_filter :load_data + def index end def bulk_update - collection_hash = Hash[params[:variant_overrides].each_with_index.map { |vo, i| [i, vo] }] - vo_set = VariantOverrideSet.new @variant_overrides, collection_attributes: collection_hash - # Ensure we're authorised to update all variant overrides - vo_set.collection.each { |vo| authorize! :update, vo } + @vo_set.collection.each { |vo| authorize! :update, vo } - if vo_set.save + if @vo_set.save # Return saved VOs with IDs - render json: vo_set.collection, each_serializer: Api::Admin::VariantOverrideSerializer + render json: @vo_set.collection, each_serializer: Api::Admin::VariantOverrideSerializer else - if vo_set.errors.present? - render json: { errors: vo_set.errors }, status: 400 + if @vo_set.errors.present? + render json: { errors: @vo_set.errors }, status: 400 else render nothing: true, status: 500 end end end + def bulk_reset + # Ensure we're authorised to update all variant overrides. + @collection.each { |vo| authorize! :bulk_reset, vo } + @collection.each(&:reset_stock!) + + if collection_errors.present? + render json: { errors: collection_errors }, status: 400 + else + render json: @collection, each_serializer: Api::Admin::VariantOverrideSerializer + end + end + private @@ -43,10 +54,28 @@ module Admin @hub_permissions = OpenFoodNetwork::Permissions.new(spree_current_user). variant_override_enterprises_per_hub - @variant_overrides = VariantOverride.for_hubs(@hubs) + end + + def load_collection + collection_hash = Hash[params[:variant_overrides].each_with_index.map { |vo, i| [i, vo] }] + @vo_set = VariantOverrideSet.new @variant_overrides, collection_attributes: collection_hash end def collection + @variant_overrides = VariantOverride.for_hubs(params[:hub_id] || @hubs) + end + + def collection_actions + [:index, :bulk_update, :bulk_reset] + end + + # This has been pulled from ModelSet as it is useful for compiling a list of errors on any generic collection (not necessarily a ModelSet) + # Could be pulled down into a lower level controller if it is useful in other high level controllers + def collection_errors + errors = ActiveModel::Errors.new self + full_messages = @collection.map { |element| element.errors.full_messages }.flatten + full_messages.each { |fm| errors.add(:base, fm) } + errors end end end diff --git a/app/helpers/admin/injection_helper.rb b/app/helpers/admin/injection_helper.rb index 6036447d9b..343e15ef29 100644 --- a/app/helpers/admin/injection_helper.rb +++ b/app/helpers/admin/injection_helper.rb @@ -31,12 +31,12 @@ module Admin admin_inject_json_ams_array ngModule, "shops", @shops, Api::Admin::IdNameSerializer end - def admin_inject_hubs - admin_inject_json_ams_array "ofn.admin", "hubs", @hubs, Api::Admin::IdNameSerializer + def admin_inject_hubs(opts={module: 'ofn.admin'}) + admin_inject_json_ams_array opts[:module], "hubs", @hubs, Api::Admin::IdNameSerializer end - def admin_inject_producers - admin_inject_json_ams_array "ofn.admin", "producers", @producers, Api::Admin::IdNameSerializer + def admin_inject_producers(opts={module: 'ofn.admin'}) + admin_inject_json_ams_array opts[:module], "producers", @producers, Api::Admin::IdNameSerializer end def admin_inject_enterprise_permissions @@ -49,7 +49,7 @@ module Admin end def admin_inject_hub_permissions - render partial: "admin/json/injection_ams", locals: {ngModule: "ofn.admin", name: "hubPermissions", json: @hub_permissions.to_json} + render partial: "admin/json/injection_ams", locals: {ngModule: "admin.variantOverrides", name: "hubPermissions", json: @hub_permissions.to_json} end def admin_inject_products @@ -69,7 +69,7 @@ module Admin end def admin_inject_variant_overrides - admin_inject_json_ams_array "ofn.admin", "variantOverrides", @variant_overrides, Api::Admin::VariantOverrideSerializer + admin_inject_json_ams_array "admin.variantOverrides", "variantOverrides", @variant_overrides, Api::Admin::VariantOverrideSerializer end def admin_inject_order_cycle_instance @@ -85,7 +85,7 @@ module Admin end def admin_inject_spree_api_key - render partial: "admin/json/injection_ams", locals: {ngModule: 'ofn.admin', name: 'SpreeApiKey', json: "'#{@spree_api_key.to_s}'"} + render partial: "admin/json/injection_ams", locals: {ngModule: 'admin.indexUtils', name: 'SpreeApiKey', json: "'#{@spree_api_key.to_s}'"} end def admin_inject_json_ams(ngModule, name, data, serializer, opts = {}) diff --git a/app/mailers/spree/base_mailer_decorator.rb b/app/mailers/spree/base_mailer_decorator.rb index 4f78c1fe1e..339abd8901 100644 --- a/app/mailers/spree/base_mailer_decorator.rb +++ b/app/mailers/spree/base_mailer_decorator.rb @@ -10,4 +10,4 @@ Spree::BaseMailer.class_eval do # This lets us specify assets using relative paths in email templates super.merge(url_options: {host: URI(spree.root_url).host }) end -end \ No newline at end of file +end diff --git a/app/models/model_set.rb b/app/models/model_set.rb index c9e1456f1a..0c0d156179 100644 --- a/app/models/model_set.rb +++ b/app/models/model_set.rb @@ -16,8 +16,8 @@ class ModelSet end end - def collection_attributes=(attributes) - attributes.each do |k, attributes| + def collection_attributes=(collection_attributes) + collection_attributes.each do |k, attributes| # attributes == {:id => 123, :next_collection_at => '...'} e = @collection.detect { |e| e.id.to_s == attributes[:id].to_s && !e.id.nil? } if e.nil? @@ -41,7 +41,11 @@ class ModelSet end def collection_to_delete - collection.select { |e| @delete_if.andand.call(e.attributes) } + # Remove all elements to be deleted from collection and return them + # Allows us to render @model_set.collection without deleted elements + deleted = [] + collection.delete_if { |e| deleted << e if @delete_if.andand.call(e.attributes) } + deleted end def collection_to_keep @@ -51,5 +55,4 @@ class ModelSet def persisted? false end - end diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 25e0ced2cc..9bc303116b 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -66,7 +66,7 @@ class AbilityDecorator def add_enterprise_management_abilities(user) # Spree performs authorize! on (:create, nil) when creating a new order from admin, and also (:search, nil) # when searching for variants to add to the order - can [:create, :search, :bulk_update], nil + can [:create, :search], nil can [:admin, :index], :overview @@ -111,7 +111,9 @@ class AbilityDecorator OpenFoodNetwork::Permissions.new(user).managed_product_enterprises.include? variant.product.supplier end - can [:admin, :index, :read, :update, :bulk_update], VariantOverride do |vo| + can [:admin, :index, :read, :update, :bulk_update, :bulk_reset], VariantOverride do |vo| + next false unless vo.hub.present? && vo.variant.andand.product.andand.supplier.present? + hub_auth = OpenFoodNetwork::Permissions.new(user). variant_override_hubs. include? vo.hub diff --git a/app/models/variant_override.rb b/app/models/variant_override.rb index 55afd99321..21820ce0db 100644 --- a/app/models/variant_override.rb +++ b/app/models/variant_override.rb @@ -3,6 +3,8 @@ class VariantOverride < ActiveRecord::Base belongs_to :variant, class_name: 'Spree::Variant' validates_presence_of :hub_id, :variant_id + # Default stock can be nil, indicating stock should not be reset or zero, meaning reset to zero. Need to ensure this can be set by the user. + validates :default_stock, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true scope :for_hubs, lambda { |hubs| where(hub_id: hubs) @@ -49,6 +51,21 @@ class VariantOverride < ActiveRecord::Base end end + def default_stock? + default_stock.present? + end + + def reset_stock! + if resettable + if default_stock? + self.attributes = { count_on_hand: default_stock } + self.save + else + Bugsnag.notify RuntimeError.new "Attempting to reset stock level for a variant with no default stock level." + end + end + self + end private diff --git a/app/models/variant_override_set.rb b/app/models/variant_override_set.rb index 985190095b..730246cebe 100644 --- a/app/models/variant_override_set.rb +++ b/app/models/variant_override_set.rb @@ -1,6 +1,16 @@ class VariantOverrideSet < ModelSet def initialize(collection, attributes={}) - super(VariantOverride, collection, attributes, nil, - proc { |attrs| attrs['price'].blank? && attrs['count_on_hand'].blank? } ) + super(VariantOverride, collection, attributes, nil, proc { |attrs| deletable?(attrs) } ) + end + + private + + def deletable?(attrs) + attrs['price'].blank? && + attrs['count_on_hand'].blank? && + attrs['default_stock'].blank? && + attrs['resettable'].blank? && + attrs['sku'].nil? && + attrs['on_demand'].nil? end end diff --git a/app/serializers/api/admin/variant_override_serializer.rb b/app/serializers/api/admin/variant_override_serializer.rb index ebe76a1049..c1e6e0038c 100644 --- a/app/serializers/api/admin/variant_override_serializer.rb +++ b/app/serializers/api/admin/variant_override_serializer.rb @@ -1,3 +1,3 @@ class Api::Admin::VariantOverrideSerializer < ActiveModel::Serializer - attributes :id, :hub_id, :variant_id, :price, :count_on_hand + attributes :id, :hub_id, :variant_id, :sku, :price, :count_on_hand, :on_demand, :default_stock, :resettable end diff --git a/app/serializers/api/admin/variant_serializer.rb b/app/serializers/api/admin/variant_serializer.rb index 510f7af333..66acfe8ece 100644 --- a/app/serializers/api/admin/variant_serializer.rb +++ b/app/serializers/api/admin/variant_serializer.rb @@ -1,5 +1,5 @@ class Api::Admin::VariantSerializer < ActiveModel::Serializer - attributes :id, :options_text, :unit_value, :unit_description, :unit_to_display, :on_demand, :display_as, :display_name, :name_to_display + attributes :id, :options_text, :unit_value, :unit_description, :unit_to_display, :on_demand, :display_as, :display_name, :name_to_display, :sku attributes :on_hand, :price has_many :variant_overrides diff --git a/app/serializers/api/line_item_serializer.rb b/app/serializers/api/line_item_serializer.rb index d791febdfc..35d3f9c540 100644 --- a/app/serializers/api/line_item_serializer.rb +++ b/app/serializers/api/line_item_serializer.rb @@ -1,5 +1,5 @@ class Api::LineItemSerializer < ActiveModel::Serializer - attributes :id, :quantity, :price + attributes :id, :quantity, :max_quantity, :price has_one :variant, serializer: Api::VariantSerializer end diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index 66790a34df..5647821bf5 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -12,25 +12,13 @@ .seven.columns.omega   .row{ 'ng-hide' => '!loaded() || filteredCustomers.length == 0' } - .controls{ :class => "sixteen columns alpha", :style => "margin-bottom: 15px;" } + .controls.sixteen.columns.alpha.omega .five.columns.alpha - %input{ :class => "fullwidth", :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' } + %input.fullwidth{ :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' } .five.columns   - -# %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "bulk_actions_dropdown", 'ofn-drop-down' => true } - -# %span{ :class => 'icon-check' }   Actions - -# %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } - -# %div.menu{ 'ng-show' => "expanded" } - -# %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "action in bulkActions", 'ng-click' => "selectedBulkAction.callback(filteredCustomers)", 'ofn-close-on-click' => true } - -# %span{ :class => 'three columns omega' } {{action.name }} + -# =render 'admin/shared/bulk_actions_dropdown' .three.columns   - .three.columns.omega - %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "columns_dropdown", 'ofn-drop-down' => true, :style => 'float:right;' } - %span{ :class => 'icon-reorder' }   Columns - %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } - %div.menu{ 'ng-show' => "expanded" } - %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true } - %span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "✓" || !column.visible && " " }} - %span{ :class => 'two columns omega' } {{column.name }} + = render 'admin/shared/columns_dropdown' .row{ 'ng-if' => 'shop && !loaded()' } .sixteen.columns.alpha#loading %img.spinner{ src: "/assets/spinning-circles.svg" } diff --git a/app/views/admin/enterprises/_enterprise_user_index.html.haml b/app/views/admin/enterprises/_enterprise_user_index.html.haml index 666ea4604d..1fb35e595c 100644 --- a/app/views/admin/enterprises/_enterprise_user_index.html.haml +++ b/app/views/admin/enterprises/_enterprise_user_index.html.haml @@ -4,21 +4,9 @@ .four.columns.alpha %input{ :class => "fullwidth", :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Search By Name' } .six.columns   - -# %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "bulk_actions_dropdown", 'ofn-drop-down' => true } - -# %span{ :class => 'icon-check' }   Actions - -# %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } - -# %div.menu{ 'ng-show' => "expanded" } - -# %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "action in bulkActions", 'ng-click' => "selectedBulkAction.callback(filteredEnterprises)", 'ofn-close-on-click' => true } - -# %span{ :class => 'three columns omega' } {{action.name }} + -# = render 'admin/shared/bulk_actions_dropdown' .three.columns   - .three.columns.omega - %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "columns_dropdown", 'ofn-drop-down' => true, :style => 'float:right;' } - %span{ :class => 'icon-reorder' }   Columns - %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } - %div.menu{ 'ng-show' => "expanded" } - %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true } - %span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "✓" || !column.visible && " " }} - %span{ :class => 'two columns omega' } {{column.name }} + = render 'admin/shared/columns_dropdown' .row{ 'ng-if' => '!loaded' } .sixteen.columns.alpha#loading %img.spinner{ src: "/assets/spinning-circles.svg" } diff --git a/app/views/admin/shared/_bulk_actions_dropdown.html.haml b/app/views/admin/shared/_bulk_actions_dropdown.html.haml new file mode 100644 index 0000000000..912fe6662a --- /dev/null +++ b/app/views/admin/shared/_bulk_actions_dropdown.html.haml @@ -0,0 +1,7 @@ +.three.columns + .ofn-drop-down#bulk-actions-dropdown{ 'ng-controller' => "DropDownCtrl" } + %span.icon-check   Actions + %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } + %div.menu{ 'ng-show' => "expanded" } + .three.columns.alpha.menu_item{ 'ng-repeat' => "action in bulkActions", 'ng-click' => "$eval(action.callback)(filteredLineItems)", 'ofn-close-on-click' => true } + %span.three.columns.omega {{action.name }} diff --git a/app/views/admin/shared/_columns_dropdown.html.haml b/app/views/admin/shared/_columns_dropdown.html.haml new file mode 100644 index 0000000000..b16d388c1d --- /dev/null +++ b/app/views/admin/shared/_columns_dropdown.html.haml @@ -0,0 +1,8 @@ +%div.three.columns.omega + %div.ofn-drop-down.right#columns-dropdown{ 'ng-controller' => "DropDownCtrl" } + %span{ :class => 'icon-reorder' }   Columns + %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } + %div.menu{ 'ng-show' => "expanded" } + %div.menu_item.three.columns.alpha.omega{ 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true } + %span.one.column.alpha.text-center {{ column.visible && "✓" || !column.visible && " " }} + %span.two.columns.omega {{column.name }} diff --git a/app/views/admin/variant_overrides/_actions.html.haml b/app/views/admin/variant_overrides/_actions.html.haml deleted file mode 100644 index 0ae6f8b96b..0000000000 --- a/app/views/admin/variant_overrides/_actions.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -.row - %input.four.columns.alpha{type: 'button', value: 'Save Changes', 'ng-click' => 'update()'} - .twelve.columns.omega - = render 'spree/admin/shared/status_message' diff --git a/app/views/admin/variant_overrides/_data.html.haml b/app/views/admin/variant_overrides/_data.html.haml index 3b5f7f125c..64a7619ea7 100644 --- a/app/views/admin/variant_overrides/_data.html.haml +++ b/app/views/admin/variant_overrides/_data.html.haml @@ -1,5 +1,5 @@ = admin_inject_spree_api_key -= admin_inject_hubs += admin_inject_hubs module: 'admin.variantOverrides' = admin_inject_hub_permissions -= admin_inject_producers += admin_inject_producers module: 'admin.variantOverrides' = admin_inject_variant_overrides diff --git a/app/views/admin/variant_overrides/_filters.html.haml b/app/views/admin/variant_overrides/_filters.html.haml new file mode 100644 index 0000000000..9dc90d7ad3 --- /dev/null +++ b/app/views/admin/variant_overrides/_filters.html.haml @@ -0,0 +1,26 @@ +.filters.sixteen.columns.alpha + .filter.four.columns.alpha + %label{ :for => 'query', ng: {class: '{disabled: !hub.id}'} }Quick Search + %br + %input.fullwidth{ :type => "text", :id => 'query', ng: { model: 'query', disabled: '!hub.id'} } + .two.columns   + .filter_select.four.columns + %label{ :for => 'hub_id', ng: { bind: 'hub_id ? "Shop" : "Select a shop"' } } + %br + %select.select2.fullwidth#hub_id{ 'ng-model' => 'hub_id', name: 'hub_id', ng: { options: 'hub.id as hub.name for (id, hub) in hubs', change: 'selectHub()' } } + .filter_select.four.columns + %label{ :for => 'producer_filter', ng: {class: '{disabled: !hub.id}'} }Producer + %br + %input.ofn-select2.fullwidth{ :id => 'producer_filter', type: 'number', style: 'display:none', data: 'producers', blank: "{id: 0, name: 'All'}", ng: { model: 'producerFilter', disabled: '!hub.id' } } + -# .filter_select{ :class => "three columns" } + -# %label{ :for => 'distributor_filter' }Hub + -# %br + -# %select{ :class => "three columns alpha", :id => 'distributor_filter', 'select2-min-search' => 5, 'ng-model' => 'distributorFilter', 'ng-options' => 'd.id as d.name for d in distributors'} + -# .filter_select{ :class => "three columns" } + -# %label{ :for => 'order_cycle_filter' }Order Cycle + -# %br + -# %select{ :class => "three columns alpha", :id => 'order_cycle_filter', 'select2-min-search' => 5, 'ng-model' => 'orderCycleFilter', 'ng-options' => 'oc.id as oc.name for oc in orderCycles', 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()'} + .filter_clear.two.columns.omega + %label{ :for => 'clear_all_filters' } + %br + %input.red.fullwidth{ :type => 'button', :id => 'clear_all_filters', :value => "Clear All", ng: { click: "resetSelectFilters()", disabled: '!hub.id'} } diff --git a/app/views/admin/variant_overrides/_hub_choice.html.haml b/app/views/admin/variant_overrides/_hub_choice.html.haml deleted file mode 100644 index aa0f7ab738..0000000000 --- a/app/views/admin/variant_overrides/_hub_choice.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -.row - .two.columns.alpha - Hub - .four.columns - %select.select2.fullwidth#hub_id{ 'ng-model' => 'hub_id', name: 'hub_id', 'ng-options' => 'hub.id as hub.name for hub in hubs' } - .ten.columns.omega - %input{ type: 'button', value: 'Go', 'ng-click' => 'selectHub()' } diff --git a/app/views/admin/variant_overrides/_products.html.haml b/app/views/admin/variant_overrides/_products.html.haml index cf11e8ac5d..ed3354de9a 100644 --- a/app/views/admin/variant_overrides/_products.html.haml +++ b/app/views/admin/variant_overrides/_products.html.haml @@ -1,10 +1,23 @@ -%table.index.bulk{ng: {show: 'hub'}} +%table.index.bulk{ ng: {show: 'hub'}} + %col.producer{ width: "20%", ng: { show: 'columns.producer.visible' } } + %col.product{ width: "20%", ng: { show: 'columns.product.visible' } } + %col.sku{ width: "20%", ng: { show: 'columns.sku.visible' } } + %col.price{ width: "10%", ng: { show: 'columns.price.visible' } } + %col.on_hand{ width: "10%", ng: { show: 'columns.on_hand.visible' } } + %col.on_demand{ width: "10%", ng: { show: 'columns.on_demand.visible' } } + %col.reset{ width: "1%", ng: { show: 'columns.reset.visible' } } + %col.reset{ width: "15%", ng: { show: 'columns.reset.visible' } } + %col.inheritance{ width: "5%", ng: { show: 'columns.inheritance.visible' } } %thead - %tr - %th Producer - %th Product - %th Price - %th On hand - %tbody{ng: {repeat: 'product in products | hubPermissions:hubPermissions:hub.id'}} + %tr{ ng: { controller: "ColumnsCtrl" } } + %th.producer{ ng: { show: 'columns.producer.visible' } } Producer + %th.product{ ng: { show: 'columns.product.visible' } } Product + %th.sku{ ng: { show: 'columns.sku.visible' } } SKU + %th.price{ ng: { show: 'columns.price.visible' } } Price + %th.on_hand{ ng: { show: 'columns.on_hand.visible' } } On hand + %th.on_demand{ ng: { show: 'columns.on_demand.visible' } } On Demand? + %th.reset{ colspan: 2, ng: { show: 'columns.reset.visible' } } Enable Stock Level Reset? + %th.inheritance{ ng: { show: 'columns.inheritance.visible' } } Inherit? + %tbody{bindonce: true, ng: {repeat: 'product in products | hubPermissions:hubPermissions:hub.id | attrFilter:{producer_id:producerFilter} | filter:query' } } = render 'admin/variant_overrides/products_product' = render 'admin/variant_overrides/products_variants' diff --git a/app/views/admin/variant_overrides/_products_product.html.haml b/app/views/admin/variant_overrides/_products_product.html.haml index 52551e8e0b..b7cb11041b 100644 --- a/app/views/admin/variant_overrides/_products_product.html.haml +++ b/app/views/admin/variant_overrides/_products_product.html.haml @@ -1,5 +1,9 @@ %tr.product.even - %td {{ producers[product.producer_id].name }} - %td {{ product.name }} - %td - %td + %td.producer{ ng: { show: 'columns.producer.visible' }, bo: { bind: 'producersByID[product.producer_id].name'} } + %td.product{ ng: { show: 'columns.product.visible' }, bo: { bind: 'product.name'} } + %td.sku{ ng: { show: 'columns.sku.visible' } } + %td.price{ ng: { show: 'columns.price.visible' } } + %td.on_hand{ ng: { show: 'columns.on_hand.visible' } } + %td.on_demand{ ng: { show: 'columns.on_demand.visible' } } + %td.reset{ colspan: 2, ng: { show: 'columns.reset.visible' } } + %td.inheritance{ ng: { show: 'columns.inheritance.visible' } } diff --git a/app/views/admin/variant_overrides/_products_variants.html.haml b/app/views/admin/variant_overrides/_products_variants.html.haml index bd48ea343c..87ec1709e4 100644 --- a/app/views/admin/variant_overrides/_products_variants.html.haml +++ b/app/views/admin/variant_overrides/_products_variants.html.haml @@ -1,10 +1,19 @@ -%tr.variant{ng: {repeat: 'variant in product.variants'}} - %td - %td - {{ variant.display_name }} - .variant-override-unit {{ variant.unit_to_display }} - %td +%tr.variant{ id: "v_{{variant.id}}", ng: {repeat: 'variant in product.variants'}} + %td.producer{ ng: { show: 'columns.producer.visible' } } + %td.product{ ng: { show: 'columns.product.visible' } } + %span{ bo: { bind: 'variant.display_name || ""'} } + .variant-override-unit{ bo: { bind: 'variant.unit_to_display'} } + %td.sku{ ng: { show: 'columns.sku.visible' } } + %input{name: 'variant-overrides-{{ variant.id }}-sku', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].sku'}, placeholder: '{{ variant.sku }}', 'ofn-track-variant-override' => 'sku'} + %td.price{ ng: { show: 'columns.price.visible' } } %input{name: 'variant-overrides-{{ variant.id }}-price', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].price'}, placeholder: '{{ variant.price }}', 'ofn-track-variant-override' => 'price'} - - %td - %input{name: 'variant-overrides-{{ variant.id }}-count-on-hand', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].count_on_hand'}, placeholder: '{{ variant.on_hand }}', 'ofn-track-variant-override' => 'price'} + %td.on_hand{ ng: { show: 'columns.on_hand.visible' } } + %input{name: 'variant-overrides-{{ variant.id }}-count_on_hand', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].count_on_hand'}, placeholder: '{{ variant.on_hand }}', 'ofn-track-variant-override' => 'count_on_hand'} + %td.on_demand{ ng: { show: 'columns.on_demand.visible' } } + %input.field{ :type => 'checkbox', name: 'variant-overrides-{{ variant.id }}-on_demand', ng: { model: 'variantOverrides[hub.id][variant.id].on_demand' }, 'ofn-track-variant-override' => 'on_demand' } + %td.reset{ ng: { show: 'columns.reset.visible' } } + %input{name: 'variant-overrides-{{ variant.id }}-resettable', type: 'checkbox', ng: {model: 'variantOverrides[hub.id][variant.id].resettable'}, placeholder: '{{ variant.resettable }}', 'ofn-track-variant-override' => 'resettable'} + %td.reset{ ng: { show: 'columns.reset.visible' } } + %input{name: 'variant-overrides-{{ variant.id }}-default_stock', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].default_stock'}, placeholder: '{{ variant.default_stock ? variant.default_stock : "Default stock"}}', 'ofn-track-variant-override' => 'default_stock'} + %td.inheritance{ ng: { show: 'columns.inheritance.visible' } } + %input.field{ :type => 'checkbox', name: 'variant-overrides-{{ variant.id }}-inherit', ng: { model: 'inherit' }, 'track-inheritance' => true } diff --git a/app/views/admin/variant_overrides/index.html.haml b/app/views/admin/variant_overrides/index.html.haml index 8d7fc4e0b1..9027445e62 100644 --- a/app/views/admin/variant_overrides/index.html.haml +++ b/app/views/admin/variant_overrides/index.html.haml @@ -1,11 +1,14 @@ = render 'admin/variant_overrides/header' = render 'admin/variant_overrides/data' -%div{ ng: { app: 'ofn.admin', controller: 'AdminVariantOverridesCtrl', init: 'initialise()' } } - = render 'admin/variant_overrides/hub_choice' +%div{ ng: { app: 'admin.variantOverrides', controller: 'AdminVariantOverridesCtrl', init: 'initialise()' } } + = render 'admin/variant_overrides/filters' + %hr.divider.sixteen.columns.alpha.omega{ ng: { show: 'hub' } } + .controls.sixteen.columns.alpha.omega{ ng: { show: 'hub' } } + %input.four.columns.alpha{ type: 'button', value: 'Reset Stock to Defaults', 'ng-click' => 'resetStock()' } + %div.nine.columns.alpha   + = render 'admin/shared/columns_dropdown' - %div{ng: {show: 'hub'}} - %h2 {{ hub.name }} - = render 'admin/variant_overrides/actions' - - = render 'admin/variant_overrides/products' + %form{ name: 'variant_overrides_form' } + %save-bar{ save: "update()", form: "variant_overrides_form" } + = render 'admin/variant_overrides/products' diff --git a/app/views/enterprises/shop.html.haml b/app/views/enterprises/shop.html.haml index 35937629fc..f2063494b4 100644 --- a/app/views/enterprises/shop.html.haml +++ b/app/views/enterprises/shop.html.haml @@ -1,5 +1,9 @@ - content_for(:title) do = current_distributor.name +- content_for(:description) do + = current_distributor.description +- content_for(:image) do + = current_distributor.logo.url = inject_enterprises diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index b4e6f7586c..4831a911a6 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,5 +1,9 @@ - content_for(:title) do = @group.name +- content_for(:description) do + = @group.description +- content_for(:image) do + = @group.logo.url -# inject all enterprises as "enterprises" -# it could be more efficient to inject only the enterprises that are related to the group diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index a933858049..0788286394 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -2,7 +2,9 @@ %head %meta{charset: 'utf-8'}/ %meta{name: 'viewport', content: "width=device-width,initial-scale=1.0"}/ - + %meta{property: "og:title", content: content_for?(:title) ? yield(:title) : t(:title)} + %meta{property: "og:description", content: content_for?(:description) ? yield(:description) : t(:description)} + %meta{property: "og:image", content: content_for?(:image) ? yield(:image) : ContentConfig.logo.url} %title= content_for?(:title) ? "#{yield(:title)} - #{t(:title)}".html_safe : "#{t(:welcome_to)} #{t(:title)}" - if Rails.env.production? = favicon_link_tag diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index dbcf9c97fd..0ab782023d 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -5,7 +5,7 @@ = render :partial => 'spree/admin/shared/order_sub_menu' %div{ ng: { app: 'admin.lineItems', controller: 'LineItemsCtrl' } } - %save-bar{ save: "submit()", saving: 'saving', dirty: "bulk_order_form.$dirty" } + %save-bar{ save: "submit()", form: "bulk_order_form" } .filters{ :class => "sixteen columns alpha" } .date_filter{ :class => "two columns alpha" } %label{ :for => 'start_date_filter' }Start Date @@ -32,8 +32,10 @@ %label{ :for => 'clear_all_filters' } %br %input.red.fullwidth{ :type => 'button', :id => 'clear_all_filters', :value => "Clear All", 'ng-click' => "resetSelectFilters()" } - %hr{ :class => "sixteen columns alpha", 'ng-show' => 'unitsVariantSelected()' } - %div#group_buy_calculation{ :class => "sixteen columns alpha", 'ng-show' => 'unitsVariantSelected()' } + + %hr.divider.sixteen.columns.alpha.omega{ ng: { show: 'unitsVariantSelected()' } } + + %div.sixteen.columns.alpha.omega#group_buy_calculation{ ng: { show: 'unitsVariantSelected()' } } %div.shared_resource{ :class => "four columns alpha" } %span{ :class => 'three columns alpha' } %input{ type: 'checkbox', :id => 'shared_resource', 'ng-model' => 'sharedResource'} @@ -70,32 +72,23 @@ %div{ :class => "eight columns alpha", 'ng-hide' => 'allFinalWeightVolumesPresent()' } %span{ :class => "eight columns alpha", style: 'color:red' } WARNING: Some variants do not have a unit value - %hr{ :class => "sixteen columns alpha", :style => "margin-bottom: 15px" } - %div{ 'ng-hide' => 'RequestMonitor.loading || lineItems.length == 0' } - .controls{ :class => "sixteen columns alpha", :style => "margin-bottom: 15px;" } - %div{ :class => "three columns alpha" } - %input{ :class => "fullwidth", :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' } - %div{ :class => "three columns" } - %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "bulk_actions_dropdown", 'ofn-drop-down' => true } - %span{ :class => 'icon-check' }   Actions - %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } - %div.menu{ 'ng-show' => "expanded" } - %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "action in bulkActions", 'ng-click' => "$eval(action.callback)(filteredLineItems)", 'ofn-close-on-click' => true } - %span{ :class => 'three columns omega' } {{action.name }} - %div{ :class => "seven columns" }   - %div{ :class => "three columns omega" } - %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "columns_dropdown", 'ofn-drop-down' => true, :style => 'float:right;' } - %span{ :class => 'icon-reorder' }   Columns - %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } - %div.menu{ 'ng-show' => "expanded" } - %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true } - %span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "✓" || !column.visible && " " }} - %span{ :class => 'two columns omega' } {{column.name }} + + %hr.divider.sixteen.columns.alpha.omega + + .controls.sixteen.columns.alpha.omega{ ng: { hide: 'RequestMonitor.loading || lineItems.length == 0' } } + %div.three.columns.alpha + %input.fullwidth{ :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' } + = render 'admin/shared/bulk_actions_dropdown' + %div.seven.columns   + = render 'admin/shared/columns_dropdown' + %div.sixteen.columns.alpha#loading{ 'ng-if' => 'RequestMonitor.loading' } %img.spinner{ src: "/assets/spinning-circles.svg" } %h1 LOADING ORDERS + %div{ :class => "sixteen columns alpha", 'ng-show' => '!RequestMonitor.loading && filteredLineItems.length == 0'} %h1#no_results No orders found. + %div{ 'ng-hide' => 'RequestMonitor.loading || filteredLineItems.length == 0' } %form{ name: 'bulk_order_form' } %table.index#listing_orders.bulk{ :class => "sixteen columns alpha" } @@ -129,6 +122,7 @@ %th.actions Ask?  %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } + %tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" } %td.bulk %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked', 'ignore-dirty' => true } diff --git a/app/views/spree/admin/products/bulk_edit.html.haml b/app/views/spree/admin/products/bulk_edit.html.haml index 22b0a195da..235544cb8a 100644 --- a/app/views/spree/admin/products/bulk_edit.html.haml +++ b/app/views/spree/admin/products/bulk_edit.html.haml @@ -4,7 +4,7 @@ %div{ ng: { app: 'ofn.admin', controller: 'AdminProductEditCtrl', init: 'initialise()' } } = render 'spree/admin/products/bulk_edit/filters' - %hr.sixteen.columns.alpha + %hr.divider.sixteen.columns.alpha.omega = render 'spree/admin/products/bulk_edit/actions' = render 'spree/admin/products/bulk_edit/indicators' = render 'spree/admin/products/bulk_edit/products' diff --git a/app/views/spree/admin/products/bulk_edit/_actions.html.haml b/app/views/spree/admin/products/bulk_edit/_actions.html.haml index 337f1d469b..7ea29bdb4c 100644 --- a/app/views/spree/admin/products/bulk_edit/_actions.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_actions.html.haml @@ -1,13 +1,6 @@ -%div.sixteen.columns.alpha{ 'ng-hide' => 'loading || products.length == 0', style: "margin-bottom: 10px" } - %div.four.columns.alpha +.controls.sixteen.columns.alpha{ 'ng-hide' => 'loading || products.length == 0' } + .four.columns.alpha %input.four.columns.alpha{ :type => 'button', :value => 'Save Changes', 'ng-click' => 'submitProducts()'} - %div.nine.columns + .nine.columns = render 'spree/admin/shared/status_message' - %div.three.columns.omega - %div.ofn_drop_down.three.columns.omega{ 'ng-controller' => "DropDownCtrl", :id => "columns_dropdown", 'ofn-drop-down' => true, :style => 'float:right;' } - %span{ :class => 'icon-reorder' }   Columns - %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } - %div.menu{ 'ng-show' => "expanded" } - %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true } - %span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "✓" || !column.visible && " " }} - %span{ :class => 'two columns omega' } {{column.name }} + = render 'admin/shared/columns_dropdown' diff --git a/app/views/spree/admin/products/bulk_edit/_filters.html.haml b/app/views/spree/admin/products/bulk_edit/_filters.html.haml index 99c1067663..3e676ede6d 100644 --- a/app/views/spree/admin/products/bulk_edit/_filters.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_filters.html.haml @@ -1,18 +1,18 @@ -%div.sixteen.columns.alpha - %div.quick_search{ :class => "four columns alpha" } +.filters.sixteen.columns.alpha.omega + .quick_search.four.columns.alpha %label{ :for => 'quick_filter' } %br - %input.search{ :class => "four columns alpha", 'ng-model' => 'query', :name => "quick_filter", :type => 'text', 'placeholder' => 'Quick Search' } - .filter_select{ :class => "four columns" } + %input.quick-search.fullwidth{ 'ng-model' => 'query', :name => "quick_filter", :type => 'text', 'placeholder' => 'Quick Search' } + .filter_select.four.columns %label{ :for => 'producer_filter' }Producer %br - %select{ :class => "four columns alpha", :id => 'producer_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'producerFilter', 'ng-options' => 'producer.id as producer.name for producer in filterProducers' } - .filter_select{ :class => "four columns" } + %select.fullwidth{ :id => 'producer_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'producerFilter', 'ng-options' => 'producer.id as producer.name for producer in filterProducers' } + .filter_select.four.columns %label{ :for => 'category_filter' }Category %br - %select{ :class => "four columns alpha", :id => 'category_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'categoryFilter', 'ng-options' => 'taxon.id as taxon.name for taxon in filterTaxons'} + %select.fullwidth{ :id => 'category_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'categoryFilter', 'ng-options' => 'taxon.id as taxon.name for taxon in filterTaxons'} %div{ :class => "one column" }   - .filter_clear{ :class => "three columns omega" } + .filter_clear.three.columns.omega %label{ :for => 'clear_all_filters' } %br %input.fullwidth.red{ :type => 'button', :id => 'clear_all_filters', :value => "Clear Filters", 'ng-click' => "resetSelectFilters()" } diff --git a/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml index cc85566577..ff345cb259 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml @@ -4,6 +4,7 @@ %a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "$last" } %td{ 'ng-show' => 'columns.producer.visible' } %td{ 'ng-show' => 'columns.sku.visible' } + %input{ 'ng-model' => "variant.sku", :name => 'variant_sku', 'ofn-track-variant' => 'sku', :type => 'text' } %td{ 'ng-show' => 'columns.name.visible' } %input{ 'ng-model' => 'variant.display_name', :name => 'variant_display_name', 'ofn-track-variant' => 'display_name', :type => 'text', placeholder: "{{ product.name }}" } %td.unit_value{ 'ng-show' => 'columns.unit.visible' } diff --git a/app/views/spree/shared/_order_details.html.haml b/app/views/spree/shared/_order_details.html.haml index 8be8d3cf50..d0a7e99ddf 100644 --- a/app/views/spree/shared/_order_details.html.haml +++ b/app/views/spree/shared/_order_details.html.haml @@ -140,7 +140,7 @@ %tr.total %td.text-right{colspan: "3"} %h5 - = t :order_produce + = t :order_total_price %td.text-right.total %h5#order_total= order.display_total.to_html diff --git a/config/locales/en.yml b/config/locales/en.yml index 701d758891..eb47b64224 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -29,6 +29,7 @@ en: home: "OFN" title: Open Food Network welcome_to: 'Welcome to ' + description: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can…" search_by_name: Search by name or suburb... producers: Aussie Producers producers_join: Australian producers are now welcome to join the Open Food Network. @@ -52,6 +53,8 @@ en: free: "free" plus_tax: "plus GST" total_monthly_bill_incl_tax: "Total Monthly Bill (Incl. Tax)" + say_no: "No" + say_yes: "Yes" sort_order_cycles_on_shopfront_by: "Sort Order Cycles On Shopfront By" diff --git a/config/routes.rb b/config/routes.rb index c8771560e9..882dfec23d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -103,6 +103,7 @@ Openfoodnetwork::Application.routes.draw do resources :variant_overrides do post :bulk_update, on: :collection + post :bulk_reset, on: :collection end resources :customers, only: [:index, :update] diff --git a/db/migrate/20150719111807_add_default_stock_to_variant_overrides.rb b/db/migrate/20150719111807_add_default_stock_to_variant_overrides.rb new file mode 100644 index 0000000000..880a1b8349 --- /dev/null +++ b/db/migrate/20150719111807_add_default_stock_to_variant_overrides.rb @@ -0,0 +1,5 @@ +class AddDefaultStockToVariantOverrides < ActiveRecord::Migration + def change + add_column :variant_overrides, :default_stock, :integer + end +end diff --git a/db/migrate/20150827194622_add_enable_reset_to_variant_overrides.rb b/db/migrate/20150827194622_add_enable_reset_to_variant_overrides.rb new file mode 100644 index 0000000000..172cce6588 --- /dev/null +++ b/db/migrate/20150827194622_add_enable_reset_to_variant_overrides.rb @@ -0,0 +1,5 @@ +class AddEnableResetToVariantOverrides < ActiveRecord::Migration + def change + add_column :variant_overrides, :enable_reset, :boolean + end +end diff --git a/db/migrate/20151126235409_add_on_demand_and_sku_to_variant_overrides.rb b/db/migrate/20151126235409_add_on_demand_and_sku_to_variant_overrides.rb new file mode 100644 index 0000000000..9c47bfbc27 --- /dev/null +++ b/db/migrate/20151126235409_add_on_demand_and_sku_to_variant_overrides.rb @@ -0,0 +1,6 @@ +class AddOnDemandAndSkuToVariantOverrides < ActiveRecord::Migration + def change + add_column :variant_overrides, :sku, :string, :default => nil, :after => :hub_id + add_column :variant_overrides, :on_demand, :boolean, :default => nil, :after => :count_on_hand + end +end diff --git a/db/migrate/20151128185900_rename_enable_reset_to_resettable.rb b/db/migrate/20151128185900_rename_enable_reset_to_resettable.rb new file mode 100644 index 0000000000..461e2efe25 --- /dev/null +++ b/db/migrate/20151128185900_rename_enable_reset_to_resettable.rb @@ -0,0 +1,3 @@ +class RenameEnableResetToResettable < ActiveRecord::Migration + rename_column :variant_overrides, :enable_reset, :resettable +end diff --git a/db/schema.rb b/db/schema.rb index fb50bec0a1..465530b949 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20151125051510) do +ActiveRecord::Schema.define(:version => 20151128185900) do create_table "account_invoices", :force => true do |t| t.integer "user_id", :null => false @@ -1159,6 +1159,10 @@ ActiveRecord::Schema.define(:version => 20151125051510) do t.integer "hub_id", :null => false t.decimal "price", :precision => 8, :scale => 2 t.integer "count_on_hand" + t.integer "default_stock" + t.boolean "resettable" + t.string "sku" + t.boolean "on_demand" end add_index "variant_overrides", ["variant_id", "hub_id"], :name => "index_variant_overrides_on_variant_id_and_hub_id" diff --git a/lib/open_food_network/scope_variant_to_hub.rb b/lib/open_food_network/scope_variant_to_hub.rb index 80396ffa17..37a2455616 100644 --- a/lib/open_food_network/scope_variant_to_hub.rb +++ b/lib/open_food_network/scope_variant_to_hub.rb @@ -26,12 +26,16 @@ module OpenFoodNetwork end def on_demand - if @variant_override.andand.count_on_hand.present? - # If we're overriding the stock level of an on_demand variant, show it as not - # on_demand, so our stock control can take effect. - false + if @variant_override.andand.on_demand.nil? + if @variant_override.andand.count_on_hand.present? + # If we're overriding the stock level of an on_demand variant, show it as not + # on_demand, so our stock control can take effect. + false + else + super + end else - super + @variant_override.andand.on_demand end end @@ -42,7 +46,10 @@ module OpenFoodNetwork super end end - end + def sku + @variant_override.andand.sku || super + end + end end end diff --git a/spec/controllers/admin/variant_overrides_controller_spec.rb b/spec/controllers/admin/variant_overrides_controller_spec.rb new file mode 100644 index 0000000000..d796f2d52f --- /dev/null +++ b/spec/controllers/admin/variant_overrides_controller_spec.rb @@ -0,0 +1,135 @@ +require 'spec_helper' + +describe Admin::VariantOverridesController, type: :controller do + # include AuthenticationWorkflow + + describe "bulk_update" do + context "json" do + let(:format) { :json } + + let(:hub) { create(:distributor_enterprise) } + let(:variant) { create(:variant) } + let!(:variant_override) { create(:variant_override, hub: hub, variant: variant) } + let(:variant_override_params) { [ { id: variant_override.id, price: 123.45, count_on_hand: 321, sku: "MySKU", on_demand: false } ] } + + context "where I don't manage the variant override hub" do + before do + user = create(:user) + user.owned_enterprises << create(:enterprise) + allow(controller).to receive(:spree_current_user) { user } + end + + it "redirects to unauthorized" do + spree_put :bulk_update, format: format, variant_overrides: variant_override_params + expect(response).to redirect_to spree.unauthorized_path + end + end + + context "where I manage the variant override hub" do + before do + allow(controller).to receive(:spree_current_user) { hub.owner } + end + + context "but the producer has not granted VO permission" do + it "redirects to unauthorized" do + spree_put :bulk_update, format: format, variant_overrides: variant_override_params + expect(response).to redirect_to spree.unauthorized_path + end + end + + context "and the producer has granted VO permission" do + before do + create(:enterprise_relationship, parent: variant.product.supplier, child: hub, permissions_list: [:create_variant_overrides]) + end + + it "allows me to update the variant override" do + spree_put :bulk_update, format: format, variant_overrides: variant_override_params + variant_override.reload + expect(variant_override.price).to eq 123.45 + expect(variant_override.count_on_hand).to eq 321 + expect(variant_override.sku).to eq "MySKU" + expect(variant_override.on_demand).to eq false + end + + context "where params for a variant override are blank" do + let(:variant_override_params) { [ { id: variant_override.id, price: "", count_on_hand: "", default_stock: nil, resettable: nil, sku: nil, on_demand: nil } ] } + + it "destroys the variant override" do + spree_put :bulk_update, format: format, variant_overrides: variant_override_params + expect(VariantOverride.find_by_id(variant_override.id)).to be_nil + end + end + end + end + end + end + + describe "bulk_reset" do + context "json" do + let(:format) { :json } + + let(:hub) { create(:distributor_enterprise) } + let(:producer) { create(:supplier_enterprise) } + let(:product) { create(:product, supplier: producer) } + let(:variant1) { create(:variant, product: product) } + let(:variant2) { create(:variant, product: product) } + let!(:variant_override1) { create(:variant_override, hub: hub, variant: variant1, count_on_hand: 5, default_stock: 7, resettable: true) } + let!(:variant_override2) { create(:variant_override, hub: hub, variant: variant2, count_on_hand: 2, default_stock: 1, resettable: false) } + + let(:params) { { format: format, hub_id: hub.id } } + + context "where I don't manage the variant override hub" do + before do + user = create(:user) + user.owned_enterprises << create(:enterprise) + allow(controller).to receive(:spree_current_user) { user } + end + + it "redirects to unauthorized" do + spree_put :bulk_reset, params + expect(response).to redirect_to spree.unauthorized_path + end + end + + context "where I manage the variant override hub" do + before do + allow(controller).to receive(:spree_current_user) { hub.owner } + end + + context "where the producer has not granted create_variant_overrides permission to the hub" do + it "restricts access" do + spree_put :bulk_reset, params + expect(response).to redirect_to spree.unauthorized_path + end + end + + context "where the producer has granted create_variant_overrides permission to the hub" do + let!(:er1) { create(:enterprise_relationship, parent: producer, child: hub, permissions_list: [:create_variant_overrides]) } + + it "updates stock to default values where reset is enabled" do + expect(variant_override1.reload.count_on_hand).to eq 5 # reset enabled + expect(variant_override2.reload.count_on_hand).to eq 2 # reset disabled + spree_put :bulk_reset, params + expect(variant_override1.reload.count_on_hand).to eq 7 # reset enabled + expect(variant_override2.reload.count_on_hand).to eq 2 # reset disabled + end + + context "and the producer has granted create_variant_overrides permission to another hub I manage" do + before { hub.owner.update_attribute(:enterprise_limit, 2) } + let(:hub2) { create(:distributor_enterprise, owner: hub.owner) } + let(:product) { create(:product, supplier: producer) } + let(:variant3) { create(:variant, product: product) } + let!(:variant_override3) { create(:variant_override, hub: hub2, variant: variant3, count_on_hand: 1, default_stock: 13, resettable: true) } + let!(:er2) { create(:enterprise_relationship, parent: producer, child: hub2, permissions_list: [:create_variant_overrides]) } + + it "does not reset count_on_hand for variant_overrides not in params" do + expect { + spree_put :bulk_reset, params + }.to_not change{variant_override3.reload.count_on_hand} + end + end + end + end + end + end +end diff --git a/spec/controllers/api/enterprises_controller_spec.rb b/spec/controllers/api/enterprises_controller_spec.rb index 77e16368d9..0e8f2ef606 100644 --- a/spec/controllers/api/enterprises_controller_spec.rb +++ b/spec/controllers/api/enterprises_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' module Api - describe EnterprisesController do + describe EnterprisesController, :type => :controller do include AuthenticationWorkflow render_views diff --git a/spec/controllers/api/order_cycles_controller_spec.rb b/spec/controllers/api/order_cycles_controller_spec.rb index 3bb9a76602..a9a86608e7 100644 --- a/spec/controllers/api/order_cycles_controller_spec.rb +++ b/spec/controllers/api/order_cycles_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' require 'spree/api/testing_support/helpers' module Api - describe OrderCyclesController do + describe OrderCyclesController, :type => :controller do include Spree::Api::TestingSupport::Helpers include AuthenticationWorkflow render_views diff --git a/spec/controllers/base_controller_spec.rb b/spec/controllers/base_controller_spec.rb index b5ef006c5b..4ea2e31ff2 100644 --- a/spec/controllers/base_controller_spec.rb +++ b/spec/controllers/base_controller_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe BaseController do +describe BaseController, :type => :controller do let(:oc) { mock_model(OrderCycle) } let(:hub) { mock_model(Enterprise, ready_for_checkout?: true) } let(:order) { mock_model(Spree::Order, distributor: hub) } diff --git a/spec/factories.rb b/spec/factories.rb index 8ae6a094c2..dd8d02e7bf 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -94,6 +94,8 @@ FactoryGirl.define do factory :variant_override, :class => VariantOverride do price 77.77 count_on_hand 11111 + default_stock 2000 + resettable false end factory :enterprise, :class => Enterprise do diff --git a/spec/features/admin/bulk_order_management_spec.rb b/spec/features/admin/bulk_order_management_spec.rb index 5a51f396b3..1e011b5939 100644 --- a/spec/features/admin/bulk_order_management_spec.rb +++ b/spec/features/admin/bulk_order_management_spec.rb @@ -119,10 +119,9 @@ feature %q{ expect(page).to_not have_selector "#save-bar" fill_in "quantity", :with => 2 expect(page).to have_selector "input[name='quantity'].ng-dirty" - expect(page).to have_selector "#save-bar" - expect(page).to have_button "Save Changes" + expect(page).to have_selector "#save-bar", text: "You have unsaved changes" click_button "Save Changes" - expect(page).to_not have_selector "#save-bar" + expect(page).to have_selector "#save-bar", text: "All changes saved" expect(page).to_not have_selector "input[name='quantity'].ng-dirty" end end @@ -132,10 +131,9 @@ feature %q{ expect(page).to_not have_selector "#save-bar" fill_in "quantity", :with => li1.variant.on_hand + li1.quantity + 10 expect(page).to have_selector "input[name='quantity'].ng-dirty" - expect(page).to have_selector "#save-bar" - expect(page).to have_button "Save Changes" + expect(page).to have_selector "#save-bar", text: "You have unsaved changes" click_button "Save Changes" - expect(page).to have_selector "#save-bar" + expect(page).to have_selector "#save-bar", text: "Fields with red borders contain errors." expect(page).to have_selector "input[name='quantity'].ng-dirty.update-error" expect(page).to have_content "exceeds available stock. Please ensure line items have a valid quantity." end @@ -158,9 +156,9 @@ feature %q{ context "modifying the weight/volume of a line item" do it "price is altered" do visit '/admin/orders/bulk_management' - first("div#columns_dropdown", :text => "COLUMNS").click - first("div#columns_dropdown div.menu div.menu_item", text: "Weight/Volume").click - first("div#columns_dropdown div.menu div.menu_item", text: "Price").click + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Weight/Volume").click + first("div#columns-dropdown div.menu div.menu_item", text: "Price").click within "tr#li_#{li1.id}" do expect(page).to have_field "price", with: "$50.00" fill_in "final_weight_volume", :with => 2000 @@ -177,8 +175,8 @@ feature %q{ context "modifying the quantity of a line item" do it "price is altered" do visit '/admin/orders/bulk_management' - first("div#columns_dropdown", :text => "COLUMNS").click - first("div#columns_dropdown div.menu div.menu_item", text: "Price").click + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Price").click within "tr#li_#{li1.id}" do expect(page).to have_field "price", with: "$#{format("%.2f",li1.price * 5)}" fill_in "quantity", :with => 6 @@ -190,8 +188,8 @@ feature %q{ context "modifying the quantity of a line item" do it "weight/volume is altered" do visit '/admin/orders/bulk_management' - first("div#columns_dropdown", :text => "COLUMNS").click - first("div#columns_dropdown div.menu div.menu_item", text: "Weight/Volume").click + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Weight/Volume").click within "tr#li_#{li1.id}" do expect(page).to have_field "final_weight_volume", with: "#{li1.final_weight_volume.round}" fill_in "quantity", :with => 6 @@ -211,8 +209,8 @@ feature %q{ expect(page).to have_selector "th", :text => "QUANTITY" expect(page).to have_selector "th", :text => "MAX" - first("div#columns_dropdown", :text => "COLUMNS").click - first("div#columns_dropdown div.menu div.menu_item", text: "Producer").click + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Producer").click expect(page).to_not have_selector "th", :text => "PRODUCER" expect(page).to have_selector "th", :text => "NAME" @@ -501,8 +499,8 @@ feature %q{ it "displays a bulk action select box with a list of actions" do list_of_actions = ['Delete Selected'] - find("div#bulk_actions_dropdown").click - within("div#bulk_actions_dropdown") do + find("div#bulk-actions-dropdown").click + within("div#bulk-actions-dropdown") do list_of_actions.each { |action_name| expect(page).to have_selector "div.menu_item", text: action_name } end end @@ -514,8 +512,8 @@ feature %q{ within("tr#li_#{li2.id} td.bulk") do check "bulk" end - find("div#bulk_actions_dropdown").click - find("div#bulk_actions_dropdown div.menu_item", :text => "Delete Selected" ).click + find("div#bulk-actions-dropdown").click + find("div#bulk-actions-dropdown div.menu_item", :text => "Delete Selected" ).click expect(page).to have_selector "tr#li_#{li1.id}", visible: true expect(page).to_not have_selector "tr#li_#{li2.id}", visible: true end @@ -534,8 +532,8 @@ feature %q{ it "only applies the delete action to filteredLineItems" do check "toggle_bulk" fill_in "quick_search", with: o1.number - find("div#bulk_actions_dropdown").click - find("div#bulk_actions_dropdown div.menu_item", :text => "Delete Selected" ).click + find("div#bulk-actions-dropdown").click + find("div#bulk-actions-dropdown div.menu_item", :text => "Delete Selected" ).click fill_in "quick_search", with: '' expect(page).to_not have_selector "tr#li_#{li1.id}", visible: true expect(page).to have_selector "tr#li_#{li2.id}", visible: true diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index 91f476d16d..9dabf93028 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -46,8 +46,8 @@ feature %q{ p2 = FactoryGirl.create(:product, available_on: Date.current-1) visit '/admin/products/bulk_edit' - first("div#columns_dropdown", :text => "COLUMNS").click - first("div#columns_dropdown div.menu div.menu_item", text: "Available On").click + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click expect(page).to have_field "available_on", with: p1.available_on.strftime("%F %T") expect(page).to have_field "available_on", with: p2.available_on.strftime("%F %T") @@ -243,11 +243,11 @@ feature %q{ visit '/admin/products/bulk_edit' - first("div#columns_dropdown", :text => "COLUMNS").click - first("div#columns_dropdown div.menu div.menu_item", text: "Available On").click - first("div#columns_dropdown div.menu div.menu_item", text: "Category").click - first("div#columns_dropdown div.menu div.menu_item", text: "Inherits Properties?").click - first("div#columns_dropdown div.menu div.menu_item", text: "SKU").click + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click + first("div#columns-dropdown div.menu div.menu_item", text: "Category").click + first("div#columns-dropdown div.menu div.menu_item", text: "Inherits Properties?").click + first("div#columns-dropdown div.menu div.menu_item", text: "SKU").click within "tr#p_#{p.id}" do expect(page).to have_field "product_name", with: p.name @@ -308,6 +308,7 @@ feature %q{ p = FactoryGirl.create(:product, supplier: s1, available_on: Date.current, variant_unit: 'volume', variant_unit_scale: 0.001, price: 3.0, on_hand: 9, unit_value: 0.25, unit_description: '(bottle)' ) v = p.variants.first + v.update_column(:sku, "VARIANTSKU") login_to_admin_section @@ -315,12 +316,18 @@ feature %q{ expect(page).to have_selector "a.view-variants" first("a.view-variants").trigger('click') + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "SKU").click + first("div#columns-dropdown", :text => "COLUMNS").click + + expect(page).to have_field "variant_sku", with: "VARIANTSKU" expect(page).to have_field "variant_price", with: "3.0" expect(page).to have_field "variant_unit_value_with_description", with: "250 (bottle)" expect(page).to have_field "variant_on_hand", with: "9" expect(page).to have_selector "span[name='on_hand']", "9" select "Volume (L)", from: "variant_unit_with_scale" + fill_in "variant_sku", with: "NEWSKU" fill_in "variant_price", with: "4.0" fill_in "variant_on_hand", with: "10" fill_in "variant_unit_value_with_description", with: "2 (8x250 mL bottles)" @@ -331,6 +338,7 @@ feature %q{ expect(page.find("#status-message")).to have_content "Changes saved." v.reload + expect(v.sku).to eq "NEWSKU" expect(v.price).to eq 4.0 expect(v.on_hand).to eq 10 expect(v.unit_value).to eq 2 # 2L in L @@ -556,8 +564,8 @@ feature %q{ visit '/admin/products/bulk_edit' - first("div#columns_dropdown", :text => "COLUMNS").click - first("div#columns_dropdown div.menu div.menu_item", text: "Available On").click + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click expect(page).to have_selector "th", :text => "NAME" expect(page).to have_selector "th", :text => "PRODUCER" @@ -565,7 +573,7 @@ feature %q{ expect(page).to have_selector "th", :text => "ON HAND" expect(page).to have_selector "th", :text => "AV. ON" - first("div#columns_dropdown div.menu div.menu_item", text: /^.{0,1}Producer$/).click + first("div#columns-dropdown div.menu div.menu_item", text: /^.{0,1}Producer$/).click expect(page).to have_no_selector "th", :text => "PRODUCER" expect(page).to have_selector "th", :text => "NAME" @@ -688,8 +696,8 @@ feature %q{ v = p.variants.first visit '/admin/products/bulk_edit' - first("div#columns_dropdown", :text => "COLUMNS").click - first("div#columns_dropdown div.menu div.menu_item", text: "Available On").click + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click within "tr#p_#{p.id}" do expect(page).to have_field "product_name", with: p.name diff --git a/spec/features/admin/customers_spec.rb b/spec/features/admin/customers_spec.rb index c84f8f35ed..5d2ffedb4e 100644 --- a/spec/features/admin/customers_spec.rb +++ b/spec/features/admin/customers_spec.rb @@ -39,8 +39,8 @@ feature 'Customers' do # Toggling columns expect(page).to have_selector "th.email" expect(page).to have_content customer1.email - first("div#columns_dropdown", :text => "COLUMNS").click - first("div#columns_dropdown div.menu div.menu_item", text: "Email").click + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Email").click expect(page).to_not have_selector "th.email" expect(page).to_not have_content customer1.email end diff --git a/spec/features/admin/orders_spec.rb b/spec/features/admin/orders_spec.rb index ce0dea54c8..725495b74b 100644 --- a/spec/features/admin/orders_spec.rb +++ b/spec/features/admin/orders_spec.rb @@ -192,7 +192,7 @@ feature %q{ order = create(:completed_order_with_totals, distributor: distributor1) visit spree.admin_order_path(order) - find("#links-dropdown .ofn_drop_down").click + find("#links-dropdown .ofn-drop-down").click within "#links-dropdown" do expect(page).to have_link "Edit", href: spree.edit_admin_order_path(order) expect(page).to have_link "Resend Confirmation", href: spree.resend_admin_order_path(order) diff --git a/spec/features/admin/variant_overrides_spec.rb b/spec/features/admin/variant_overrides_spec.rb index ee615f44f5..378afa3626 100644 --- a/spec/features/admin/variant_overrides_spec.rb +++ b/spec/features/admin/variant_overrides_spec.rb @@ -26,14 +26,6 @@ feature %q{ page.should have_select2 'hub_id', options: ['', hub.name, hub2.name] end - - it "displays the hub" do - visit '/admin/variant_overrides' - select2_select hub.name, from: 'hub_id' - click_button 'Go' - - page.should have_selector 'h2', text: hub.name - end end context "when a hub is selected" do @@ -59,29 +51,60 @@ feature %q{ before do visit '/admin/variant_overrides' select2_select hub.name, from: 'hub_id' - click_button 'Go' end it "displays the list of products with variants" do page.should have_table_row ['PRODUCER', 'PRODUCT', 'PRICE', 'ON HAND'] - page.should have_table_row [producer.name, product.name, '', ''] page.should have_input "variant-overrides-#{variant.id}-price", placeholder: '1.23' - page.should have_input "variant-overrides-#{variant.id}-count-on-hand", placeholder: '12' + page.should have_input "variant-overrides-#{variant.id}-count_on_hand", placeholder: '12' page.should have_table_row [producer_related.name, product_related.name, '', ''] page.should have_input "variant-overrides-#{variant_related.id}-price", placeholder: '2.34' - page.should have_input "variant-overrides-#{variant_related.id}-count-on-hand", placeholder: '23' - end + page.should have_input "variant-overrides-#{variant_related.id}-count_on_hand", placeholder: '23' - it "filters the products to those the hub can override" do + # filters the products to those the hub can override page.should_not have_content producer_unrelated.name page.should_not have_content product_unrelated.name + + # Filters based on the producer select filter + expect(page).to have_selector "#v_#{variant.id}" + expect(page).to have_selector "#v_#{variant_related.id}" + select2_select producer.name, from: 'producer_filter' + expect(page).to have_selector "#v_#{variant.id}" + expect(page).to_not have_selector "#v_#{variant_related.id}" + select2_select 'All', from: 'producer_filter' + + # Filters based on the quick search box + expect(page).to have_selector "#v_#{variant.id}" + expect(page).to have_selector "#v_#{variant_related.id}" + fill_in 'query', with: product.name + expect(page).to have_selector "#v_#{variant.id}" + expect(page).to_not have_selector "#v_#{variant_related.id}" + fill_in 'query', with: '' + + # Clears the filters + expect(page).to have_selector "#v_#{variant.id}" + expect(page).to have_selector "#v_#{variant_related.id}" + select2_select producer.name, from: 'producer_filter' + fill_in 'query', with: product_related.name + expect(page).to_not have_selector "#v_#{variant.id}" + expect(page).to_not have_selector "#v_#{variant_related.id}" + click_button 'Clear All' + expect(page).to have_selector "#v_#{variant.id}" + expect(page).to have_selector "#v_#{variant_related.id}" end it "creates new overrides" do + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "SKU").click + first("div#columns-dropdown div.menu div.menu_item", text: "On Demand").click + first("div#columns-dropdown", :text => "COLUMNS").click + + fill_in "variant-overrides-#{variant.id}-sku", with: 'NEWSKU' fill_in "variant-overrides-#{variant.id}-price", with: '777.77' - fill_in "variant-overrides-#{variant.id}-count-on-hand", with: '123' + fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '123' + check "variant-overrides-#{variant.id}-on_demand" page.should have_content "Changes to one override remain unsaved." expect do @@ -92,15 +115,17 @@ feature %q{ vo = VariantOverride.last vo.variant_id.should == variant.id vo.hub_id.should == hub.id + vo.sku.should == "NEWSKU" vo.price.should == 777.77 vo.count_on_hand.should == 123 + vo.on_demand.should == true end describe "creating and then updating the new override" do it "updates the same override instead of creating a duplicate" do # When I create a new override fill_in "variant-overrides-#{variant.id}-price", with: '777.77' - fill_in "variant-overrides-#{variant.id}-count-on-hand", with: '123' + fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '123' page.should have_content "Changes to one override remain unsaved." expect do @@ -110,7 +135,7 @@ feature %q{ # And I update its settings without reloading the page fill_in "variant-overrides-#{variant.id}-price", with: '111.11' - fill_in "variant-overrides-#{variant.id}-count-on-hand", with: '111' + fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '111' page.should have_content "Changes to one override remain unsaved." # Then I shouldn't see a new override @@ -130,7 +155,7 @@ feature %q{ it "displays an error when unauthorised to access the page" do fill_in "variant-overrides-#{variant.id}-price", with: '777.77' - fill_in "variant-overrides-#{variant.id}-count-on-hand", with: '123' + fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '123' page.should have_content "Changes to one override remain unsaved." user.enterprises.clear @@ -143,7 +168,7 @@ feature %q{ it "displays an error when unauthorised to update a particular override" do fill_in "variant-overrides-#{variant_related.id}-price", with: '777.77' - fill_in "variant-overrides-#{variant_related.id}-count-on-hand", with: '123' + fill_in "variant-overrides-#{variant_related.id}-count_on_hand", with: '123' page.should have_content "Changes to one override remain unsaved." er2.destroy @@ -156,23 +181,27 @@ feature %q{ end context "with overrides" do - let!(:vo) { create(:variant_override, variant: variant, hub: hub, price: 77.77, count_on_hand: 11111) } + let!(:vo) { create(:variant_override, variant: variant, hub: hub, price: 77.77, count_on_hand: 11111, default_stock: 1000, resettable: true) } let!(:vo_no_auth) { create(:variant_override, variant: variant, hub: hub3, price: 1, count_on_hand: 2) } + let!(:product2) { create(:simple_product, supplier: producer, variant_unit: 'weight', variant_unit_scale: 1) } + let!(:variant2) { create(:variant, product: product2, unit_value: 8, price: 1.00, on_hand: 12) } + let!(:vo_no_reset) { create(:variant_override, variant: variant2, hub: hub, price: 3.99, count_on_hand: 40, default_stock: 100, resettable: false) } + let!(:variant3) { create(:variant, product: product, unit_value: 2, price: 5.00, on_hand: 6) } + let!(:vo3) { create(:variant_override, variant: variant3, hub: hub, price: 6, count_on_hand: 7, sku: "SOMESKU", default_stock: 100, resettable: false) } before do visit '/admin/variant_overrides' select2_select hub.name, from: 'hub_id' - click_button 'Go' end it "product values are affected by overrides" do page.should have_input "variant-overrides-#{variant.id}-price", with: '77.77', placeholder: '1.23' - page.should have_input "variant-overrides-#{variant.id}-count-on-hand", with: '11111', placeholder: '12' + page.should have_input "variant-overrides-#{variant.id}-count_on_hand", with: '11111', placeholder: '12' end it "updates existing overrides" do fill_in "variant-overrides-#{variant.id}-price", with: '22.22' - fill_in "variant-overrides-#{variant.id}-count-on-hand", with: '8888' + fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '8888' page.should have_content "Changes to one override remain unsaved." expect do @@ -187,17 +216,54 @@ feature %q{ vo.count_on_hand.should == 8888 end + # Any new fields added to the VO model need to be added to this test it "deletes overrides when values are cleared" do + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "On Demand").click + first("div#columns-dropdown div.menu div.menu_item", text: "Reset Stock Level").click + first("div#columns-dropdown", :text => "COLUMNS").click + + # Clearing values manually fill_in "variant-overrides-#{variant.id}-price", with: '' - fill_in "variant-overrides-#{variant.id}-count-on-hand", with: '' + fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '' + fill_in "variant-overrides-#{variant.id}-default_stock", with: '' + page.uncheck "variant-overrides-#{variant.id}-resettable" page.should have_content "Changes to one override remain unsaved." + # Clearing values by 'inheriting' + first("div#columns-dropdown", :text => "COLUMNS").click + first("div#columns-dropdown div.menu div.menu_item", text: "Inheritance").click + first("div#columns-dropdown", :text => "COLUMNS").click + page.check "variant-overrides-#{variant3.id}-inherit" + expect do click_button 'Save Changes' page.should have_content "Changes saved." - end.to change(VariantOverride, :count).by(-1) + end.to change(VariantOverride, :count).by(-2) VariantOverride.where(id: vo.id).should be_empty + VariantOverride.where(id: vo3.id).should be_empty + end + + it "resets stock to defaults" do + click_button 'Reset Stock to Defaults' + page.should have_content 'Stocks reset to defaults.' + vo.reload + page.should have_input "variant-overrides-#{variant.id}-count_on_hand", with: '1000', placeholder: '12' + vo.count_on_hand.should == 1000 + end + + it "doesn't reset stock levels if the behaviour is disabled" do + click_button 'Reset Stock to Defaults' + vo_no_reset.reload + page.should have_input "variant-overrides-#{variant2.id}-count_on_hand", with: '40', placeholder: '12' + vo_no_reset.count_on_hand.should == 40 + end + + it "prompts to save changes before reset if any are pending" do + fill_in "variant-overrides-#{variant.id}-price", with: '200' + click_button 'Reset Stock to Defaults' + page.should have_content "Save changes first" end end end diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index 12e9d8323b..e81a792881 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -187,7 +187,7 @@ feature "As a consumer I want to shop with a distributor", js: true do visit shop_path end - it "should save group buy data to the cart" do + it "should save group buy data to the cart and display it on shopfront reload" do # -- Quantity fill_in "variants[#{variant.id}]", with: 6 page.should have_in_cart product.name @@ -202,6 +202,11 @@ feature "As a consumer I want to shop with a distributor", js: true do li = Spree::Order.order(:created_at).last.line_items.order(:created_at).last li.max_quantity.should == 7 + + # -- Reload + visit shop_path + page.should have_field "variants[#{variant.id}]", with: 6 + page.should have_field "variant_attributes[#{variant.id}][max_quantity]", with: 7 end end end diff --git a/spec/features/consumer/shopping/variant_overrides_spec.rb b/spec/features/consumer/shopping/variant_overrides_spec.rb index f850436e52..92945a60be 100644 --- a/spec/features/consumer/shopping/variant_overrides_spec.rb +++ b/spec/features/consumer/shopping/variant_overrides_spec.rb @@ -22,12 +22,12 @@ feature "shopping with variant overrides defined", js: true do let(:v4) { create(:variant, product: p1, price: 44.44, unit_value: 4) } let(:v5) { create(:variant, product: p3, price: 55.55, unit_value: 5, on_demand: true) } let(:v6) { create(:variant, product: p3, price: 66.66, unit_value: 6, on_demand: true) } - let!(:vo1) { create(:variant_override, hub: hub, variant: v1, price: 55.55, count_on_hand: nil) } - let!(:vo2) { create(:variant_override, hub: hub, variant: v2, count_on_hand: 0) } - let!(:vo3) { create(:variant_override, hub: hub, variant: v3, count_on_hand: 0) } - let!(:vo4) { create(:variant_override, hub: hub, variant: v4, count_on_hand: 3) } - let!(:vo5) { create(:variant_override, hub: hub, variant: v5, count_on_hand: 0) } - let!(:vo6) { create(:variant_override, hub: hub, variant: v6, count_on_hand: 6) } + let!(:vo1) { create(:variant_override, hub: hub, variant: v1, price: 55.55, count_on_hand: nil, default_stock: nil, resettable: false) } + let!(:vo2) { create(:variant_override, hub: hub, variant: v2, count_on_hand: 0, default_stock: nil, resettable: false) } + let!(:vo3) { create(:variant_override, hub: hub, variant: v3, count_on_hand: 0, default_stock: nil, resettable: false) } + let!(:vo4) { create(:variant_override, hub: hub, variant: v4, count_on_hand: 3, default_stock: nil, resettable: false) } + let!(:vo5) { create(:variant_override, hub: hub, variant: v5, count_on_hand: 0, default_stock: nil, resettable: false) } + let!(:vo6) { create(:variant_override, hub: hub, variant: v6, count_on_hand: 6, default_stock: nil, resettable: false) } let(:ef) { create(:enterprise_fee, enterprise: hub, fee_type: 'packing', calculator: Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10)) } before do @@ -143,7 +143,6 @@ feature "shopping with variant overrides defined", js: true do it "does not subtract stock from overrides that do not override count_on_hand" do fill_in "variants[#{v1.id}]", with: "2" click_checkout - expect do complete_checkout end.to change { v1.reload.count_on_hand }.by(-2) @@ -192,7 +191,7 @@ feature "shopping with variant overrides defined", js: true do within "#payment" do choose pm.name end - + place_order page.should have_content "Your order has been processed successfully" end diff --git a/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee b/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee index bdb62e8d37..699e1bc4ab 100644 --- a/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee @@ -1,29 +1,39 @@ describe "VariantOverridesCtrl", -> ctrl = null - scope = null + scope = {} hubs = [{id: 1, name: 'Hub'}] producers = [{id: 2, name: 'Producer'}] products = [{id: 1, name: 'Product'}] hubPermissions = {} VariantOverrides = null variantOverrides = {} + DirtyVariantOverrides = null + dirtyVariantOverrides = {} + StatusMessage = null + statusMessage = {} beforeEach -> - module 'ofn.admin' + module 'admin.variantOverrides' module ($provide) -> $provide.value 'SpreeApiKey', 'API_KEY' $provide.value 'variantOverrides', variantOverrides + $provide.value 'dirtyVariantOverrides', dirtyVariantOverrides null - scope = {} - inject ($controller, Indexer, _VariantOverrides_) -> + inject ($controller, _VariantOverrides_, _DirtyVariantOverrides_, _StatusMessage_) -> VariantOverrides = _VariantOverrides_ - ctrl = $controller 'AdminVariantOverridesCtrl', {$scope: scope, Indexer: Indexer, hubs: hubs, producers: producers, products: products, hubPermissions: hubPermissions, VariantOverrides: _VariantOverrides_} + DirtyVariantOverrides = _DirtyVariantOverrides_ + StatusMessage = _StatusMessage_ + ctrl = $controller 'AdminVariantOverridesCtrl', { $scope: scope, hubs: hubs, producers: producers, products: products, hubPermissions: hubPermissions, VariantOverrides: VariantOverrides, DirtyVariantOverrides: DirtyVariantOverrides, StatusMessage: StatusMessage} it "initialises the hub list and the chosen hub", -> - expect(scope.hubs).toEqual hubs + expect(scope.hubs).toEqual { 1: {id: 1, name: 'Hub'} } expect(scope.hub).toBeNull() + it "initialises select filters", -> + expect(scope.producerFilter).toEqual 0 + expect(scope.query).toEqual '' + it "adds products", -> spyOn(VariantOverrides, "ensureDataFor") expect(scope.products).toEqual [] @@ -54,4 +64,23 @@ describe "VariantOverridesCtrl", -> expect(scope.updateError(data, 400)).toEqual "I had some trouble saving: Hub can't be blank, Variant can't be blank" it "returns a generic message otherwise", -> - expect(scope.updateError({}, 500)).toEqual "Oh no! I was unable to save your changes." \ No newline at end of file + expect(scope.updateError({}, 500)).toEqual "Oh no! I was unable to save your changes." + + describe "setting stock to defaults", -> + it "prompts to save changes if there are any pending", -> + spyOn(StatusMessage, "display") + DirtyVariantOverrides.add {hub_id: 1, variant_id: 1} + scope.resetStock() + expect(StatusMessage.display).toHaveBeenCalledWith 'alert', 'Save changes first.' + + it "updates and refreshes on hand value for variant overrides with a default stock level", inject ($httpBackend) -> + scope.hub_id = 123 + variant_overrides_mock = "mock object" + spyOn(StatusMessage, "display") + spyOn(VariantOverrides, "updateData") + $httpBackend.expectPOST("/admin/variant_overrides/bulk_reset", hub_id: 123).respond 200, variant_overrides_mock + scope.resetStock() + expect(StatusMessage.display).toHaveBeenCalledWith 'progress', 'Changing on hand stock levels...' + $httpBackend.flush() + expect(VariantOverrides.updateData).toHaveBeenCalledWith variant_overrides_mock + expect(StatusMessage.display).toHaveBeenCalledWith 'success', 'Stocks reset to defaults.' diff --git a/spec/javascripts/unit/admin/services/dirty_variant_overrides_spec.js.coffee b/spec/javascripts/unit/admin/services/dirty_variant_overrides_spec.js.coffee index 653560a989..553bba5713 100644 --- a/spec/javascripts/unit/admin/services/dirty_variant_overrides_spec.js.coffee +++ b/spec/javascripts/unit/admin/services/dirty_variant_overrides_spec.js.coffee @@ -7,7 +7,7 @@ describe "maintaining a list of dirty variant overrides", -> count_on_hand: 4 beforeEach -> - module "ofn.admin" + module "admin.variantOverrides" beforeEach inject (_DirtyVariantOverrides_) -> DirtyVariantOverrides = _DirtyVariantOverrides_ diff --git a/spec/javascripts/unit/admin/services/indexer_spec.js.coffee b/spec/javascripts/unit/admin/services/indexer_spec.js.coffee index f17f8bd83c..22f263e02b 100644 --- a/spec/javascripts/unit/admin/services/indexer_spec.js.coffee +++ b/spec/javascripts/unit/admin/services/indexer_spec.js.coffee @@ -2,7 +2,7 @@ describe "indexer", -> Indexer = null beforeEach -> - module "ofn.admin" + module "admin.indexUtils" beforeEach inject (_Indexer_) -> Indexer = _Indexer_ diff --git a/spec/javascripts/unit/admin/services/variant_overrides_spec.js.coffee b/spec/javascripts/unit/admin/services/variant_overrides_spec.js.coffee index 532bb1d65c..73dbdabee8 100644 --- a/spec/javascripts/unit/admin/services/variant_overrides_spec.js.coffee +++ b/spec/javascripts/unit/admin/services/variant_overrides_spec.js.coffee @@ -1,27 +1,28 @@ describe "VariantOverrides service", -> - VariantOverrides = null + VariantOverrides = $httpBackend = null variantOverrides = [ - {id: 1, hub_id: 10, variant_id: 100, price: 1, count_on_hand: 1} - {id: 2, hub_id: 10, variant_id: 200, price: 2, count_on_hand: 2} - {id: 3, hub_id: 20, variant_id: 300, price: 3, count_on_hand: 3} + {id: 1, hub_id: 10, variant_id: 100, sku: "V100", price: 1, count_on_hand: 1, on_demand: null, default_stock: null, resettable: false } + {id: 2, hub_id: 10, variant_id: 200, sku: "V200", price: 2, count_on_hand: 2, on_demand: null, default_stock: null, resettable: false} + {id: 3, hub_id: 20, variant_id: 300, sku: "V300", price: 3, count_on_hand: 3, on_demand: null, default_stock: null, resettable: false} ] beforeEach -> - module "ofn.admin" + module "admin.variantOverrides" module ($provide) -> $provide.value "variantOverrides", variantOverrides null - beforeEach inject (_VariantOverrides_) -> + beforeEach inject (_VariantOverrides_, _$httpBackend_) -> VariantOverrides = _VariantOverrides_ + $httpBackend = _$httpBackend_ it "indexes variant overrides by hub_id -> variant_id", -> expect(VariantOverrides.variantOverrides).toEqual 10: - 100: {id: 1, hub_id: 10, variant_id: 100, price: 1, count_on_hand: 1} - 200: {id: 2, hub_id: 10, variant_id: 200, price: 2, count_on_hand: 2} + 100: {id: 1, hub_id: 10, variant_id: 100, sku: "V100", price: 1, count_on_hand: 1, on_demand: null, default_stock: null, resettable: false } + 200: {id: 2, hub_id: 10, variant_id: 200, sku: "V200", price: 2, count_on_hand: 2, on_demand: null, default_stock: null, resettable: false } 20: - 300: {id: 3, hub_id: 20, variant_id: 300, price: 3, count_on_hand: 3} + 300: {id: 3, hub_id: 20, variant_id: 300, sku: "V300", price: 3, count_on_hand: 3, on_demand: null, default_stock: null, resettable: false } it "ensures blank data available for some products", -> hubs = [{id: 10}, {id: 20}, {id: 30}] @@ -32,37 +33,50 @@ describe "VariantOverrides service", -> } ] VariantOverrides.ensureDataFor hubs, products - expect(VariantOverrides.variantOverrides).toEqual - 10: - 100: {id: 1, hub_id: 10, variant_id: 100, price: 1, count_on_hand: 1} - 200: {id: 2, hub_id: 10, variant_id: 200, price: 2, count_on_hand: 2} - 300: { hub_id: 10, variant_id: 300, price: '', count_on_hand: ''} - 400: { hub_id: 10, variant_id: 400, price: '', count_on_hand: ''} - 500: { hub_id: 10, variant_id: 500, price: '', count_on_hand: ''} - 20: - 100: { hub_id: 20, variant_id: 100, price: '', count_on_hand: ''} - 200: { hub_id: 20, variant_id: 200, price: '', count_on_hand: ''} - 300: {id: 3, hub_id: 20, variant_id: 300, price: 3, count_on_hand: 3} - 400: { hub_id: 20, variant_id: 400, price: '', count_on_hand: ''} - 500: { hub_id: 20, variant_id: 500, price: '', count_on_hand: ''} - 30: - 100: { hub_id: 30, variant_id: 100, price: '', count_on_hand: ''} - 200: { hub_id: 30, variant_id: 200, price: '', count_on_hand: ''} - 300: { hub_id: 30, variant_id: 300, price: '', count_on_hand: ''} - 400: { hub_id: 30, variant_id: 400, price: '', count_on_hand: ''} - 500: { hub_id: 30, variant_id: 500, price: '', count_on_hand: ''} + expect(VariantOverrides.variantOverrides[10]).toEqual + 100: { id: 1, hub_id: 10, variant_id: 100, sku: "V100", price: 1, count_on_hand: 1, on_demand: null, default_stock: null, resettable: false } + 200: { id: 2, hub_id: 10, variant_id: 200, sku: "V200", price: 2, count_on_hand: 2, on_demand: null, default_stock: null, resettable: false } + 300: { hub_id: 10, variant_id: 300, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 400: { hub_id: 10, variant_id: 400, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 500: { hub_id: 10, variant_id: 500, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + expect(VariantOverrides.variantOverrides[20]).toEqual + 100: { hub_id: 20, variant_id: 100, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 200: { hub_id: 20, variant_id: 200, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 300: { id: 3, hub_id: 20, variant_id: 300, sku: "V300", price: 3, count_on_hand: 3, on_demand: null, default_stock: null, resettable: false } + 400: { hub_id: 20, variant_id: 400, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 500: { hub_id: 20, variant_id: 500, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + expect(VariantOverrides.variantOverrides[30]).toEqual + 100: { hub_id: 30, variant_id: 100, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 200: { hub_id: 30, variant_id: 200, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 300: { hub_id: 30, variant_id: 300, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 400: { hub_id: 30, variant_id: 400, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 500: { hub_id: 30, variant_id: 500, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } it "updates the IDs of variant overrides", -> VariantOverrides.variantOverrides[2] = {} - VariantOverrides.variantOverrides[2][3] = {hub_id: 2, variant_id: 3, price: "4.0", count_on_hand: 5} - VariantOverrides.variantOverrides[2][8] = {hub_id: 2, variant_id: 8, price: "9.0", count_on_hand: 10} + VariantOverrides.variantOverrides[2][3] = {hub_id: 2, variant_id: 3, price: "4.0", count_on_hand: 5, default_stock: '', resettable: false} + VariantOverrides.variantOverrides[2][8] = {hub_id: 2, variant_id: 8, price: "9.0", count_on_hand: 10, default_stock: '', resettable: false} updatedVos = [ - {id: 1, hub_id: 2, variant_id: 3, price: "4.0", count_on_hand: 5} - {id: 6, hub_id: 2, variant_id: 8, price: "9.0", count_on_hand: 10} + {id: 1, hub_id: 2, variant_id: 3, price: "4.0", count_on_hand: 5, default_stock: '', resettable: false} + {id: 6, hub_id: 2, variant_id: 8, price: "9.0", count_on_hand: 10, default_stock: '', resettable: false} ] VariantOverrides.updateIds updatedVos expect(VariantOverrides.variantOverrides[2][3].id).toEqual 1 expect(VariantOverrides.variantOverrides[2][8].id).toEqual 6 + + it "updates the variant overrides on the page with new data", -> + VariantOverrides.variantOverrides[1] = + 3: {id: 1, hub_id: 1, variant_id: 3, price: "4.0", count_on_hand: 5, default_stock: 3, resettable: true} + 8: {id: 2, hub_id: 1, variant_id: 8, price: "9.0", count_on_hand: 10, default_stock: '', resettable: false} + # Updated count on hand to 3 + updatedVos = [ + {id: 1, hub_id: 1, variant_id: 3, price: "4.0", count_on_hand: 3, default_stock: 3, resettable: true} + ] + + VariantOverrides.updateData(updatedVos) + expect(VariantOverrides.variantOverrides[1]).toEqual + 3: {id: 1, hub_id: 1, variant_id: 3, price: "4.0", count_on_hand: 3, default_stock: 3, resettable: true} + 8: {id: 2, hub_id: 1, variant_id: 8, price: "9.0", count_on_hand: 10, default_stock: '', resettable: false} diff --git a/spec/lib/open_food_network/scope_variant_to_hub_spec.rb b/spec/lib/open_food_network/scope_variant_to_hub_spec.rb index 2a249f9c7a..82b24c7e02 100644 --- a/spec/lib/open_food_network/scope_variant_to_hub_spec.rb +++ b/spec/lib/open_food_network/scope_variant_to_hub_spec.rb @@ -3,8 +3,8 @@ require 'open_food_network/scope_variant_to_hub' module OpenFoodNetwork describe ScopeVariantToHub do let(:hub) { create(:distributor_enterprise) } - let(:v) { create(:variant, price: 11.11, count_on_hand: 1) } - let(:vo) { create(:variant_override, hub: hub, variant: v, price: 22.22, count_on_hand: 2) } + let(:v) { create(:variant, price: 11.11, count_on_hand: 1, on_demand: true, sku: "VARIANTSKU") } + let(:vo) { create(:variant_override, hub: hub, variant: v, price: 22.22, count_on_hand: 2, on_demand: false, sku: "VOSKU") } let(:vo_price_only) { create(:variant_override, hub: hub, variant: v, price: 22.22, count_on_hand: nil) } let(:scoper) { ScopeVariantToHub.new(hub) } @@ -66,6 +66,75 @@ module OpenFoodNetwork v.on_demand.should be_true end end + + describe "overriding on_demand" do + context "when an override exists" do + before { vo } + + context "with an on_demand set" do + it "returns the overridden on_demand" do + scoper.scope v + expect(v.on_demand).to be_false + end + end + + context "without an on_demand set" do + before { vo.update_column(:on_demand, nil) } + + context "when count_on_hand is set" do + it "returns false" do + scoper.scope v + expect(v.on_demand).to be_false + end + end + + context "when count_on_hand is not set" do + before { vo.update_column(:count_on_hand, nil) } + + it "returns the variant's on_demand" do + scoper.scope v + expect(v.on_demand).to be_true + end + end + end + end + + context "when no override exists" do + it "returns the variant's on_demand" do + scoper.scope v + expect(v.on_demand).to be_true + end + end + end + + describe "overriding sku" do + context "when an override exists" do + before { vo } + + context "with an sku set" do + it "returns the overridden sku" do + scoper.scope v + expect(v.sku).to eq "VOSKU" + end + end + + context "without an sku set" do + before { vo.update_column(:sku, nil) } + + it "returns the variant's sku" do + scoper.scope v + expect(v.sku).to eq "VARIANTSKU" + end + end + end + + context "when no override exists" do + it "returns the variant's sku" do + scoper.scope v + expect(v.sku).to eq "VARIANTSKU" + end + end + end end end end diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index 6b60707775..4af3d233b1 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -323,7 +323,7 @@ module Spree let!(:er1) { create(:enterprise_relationship, parent: s1, child: d1, permissions_list: [:create_variant_overrides]) } it "should be able to access variant overrides page" do - should have_ability([:admin, :index, :bulk_update], for: VariantOverride) + should have_ability([:admin, :index, :bulk_update, :bulk_reset], for: VariantOverride) end it "should be able to read/write their own variant overrides" do diff --git a/spec/models/variant_override_spec.rb b/spec/models/variant_override_spec.rb index e9f22a33be..ed2f2c00f4 100644 --- a/spec/models/variant_override_spec.rb +++ b/spec/models/variant_override_spec.rb @@ -49,16 +49,16 @@ describe VariantOverride do describe "checking if stock levels have been overriden" do it "returns true when stock level has been overridden" do create(:variant_override, variant: variant, hub: hub, count_on_hand: 12) - VariantOverride.stock_overridden?(hub, variant).should be_true + VariantOverride.stock_overridden?(hub, variant).should be true end it "returns false when the override has no stock level" do create(:variant_override, variant: variant, hub: hub, count_on_hand: nil) - VariantOverride.stock_overridden?(hub, variant).should be_false + VariantOverride.stock_overridden?(hub, variant).should be false end it "returns false when there is no override for the hub/variant" do - VariantOverride.stock_overridden?(hub, variant).should be_false + VariantOverride.stock_overridden?(hub, variant).should be false end end @@ -69,16 +69,40 @@ describe VariantOverride do vo.reload.count_on_hand.should == 10 end - it "silently logs an error if the variant override doesn't have a stock level" do - vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: nil) - Bugsnag.should_receive(:notify) - VariantOverride.decrement_stock! hub, variant, 2 - vo.reload.count_on_hand.should be_nil - end - it "silently logs an error if the variant override does not exist" do Bugsnag.should_receive(:notify) VariantOverride.decrement_stock! hub, variant, 2 end end + + describe "checking default stock value is present" do + it "returns true when a default stock level has been set" do + vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock: 20) + vo.default_stock?.should be true + end + + it "returns false when the override has no default stock level" do + vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock:nil) + vo.default_stock?.should be false + end + end + + describe "resetting stock levels" do + it "resets the on hand level to the value in the default_stock field" do + vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock: 20, resettable: true) + vo.reset_stock! + vo.reload.count_on_hand.should == 20 + end + it "silently logs an error if the variant override doesn't have a default stock level" do + vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock:nil, resettable: true) + Bugsnag.should_receive(:notify) + vo.reset_stock! + vo.reload.count_on_hand.should == 12 + end + it "doesn't reset the level if the behaviour is disabled" do + vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock: 10, resettable: false) + vo.reset_stock! + vo.reload.count_on_hand.should == 12 + end + end end