diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index 81ad95f9ca..42e727bb6f 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -20,30 +20,31 @@ productEditModule.directive "ofnDecimal", -> viewValue -productEditModule.directive "ofnTrackProduct", -> +productEditModule.directive "ofnTrackProduct", ['$parse', ($parse) -> require: "ngModel" link: (scope, element, attrs, ngModel) -> - property_name = attrs.ofnTrackProduct ngModel.$parsers.push (viewValue) -> if ngModel.$dirty - addDirtyProperty scope.dirtyProducts, scope.product.id, property_name, viewValue + parsedPropertyName = $parse(attrs.ofnTrackProduct) + addDirtyProperty scope.dirtyProducts, scope.product.id, parsedPropertyName, viewValue scope.displayDirtyProducts() viewValue + ] -productEditModule.directive "ofnTrackVariant", -> +productEditModule.directive "ofnTrackVariant", ['$parse', ($parse) -> require: "ngModel" link: (scope, element, attrs, ngModel) -> - property_name = attrs.ofnTrackVariant ngModel.$parsers.push (viewValue) -> dirtyVariants = {} dirtyVariants = scope.dirtyProducts[scope.product.id].variants if scope.dirtyProducts.hasOwnProperty(scope.product.id) and scope.dirtyProducts[scope.product.id].hasOwnProperty("variants") if ngModel.$dirty - addDirtyProperty dirtyVariants, scope.variant.id, property_name, viewValue - addDirtyProperty scope.dirtyProducts, scope.product.id, "variants", dirtyVariants + parsedPropertyName = $parse(attrs.ofnTrackVariant) + addDirtyProperty dirtyVariants, scope.variant.id, parsedPropertyName, viewValue + addDirtyProperty scope.dirtyProducts, scope.product.id, $parse("variants"), dirtyVariants scope.displayDirtyProducts() viewValue - + ] productEditModule.directive "ofnToggleVariants", -> link: (scope, element, attrs) -> @@ -215,12 +216,18 @@ productEditModule.controller "AdminProductEditCtrl", [ if product.variants for variant in product.variants - unit_value = $scope.variantUnitValue product, variant - variant.unit_value_with_description = "#{unit_value || ''} #{variant.unit_description || ''}".trim() + $scope.loadVariantVariantUnit product, variant + $scope.loadVariantVariantUnit product, product.master if product.master + + + $scope.loadVariantVariantUnit = (product, variant) -> + unit_value = $scope.variantUnitValue product, variant + unit_value = if unit_value? then unit_value else '' + variant.unit_value_with_description = "#{unit_value} #{variant.unit_description || ''}".trim() $scope.variantUnitValue = (product, variant) -> - if variant.unit_value + if variant.unit_value? if product.variant_unit_scale variant.unit_value / product.variant_unit_scale else @@ -279,6 +286,23 @@ productEditModule.controller "AdminProductEditCtrl", [ window.location = "/admin/products/" + product.permalink_live + ((if variant then "/variants/" + variant.id else "")) + "/edit" + $scope.addVariant = (product) -> + product.variants.push + id: $scope.nextVariantId() + price: null + unit_value: null + unit_description: null + on_demand: false + on_hand: null + $scope.displayProperties[product.id].showVariants = true + + + $scope.nextVariantId = -> + $scope.variantIdCounter = 0 unless $scope.variantIdCounter? + $scope.variantIdCounter -= 1 + $scope.variantIdCounter + + $scope.deleteProduct = (product) -> if confirm("Are you sure?") $http( @@ -291,14 +315,20 @@ productEditModule.controller "AdminProductEditCtrl", [ $scope.deleteVariant = (product, variant) -> - if confirm("Are you sure?") - $http( - method: "DELETE" - url: "/api/products/" + product.id + "/variants/" + variant.id - ).success (data) -> - product.variants.splice product.variants.indexOf(variant), 1 - delete $scope.dirtyProducts[product.id].variants[variant.id] if $scope.dirtyProducts.hasOwnProperty(product.id) and $scope.dirtyProducts[product.id].hasOwnProperty("variants") and $scope.dirtyProducts[product.id].variants.hasOwnProperty(variant.id) - $scope.displayDirtyProducts() + if !$scope.variantSaved(variant) + $scope.removeVariant(product, variant) + else + if confirm("Are you sure?") + $http( + method: "DELETE" + url: "/api/products/" + product.id + "/variants/" + variant.id + ).success (data) -> + $scope.removeVariant(product, variant) + + $scope.removeVariant = (product, variant) -> + product.variants.splice product.variants.indexOf(variant), 1 + delete $scope.dirtyProducts[product.id].variants[variant.id] if $scope.dirtyProducts.hasOwnProperty(product.id) and $scope.dirtyProducts[product.id].hasOwnProperty("variants") and $scope.dirtyProducts[product.id].variants.hasOwnProperty(variant.id) + $scope.displayDirtyProducts() $scope.cloneProduct = (product) -> @@ -319,10 +349,31 @@ productEditModule.controller "AdminProductEditCtrl", [ Object.keys(product.variants).length > 0 + $scope.hasUnit = (product) -> + product.variant_unit_with_scale? + + + $scope.variantSaved = (variant) -> + variant.hasOwnProperty('id') && variant.id > 0 + + $scope.hasOnDemandVariants = (product) -> (variant for id, variant of product.variants when variant.on_demand).length > 0 + $scope.submitProducts = -> + # Pack pack $scope.products, so they will match the list returned from the server, + # then pack $scope.dirtyProducts, ensuring that the correct product info is sent to the server. + $scope.packProduct product for id, product of $scope.products + $scope.packProduct product for id, product of $scope.dirtyProducts + + productsToSubmit = filterSubmitProducts($scope.dirtyProducts) + if productsToSubmit.length > 0 + $scope.updateProducts productsToSubmit # Don't submit an empty list + else + $scope.setMessage $scope.updateStatusMessage, "No changes to update.", color: "grey", 3000 + + $scope.updateProducts = (productsToSubmit) -> $scope.displayUpdating() $http( @@ -338,27 +389,19 @@ productEditModule.controller "AdminProductEditCtrl", [ # doing things. TODO: Review together and decide on strategy here. -- Rohan, 14-1-2014 #if subset($scope.productsWithoutDerivedAttributes(), data) - if angular.toJson($scope.productsWithoutDerivedAttributes($scope.products)) == angular.toJson($scope.productsWithoutDerivedAttributes(data)) + if $scope.productListsMatch $scope.products, data $scope.resetProducts data $timeout -> $scope.displaySuccess() else + # console.log angular.toJson($scope.productsWithoutDerivedAttributes($scope.products)) + # console.log "---" + # console.log angular.toJson($scope.productsWithoutDerivedAttributes(data)) + # console.log "---" $scope.displayFailure "Product lists do not match." ).error (data, status) -> $scope.displayFailure "Server returned with error status: " + status - $scope.submitProducts = -> - # Pack pack $scope.products, so they will match the list returned from the server, - # then pack $scope.dirtyProducts, ensuring that the correct product info is sent to the server. - $scope.packProduct product for id, product of $scope.products - $scope.packProduct product for id, product of $scope.dirtyProducts - - productsToSubmit = filterSubmitProducts($scope.dirtyProducts) - if productsToSubmit.length > 0 - $scope.updateProducts productsToSubmit # Don't submit an empty list - else - $scope.setMessage $scope.updateStatusMessage, "No changes to update.", color: "grey", 3000 - $scope.packProduct = (product) -> if product.variant_unit_with_scale match = product.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/) @@ -368,6 +411,7 @@ productEditModule.controller "AdminProductEditCtrl", [ else product.variant_unit = product.variant_unit_with_scale product.variant_unit_scale = null + $scope.packVariant product, product.master if product.master if product.variants for id, variant of product.variants $scope.packVariant product, variant @@ -375,14 +419,33 @@ productEditModule.controller "AdminProductEditCtrl", [ $scope.packVariant = (product, variant) -> if variant.hasOwnProperty("unit_value_with_description") - match = variant.unit_value_with_description.match(/^([\d\.]+|)( |)(.*)$/) + match = variant.unit_value_with_description.match(/^([\d\.]+(?= |$)|)( |)(.*)$/) if match product = $scope.findProduct(product.id) - variant.unit_value = parseFloat(match[1]) || null + variant.unit_value = parseFloat(match[1]) + variant.unit_value = null if isNaN(variant.unit_value) variant.unit_value *= product.variant_unit_scale if variant.unit_value && product.variant_unit_scale variant.unit_description = match[3] + $scope.productListsMatch = (clientProducts, serverProducts) -> + $scope.copyNewVariantIds clientProducts, serverProducts + angular.toJson($scope.productsWithoutDerivedAttributes(clientProducts)) == angular.toJson($scope.productsWithoutDerivedAttributes(serverProducts)) + + + # When variants are created clientside, they are given a negative id. The server + # responds with a real id, which would cause the productListsMatch() check to fail. + # To avoid that false negative, we copy the server variant id to the client for any + # negative ids. + $scope.copyNewVariantIds = (clientProducts, serverProducts) -> + if clientProducts? + for product, i in clientProducts + if product.variants? + for variant, j in product.variants + if variant.id < 0 + variant.id = serverProducts[i].variants[j].id + + $scope.productsWithoutDerivedAttributes = (products) -> products_filtered = [] if products @@ -394,6 +457,7 @@ productEditModule.controller "AdminProductEditCtrl", [ delete variant.unit_value_with_description # If we end up live-updating this field, we might want to reinstate its verification here delete variant.options_text + delete product.master products_filtered @@ -456,35 +520,30 @@ productEditModule.filter "rangeArray", -> input.push(i) for i in [start..end] input + filterSubmitProducts = (productsToFilter) -> filteredProducts = [] if productsToFilter instanceof Object angular.forEach productsToFilter, (product) -> if product.hasOwnProperty("id") - filteredProduct = {} + filteredProduct = {id: product.id} filteredVariants = [] + hasUpdatableProperty = false + if product.hasOwnProperty("variants") angular.forEach product.variants, (variant) -> - if not variant.deleted_at? and variant.hasOwnProperty("id") - hasUpdateableProperty = false - filteredVariant = {} - filteredVariant.id = variant.id - if variant.hasOwnProperty("on_hand") - filteredVariant.on_hand = variant.on_hand - hasUpdatableProperty = true - if variant.hasOwnProperty("price") - filteredVariant.price = variant.price - hasUpdatableProperty = true - if variant.hasOwnProperty("unit_value") - filteredVariant.unit_value = variant.unit_value - hasUpdatableProperty = true - if variant.hasOwnProperty("unit_description") - filteredVariant.unit_description = variant.unit_description - hasUpdatableProperty = true - filteredVariants.push filteredVariant if hasUpdatableProperty + result = filterSubmitVariant variant + filteredVariant = result.filteredVariant + variantHasUpdatableProperty = result.hasUpdatableProperty + filteredVariants.push filteredVariant if variantHasUpdatableProperty + + if product.master?.hasOwnProperty("unit_value") + filteredProduct.unit_value = product.master.unit_value + hasUpdatableProperty = true + if product.master?.hasOwnProperty("unit_description") + filteredProduct.unit_description = product.master.unit_description + hasUpdatableProperty = true - hasUpdatableProperty = false - filteredProduct.id = product.id if product.hasOwnProperty("name") filteredProduct.name = product.name hasUpdatableProperty = true @@ -515,13 +574,31 @@ filterSubmitProducts = (productsToFilter) -> filteredProducts -addDirtyProperty = (dirtyObjects, objectID, propertyName, propertyValue) -> - if dirtyObjects.hasOwnProperty(objectID) - dirtyObjects[objectID][propertyName] = propertyValue - else +filterSubmitVariant = (variant) -> + hasUpdatableProperty = false + filteredVariant = {} + if not variant.deleted_at? and variant.hasOwnProperty("id") + filteredVariant.id = variant.id unless variant.id <= 0 + if variant.hasOwnProperty("on_hand") + filteredVariant.on_hand = variant.on_hand + hasUpdatableProperty = true + if variant.hasOwnProperty("price") + filteredVariant.price = variant.price + hasUpdatableProperty = true + if variant.hasOwnProperty("unit_value") + filteredVariant.unit_value = variant.unit_value + hasUpdatableProperty = true + if variant.hasOwnProperty("unit_description") + filteredVariant.unit_description = variant.unit_description + hasUpdatableProperty = true + {filteredVariant: filteredVariant, hasUpdatableProperty: hasUpdatableProperty} + + +addDirtyProperty = (dirtyObjects, objectID, parsedPropertyName, propertyValue) -> + if !dirtyObjects.hasOwnProperty(objectID) dirtyObjects[objectID] = {} dirtyObjects[objectID]["id"] = objectID - dirtyObjects[objectID][propertyName] = propertyValue + parsedPropertyName.assign(dirtyObjects[objectID], propertyValue) removeCleanProperty = (dirtyObjects, objectID, propertyName) -> diff --git a/app/assets/stylesheets/admin/products.css.scss b/app/assets/stylesheets/admin/products.css.scss index 026e4cf83b..20f9c49a84 100644 --- a/app/assets/stylesheets/admin/products.css.scss +++ b/app/assets/stylesheets/admin/products.css.scss @@ -106,6 +106,8 @@ ul.column-list { } table#listing_products.bulk { + clear: both; + td.supplier { select { width: 125px; diff --git a/app/assets/stylesheets/darkswarm/footer.sass b/app/assets/stylesheets/darkswarm/footer.sass index ba2424e94b..df649a8d59 100644 --- a/app/assets/stylesheets/darkswarm/footer.sass +++ b/app/assets/stylesheets/darkswarm/footer.sass @@ -10,3 +10,7 @@ img display: block margin: 0px auto 8px + + .contact + strong + padding-right: 1em diff --git a/app/assets/stylesheets/darkswarm/shop.css.sass b/app/assets/stylesheets/darkswarm/shop.css.sass index 6127a84faf..1b9109cfaa 100644 --- a/app/assets/stylesheets/darkswarm/shop.css.sass +++ b/app/assets/stylesheets/darkswarm/shop.css.sass @@ -122,7 +122,7 @@ product //&.notes //width: 140px &.variant - width: 100px + width: 180px &.quantity, &.bulk, &.price width: 90px .notes diff --git a/app/controllers/spree/admin/variants_controller_decorator.rb b/app/controllers/spree/admin/variants_controller_decorator.rb new file mode 100644 index 0000000000..a567f0a8c9 --- /dev/null +++ b/app/controllers/spree/admin/variants_controller_decorator.rb @@ -0,0 +1,3 @@ +Spree::Admin::VariantsController.class_eval do + helper 'spree/products' +end diff --git a/app/helpers/spree/products_helper_decorator.rb b/app/helpers/spree/products_helper_decorator.rb index 8c7b7fbe54..99596a73e9 100644 --- a/app/helpers/spree/products_helper_decorator.rb +++ b/app/helpers/spree/products_helper_decorator.rb @@ -4,5 +4,15 @@ module Spree def variant_price_diff(variant) "(#{number_to_currency variant.price})" end + + + def product_has_variant_unit_option_type?(product) + product.option_types.any? { |option_type| variant_unit_option_type? option_type } + end + + + def variant_unit_option_type?(option_type) + Spree::Product.all_variant_unit_option_types.include? option_type + end end end diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index 3f570bdd2f..33ccf73a81 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -11,8 +11,9 @@ Spree::Product.class_eval do has_many :distributors, :through => :product_distributions accepts_nested_attributes_for :product_distributions, :allow_destroy => true + delegate_belongs_to :master, :unit_value, :unit_description - attr_accessible :supplier_id, :distributor_ids, :product_distributions_attributes, :group_buy, :group_buy_unit_size, :variant_unit, :variant_unit_scale, :variant_unit_name, :notes + attr_accessible :supplier_id, :distributor_ids, :product_distributions_attributes, :group_buy, :group_buy_unit_size, :variant_unit, :variant_unit_scale, :variant_unit_name, :unit_value, :unit_description, :notes validates_presence_of :supplier diff --git a/app/models/spree/product_set.rb b/app/models/spree/product_set.rb index db3095187e..e79408d8c5 100644 --- a/app/models/spree/product_set.rb +++ b/app/models/spree/product_set.rb @@ -20,7 +20,11 @@ class Spree::ProductSet < ModelSet def update_variants_attributes(product, variants_attributes) variants_attributes.each do |attributes| e = product.variants.detect { |e| e.id.to_s == attributes[:id].to_s && !e.id.nil? } - e.update_attributes(attributes.except(:id)) if e.present? + if e.present? + e.update_attributes(attributes.except(:id)) + else + product.variants.create attributes + end end end diff --git a/app/models/spree/variant_decorator.rb b/app/models/spree/variant_decorator.rb index de9a9fa9d0..12a4ca1a0e 100644 --- a/app/models/spree/variant_decorator.rb +++ b/app/models/spree/variant_decorator.rb @@ -16,14 +16,11 @@ Spree::Variant.class_eval do price + fees_for(distributor, order_cycle) end - # TODO: This method seems a little redundant. Though perhaps a useful interface. - # Consider removing. def fees_for(distributor, order_cycle) order_cycle.fees_for(self, distributor) end - # Copied and modified from Spree::Variant def options_text values = self.option_values.joins(:option_type).order("#{Spree::OptionType.table_name}.position asc") diff --git a/app/overrides/spree/admin/variants/_form/add_unit_value_and_description.html.haml.deface b/app/overrides/spree/admin/variants/_form/add_unit_value_and_description.html.haml.deface new file mode 100644 index 0000000000..8696134177 --- /dev/null +++ b/app/overrides/spree/admin/variants/_form/add_unit_value_and_description.html.haml.deface @@ -0,0 +1,10 @@ +/ insert_top "[data-hook='admin_variant_form_fields']" + +- if product_has_variant_unit_option_type?(@product) + .field{"data-hook" => "unit_value"} + = f.label :unit_value, "Unit Value" + = f.text_field :unit_value, class: "fullwidth" + + .field{"data-hook" => "unit_description"} + = f.label :unit_description, "Unit Description" + = f.text_field :unit_description, class: "fullwidth" diff --git a/app/overrides/spree/admin/variants/_form/hide_unit_option_types.html.haml.deface b/app/overrides/spree/admin/variants/_form/hide_unit_option_types.html.haml.deface new file mode 100644 index 0000000000..16a0e572b4 --- /dev/null +++ b/app/overrides/spree/admin/variants/_form/hide_unit_option_types.html.haml.deface @@ -0,0 +1,10 @@ +/ replace "[data-hook='presentation']" + +- unless variant_unit_option_type?(option.option_type) + .field{"data-hook" => "presentation"} + = label :new_variant, option.option_type.presentation + - if @variant.new_record? + = select(:new_variant, option.option_type.presentation, option.option_type.option_values.collect {|ov| [ ov.presentation, ov.id ] }, {}, {:class => 'select2 fullwidth'}) + - else + - if opt = @variant.option_values.detect {|o| o.option_type == option.option_type }.try(:presentation) + = text_field(:new_variant, option.option_type.presentation, :value => opt, :disabled => 'disabled', :class => 'fullwidth') diff --git a/app/views/shop/shop/_contact_us.html.haml b/app/views/shop/shop/_contact_us.html.haml index 2cb4d65984..58a69ed38f 100644 --- a/app/views/shop/shop/_contact_us.html.haml +++ b/app/views/shop/shop/_contact_us.html.haml @@ -1,8 +1,18 @@ .contact.small-2.large-3.columns %h3 Contact - %ul - %li= @distributor.email - %li= @distributor.website - = @distributor.address.address1 - = @distributor.address.address2 - = @distributor.address.city + + %p + %strong E + %a{href: "mailto:#{@distributor.email}"}= @distributor.email + + - unless @distributor.website.blank? + %p + %strong W + %a{href: @distributor.website}= @distributor.website + + %p + = @distributor.address.address1 + %br + = @distributor.address.address2 + %br + = @distributor.address.city diff --git a/app/views/shop/shop/_products.html.haml b/app/views/shop/shop/_products.html.haml index 85bd5cf7ac..3456ec29e7 100644 --- a/app/views/shop/shop/_products.html.haml +++ b/app/views/shop/shop/_products.html.haml @@ -21,7 +21,7 @@ {{ product.supplier.name }} %td.notes {{ product.notes | truncate:80 }} %td - {{ product.master.options_text }} + %span{"ng-hide" => "product.variants.length > 0"} {{ product.master.options_text }} %span{"ng-show" => "product.variants.length > 0"} %img.collapse{src: "/assets/collapse.png", "ng-show" => "product.show_variants", diff --git a/app/views/shop/shop/_variant.html.haml b/app/views/shop/shop/_variant.html.haml index 0adb5a2421..9a71ac8b39 100644 --- a/app/views/shop/shop/_variant.html.haml +++ b/app/views/shop/shop/_variant.html.haml @@ -1,5 +1,6 @@ -%td{colspan: 2} +%td +%td.notes %td {{variant.options_text}} %td %input{type: :number, diff --git a/app/views/spree/admin/products/bulk_edit.html.haml b/app/views/spree/admin/products/bulk_edit.html.haml index e2ae2dcee4..28cefe564f 100644 --- a/app/views/spree/admin/products/bulk_edit.html.haml +++ b/app/views/spree/admin/products/bulk_edit.html.haml @@ -111,13 +111,16 @@ %tr.product %td.left-actions %a{ 'ofn-toggle-variants' => 'true', :class => "view-variants icon-chevron-right", 'ng-show' => 'hasVariants(product)' } + %a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "!hasVariants(product) && hasUnit(product)" } %td.supplier{ 'ng-show' => 'columns.supplier.visible' } %select.select2{ 'ng-model' => 'product.supplier', :name => 'supplier', 'ofn-track-product' => 'supplier', 'ng-options' => 's.name for s in suppliers' } %td{ 'ng-show' => 'columns.name.visible' } %input{ 'ng-model' => "product.name", :name => 'product_name', 'ofn-track-product' => 'name', :type => 'text' } %td.unit{ 'ng-show' => 'columns.unit.visible' } %select.select2{ 'ng-model' => 'product.variant_unit_with_scale', :name => 'variant_unit_with_scale', 'ofn-track-product' => 'variant_unit_with_scale', 'ng-options' => 'unit[1] as unit[0] for unit in variant_unit_options' } - %input{ 'ng-model' => 'product.variant_unit_name', :name => 'variant_unit_name', 'ofn-track-product' => 'variant_unit_name', 'ng-show' => "product.variant_unit_with_scale == 'items'", :type => 'text' } + %option{'value' => '', 'ng-hide' => "hasVariants(product)"} + %input{ 'ng-model' => 'product.master.unit_value_with_description', :name => 'master_unit_value_with_description', 'ofn-track-product' => 'master.unit_value_with_description', :type => 'text', :placeholder => 'value', 'ng-show' => "!hasVariants(product) && hasUnit(product)" } + %input{ 'ng-model' => 'product.variant_unit_name', :name => 'variant_unit_name', 'ofn-track-product' => 'variant_unit_name', :placeholder => 'unit', 'ng-show' => "product.variant_unit_with_scale == 'items'", :type => 'text' } %td{ 'ng-show' => 'columns.price.visible' } %input{ 'ng-model' => 'product.price', 'ofn-decimal' => :true, :name => 'price', 'ofn-track-product' => 'price', :type => 'text' } %td{ 'ng-show' => 'columns.on_hand.visible' } @@ -133,7 +136,8 @@ %a{ 'ng-click' => 'deleteProduct(product)', :class => "delete-product icon-trash no-text" } %tr.variant{ 'ng-repeat' => 'variant in product.variants', 'ng-show' => 'displayProperties[product.id].showVariants', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" } %td.left-actions - %a{ :class => "variant-item icon-caret-right" } + %a{ :class => "variant-item icon-caret-right", 'ng-hide' => "$last" } + %a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "$last" } %td{ 'ng-show' => 'columns.supplier.visible' } %td{ 'ng-show' => 'columns.name.visible' } {{ variant.options_text }} @@ -146,7 +150,7 @@ %span{ 'ng-bind' => 'variant.on_hand', :name => 'variant_on_hand', 'ng-show' => 'variant.on_demand' } %td{ 'ng-show' => 'columns.available_on.visible' } %td.actions - %a{ 'ng-click' => 'editWarn(product,variant)', :class => "edit-variant icon-edit no-text" } + %a{ 'ng-click' => 'editWarn(product,variant)', :class => "edit-variant icon-edit no-text", 'ng-show' => "variantSaved(variant)" } %td.actions %td.actions %a{ 'ng-click' => 'deleteVariant(product,variant)', :class => "delete-variant icon-trash no-text" } diff --git a/app/views/spree/api/products/bulk_show.v1.rabl b/app/views/spree/api/products/bulk_show.v1.rabl index 1f07d3d91f..a220737007 100644 --- a/app/views/spree/api/products/bulk_show.v1.rabl +++ b/app/views/spree/api/products/bulk_show.v1.rabl @@ -7,8 +7,11 @@ node( :on_hand ) { |p| p.on_hand.to_f.finite? ? p.on_hand : "On demand" } node( :available_on ) { |p| p.available_on.blank? ? "" : p.available_on.strftime("%F %T") } node( :permalink_live ) { |p| p.permalink } node( :supplier ) do |p| - partial 'spree/api/enterprises/bulk_show', :object => p.supplier + partial 'spree/api/enterprises/bulk_show', :object => p.supplier end node( :variants ) do |p| - partial 'spree/api/variants/bulk_index', :object => p.variants.order('id ASC') + partial 'spree/api/variants/bulk_index', :object => p.variants.reorder('spree_variants.id ASC') +end +node( :master ) do |p| + partial 'spree/api/variants/bulk_show', :object => p.master end diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index 352c3d14c7..4644aae8c1 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -229,7 +229,8 @@ feature %q{ end end - scenario "create a new product" do + + scenario "creating a new product" do s = FactoryGirl.create(:supplier_enterprise) d = FactoryGirl.create(:distributor_enterprise) @@ -253,6 +254,57 @@ feature %q{ page.should have_field "product_name", with: 'Big Bag Of Apples' end + + scenario "creating new variants" do + # Given a product without variants or a unit + p = FactoryGirl.create(:product, variant_unit: nil, variant_unit_scale: nil) + login_to_admin_section + visit '/admin/products/bulk_edit' + + # I should not see an add variant button + page.should_not have_selector 'a.add-variant', visible: true + + # When I set the unit + select "Weight (kg)", from: "variant_unit_with_scale" + + # I should see an add variant button + page.should have_selector 'a.add-variant', visible: true + + # When I add three variants + page.find('a.add-variant', visible: true).click + page.find('a.add-variant', visible: true).click + page.find('a.add-variant', visible: true).click + + # They should be added, and should see no edit buttons + page.all("tr.variant").count.should == 3 + page.should_not have_selector "a.edit-variant", visible: true + + # When I remove two, they should be removed + page.all('a.delete-variant').first.click + page.all('a.delete-variant').first.click + page.all("tr.variant").count.should == 1 + + # When I fill out variant details and hit update + fill_in "variant_unit_value_with_description", with: "4000 (12x250 mL bottles)" + fill_in "variant_price", with: "4.0" + fill_in "variant_on_hand", with: "10" + click_button 'Update' + page.find("span#update-status-message").should have_content "Update complete" + + # Then I should see edit buttons for the new variant + page.should have_selector "a.edit-variant", visible: true + + # And the variants should be saved + visit '/admin/products/bulk_edit' + page.should have_selector "a.view-variants" + first("a.view-variants").click + + page.should have_field "variant_unit_value_with_description", with: "4000 (12x250 mL bottles)" + page.should have_field "variant_price", with: "4.0" + page.should have_field "variant_on_hand", with: "10" + end + + scenario "updating a product with no variants (except master)" do s1 = FactoryGirl.create(:supplier_enterprise) s2 = FactoryGirl.create(:supplier_enterprise) @@ -295,7 +347,7 @@ feature %q{ page.should have_field "on_hand", with: "18" end - scenario "updating a product with an items variant unit" do + scenario "updating a product with a variant unit of 'items'" do p = FactoryGirl.create(:product, variant_unit: 'weight', variant_unit_scale: 1000) login_to_admin_section @@ -341,6 +393,49 @@ feature %q{ end + describe "setting the master unit value for a product without variants" do + it "sets the master unit value" do + p = FactoryGirl.create(:product, variant_unit: nil, variant_unit_scale: nil) + + login_to_admin_section + + visit '/admin/products/bulk_edit' + + page.should have_select "variant_unit_with_scale", selected: '' + page.should_not have_field "master_unit_value_with_description", visible: true + + select "Weight (kg)", from: "variant_unit_with_scale" + fill_in "master_unit_value_with_description", with: '123 abc' + + click_button 'Update' + page.find("span#update-status-message").should have_content "Update complete" + + visit '/admin/products/bulk_edit' + + page.should have_select "variant_unit_with_scale", selected: "Weight (kg)" + page.should have_field "master_unit_value_with_description", with: "123 abc" + + p.reload + p.variant_unit.should == 'weight' + p.variant_unit_scale.should == 1000 + p.master.unit_value.should == 123000 + p.master.unit_description.should == 'abc' + end + + it "does not show the field when the product has variants" do + p = FactoryGirl.create(:product, variant_unit: nil, variant_unit_scale: nil) + v = FactoryGirl.create(:variant, product: p, unit_value: nil, unit_description: nil) + + login_to_admin_section + + visit '/admin/products/bulk_edit' + + select "Weight (kg)", from: "variant_unit_with_scale" + page.should_not have_field "master_unit_value_with_description", visible: true + end + end + + scenario "updating a product with variants" do s1 = FactoryGirl.create(:supplier_enterprise) s2 = FactoryGirl.create(:supplier_enterprise) diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 8f2c1b2a34..5cadd11a29 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -189,24 +189,20 @@ feature %q{ end # And the suppliers should have fees - page.find('#order_cycle_incoming_exchange_0_enterprise_fees_0_enterprise_id option[selected=selected]'). - text.should == oc.suppliers.first.name - page.find('#order_cycle_incoming_exchange_0_enterprise_fees_0_enterprise_fee_id option[selected=selected]'). - text.should == oc.suppliers.first.enterprise_fees.first.name + page.should have_select 'order_cycle_incoming_exchange_0_enterprise_fees_0_enterprise_id', selected: oc.suppliers.first.name + page.should have_select 'order_cycle_incoming_exchange_0_enterprise_fees_0_enterprise_fee_id', selected: oc.suppliers.first.enterprise_fees.first.name - page.find('#order_cycle_incoming_exchange_1_enterprise_fees_0_enterprise_id option[selected=selected]'). - text.should == oc.suppliers.last.name - page.find('#order_cycle_incoming_exchange_1_enterprise_fees_0_enterprise_fee_id option[selected=selected]'). - text.should == oc.suppliers.last.enterprise_fees.first.name + page.should have_select 'order_cycle_incoming_exchange_1_enterprise_fees_0_enterprise_id', selected: oc.suppliers.last.name + page.should have_select 'order_cycle_incoming_exchange_1_enterprise_fees_0_enterprise_fee_id', selected: oc.suppliers.last.enterprise_fees.first.name # And I should see the distributors page.should have_selector 'td.distributor_name', :text => oc.distributors.first.name page.should have_selector 'td.distributor_name', :text => oc.distributors.last.name - page.find('#order_cycle_outgoing_exchange_0_pickup_time').value.should == 'time 0' - page.find('#order_cycle_outgoing_exchange_0_pickup_instructions').value.should == 'instructions 0' - page.find('#order_cycle_outgoing_exchange_1_pickup_time').value.should == 'time 1' - page.find('#order_cycle_outgoing_exchange_1_pickup_instructions').value.should == 'instructions 1' + page.should have_field 'order_cycle_outgoing_exchange_0_pickup_time', with: 'time 0' + page.should have_field 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'instructions 0' + page.should have_field 'order_cycle_outgoing_exchange_1_pickup_time', with: 'time 1' + page.should have_field 'order_cycle_outgoing_exchange_1_pickup_instructions', with: 'instructions 1' # And the distributors should have products page.all('table.exchanges tbody tr.distributor').each do |row| @@ -219,15 +215,11 @@ feature %q{ end # And the distributors should have fees - page.find('#order_cycle_outgoing_exchange_0_enterprise_fees_0_enterprise_id option[selected=selected]'). - text.should == oc.distributors.first.name - page.find('#order_cycle_outgoing_exchange_0_enterprise_fees_0_enterprise_fee_id option[selected=selected]'). - text.should == oc.distributors.first.enterprise_fees.first.name + page.should have_select 'order_cycle_outgoing_exchange_0_enterprise_fees_0_enterprise_id', selected: oc.distributors.first.name + page.should have_select 'order_cycle_outgoing_exchange_0_enterprise_fees_0_enterprise_fee_id', selected: oc.distributors.first.enterprise_fees.first.name - page.find('#order_cycle_outgoing_exchange_1_enterprise_fees_0_enterprise_id option[selected=selected]'). - text.should == oc.distributors.last.name - page.find('#order_cycle_outgoing_exchange_1_enterprise_fees_0_enterprise_fee_id option[selected=selected]'). - text.should == oc.distributors.last.enterprise_fees.first.name + page.should have_select 'order_cycle_outgoing_exchange_1_enterprise_fees_0_enterprise_id', selected: oc.distributors.last.name + page.should have_select 'order_cycle_outgoing_exchange_1_enterprise_fees_0_enterprise_fee_id', selected: oc.distributors.last.enterprise_fees.first.name end diff --git a/spec/features/admin/products_spec.rb b/spec/features/admin/products_spec.rb index 3399785a3c..58d9d57775 100644 --- a/spec/features/admin/products_spec.rb +++ b/spec/features/admin/products_spec.rb @@ -1,8 +1,8 @@ require "spec_helper" feature %q{ - As a supplier - I want set a supplier and distributor(s) for a product + As an admin + I want to set a supplier and distributor(s) for a product } do include AuthenticationWorkflow include WebHelper diff --git a/spec/features/admin/variants_spec.rb b/spec/features/admin/variants_spec.rb new file mode 100644 index 0000000000..7e178ab443 --- /dev/null +++ b/spec/features/admin/variants_spec.rb @@ -0,0 +1,65 @@ +require "spec_helper" + +feature %q{ + As an admin + I want to manage product variants +} do + include AuthenticationWorkflow + include WebHelper + + scenario "editing unit value and description for a variant" do + # Given a product with unit-related option types, with a variant + p = create(:simple_product, variant_unit: "weight", variant_unit_scale: "1") + v = create(:variant, product: p, unit_value: 1, unit_description: 'foo') + + # And the product has option types for the unit-related and non-unit-related option values + p.option_types << v.option_values.first.option_type + + # When I view the variant + login_to_admin_section + click_link 'Products' + within('#sub_nav') { click_link 'Products' } + click_link p.name + click_link 'Variants' + page.find('table.index .icon-edit').click + + # Then I should not see a traditional option value field for the unit-related option value + page.all("div[data-hook='presentation'] input").count.should == 1 + + # And I should see unit value and description fields for the unit-related option value + page.should have_field "variant_unit_value", with: "1" + page.should have_field "variant_unit_description", with: "foo" + + # When I update the fields and save the variant + fill_in "variant_unit_value", with: "123" + fill_in "variant_unit_description", with: "bar" + click_button 'Update' + page.should have_content %Q(Variant "#{p.name}" has been successfully updated!) + + # Then the unit value and description should have been saved + v.reload + v.unit_value.should == 123 + v.unit_description.should == 'bar' + end + + it "does not show unit value or description fields when the product does not have a unit-related option type" do + # Given a product without unit-related option types, with a variant + p = create(:simple_product, variant_unit: nil, variant_unit_scale: nil) + v = create(:variant, product: p, unit_value: nil, unit_description: nil) + + # And the product has option types for the variant's option values + p.option_types << v.option_values.first.option_type + + # When I view the variant + login_to_admin_section + click_link 'Products' + within('#sub_nav') { click_link 'Products' } + click_link p.name + click_link 'Variants' + page.find('table.index .icon-edit').click + + # Then I should not see unit value and description fields + page.should_not have_field "variant_unit_value" + page.should_not have_field "variant_unit_description" + end +end diff --git a/spec/javascripts/unit/bulk_product_update_spec.js.coffee b/spec/javascripts/unit/bulk_product_update_spec.js.coffee index 9fe79278d4..bd85419ac9 100644 --- a/spec/javascripts/unit/bulk_product_update_spec.js.coffee +++ b/spec/javascripts/unit/bulk_product_update_spec.js.coffee @@ -121,6 +121,27 @@ describe "filtering products for submission to database", -> ] ] + it "returns variants with a negative id without that id", -> + testProduct = + id: 1 + variants: [ + id: -1 + on_hand: 5 + price: 12.0 + unit_value: 250 + unit_description: "(bottle)" + ] + + expect(filterSubmitProducts([testProduct])).toEqual [ + id: 1 + variants_attributes: [ + on_hand: 5 + price: 12.0 + unit_value: 250 + unit_description: "(bottle)" + ] + ] + it "does not return variants_attributes property if variants is an empty array", -> testProduct = id: 1 @@ -171,6 +192,10 @@ describe "filtering products for submission to database", -> group_buy: null group_buy_unit_size: null on_demand: false + master: + id: 2 + unit_value: 250 + unit_description: "foo" variants: [ id: 1 on_hand: 2 @@ -190,6 +215,8 @@ describe "filtering products for submission to database", -> variant_unit: 'volume' variant_unit_scale: 1 variant_unit_name: 'loaf' + unit_value: 250 + unit_description: "foo" available_on: available_on variants_attributes: [ id: 1 @@ -202,10 +229,17 @@ describe "filtering products for submission to database", -> describe "Maintaining a live record of dirty products and properties", -> + parse = null + beforeEach -> + module "ofn.bulk_product_edit" + beforeEach inject(($parse) -> + parse = $parse + ) + describe "adding product properties to the dirtyProducts object", -> # Applies to both products and variants it "adds the product and the property to the list if property is dirty", -> dirtyProducts = {} - addDirtyProperty dirtyProducts, 1, "name", "Product 1" + addDirtyProperty dirtyProducts, 1, parse("name"), "Product 1" expect(dirtyProducts).toEqual 1: id: 1 name: "Product 1" @@ -216,7 +250,7 @@ describe "Maintaining a live record of dirty products and properties", -> id: 1 notaname: "something" - addDirtyProperty dirtyProducts, 1, "name", "Product 3" + addDirtyProperty dirtyProducts, 1, parse("name"), "Product 3" expect(dirtyProducts).toEqual 1: id: 1 notaname: "something" @@ -228,7 +262,7 @@ describe "Maintaining a live record of dirty products and properties", -> id: 1 name: "Product 1" - addDirtyProperty dirtyProducts, 1, "name", "Product 2" + addDirtyProperty dirtyProducts, 1, parse("name"), "Product 2" expect(dirtyProducts).toEqual 1: id: 1 name: "Product 2" @@ -420,12 +454,24 @@ describe "AdminProductEditCtrl", -> scope.loadVariantUnit product expect(product.variant_unit_with_scale).toEqual "items" + it "loads data for variants (inc. master)", -> + spyOn scope, "loadVariantVariantUnit" + + product = + variant_unit_scale: 1.0 + master: {id: 1, unit_value: 1, unit_description: '(one)'} + variants: [{id: 2, unit_value: 2, unit_description: '(two)'}] + scope.loadVariantUnit product + + expect(scope.loadVariantVariantUnit).toHaveBeenCalledWith product, product.variants[0] + expect(scope.loadVariantVariantUnit).toHaveBeenCalledWith product, product.master + describe "setting variant unit_value_with_description", -> it "sets by combining unit_value and unit_description", -> product = variant_unit_scale: 1.0 variants: [{id: 1, unit_value: 1, unit_description: '(bottle)'}] - scope.loadVariantUnit product + scope.loadVariantVariantUnit product, product.variants[0] expect(product.variants[0]).toEqual id: 1 unit_value: 1 @@ -436,23 +482,30 @@ describe "AdminProductEditCtrl", -> product = variant_unit_scale: 1.0 variants: [{id: 1, unit_value: 1}] - scope.loadVariantUnit product + scope.loadVariantVariantUnit product, product.variants[0] expect(product.variants[0].unit_value_with_description).toEqual '1' it "uses unit_description when value is missing", -> product = variant_unit_scale: 1.0 variants: [{id: 1, unit_description: 'Small'}] - scope.loadVariantUnit product + scope.loadVariantVariantUnit product, product.variants[0] expect(product.variants[0].unit_value_with_description).toEqual 'Small' it "converts values from base value to chosen unit", -> product = variant_unit_scale: 1000.0 variants: [{id: 1, unit_value: 2500}] - scope.loadVariantUnit product + scope.loadVariantVariantUnit product, product.variants[0] expect(product.variants[0].unit_value_with_description).toEqual '2.5' + it "displays a unit_value of zero", -> + product = + variant_unit_scale: 1.0 + variants: [{id: 1, unit_value: 0}] + scope.loadVariantVariantUnit product, product.variants[0] + expect(product.variants[0].unit_value_with_description).toEqual '0' + describe "calculating the scaled unit value for a variant", -> it "returns the scaled value when variant has a unit_value", -> @@ -465,6 +518,11 @@ describe "AdminProductEditCtrl", -> variant = {unit_value: 5} expect(scope.variantUnitValue(product, variant)).toEqual 5 + it "returns zero when the value is zero", -> + product = {} + variant = {unit_value: 0} + expect(scope.variantUnitValue(product, variant)).toEqual 0 + it "returns null when the variant has no unit_value", -> product = {} variant = {} @@ -578,6 +636,43 @@ describe "AdminProductEditCtrl", -> expect(scope.hasOnDemandVariants(product)).toBe(false) + describe "determining whether a product has variants", -> + it "returns true when it does", -> + product = + variants: [{id: 1}, {id: 2}] + expect(scope.hasVariants(product)).toBe(true) + + it "returns false when it does not", -> + product = + variants: [] + expect(scope.hasVariants(product)).toBe(false) + + + describe "determining whether a product has a unit", -> + it "returns true when it does", -> + product = + variant_unit_with_scale: 'weight_1000' + expect(scope.hasUnit(product)).toBe(true) + + it "returns false when its unit is undefined", -> + product = {} + expect(scope.hasUnit(product)).toBe(false) + + + describe "determining whether a variant has been saved", -> + it "returns true when it has a positive id", -> + variant = {id: 1} + expect(scope.variantSaved(variant)).toBe(true) + + it "returns false when it has no id", -> + variant = {} + expect(scope.variantSaved(variant)).toBe(false) + + it "returns false when it has a negative id", -> + variant = {id: -1} + expect(scope.variantSaved(variant)).toBe(false) + + describe "submitting products to be updated", -> describe "packing products", -> it "extracts variant_unit_with_scale into variant_unit and variant_unit_scale", -> @@ -610,6 +705,17 @@ describe "AdminProductEditCtrl", -> variant_unit_scale: null variant_unit_with_scale: 'items' + it "packs the master variant", -> + spyOn scope, "packVariant" + testVariant = {id: 1} + testProduct = + id: 1 + master: testVariant + + scope.packProduct(testProduct) + + expect(scope.packVariant).toHaveBeenCalledWith(testProduct, testVariant) + it "packs each variant", -> spyOn scope, "packVariant" testVariant = {id: 1} @@ -654,6 +760,14 @@ describe "AdminProductEditCtrl", -> unit_description: 'Medium' unit_value_with_description: "Medium" + it "extracts into unit_description when a string starting with a number is provided", -> + testVariant = {unit_value_with_description: "1kg"} + scope.packVariant(testProduct, testVariant) + expect(testVariant).toEqual + unit_value: null + unit_description: '1kg' + unit_value_with_description: "1kg" + it "sets blank values when no value provided", -> testVariant = {unit_value_with_description: ""} scope.packVariant(testProduct, testVariant) @@ -667,6 +781,15 @@ describe "AdminProductEditCtrl", -> scope.packVariant(testProduct, testVariant) expect(testVariant).toEqual {} + it "sets zero when the field is zero", -> + testProduct = {id: 123, variant_unit_scale: 1.0} + testVariant = {unit_value_with_description: "0"} + scope.packVariant(testProduct, testVariant) + expect(testVariant).toEqual + unit_value: 0 + unit_description: '' + unit_value_with_description: "0" + it "converts value from chosen unit to base unit", -> testProduct = {id: 123, variant_unit_scale: 1000} testVariant = {unit_value_with_description: "250.5"} @@ -789,6 +912,24 @@ describe "AdminProductEditCtrl", -> expect(scope.displayFailure).toHaveBeenCalled() + describe "copying new variant ids from server to client", -> + it "copies server ids to the client where the client id is negative", -> + clientProducts = [ + { + id: 123 + variants: [{id: 1}, {id: -2}, {id: -3}] + } + ] + serverProducts = [ + { + id: 123 + variants: [{id: 1}, {id: 4534}, {id: 3453}] + } + ] + scope.copyNewVariantIds(clientProducts, serverProducts) + expect(clientProducts).toEqual(serverProducts) + + describe "fetching products without derived attributes", -> it "returns products without the variant_unit_with_scale field", -> scope.products = [{id: 123, variant_unit_with_scale: 'weight_1000'}] @@ -820,6 +961,14 @@ describe "AdminProductEditCtrl", -> } ] + it "removes the master variant", -> + scope.products = [{id: 123, master: {id: 234, unit_value_with_description: 'foo'}}] + expect(scope.productsWithoutDerivedAttributes(scope.products)).toEqual [ + { + id: 123 + } + ] + describe "deep copying products", -> it "copies products", -> @@ -845,6 +994,27 @@ describe "AdminProductEditCtrl", -> expect(scope.findProduct(123)).toBeNull() + describe "adding variants", -> + beforeEach -> + scope.displayProperties ||= {123: {}} + + it "adds first and subsequent variants", -> + product = {id: 123, variants: []} + scope.addVariant(product) + scope.addVariant(product) + expect(product).toEqual + id: 123 + variants: [ + {id: -1, price: null, unit_value: null, unit_description: null, on_demand: false, on_hand: null} + {id: -2, price: null, unit_value: null, unit_description: null, on_demand: false, on_hand: null} + ] + + it "shows the variant(s)", -> + product = {id: 123, variants: []} + scope.addVariant(product) + expect(scope.displayProperties[123].showVariants).toBe(true) + + describe "deleting products", -> it "deletes products with a http delete request to /api/products/id", -> spyOn(window, "confirm").andReturn true @@ -898,83 +1068,100 @@ describe "AdminProductEditCtrl", -> describe "deleting variants", -> - it "deletes variants with a http delete request to /api/products/product_id/variants/(variant_id)", -> - spyOn(window, "confirm").andReturn true - scope.products = [ - { - id: 9 - permalink_live: "apples" - variants: [ - id: 3 - price: 12 - ] - } - { - id: 13 - permalink_live: "oranges" - } - ] - scope.dirtyProducts = {} - httpBackend.expectDELETE("/api/products/9/variants/3").respond 200, "data" - scope.deleteVariant scope.products[0], scope.products[0].variants[0] - httpBackend.flush() + describe "when the variant has not been saved", -> + it "removes the variant from products and dirtyProducts", -> + spyOn(window, "confirm").andReturn true + scope.products = [ + {id: 1, variants: [{id: -1}]} + ] + scope.dirtyProducts = + 1: {id: 1, variants: {'-1': {id: -1}}} + scope.deleteVariant scope.products[0], scope.products[0].variants[0] + expect(scope.products).toEqual([ + {id: 1, variants: []} + ]) + expect(scope.dirtyProducts).toEqual + 1: {id: 1, variants: {}} - it "removes the specified variant from both the variants object and scope.dirtyProducts (if it exists there)", -> - spyOn(window, "confirm").andReturn true - scope.products = [ - { - id: 9 - permalink_live: "apples" - variants: [ - { + + describe "when the variant has been saved", -> + it "deletes variants with a http delete request to /api/products/product_id/variants/(variant_id)", -> + spyOn(window, "confirm").andReturn true + scope.products = [ + { + id: 9 + permalink_live: "apples" + variants: [ id: 3 - price: 12.0 - } - { - id: 4 - price: 6.0 - } - ] - } - { - id: 13 - permalink_live: "oranges" - } - ] - scope.dirtyProducts = - 9: - id: 9 - variants: - 3: - id: 3 - price: 12.0 + price: 12 + ] + } + { + id: 13 + permalink_live: "oranges" + } + ] + scope.dirtyProducts = {} + httpBackend.expectDELETE("/api/products/9/variants/3").respond 200, "data" + scope.deleteVariant scope.products[0], scope.products[0].variants[0] + httpBackend.flush() - 4: - id: 4 - price: 6.0 + it "removes the specified variant from both the variants object and scope.dirtyProducts (if it exists there)", -> + spyOn(window, "confirm").andReturn true + scope.products = [ + { + id: 9 + permalink_live: "apples" + variants: [ + { + id: 3 + price: 12.0 + } + { + id: 4 + price: 6.0 + } + ] + } + { + id: 13 + permalink_live: "oranges" + } + ] + scope.dirtyProducts = + 9: + id: 9 + variants: + 3: + id: 3 + price: 12.0 + + 4: + id: 4 + price: 6.0 + + 13: + id: 13 + name: "P1" - 13: - id: 13 - name: "P1" + httpBackend.expectDELETE("/api/products/9/variants/3").respond 200, "data" + scope.deleteVariant scope.products[0], scope.products[0].variants[0] + httpBackend.flush() + expect(scope.products[0].variants).toEqual [ + id: 4 + price: 6.0 + ] + expect(scope.dirtyProducts).toEqual + 9: + id: 9 + variants: + 4: + id: 4 + price: 6.0 - httpBackend.expectDELETE("/api/products/9/variants/3").respond 200, "data" - scope.deleteVariant scope.products[0], scope.products[0].variants[0] - httpBackend.flush() - expect(scope.products[0].variants).toEqual [ - id: 4 - price: 6.0 - ] - expect(scope.dirtyProducts).toEqual - 9: - id: 9 - variants: - 4: - id: 4 - price: 6.0 - - 13: - id: 13 - name: "P1" + 13: + id: 13 + name: "P1"