mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-03-06 02:51:34 +00:00
Merge branch 'master' into checkout
This commit is contained in:
@@ -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) ->
|
||||
|
||||
@@ -106,6 +106,8 @@ ul.column-list {
|
||||
}
|
||||
|
||||
table#listing_products.bulk {
|
||||
clear: both;
|
||||
|
||||
td.supplier {
|
||||
select {
|
||||
width: 125px;
|
||||
|
||||
@@ -10,3 +10,7 @@
|
||||
img
|
||||
display: block
|
||||
margin: 0px auto 8px
|
||||
|
||||
.contact
|
||||
strong
|
||||
padding-right: 1em
|
||||
|
||||
@@ -122,7 +122,7 @@ product
|
||||
//&.notes
|
||||
//width: 140px
|
||||
&.variant
|
||||
width: 100px
|
||||
width: 180px
|
||||
&.quantity, &.bulk, &.price
|
||||
width: 90px
|
||||
.notes
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
Spree::Admin::VariantsController.class_eval do
|
||||
helper 'spree/products'
|
||||
end
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"
|
||||
@@ -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')
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
|
||||
%td{colspan: 2}
|
||||
%td
|
||||
%td.notes
|
||||
%td {{variant.options_text}}
|
||||
%td
|
||||
%input{type: :number,
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
65
spec/features/admin/variants_spec.rb
Normal file
65
spec/features/admin/variants_spec.rb
Normal file
@@ -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
|
||||
@@ -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"
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user