PI reset absent products

This commit is contained in:
Matt-Yorkley
2017-03-05 21:16:07 +00:00
committed by Rob Harrington
parent 14fb40a996
commit 24fcc3dd34
8 changed files with 177 additions and 29 deletions

View File

@@ -0,0 +1,9 @@
angular.module("ofn.admin").controller "ImportOptionsFormCtrl", ($scope, $timeout, $rootScope, ProductImportService) ->
$scope.toggleResetAbsent = () ->
ProductImportService.updateResetAbsent($scope.supplierId, $scope.nonUpdated, $scope.resetAbsent)
$scope.resetCount = ProductImportService.resetCount
$rootScope.$watch 'resetCount', (newValue) ->
$scope.resetCount = newValue if newValue || newValue == 0

View File

@@ -0,0 +1,15 @@
angular.module("ofn.admin").factory "ProductImportService", ($rootScope, $timeout) ->
new class ProductImportService
suppliers: {}
resetCount: 0
updateResetAbsent: (supplierId, nonUpdated, resetAbsent) ->
if resetAbsent
@suppliers[supplierId] = nonUpdated
@resetCount += nonUpdated
else
@suppliers[supplierId] = null
@resetCount -= nonUpdated
$rootScope.resetCount = @resetCount

View File

@@ -24,16 +24,23 @@ div.panel-section {
text-align: center;
padding-top: 0.18em;
.fa {
i {
font-size: 1.5em;
line-height: 0.9em;
}
.fa-warning {
color: #ee4728;
}
.fa-check-circle {
color: #86d83a;
}
}
.neutral {
color: #BFBFBF;
}
.warning {
color: #ee4728;
}
.success {
color: #86d83a;
}
.info {
color: #68b7c0;
}
div.header-count {
@@ -145,9 +152,7 @@ table.import-settings {
}
.panel-section.import-settings {
.header-icon {
color: #BFBFBF;
}
.header-description {
padding-left: 1em;
}

View File

@@ -25,6 +25,8 @@ class ProductImporter
editable_enterprises.map { |e| @editable_enterprises[e.name] = e.id }
@non_display_attributes = 'id', 'product_id', 'variant_id', 'supplier_id', 'primary_taxon_id', 'category_id', 'shipping_category_id', 'tax_category_id', 'on_hand_nil'
@supplier_products = {total: 0, by_supplier: {}}
@updated_ids = []
validate_all if @sheet
else
@@ -44,6 +46,18 @@ class ProductImporter
@sheet ? @sheet.last_row - 1 : 0
end
def supplier_products
# Return indexed data about existing product count and update count per supplier
@supplier_products[:by_supplier].each do |supplier_id, supplier_data|
supplier_data[:updates_count] = 0 if supplier_data[:updates_count].blank?
if supplier_data[:updates_count] and supplier_data[:existing_products]
@supplier_products[:by_supplier][supplier_id][:non_updated] = supplier_data[:existing_products] - supplier_data[:updates_count]
end
end
@supplier_products
end
def valid_count
@valid_entries.count
end
@@ -140,7 +154,7 @@ class ProductImporter
def validate_all
entries.each_with_index do |entry, i|
line_number = i+2
line_number = i+2 # Roo counts "line 2" as the first line of data
supplier_validation(line_number, entry)
category_validation(line_number, entry)
@@ -149,9 +163,26 @@ class ProductImporter
mark_as_valid(line_number, entry) unless entry_invalid?(line_number)
end
count_existing_products
delete_uploaded_file if item_count.zero? or valid_count.zero?
end
def count_existing_products
@suppliers_index.each do |supplier_name, supplier_id|
if supplier_id
products_count = Spree::Variant.joins(:product).
where('spree_products.supplier_id IN (?)
AND spree_variants.is_master = false
AND spree_variants.deleted_at IS NULL', supplier_id).
count
@supplier_products[:by_supplier][supplier_id] = {existing_products: products_count}
@supplier_products[:total] += products_count
end
end
end
def entry_invalid?(line_number)
!!@invalid_entries[line_number]
end
@@ -246,14 +277,14 @@ class ProductImporter
end
def save_all_valid
updated = {}
already_created = {}
@products_to_create.each do |line_number, data|
entry = data[:entry]
# If we've already added a new product with these attributes
# from this spreadsheet, mark this entry as a new variant with
# the new product id, as this is a now variant of that product...
if updated[entry['supplier_id']] && updated[entry['supplier_id']][entry['name']]
product_id = updated[entry['supplier_id']][entry['name']]
if already_created[entry['supplier_id']] and already_created[entry['supplier_id']][entry['name']]
product_id = already_created[entry['supplier_id']][entry['name']]
mark_as_new_variant(line_number, entry, product_id)
next
end
@@ -264,11 +295,12 @@ class ProductImporter
if product.save
ensure_variant_updated(entry, product)
@products_created += 1
@updated_ids.push product.variants.first.id
else
self.errors.add("Line #{line_number}:", product.errors.full_messages)
end
updated[entry['supplier_id']] = {entry['name'] => product.id}
already_created[entry['supplier_id']] = {entry['name'] => product.id}
end
@variants_to_update.each do |line_number, data|
@@ -276,6 +308,7 @@ class ProductImporter
assign_defaults(variant, data[:entry])
if variant.valid? and variant.save
@variants_updated += 1
@updated_ids.push variant.id
else
self.errors.add("Line #{line_number}:", variant.errors.full_messages)
end
@@ -286,16 +319,36 @@ class ProductImporter
assign_defaults(new_variant, data[:entry])
if new_variant.valid? and new_variant.save
@variants_created += 1
@updated_ids.push new_variant.id
else
self.errors.add("Line #{line_number}:", new_variant.errors.full_messages)
end
end
self.errors.add(:importer, "did not save any products successfully") if total_saved_count == 0
self.errors.add(:importer, "did not save any products successfully") if total_saved_count.zero?
reset_absent_products
total_saved_count
end
def reset_absent_products
return if total_saved_count.zero?
enterprises_to_reset = []
@import_settings.each do |enterprise_id, settings|
enterprises_to_reset.push enterprise_id if settings['reset_all_absent']
end
unless enterprises_to_reset.empty? or @updated_ids.empty?
# Set stock to zero for all products in selected enterprises that were not
# present in the uploaded spreadsheet.
Spree::Variant.joins(:product).
where('spree_products.supplier_id IN (?)
AND spree_variants.id NOT IN (?)', enterprises_to_reset, @updated_ids).
update_all(count_on_hand: 0)
end
end
def assign_defaults(object, entry)
@import_settings[entry['supplier_id'].to_s]['defaults'].each do |attribute, setting|
case setting['mode']
@@ -356,6 +409,7 @@ class ProductImporter
check_on_hand_nil(entry, existing_variant)
if existing_variant.valid?
@variants_to_update[line_number] = {entry: entry, variant: existing_variant} unless entry_invalid?(line_number)
updates_count_per_supplier(entry['supplier_id']) unless entry_invalid?(line_number)
else
mark_as_invalid(line_number, entry, existing_variant.errors.full_messages)
end
@@ -372,6 +426,14 @@ class ProductImporter
end
end
def updates_count_per_supplier(supplier_id)
if @supplier_products[:by_supplier][supplier_id] and @supplier_products[:by_supplier][supplier_id][:updates_count]
@supplier_products[:by_supplier][supplier_id][:updates_count] += 1
else
@supplier_products[:by_supplier][supplier_id] = {updates_count: 1}
end
end
def check_on_hand_nil(entry, variant)
if entry['on_hand'].blank?
variant.on_hand = 0

View File

@@ -7,7 +7,7 @@
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active}'}}
%div.header-caret
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}"}}
%div.header-icon
%div.header-icon.neutral
%i.fa.fa-edit
-#%div.header-count
-# %strong.invalid-count= @importer.invalid_count

View File

@@ -4,9 +4,32 @@
%div.panel-section
%div.panel-header
%div.header-caret
-#%i.icon-chevron-right{ng: {hide: 'active'}}
-#%i.icon-chevron-down{ng: {hide: '!active'}}
%div.header-icon
%div.header-icon.info
%i.fa.fa-info-circle
%div.header-count
%strong.item-count= @importer.supplier_products[:total]
%div.header-description
Existing products in referenced enterprise(s)
-#%div.panel-content{ng: {hide: '!active'}}
-# Content goes here
%div.panel-section{ng: {controller: 'ImportOptionsFormCtrl', hide: 'resetCount == 0'}}
%div.panel-header
%div.header-caret
%div.header-icon.info
%i.fa.fa-info-circle
%div.header-count
%strong.reset-count
{{resetCount}}
%div.header-description
Existing products will have their stock reset to zero
-#%div.panel-content{ng: {hide: '!active'}}
-# Content goes here
%div.panel-section
%div.panel-header
%div.header-caret
%div.header-icon.success
%i.fa.fa-check-circle
%div.header-count
%strong.item-count= @importer.item_count
@@ -19,7 +42,7 @@
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && count}'}}
%div.header-caret
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'count == 0'}}
%div.header-icon
%div.header-icon.warning
%i.fa.fa-warning
%div.header-count
%strong.invalid-count= @importer.invalid_count
@@ -34,7 +57,7 @@
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && count}'}}
%div.header-caret
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'count == 0'}}
%div.header-icon
%div.header-icon.success
%i.fa.fa-check-circle
%div.header-count
%strong.create-count= @importer.products_create_count
@@ -47,7 +70,7 @@
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && count}'}}
%div.header-caret
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'count == 0'}}
%div.header-icon
%div.header-icon.success
%i.fa.fa-check-circle
%div.header-count
%strong.update-count= @importer.products_update_count

View File

@@ -1,9 +1,9 @@
%table.import-settings
%table.import-settings{ng: {controller: 'ImportOptionsFormCtrl', init: "supplierId = #{id}; nonUpdated = #{@importer.supplier_products[:by_supplier][id][:non_updated]}"}}
%tr
%td.description
Remove absent products?
%td
= check_box_tag "settings[#{id}][products_absent]", 1, false
= check_box_tag "settings[#{id}][reset_all_absent]", 1, false, :'ng-model' => 'resetAbsent', :'ng-change' => 'toggleResetAbsent()'
%td
%tr
%td.description

View File

@@ -14,8 +14,12 @@ feature "Product Import", js: true do
let!(:tax_category) { create(:tax_category) }
let!(:tax_category2) { create(:tax_category) }
let!(:shipping_category) { create(:shipping_category) }
let!(:product) { create(:simple_product, supplier: enterprise, name: 'Hypothetical Cake') }
let!(:variant) { create(:variant, product_id: product.id, price: '8.50', count_on_hand: '100', unit_value: '500', display_name: 'Preexisting Banana') }
let!(:product) { create(:simple_product, supplier: enterprise2, name: 'Hypothetical Cake') }
let!(:variant) { create(:variant, product_id: product.id, price: '8.50', on_hand: '100', unit_value: '500', display_name: 'Preexisting Banana') }
let!(:product2) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Beans') }
let!(:product3) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Sprouts') }
let!(:product4) { create(:simple_product, supplier: enterprise2, on_hand: '100', name: 'Lettuce') }
describe "when importing products from uploaded file" do
before { quick_login_as_admin }
@@ -104,8 +108,8 @@ feature "Product Import", js: true do
it "can add new variants to existing products and update price and stock level of existing products" do
csv_data = CSV.generate do |csv|
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale", "display_name"]
csv << ["Hypothetical Cake", "User Enterprise", "Cake", "5", "5.50", "500", "weight", "1", "Preexisting Banana"]
csv << ["Hypothetical Cake", "User Enterprise", "Cake", "6", "3.50", "500", "weight", "1", "Emergent Coffee"]
csv << ["Hypothetical Cake", "Another Enterprise", "Cake", "5", "5.50", "500", "weight", "1", "Preexisting Banana"]
csv << ["Hypothetical Cake", "Another Enterprise", "Cake", "6", "3.50", "500", "weight", "1", "Emergent Coffee"]
end
File.write('/tmp/test.csv', csv_data)
@@ -236,6 +240,36 @@ feature "Product Import", js: true do
describe "applying settings and defaults on import" do
before { quick_login_as_admin }
it "can set all products for an enterprise that are not present in the uploaded file to zero stock" do
csv_data = CSV.generate do |csv|
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale"]
csv << ["Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "weight", "1"]
csv << ["Potatoes", "User Enterprise", "Vegetables", "6", "6.50", "1000", "weight", "1000"]
end
File.write('/tmp/test.csv', csv_data)
visit main_app.admin_product_import_path
attach_file 'file', '/tmp/test.csv'
click_button 'Import'
within 'div.import-settings' do
find('div.header-description').click # Import settings tab
check "settings_#{enterprise.id}_reset_all_absent"
end
expect(page).to have_selector '.reset-count', text: "2"
click_button 'Save'
expect(page).to have_content "Products created: 2"
Spree::Product.find_by_name('Carrots').on_hand.should == 5 # Present in file
Spree::Product.find_by_name('Potatoes').on_hand.should == 6 # Present in file
Spree::Product.find_by_name('Beans').on_hand.should == 0 # In enterprise, not in file
Spree::Product.find_by_name('Sprouts').on_hand.should == 0 # In enterprise, not in file
Spree::Product.find_by_name('Lettuce').on_hand.should == 100 # In different enterprise; unchanged
end
it "overwrites fields with selected defaults" do
csv_data = CSV.generate do |csv|
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale", "tax_category_id", "available_on"]